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