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