xref: /PHP-8.0/ext/opcache/Optimizer/pass3.c (revision 7d483418)
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: Andi Gutmans <andi@php.net>                                 |
16    |          Zeev Suraski <zeev@php.net>                                 |
17    |          Stanislav Malyshev <stas@zend.com>                          |
18    |          Dmitry Stogov <dmitry@php.net>                              |
19    +----------------------------------------------------------------------+
20 */
21 
22 /* pass 3: (Jump optimization)
23  * - optimize series of JMPs
24  */
25 
26 #include "php.h"
27 #include "Optimizer/zend_optimizer.h"
28 #include "Optimizer/zend_optimizer_internal.h"
29 #include "zend_API.h"
30 #include "zend_constants.h"
31 #include "zend_execute.h"
32 #include "zend_vm.h"
33 
34 /* we use "jmp_hitlist" to avoid infinity loops during jmp optimization */
in_hitlist(zend_op * target,zend_op ** jmp_hitlist,int jmp_hitlist_count)35 static zend_always_inline int in_hitlist(zend_op *target, zend_op **jmp_hitlist, int jmp_hitlist_count)
36 {
37 	int i;
38 
39 	for (i = 0; i < jmp_hitlist_count; i++) {
40 		if (jmp_hitlist[i] == target) {
41 			return 1;
42 		}
43 	}
44 	return 0;
45 }
46 
47 #define CHECK_LOOP(target) \
48 	if (EXPECTED(!in_hitlist(target, jmp_hitlist, jmp_hitlist_count))) { \
49 		jmp_hitlist[jmp_hitlist_count++] = target;	\
50 	} else { \
51 		break; \
52 	}
53 
zend_optimizer_pass3(zend_op_array * op_array,zend_optimizer_ctx * ctx)54 void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx)
55 {
56 	zend_op *opline;
57 	zend_op *end;
58 	zend_op *target;
59 	zend_op **jmp_hitlist;
60 	int jmp_hitlist_count;
61 	ALLOCA_FLAG(use_heap);
62 
63 	jmp_hitlist = (zend_op**)do_alloca(sizeof(zend_op*)*op_array->last, use_heap);
64 	opline = op_array->opcodes;
65 	end =  opline + op_array->last;
66 
67 	while (opline < end) {
68 
69 		switch (opline->opcode) {
70 			case ZEND_JMP:
71 				jmp_hitlist_count = 0;
72 
73 				target = ZEND_OP1_JMP_ADDR(opline);
74 				while (1) {
75 					if (target->opcode == ZEND_JMP) {
76 						/* convert JMP L1 ... L1: JMP L2 to JMP L2 .. L1: JMP L2 */
77 						target = ZEND_OP1_JMP_ADDR(target);
78 						CHECK_LOOP(target);
79 					} else if (target->opcode == ZEND_NOP) {
80 						target = target + 1;
81 					} else {
82 						break;
83 					}
84 					ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target);
85 				}
86 
87 				if (target == opline + 1) {
88 					/* convert L: JMP L+1 to NOP */
89 					MAKE_NOP(opline);
90 				} else if (target->opcode == ZEND_JMPZNZ) {
91 					/* JMP L, L: JMPZNZ L1,L2 -> JMPZNZ L1,L2 */
92 					*opline = *target;
93 					if (opline->op1_type == IS_CONST) {
94 						zval zv;
95 						ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(opline));
96 						opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
97 					}
98 					/* Jump addresses may be encoded as offsets, recompute them. */
99 					ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(target));
100 					opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline,
101 						ZEND_OFFSET_TO_OPLINE(target, target->extended_value));
102 					goto optimize_jmpznz;
103 				} else if ((target->opcode == ZEND_RETURN ||
104 				            target->opcode == ZEND_RETURN_BY_REF ||
105 				            target->opcode == ZEND_GENERATOR_RETURN ||
106 				            target->opcode == ZEND_EXIT) &&
107 				           !(op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK)) {
108 					/* JMP L, L: RETURN to immediate RETURN */
109 					*opline = *target;
110 					if (opline->op1_type == IS_CONST) {
111 						zval zv;
112 						ZVAL_COPY(&zv, &ZEND_OP1_LITERAL(opline));
113 						opline->op1.constant = zend_optimizer_add_literal(op_array, &zv);
114 					}
115 				} else if (opline > op_array->opcodes &&
116 				           ((opline-1)->opcode == ZEND_JMPZ ||
117 				            (opline-1)->opcode == ZEND_JMPNZ)) {
118 				    if (ZEND_OP2_JMP_ADDR(opline-1) == target) {
119 						/* JMPZ(X,L1), JMP(L1) -> NOP, JMP(L1) */
120 						if ((opline-1)->op1_type == IS_CV) {
121 							(opline-1)->opcode = ZEND_CHECK_VAR;
122 							(opline-1)->op2.num = 0;
123 						} else if ((opline-1)->op1_type & (IS_TMP_VAR|IS_VAR)) {
124 							(opline-1)->opcode = ZEND_FREE;
125 							(opline-1)->op2.num = 0;
126 						} else {
127 							MAKE_NOP(opline-1);
128 						}
129 				    } else {
130 						/* JMPZ(X,L1), JMP(L2) -> JMPZNZ(X,L1,L2) */
131 						if ((opline-1)->opcode == ZEND_JMPZ) {
132 							(opline-1)->extended_value = ZEND_OPLINE_TO_OFFSET((opline-1), target);
133 						} else {
134 							(opline-1)->extended_value = ZEND_OPLINE_TO_OFFSET((opline-1), ZEND_OP2_JMP_ADDR(opline-1));
135 							ZEND_SET_OP_JMP_ADDR((opline-1), (opline-1)->op2, target);
136 						}
137 						(opline-1)->opcode = ZEND_JMPZNZ;
138 				    }
139 				}
140 				break;
141 
142 			case ZEND_JMP_SET:
143 			case ZEND_COALESCE:
144 				jmp_hitlist_count = 0;
145 
146 				target = ZEND_OP2_JMP_ADDR(opline);
147 				while (1) {
148 					if (target->opcode == ZEND_JMP) {
149 						target = ZEND_OP1_JMP_ADDR(target);
150 						CHECK_LOOP(target);
151 					} else if (target->opcode == ZEND_NOP) {
152 						target = target + 1;
153 					} else {
154 						break;
155 					}
156 					ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
157 				}
158 				break;
159 
160 			case ZEND_JMPZ:
161 			case ZEND_JMPNZ:
162 				jmp_hitlist_count = 0;
163 
164 				target = ZEND_OP2_JMP_ADDR(opline);
165 				while (1) {
166 					if (target->opcode == ZEND_JMP) {
167 						/* plain JMP */
168 						/* JMPZ(X,L1), L1: JMP(L2) => JMPZ(X,L2), L1: JMP(L2) */
169 						target = ZEND_OP1_JMP_ADDR(target);
170 						CHECK_LOOP(target);
171 					} else if (target->opcode == opline->opcode &&
172 					           SAME_VAR(opline->op1, target->op1)) {
173 						/* same opcode and same var as this opcode */
174 						/* JMPZ(X,L1), L1: JMPZ(X,L2) => JMPZ(X,L2), L1: JMPZ(X,L2) */
175 						target = ZEND_OP2_JMP_ADDR(target);
176 						CHECK_LOOP(target);
177 					} else if (target->opcode == INV_COND(opline->opcode) &&
178 					           SAME_VAR(opline->op1, target->op1)) {
179 						/* convert JMPZ(X,L1), L1: JMPNZ(X,L2) to
180 						   JMPZ(X,L1+1) */
181 						target = target + 1;
182 					} else if (target->opcode == ZEND_JMPZNZ &&
183 					           SAME_VAR(opline->op1, target->op1)) {
184 						target = (opline->opcode == ZEND_JMPZ) ?
185 							ZEND_OP2_JMP_ADDR(target) :
186 							ZEND_OFFSET_TO_OPLINE(target, target->extended_value);
187 						CHECK_LOOP(target);
188 					} else if (target->opcode == ZEND_NOP) {
189 						target = target + 1;
190 					} else {
191 						break;
192 					}
193 					ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
194 				}
195 
196 				/* convert L: JMPZ L+1 to NOP */
197 				if (target == opline + 1) {
198 					if (opline->op1_type == IS_CV) {
199 						opline->opcode = ZEND_CHECK_VAR;
200 						opline->op2.num = 0;
201 					} else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
202 						opline->opcode = ZEND_FREE;
203 						opline->op2.num = 0;
204 					} else {
205 						MAKE_NOP(opline);
206 					}
207 				}
208 				break;
209 
210 			case ZEND_JMPZ_EX:
211 			case ZEND_JMPNZ_EX:
212 				jmp_hitlist_count = 0;
213 
214 				target = ZEND_OP2_JMP_ADDR(opline);
215 				while (1) {
216 					if (target->opcode == ZEND_JMP) {
217 						/* plain JMP */
218 						/* JMPZ_EX(X,L1), L1: JMP(L2) => JMPZ_EX(X,L2), L1: JMP(L2) */
219 						target = ZEND_OP1_JMP_ADDR(target);
220 						CHECK_LOOP(target);
221 					} else if (target->opcode == opline->opcode-3 &&
222 					           (SAME_VAR(target->op1, opline->result) ||
223 					            SAME_VAR(target->op1, opline->op1))) {
224 						/* convert T=JMPZ_EX(X,L1), L1: JMPZ(T,L2) to
225 						   JMPZ_EX(X,L2) */
226 						target = ZEND_OP2_JMP_ADDR(target);
227 						CHECK_LOOP(target);
228 					} else if (target->opcode == opline->opcode &&
229 					           target->result.var == opline->result.var &&
230 					           (SAME_VAR(target->op1, opline->result) ||
231 					            SAME_VAR(target->op1, opline->op1))) {
232 						/* convert T=JMPZ_EX(X,L1), L1: T=JMPZ_EX(T,L2) to
233 						   JMPZ_EX(X,L2) */
234 						target = ZEND_OP2_JMP_ADDR(target);
235 						CHECK_LOOP(target);
236 					} else if (target->opcode == ZEND_JMPZNZ &&
237 					           (SAME_VAR(target->op1, opline->result) ||
238 					            SAME_VAR(target->op1, opline->op1))) {
239 						/* Check for JMPZNZ with same cond variable */
240 						target = (opline->opcode == ZEND_JMPZ_EX) ?
241 							ZEND_OP2_JMP_ADDR(target) :
242 							ZEND_OFFSET_TO_OPLINE(target, target->extended_value);
243 						CHECK_LOOP(target);
244 					} else if (target->opcode == INV_EX_COND(opline->opcode) &&
245 					           (SAME_VAR(target->op1, opline->result) ||
246 					            SAME_VAR(target->op1, opline->op1))) {
247 					   /* convert T=JMPZ_EX(X,L1), L1: JMPNZ(T,L2) to
248 						  JMPZ_EX(X,L1+1) */
249 						target = target + 1;
250 					} else if (target->opcode == INV_EX_COND_EX(opline->opcode) &&
251 					           target->result.var == opline->result.var &&
252 					           (SAME_VAR(target->op1, opline->result) ||
253 					            SAME_VAR(target->op1, opline->op1))) {
254 					   /* convert T=JMPZ_EX(X,L1), L1: T=JMPNZ_EX(T,L2) to
255 						  JMPZ_EX(X,L1+1) */
256 						target = target + 1;
257 					} else if (target->opcode == ZEND_BOOL &&
258 					           (SAME_VAR(target->op1, opline->result) ||
259 					            SAME_VAR(target->op1, opline->op1))) {
260 						/* convert Y = JMPZ_EX(X,L1), L1: Z = BOOL(Y) to
261 						   Z = JMPZ_EX(X,L1+1) */
262 
263 						/* NOTE: This optimization pattern is not safe, but works, */
264 						/*       because result of JMPZ_EX instruction             */
265 						/*       is not used on the following path and             */
266 						/*       should be used once on the branch path.           */
267 						/*                                                         */
268 						/*       The pattern works well only if jums processed in  */
269 						/*       direct order, otherwise it breaks JMPZ_EX         */
270 						/*       sequences too early.                              */
271 						opline->result.var = target->result.var;
272 						target = target + 1;
273 						CHECK_LOOP(target);
274 					} else if (target->opcode == ZEND_NOP) {
275 						target = target + 1;
276 					} else {
277 						break;
278 					}
279 					ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
280 				}
281 
282 				/* convert L: T = JMPZ_EX X,L+1 to T = BOOL(X) */
283 				if (target == opline + 1) {
284 					opline->opcode = ZEND_BOOL;
285 					opline->op2.num = 0;
286 				}
287 				break;
288 
289 			case ZEND_JMPZNZ:
290 optimize_jmpznz:
291 				jmp_hitlist_count = 0;
292 				target = ZEND_OP2_JMP_ADDR(opline);
293 				while (1) {
294 					if (target->opcode == ZEND_JMP) {
295 						/* JMPZNZ(X,L1,L2), L1: JMP(L3) => JMPZNZ(X,L3,L2), L1: JMP(L3) */
296 						target = ZEND_OP1_JMP_ADDR(target);
297 						CHECK_LOOP(target);
298 					} else if ((target->opcode == ZEND_JMPZ || target->opcode == ZEND_JMPZNZ) &&
299 					           SAME_VAR(target->op1, opline->op1)) {
300 						/* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
301 						target = ZEND_OP2_JMP_ADDR(target);
302 						CHECK_LOOP(target);
303 					} else if (target->opcode == ZEND_JMPNZ &&
304 					           SAME_VAR(target->op1, opline->op1)) {
305 						/* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
306 						target = target + 1;
307 					} else if (target->opcode == ZEND_NOP) {
308 						target = target + 1;
309 					} else {
310 						break;
311 					}
312 					ZEND_SET_OP_JMP_ADDR(opline, opline->op2, target);
313 				}
314 
315 				jmp_hitlist_count = 0;
316 				target = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value);
317 				while (1) {
318 					if (target->opcode == ZEND_JMP) {
319 						/* JMPZNZ(X,L1,L2), L2: JMP(L3) => JMPZNZ(X,L1,L3), L2: JMP(L3) */
320 						target = ZEND_OP1_JMP_ADDR(target);
321 						CHECK_LOOP(target);
322 					} else if (target->opcode == ZEND_JMPNZ &&
323 					           SAME_VAR(target->op1, opline->op1)) {
324 						/* JMPZNZ(X, L1, L2), L1: X = JMPNZ(X, L3) -> JMPZNZ(X, L1+1, L2) */
325 						target = ZEND_OP2_JMP_ADDR(target);
326 						CHECK_LOOP(target);
327 					} else if (target->opcode == ZEND_JMPZ &&
328 					           SAME_VAR(target->op1, opline->op1)) {
329 						/* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
330 						target = target + 1;
331 					} else if (target->opcode == ZEND_JMPZNZ &&
332 					           SAME_VAR(target->op1, opline->op1)) {
333 						/* JMPZNZ(X, L1, L2), L1: JMPZ(X, L3) -> JMPZNZ(X, L3, L2) */
334 						target = ZEND_OFFSET_TO_OPLINE(target, target->extended_value);
335 						CHECK_LOOP(target);
336 					} else if (target->opcode == ZEND_NOP) {
337 						target = target + 1;
338 					} else {
339 						break;
340 					}
341 					opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, target);
342 				}
343 
344 				if (ZEND_OP2_JMP_ADDR(opline) == target &&
345 				    !(opline->op1_type & (IS_VAR|IS_TMP_VAR))) {
346 					/* JMPZNZ(?,L,L) -> JMP(L) */
347 					opline->opcode = ZEND_JMP;
348 					ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target);
349 					SET_UNUSED(opline->op1);
350 					SET_UNUSED(opline->op2);
351 					opline->extended_value = 0;
352 				}
353 				/* Don't convert JMPZNZ back to JMPZ/JMPNZ, because the
354 				   following JMP is not removed yet. */
355 				break;
356 		}
357 		opline++;
358 	}
359 	free_alloca(jmp_hitlist, use_heap);
360 }
361