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