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