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