1 /*
2 +----------------------------------------------------------------------+
3 | Zend Engine, CFG - Control Flow Graph |
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@zend.com> |
16 +----------------------------------------------------------------------+
17 */
18
19 #include "php.h"
20 #include "zend_compile.h"
21 #include "zend_cfg.h"
22 #include "zend_func_info.h"
23 #include "zend_worklist.h"
24 #include "zend_optimizer.h"
25 #include "zend_optimizer_internal.h"
26
zend_mark_reachable(zend_op * opcodes,zend_cfg * cfg,zend_basic_block * b)27 static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_block *b) /* {{{ */
28 {
29 zend_uchar opcode;
30 zend_basic_block *b0;
31 int successor_0, successor_1;
32 zend_basic_block *blocks = cfg->blocks;
33
34 while (1) {
35 b->flags |= ZEND_BB_REACHABLE;
36 successor_0 = b->successors[0];
37 if (successor_0 >= 0) {
38 successor_1 = b->successors[1];
39 if (successor_1 >= 0) {
40 b0 = blocks + successor_0;
41 b0->flags |= ZEND_BB_TARGET;
42 if (!(b0->flags & ZEND_BB_REACHABLE)) {
43 zend_mark_reachable(opcodes, cfg, b0);
44 }
45
46 ZEND_ASSERT(b->len != 0);
47 opcode = opcodes[b->start + b->len - 1].opcode;
48 b = blocks + successor_1;
49 if (opcode == ZEND_JMPZNZ) {
50 b->flags |= ZEND_BB_TARGET;
51 } else {
52 b->flags |= ZEND_BB_FOLLOW;
53 }
54 } else if (b->len != 0) {
55 opcode = opcodes[b->start + b->len - 1].opcode;
56 b = blocks + successor_0;
57 if (opcode == ZEND_JMP) {
58 b->flags |= ZEND_BB_TARGET;
59 } else {
60 b->flags |= ZEND_BB_FOLLOW;
61
62 if (cfg->split_at_calls) {
63 if (opcode == ZEND_INCLUDE_OR_EVAL ||
64 opcode == ZEND_GENERATOR_CREATE ||
65 opcode == ZEND_YIELD ||
66 opcode == ZEND_YIELD_FROM ||
67 opcode == ZEND_DO_FCALL ||
68 opcode == ZEND_DO_UCALL ||
69 opcode == ZEND_DO_FCALL_BY_NAME) {
70 b->flags |= ZEND_BB_ENTRY;
71 }
72 }
73 if (cfg->split_at_recv) {
74 if (opcode == ZEND_RECV ||
75 opcode == ZEND_RECV_INIT) {
76 b->flags |= ZEND_BB_RECV_ENTRY;
77 }
78 }
79 }
80 } else {
81 b = blocks + successor_0;
82 b->flags |= ZEND_BB_FOLLOW;
83 }
84 if (b->flags & ZEND_BB_REACHABLE) return;
85 } else {
86 b->flags |= ZEND_BB_EXIT;
87 return;
88 }
89 }
90 }
91 /* }}} */
92
zend_mark_reachable_blocks(const zend_op_array * op_array,zend_cfg * cfg,int start)93 static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg, int start) /* {{{ */
94 {
95 zend_basic_block *blocks = cfg->blocks;
96
97 blocks[start].flags = ZEND_BB_START;
98 zend_mark_reachable(op_array->opcodes, cfg, blocks + start);
99
100 if (op_array->last_live_range || op_array->last_try_catch) {
101 zend_basic_block *b;
102 int j, changed;
103 uint32_t *block_map = cfg->map;
104
105 do {
106 changed = 0;
107
108 /* Add live range paths */
109 for (j = 0; j < op_array->last_live_range; j++) {
110 zend_live_range *live_range = &op_array->live_range[j];
111 if (live_range->var == (uint32_t)-1) {
112 /* this live range already removed */
113 continue;
114 }
115 b = blocks + block_map[live_range->start];
116 if (b->flags & ZEND_BB_REACHABLE) {
117 while (b->len > 0 && op_array->opcodes[b->start].opcode == ZEND_NOP) {
118 /* check if NOP breaks incorrect smart branch */
119 if (b->len == 2
120 && (op_array->opcodes[b->start + 1].opcode == ZEND_JMPZ
121 || op_array->opcodes[b->start + 1].opcode == ZEND_JMPNZ)
122 && (op_array->opcodes[b->start + 1].op1_type & (IS_CV|IS_CONST))
123 && b->start > 0
124 && zend_is_smart_branch(op_array->opcodes + b->start - 1)) {
125 break;
126 }
127 b->start++;
128 b->len--;
129 }
130 if (b->len == 0 && (uint32_t)b->successors[0] == block_map[live_range->end]) {
131 /* mark as removed (empty live range) */
132 live_range->var = (uint32_t)-1;
133 continue;
134 }
135 b->flags |= ZEND_BB_GEN_VAR;
136 b = blocks + block_map[live_range->end];
137 b->flags |= ZEND_BB_KILL_VAR;
138 if (!(b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE))) {
139 if (cfg->split_at_live_ranges) {
140 changed = 1;
141 zend_mark_reachable(op_array->opcodes, cfg, b);
142 } else {
143 ZEND_ASSERT(b->start == live_range->end);
144 b->flags |= ZEND_BB_UNREACHABLE_FREE;
145 }
146 }
147 } else {
148 ZEND_ASSERT(!(blocks[block_map[live_range->end]].flags & ZEND_BB_REACHABLE));
149 }
150 }
151
152 /* Add exception paths */
153 for (j = 0; j < op_array->last_try_catch; j++) {
154
155 /* check for jumps into the middle of try block */
156 b = blocks + block_map[op_array->try_catch_array[j].try_op];
157 if (!(b->flags & ZEND_BB_REACHABLE)) {
158 zend_basic_block *end;
159
160 if (op_array->try_catch_array[j].catch_op) {
161 end = blocks + block_map[op_array->try_catch_array[j].catch_op];
162 while (b != end) {
163 if (b->flags & ZEND_BB_REACHABLE) {
164 op_array->try_catch_array[j].try_op = b->start;
165 break;
166 }
167 b++;
168 }
169 }
170 b = blocks + block_map[op_array->try_catch_array[j].try_op];
171 if (!(b->flags & ZEND_BB_REACHABLE)) {
172 if (op_array->try_catch_array[j].finally_op) {
173 end = blocks + block_map[op_array->try_catch_array[j].finally_op];
174 while (b != end) {
175 if (b->flags & ZEND_BB_REACHABLE) {
176 op_array->try_catch_array[j].try_op = op_array->try_catch_array[j].catch_op;
177 changed = 1;
178 zend_mark_reachable(op_array->opcodes, cfg, blocks + block_map[op_array->try_catch_array[j].try_op]);
179 break;
180 }
181 b++;
182 }
183 }
184 }
185 }
186
187 b = blocks + block_map[op_array->try_catch_array[j].try_op];
188 if (b->flags & ZEND_BB_REACHABLE) {
189 b->flags |= ZEND_BB_TRY;
190 if (op_array->try_catch_array[j].catch_op) {
191 b = blocks + block_map[op_array->try_catch_array[j].catch_op];
192 b->flags |= ZEND_BB_CATCH;
193 if (!(b->flags & ZEND_BB_REACHABLE)) {
194 changed = 1;
195 zend_mark_reachable(op_array->opcodes, cfg, b);
196 }
197 }
198 if (op_array->try_catch_array[j].finally_op) {
199 b = blocks + block_map[op_array->try_catch_array[j].finally_op];
200 b->flags |= ZEND_BB_FINALLY;
201 if (!(b->flags & ZEND_BB_REACHABLE)) {
202 changed = 1;
203 zend_mark_reachable(op_array->opcodes, cfg, b);
204 }
205 }
206 if (op_array->try_catch_array[j].finally_end) {
207 b = blocks + block_map[op_array->try_catch_array[j].finally_end];
208 b->flags |= ZEND_BB_FINALLY_END;
209 if (!(b->flags & ZEND_BB_REACHABLE)) {
210 changed = 1;
211 zend_mark_reachable(op_array->opcodes, cfg, b);
212 }
213 }
214 } else {
215 if (op_array->try_catch_array[j].catch_op) {
216 ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].catch_op]].flags & ZEND_BB_REACHABLE));
217 }
218 if (op_array->try_catch_array[j].finally_op) {
219 ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_op]].flags & ZEND_BB_REACHABLE));
220 }
221 if (op_array->try_catch_array[j].finally_end) {
222 ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_end]].flags & ZEND_BB_REACHABLE));
223 }
224 }
225 }
226 } while (changed);
227 }
228 }
229 /* }}} */
230
zend_cfg_remark_reachable_blocks(const zend_op_array * op_array,zend_cfg * cfg)231 void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
232 {
233 zend_basic_block *blocks = cfg->blocks;
234 int i;
235 int start = 0;
236
237 for (i = 0; i < cfg->blocks_count; i++) {
238 if (blocks[i].flags & ZEND_BB_REACHABLE) {
239 start = i;
240 i++;
241 break;
242 }
243 }
244
245 /* clear all flags */
246 for (i = 0; i < cfg->blocks_count; i++) {
247 blocks[i].flags = 0;
248 }
249
250 zend_mark_reachable_blocks(op_array, cfg, start);
251 }
252 /* }}} */
253
record_successor(zend_basic_block * blocks,int pred,int n,int succ)254 static void record_successor(zend_basic_block *blocks, int pred, int n, int succ)
255 {
256 blocks[pred].successors[n] = succ;
257 }
258
initialize_block(zend_basic_block * block)259 static void initialize_block(zend_basic_block *block) {
260 block->flags = 0;
261 block->successors[0] = -1;
262 block->successors[1] = -1;
263 block->predecessors_count = 0;
264 block->predecessor_offset = -1;
265 block->idom = -1;
266 block->loop_header = -1;
267 block->level = -1;
268 block->children = -1;
269 block->next_child = -1;
270 }
271
272 #define BB_START(i) do { \
273 if (!block_map[i]) { blocks_count++;} \
274 block_map[i]++; \
275 } while (0)
276
zend_build_cfg(zend_arena ** arena,const zend_op_array * op_array,uint32_t build_flags,zend_cfg * cfg,uint32_t * func_flags)277 int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg, uint32_t *func_flags) /* {{{ */
278 {
279 uint32_t flags = 0;
280 uint32_t i;
281 int j;
282 uint32_t *block_map;
283 zend_function *fn;
284 int blocks_count = 0;
285 zend_basic_block *blocks;
286 zval *zv;
287 zend_bool extra_entry_block = 0;
288
289 cfg->split_at_live_ranges = (build_flags & ZEND_CFG_SPLIT_AT_LIVE_RANGES) != 0;
290 cfg->split_at_calls = (build_flags & ZEND_CFG_STACKLESS) != 0;
291 cfg->split_at_recv = (build_flags & ZEND_CFG_RECV_ENTRY) != 0 && (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0;
292
293 cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t));
294 if (!block_map) {
295 return FAILURE;
296 }
297
298 /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */
299 BB_START(0);
300 for (i = 0; i < op_array->last; i++) {
301 zend_op *opline = op_array->opcodes + i;
302 switch(opline->opcode) {
303 case ZEND_RECV:
304 case ZEND_RECV_INIT:
305 if (build_flags & ZEND_CFG_RECV_ENTRY) {
306 BB_START(i + 1);
307 }
308 break;
309 case ZEND_RETURN:
310 case ZEND_RETURN_BY_REF:
311 case ZEND_GENERATOR_RETURN:
312 case ZEND_EXIT:
313 case ZEND_THROW:
314 if (i + 1 < op_array->last) {
315 BB_START(i + 1);
316 }
317 break;
318 case ZEND_INCLUDE_OR_EVAL:
319 flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
320 case ZEND_GENERATOR_CREATE:
321 case ZEND_YIELD:
322 case ZEND_YIELD_FROM:
323 if (build_flags & ZEND_CFG_STACKLESS) {
324 BB_START(i + 1);
325 }
326 break;
327 case ZEND_DO_FCALL:
328 case ZEND_DO_UCALL:
329 case ZEND_DO_FCALL_BY_NAME:
330 flags |= ZEND_FUNC_HAS_CALLS;
331 if (build_flags & ZEND_CFG_STACKLESS) {
332 BB_START(i + 1);
333 }
334 break;
335 case ZEND_DO_ICALL:
336 flags |= ZEND_FUNC_HAS_CALLS;
337 break;
338 case ZEND_INIT_FCALL:
339 case ZEND_INIT_NS_FCALL_BY_NAME:
340 zv = CRT_CONSTANT(opline->op2);
341 if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
342 /* The third literal is the lowercased unqualified name */
343 zv += 2;
344 }
345 if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) {
346 if (fn->type == ZEND_INTERNAL_FUNCTION) {
347 flags |= zend_optimizer_classify_function(
348 Z_STR_P(zv), opline->extended_value);
349 }
350 }
351 break;
352 case ZEND_FAST_CALL:
353 BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes);
354 BB_START(i + 1);
355 break;
356 case ZEND_FAST_RET:
357 if (i + 1 < op_array->last) {
358 BB_START(i + 1);
359 }
360 break;
361 case ZEND_JMP:
362 BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes);
363 if (i + 1 < op_array->last) {
364 BB_START(i + 1);
365 }
366 break;
367 case ZEND_JMPZNZ:
368 BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
369 BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
370 if (i + 1 < op_array->last) {
371 BB_START(i + 1);
372 }
373 break;
374 case ZEND_JMPZ:
375 case ZEND_JMPNZ:
376 case ZEND_JMPZ_EX:
377 case ZEND_JMPNZ_EX:
378 case ZEND_JMP_SET:
379 case ZEND_COALESCE:
380 case ZEND_ASSERT_CHECK:
381 BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
382 BB_START(i + 1);
383 break;
384 case ZEND_CATCH:
385 if (!opline->result.num) {
386 BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
387 }
388 BB_START(i + 1);
389 break;
390 case ZEND_DECLARE_ANON_CLASS:
391 case ZEND_DECLARE_ANON_INHERITED_CLASS:
392 case ZEND_FE_FETCH_R:
393 case ZEND_FE_FETCH_RW:
394 BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
395 BB_START(i + 1);
396 break;
397 case ZEND_FE_RESET_R:
398 case ZEND_FE_RESET_RW:
399 BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
400 BB_START(i + 1);
401 break;
402 case ZEND_UNSET_VAR:
403 case ZEND_ISSET_ISEMPTY_VAR:
404 if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) &&
405 !(opline->extended_value & ZEND_QUICK_SET)) {
406 flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
407 } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL ||
408 (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) &&
409 !op_array->function_name) {
410 flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
411 }
412 break;
413 case ZEND_FETCH_R:
414 case ZEND_FETCH_W:
415 case ZEND_FETCH_RW:
416 case ZEND_FETCH_FUNC_ARG:
417 case ZEND_FETCH_IS:
418 case ZEND_FETCH_UNSET:
419 if ((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) {
420 flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
421 } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL ||
422 (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) &&
423 !op_array->function_name) {
424 flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
425 }
426 break;
427 }
428 }
429
430 /* If the entry block has predecessors, we may need to split it */
431 if ((build_flags & ZEND_CFG_NO_ENTRY_PREDECESSORS)
432 && op_array->last > 0 && block_map[0] > 1) {
433 extra_entry_block = 1;
434 }
435
436 if (cfg->split_at_live_ranges) {
437 for (j = 0; j < op_array->last_live_range; j++) {
438 BB_START(op_array->live_range[j].start);
439 BB_START(op_array->live_range[j].end);
440 }
441 }
442
443 if (op_array->last_try_catch) {
444 for (j = 0; j < op_array->last_try_catch; j++) {
445 BB_START(op_array->try_catch_array[j].try_op);
446 if (op_array->try_catch_array[j].catch_op) {
447 BB_START(op_array->try_catch_array[j].catch_op);
448 }
449 if (op_array->try_catch_array[j].finally_op) {
450 BB_START(op_array->try_catch_array[j].finally_op);
451 }
452 if (op_array->try_catch_array[j].finally_end) {
453 BB_START(op_array->try_catch_array[j].finally_end);
454 }
455 }
456 }
457
458 blocks_count += extra_entry_block;
459 cfg->blocks_count = blocks_count;
460
461 /* Build CFG, Step 2: Build Array of Basic Blocks */
462 cfg->blocks = blocks = zend_arena_calloc(arena, sizeof(zend_basic_block), blocks_count);
463 if (!blocks) {
464 return FAILURE;
465 }
466
467 blocks_count = -1;
468
469 if (extra_entry_block) {
470 initialize_block(&blocks[0]);
471 blocks[0].start = 0;
472 blocks[0].len = 0;
473 blocks_count++;
474 }
475
476 for (i = 0; i < op_array->last; i++) {
477 if (block_map[i]) {
478 if (blocks_count >= 0) {
479 blocks[blocks_count].len = i - blocks[blocks_count].start;
480 }
481 blocks_count++;
482 initialize_block(&blocks[blocks_count]);
483 blocks[blocks_count].start = i;
484 }
485 block_map[i] = blocks_count;
486 }
487
488 blocks[blocks_count].len = i - blocks[blocks_count].start;
489 blocks_count++;
490
491 /* Build CFG, Step 3: Calculate successors */
492 for (j = 0; j < blocks_count; j++) {
493 zend_op *opline;
494 if (blocks[j].len == 0) {
495 record_successor(blocks, j, 0, j + 1);
496 continue;
497 }
498
499 opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
500 switch (opline->opcode) {
501 case ZEND_FAST_RET:
502 case ZEND_RETURN:
503 case ZEND_RETURN_BY_REF:
504 case ZEND_GENERATOR_RETURN:
505 case ZEND_EXIT:
506 case ZEND_THROW:
507 break;
508 case ZEND_JMP:
509 record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]);
510 break;
511 case ZEND_JMPZNZ:
512 record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]);
513 record_successor(blocks, j, 1, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]);
514 break;
515 case ZEND_JMPZ:
516 case ZEND_JMPNZ:
517 case ZEND_JMPZ_EX:
518 case ZEND_JMPNZ_EX:
519 case ZEND_JMP_SET:
520 case ZEND_COALESCE:
521 case ZEND_ASSERT_CHECK:
522 record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]);
523 record_successor(blocks, j, 1, j + 1);
524 break;
525 case ZEND_CATCH:
526 if (!opline->result.num) {
527 record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]);
528 record_successor(blocks, j, 1, j + 1);
529 } else {
530 record_successor(blocks, j, 0, j + 1);
531 }
532 break;
533 case ZEND_DECLARE_ANON_CLASS:
534 case ZEND_DECLARE_ANON_INHERITED_CLASS:
535 case ZEND_FE_FETCH_R:
536 case ZEND_FE_FETCH_RW:
537 record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]);
538 record_successor(blocks, j, 1, j + 1);
539 break;
540 case ZEND_FE_RESET_R:
541 case ZEND_FE_RESET_RW:
542 record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]);
543 record_successor(blocks, j, 1, j + 1);
544 break;
545 case ZEND_FAST_CALL:
546 record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]);
547 record_successor(blocks, j, 1, j + 1);
548 break;
549 default:
550 record_successor(blocks, j, 0, j + 1);
551 break;
552 }
553 }
554
555 /* Build CFG, Step 4, Mark Reachable Basic Blocks */
556 zend_mark_reachable_blocks(op_array, cfg, 0);
557
558 if (func_flags) {
559 *func_flags |= flags;
560 }
561
562 return SUCCESS;
563 }
564 /* }}} */
565
zend_cfg_build_predecessors(zend_arena ** arena,zend_cfg * cfg)566 int zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */
567 {
568 int j, edges;
569 zend_basic_block *b;
570 zend_basic_block *blocks = cfg->blocks;
571 zend_basic_block *end = blocks + cfg->blocks_count;
572 int *predecessors;
573
574 edges = 0;
575 for (b = blocks; b < end; b++) {
576 b->predecessors_count = 0;
577 }
578 for (b = blocks; b < end; b++) {
579 if (!(b->flags & ZEND_BB_REACHABLE)) {
580 b->successors[0] = -1;
581 b->successors[1] = -1;
582 b->predecessors_count = 0;
583 } else {
584 if (b->successors[0] >= 0) {
585 edges++;
586 blocks[b->successors[0]].predecessors_count++;
587 if (b->successors[1] >= 0 && b->successors[1] != b->successors[0]) {
588 edges++;
589 blocks[b->successors[1]].predecessors_count++;
590 }
591 }
592 }
593 }
594
595 cfg->predecessors = predecessors = (int*)zend_arena_calloc(arena, sizeof(int), edges);
596
597 if (!predecessors) {
598 return FAILURE;
599 }
600
601 edges = 0;
602 for (b = blocks; b < end; b++) {
603 if (b->flags & ZEND_BB_REACHABLE) {
604 b->predecessor_offset = edges;
605 edges += b->predecessors_count;
606 b->predecessors_count = 0;
607 }
608 }
609
610 for (j = 0; j < cfg->blocks_count; j++) {
611 if (blocks[j].flags & ZEND_BB_REACHABLE) {
612 if (blocks[j].successors[0] >= 0) {
613 zend_basic_block *b = blocks + blocks[j].successors[0];
614 predecessors[b->predecessor_offset + b->predecessors_count] = j;
615 b->predecessors_count++;
616 if (blocks[j].successors[1] >= 0
617 && blocks[j].successors[1] != blocks[j].successors[0]) {
618 zend_basic_block *b = blocks + blocks[j].successors[1];
619 predecessors[b->predecessor_offset + b->predecessors_count] = j;
620 b->predecessors_count++;
621 }
622 }
623 }
624 }
625
626 return SUCCESS;
627 }
628 /* }}} */
629
630 /* Computes a postorder numbering of the CFG */
compute_postnum_recursive(int * postnum,int * cur,const zend_cfg * cfg,int block_num)631 static void compute_postnum_recursive(
632 int *postnum, int *cur, const zend_cfg *cfg, int block_num) /* {{{ */
633 {
634 zend_basic_block *block = &cfg->blocks[block_num];
635 if (postnum[block_num] != -1) {
636 return;
637 }
638
639 postnum[block_num] = -2; /* Marker for "currently visiting" */
640 if (block->successors[0] >= 0) {
641 compute_postnum_recursive(postnum, cur, cfg, block->successors[0]);
642 if (block->successors[1] >= 0) {
643 compute_postnum_recursive(postnum, cur, cfg, block->successors[1]);
644 }
645 }
646 postnum[block_num] = (*cur)++;
647 }
648 /* }}} */
649
650 /* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by
651 * Cooper, Harvey and Kennedy. */
zend_cfg_compute_dominators_tree(const zend_op_array * op_array,zend_cfg * cfg)652 int zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
653 {
654 zend_basic_block *blocks = cfg->blocks;
655 int blocks_count = cfg->blocks_count;
656 int j, k, changed;
657
658 ALLOCA_FLAG(use_heap)
659 int *postnum = do_alloca(sizeof(int) * cfg->blocks_count, use_heap);
660 memset(postnum, -1, sizeof(int) * cfg->blocks_count);
661 j = 0;
662 compute_postnum_recursive(postnum, &j, cfg, 0);
663
664 /* FIXME: move declarations */
665 blocks[0].idom = 0;
666 do {
667 changed = 0;
668 /* Iterating in RPO here would converge faster */
669 for (j = 1; j < blocks_count; j++) {
670 int idom = -1;
671
672 if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
673 continue;
674 }
675 for (k = 0; k < blocks[j].predecessors_count; k++) {
676 int pred = cfg->predecessors[blocks[j].predecessor_offset + k];
677
678 if (idom < 0) {
679 if (blocks[pred].idom >= 0)
680 idom = pred;
681 continue;
682 }
683
684 if (blocks[pred].idom >= 0) {
685 while (idom != pred) {
686 while (postnum[pred] < postnum[idom]) pred = blocks[pred].idom;
687 while (postnum[idom] < postnum[pred]) idom = blocks[idom].idom;
688 }
689 }
690 }
691
692 if (idom >= 0 && blocks[j].idom != idom) {
693 blocks[j].idom = idom;
694 changed = 1;
695 }
696 }
697 } while (changed);
698 blocks[0].idom = -1;
699
700 for (j = 1; j < blocks_count; j++) {
701 if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
702 continue;
703 }
704 if (blocks[j].idom >= 0) {
705 /* Sort by block number to traverse children in pre-order */
706 if (blocks[blocks[j].idom].children < 0 ||
707 j < blocks[blocks[j].idom].children) {
708 blocks[j].next_child = blocks[blocks[j].idom].children;
709 blocks[blocks[j].idom].children = j;
710 } else {
711 int k = blocks[blocks[j].idom].children;
712 while (blocks[k].next_child >=0 && j > blocks[k].next_child) {
713 k = blocks[k].next_child;
714 }
715 blocks[j].next_child = blocks[k].next_child;
716 blocks[k].next_child = j;
717 }
718 }
719 }
720
721 for (j = 0; j < blocks_count; j++) {
722 int idom = blocks[j].idom, level = 0;
723 if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
724 continue;
725 }
726 while (idom >= 0) {
727 level++;
728 if (blocks[idom].level >= 0) {
729 level += blocks[idom].level;
730 break;
731 } else {
732 idom = blocks[idom].idom;
733 }
734 }
735 blocks[j].level = level;
736 }
737
738 free_alloca(postnum, use_heap);
739 return SUCCESS;
740 }
741 /* }}} */
742
dominates(zend_basic_block * blocks,int a,int b)743 static int dominates(zend_basic_block *blocks, int a, int b) /* {{{ */
744 {
745 while (blocks[b].level > blocks[a].level) {
746 b = blocks[b].idom;
747 }
748 return a == b;
749 }
750 /* }}} */
751
752 typedef struct {
753 int id;
754 int level;
755 } block_info;
compare_block_level(const block_info * a,const block_info * b)756 static int compare_block_level(const block_info *a, const block_info *b) {
757 return b->level - a->level;
758 }
swap_blocks(block_info * a,block_info * b)759 static void swap_blocks(block_info *a, block_info *b) {
760 block_info tmp = *a;
761 *a = *b;
762 *b = tmp;
763 }
764
zend_cfg_identify_loops(const zend_op_array * op_array,zend_cfg * cfg,uint32_t * flags)765 int zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg, uint32_t *flags) /* {{{ */
766 {
767 int i, j, k, n;
768 int time;
769 zend_basic_block *blocks = cfg->blocks;
770 int *entry_times, *exit_times;
771 zend_worklist work;
772 int flag = ZEND_FUNC_NO_LOOPS;
773 block_info *sorted_blocks;
774 ALLOCA_FLAG(list_use_heap)
775 ALLOCA_FLAG(tree_use_heap)
776 ALLOCA_FLAG(sorted_blocks_use_heap)
777
778 ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap);
779
780 /* We don't materialize the DJ spanning tree explicitly, as we are only interested in ancestor
781 * queries. These are implemented by checking entry/exit times of the DFS search. */
782 entry_times = do_alloca(2 * sizeof(int) * cfg->blocks_count, tree_use_heap);
783 exit_times = entry_times + cfg->blocks_count;
784 memset(entry_times, -1, 2 * sizeof(int) * cfg->blocks_count);
785
786 zend_worklist_push(&work, 0);
787 time = 0;
788 while (zend_worklist_len(&work)) {
789 next:
790 i = zend_worklist_peek(&work);
791 if (entry_times[i] == -1) {
792 entry_times[i] = time++;
793 }
794 /* Visit blocks immediately dominated by i. */
795 for (j = blocks[i].children; j >= 0; j = blocks[j].next_child) {
796 if (zend_worklist_push(&work, j)) {
797 goto next;
798 }
799 }
800 /* Visit join edges. */
801 for (j = 0; j < 2; j++) {
802 int succ = blocks[i].successors[j];
803 if (succ < 0) {
804 continue;
805 } else if (blocks[succ].idom == i) {
806 continue;
807 } else if (zend_worklist_push(&work, succ)) {
808 goto next;
809 }
810 }
811 exit_times[i] = time++;
812 zend_worklist_pop(&work);
813 }
814
815 /* Sort blocks by decreasing level, which is the order in which we want to process them */
816 sorted_blocks = do_alloca(sizeof(block_info) * cfg->blocks_count, sorted_blocks_use_heap);
817 for (i = 0; i < cfg->blocks_count; i++) {
818 sorted_blocks[i].id = i;
819 sorted_blocks[i].level = blocks[i].level;
820 }
821 zend_sort(sorted_blocks, cfg->blocks_count, sizeof(block_info),
822 (compare_func_t) compare_block_level, (swap_func_t) swap_blocks);
823
824 /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ
825 Graphs". */
826
827 for (n = 0; n < cfg->blocks_count; n++) {
828 i = sorted_blocks[n].id;
829
830 zend_bitset_clear(work.visited, zend_bitset_len(cfg->blocks_count));
831 for (j = 0; j < blocks[i].predecessors_count; j++) {
832 int pred = cfg->predecessors[blocks[i].predecessor_offset + j];
833
834 /* A join edge is one for which the predecessor does not
835 immediately dominate the successor. */
836 if (blocks[i].idom == pred) {
837 continue;
838 }
839
840 /* In a loop back-edge (back-join edge), the successor dominates
841 the predecessor. */
842 if (dominates(blocks, i, pred)) {
843 blocks[i].flags |= ZEND_BB_LOOP_HEADER;
844 flag &= ~ZEND_FUNC_NO_LOOPS;
845 zend_worklist_push(&work, pred);
846 } else {
847 /* Otherwise it's a cross-join edge. See if it's a branch
848 to an ancestor on the DJ spanning tree. */
849 if (entry_times[pred] > entry_times[i] && exit_times[pred] < exit_times[i]) {
850 blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP;
851 flag |= ZEND_FUNC_IRREDUCIBLE;
852 flag &= ~ZEND_FUNC_NO_LOOPS;
853 }
854 }
855 }
856 while (zend_worklist_len(&work)) {
857 j = zend_worklist_pop(&work);
858 while (blocks[j].loop_header >= 0) {
859 j = blocks[j].loop_header;
860 }
861 if (j != i) {
862 blocks[j].loop_header = i;
863 for (k = 0; k < blocks[j].predecessors_count; k++) {
864 zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]);
865 }
866 }
867 }
868 }
869
870 free_alloca(sorted_blocks, sorted_blocks_use_heap);
871 free_alloca(entry_times, tree_use_heap);
872 ZEND_WORKLIST_FREE_ALLOCA(&work, list_use_heap);
873 *flags |= flag;
874
875 return SUCCESS;
876 }
877 /* }}} */
878
879 /*
880 * Local variables:
881 * tab-width: 4
882 * c-basic-offset: 4
883 * indent-tabs-mode: t
884 * End:
885 */
886