xref: /PHP-7.3/ext/opcache/Optimizer/dce.c (revision 4eeb41d1)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine, DCE - Dead Code Elimination                             |
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: Nikita Popov <nikic@php.net>                                |
16    |          Dmitry Stogov <dmitry@php.net>                              |
17    +----------------------------------------------------------------------+
18 */
19 
20 #include "ZendAccelerator.h"
21 #include "Optimizer/zend_optimizer_internal.h"
22 #include "Optimizer/zend_inference.h"
23 #include "Optimizer/zend_ssa.h"
24 #include "Optimizer/zend_func_info.h"
25 #include "Optimizer/zend_call_graph.h"
26 #include "zend_bitset.h"
27 
28 /* This pass implements a form of dead code elimination (DCE). The algorithm optimistically assumes
29  * that all instructions and phis are dead. Instructions with immediate side-effects are then marked
30  * as live. We then recursively (using a worklist) propagate liveness to the instructions that def
31  * the used operands.
32  *
33  * Notes:
34  *  * This pass does not perform unreachable code elimination. This happens as part of the SCCP
35  *    pass.
36  *  * The DCE is performed without taking control-dependence into account, i.e. all conditional
37  *    branches are assumed to be live. It's possible to take control-dependence into account using
38  *    the DCE algorithm described by Cytron et al., however it requires the construction of a
39  *    postdominator tree and of postdominance frontiers, which does not seem worthwhile at this
40  *    point.
41  *  * We separate intrinsic side-effects from potential side-effects in the form of notices thrown
42  *    by the instruction (in case we want to make this configurable). See may_have_side_effects() and
43  *    zend_may_throw().
44  *  * We often cannot DCE assignments and unsets while guaranteeing that dtors run in the same
45  *    order. There is an optimization option to allow reordering of dtor effects.
46  *  * The algorithm is able to eliminate dead modifications of non-escaping arrays
47  *    and objects as well as dead arrays and objects allocations.
48  */
49 
50 typedef struct {
51 	zend_ssa *ssa;
52 	zend_op_array *op_array;
53 	zend_bitset instr_dead;
54 	zend_bitset phi_dead;
55 	zend_bitset instr_worklist;
56 	zend_bitset phi_worklist;
57 	zend_bitset phi_worklist_no_val;
58 	uint32_t instr_worklist_len;
59 	uint32_t phi_worklist_len;
60 	unsigned reorder_dtor_effects : 1;
61 } context;
62 
is_bad_mod(const zend_ssa * ssa,int use,int def)63 static inline zend_bool is_bad_mod(const zend_ssa *ssa, int use, int def) {
64 	if (def < 0) {
65 		/* This modification is not tracked by SSA, assume the worst */
66 		return 1;
67 	}
68 	if (ssa->var_info[use].type & MAY_BE_REF) {
69 		/* Modification of reference may have side-effect */
70 		return 1;
71 	}
72 	return 0;
73 }
74 
may_have_side_effects(zend_op_array * op_array,zend_ssa * ssa,const zend_op * opline,const zend_ssa_op * ssa_op,zend_bool reorder_dtor_effects)75 static inline zend_bool may_have_side_effects(
76 		zend_op_array *op_array, zend_ssa *ssa,
77 		const zend_op *opline, const zend_ssa_op *ssa_op,
78 		zend_bool reorder_dtor_effects) {
79 	switch (opline->opcode) {
80 		case ZEND_NOP:
81 		case ZEND_IS_IDENTICAL:
82 		case ZEND_IS_NOT_IDENTICAL:
83 		case ZEND_QM_ASSIGN:
84 		case ZEND_FREE:
85 		case ZEND_TYPE_CHECK:
86 		case ZEND_DEFINED:
87 		case ZEND_ADD:
88 		case ZEND_SUB:
89 		case ZEND_MUL:
90 		case ZEND_POW:
91 		case ZEND_BW_OR:
92 		case ZEND_BW_AND:
93 		case ZEND_BW_XOR:
94 		case ZEND_CONCAT:
95 		case ZEND_FAST_CONCAT:
96 		case ZEND_DIV:
97 		case ZEND_MOD:
98 		case ZEND_BOOL_XOR:
99 		case ZEND_BOOL:
100 		case ZEND_BOOL_NOT:
101 		case ZEND_BW_NOT:
102 		case ZEND_SL:
103 		case ZEND_SR:
104 		case ZEND_IS_EQUAL:
105 		case ZEND_IS_NOT_EQUAL:
106 		case ZEND_IS_SMALLER:
107 		case ZEND_IS_SMALLER_OR_EQUAL:
108 		case ZEND_CASE:
109 		case ZEND_CAST:
110 		case ZEND_ROPE_INIT:
111 		case ZEND_ROPE_ADD:
112 		case ZEND_INIT_ARRAY:
113 		case ZEND_ADD_ARRAY_ELEMENT:
114 		case ZEND_SPACESHIP:
115 		case ZEND_STRLEN:
116 		case ZEND_COUNT:
117 		case ZEND_GET_TYPE:
118 		case ZEND_ISSET_ISEMPTY_THIS:
119 		case ZEND_ISSET_ISEMPTY_DIM_OBJ:
120 		case ZEND_FETCH_DIM_IS:
121 		case ZEND_ISSET_ISEMPTY_CV:
122 		case ZEND_ISSET_ISEMPTY_VAR:
123 		case ZEND_FETCH_IS:
124 		case ZEND_IN_ARRAY:
125 		case ZEND_FUNC_NUM_ARGS:
126 		case ZEND_FUNC_GET_ARGS:
127 			/* No side effects */
128 			return 0;
129 		case ZEND_ROPE_END:
130 			/* TODO: Rope dce optmization, see #76446 */
131 			return 1;
132 		case ZEND_JMP:
133 		case ZEND_JMPZ:
134 		case ZEND_JMPNZ:
135 		case ZEND_JMPZNZ:
136 		case ZEND_JMPZ_EX:
137 		case ZEND_JMPNZ_EX:
138 		case ZEND_JMP_SET:
139 		case ZEND_COALESCE:
140 		case ZEND_ASSERT_CHECK:
141 			/* For our purposes a jumps and branches are side effects. */
142 			return 1;
143 		case ZEND_BEGIN_SILENCE:
144 		case ZEND_END_SILENCE:
145 		case ZEND_ECHO:
146 		case ZEND_INCLUDE_OR_EVAL:
147 		case ZEND_THROW:
148 		case ZEND_EXT_STMT:
149 		case ZEND_EXT_FCALL_BEGIN:
150 		case ZEND_EXT_FCALL_END:
151 		case ZEND_EXT_NOP:
152 		case ZEND_TICKS:
153 		case ZEND_YIELD:
154 		case ZEND_YIELD_FROM:
155 			/* Intrinsic side effects */
156 			return 1;
157 		case ZEND_DO_FCALL:
158 		case ZEND_DO_FCALL_BY_NAME:
159 		case ZEND_DO_ICALL:
160 		case ZEND_DO_UCALL:
161 			/* For now assume all calls have side effects */
162 			return 1;
163 		case ZEND_RECV:
164 		case ZEND_RECV_INIT:
165 			/* Even though RECV_INIT can be side-effect free, these cannot be simply dropped
166 			 * due to the prologue skipping code. */
167 			return 1;
168 		case ZEND_ASSIGN_REF:
169 			return 1;
170 		case ZEND_ASSIGN:
171 		{
172 			if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)) {
173 				return 1;
174 			}
175 			if (!reorder_dtor_effects) {
176 				if (opline->op2_type != IS_CONST
177 					&& (OP2_INFO() & MAY_HAVE_DTOR)
178 					&& ssa->vars[ssa_op->op2_use].escape_state != ESCAPE_STATE_NO_ESCAPE) {
179 					/* DCE might shorten lifetime */
180 					return 1;
181 				}
182 			}
183 			return 0;
184 		}
185 		case ZEND_UNSET_VAR:
186 			return 1;
187 		case ZEND_UNSET_CV:
188 		{
189 			uint32_t t1 = OP1_INFO();
190 			if (t1 & MAY_BE_REF) {
191 				/* We don't consider uses as the LHS of an assignment as real uses during DCE, so
192 				 * an unset may be considered dead even if there is a later assignment to the
193 				 * variable. Removing the unset in this case would not be correct if the variable
194 				 * is a reference, because unset breaks references. */
195 				return 1;
196 			}
197 			return 0;
198 		}
199 		case ZEND_PRE_INC:
200 		case ZEND_POST_INC:
201 		case ZEND_PRE_DEC:
202 		case ZEND_POST_DEC:
203 			return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def);
204 		case ZEND_ASSIGN_ADD:
205 		case ZEND_ASSIGN_SUB:
206 		case ZEND_ASSIGN_MUL:
207 		case ZEND_ASSIGN_DIV:
208 		case ZEND_ASSIGN_MOD:
209 		case ZEND_ASSIGN_SL:
210 		case ZEND_ASSIGN_SR:
211 		case ZEND_ASSIGN_CONCAT:
212 		case ZEND_ASSIGN_BW_OR:
213 		case ZEND_ASSIGN_BW_AND:
214 		case ZEND_ASSIGN_BW_XOR:
215 		case ZEND_ASSIGN_POW:
216 			return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
217 				|| (opline->extended_value
218 					&& ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE);
219 		case ZEND_ASSIGN_DIM:
220 		case ZEND_ASSIGN_OBJ:
221 			if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
222 				|| ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
223 				return 1;
224 			}
225 			if (!reorder_dtor_effects) {
226 				opline++;
227 				ssa_op++;
228 				if (opline->op1_type != IS_CONST
229 					&& (OP1_INFO() & MAY_HAVE_DTOR)) {
230 					/* DCE might shorten lifetime */
231 					return 1;
232 				}
233 			}
234 			return 0;
235 		case ZEND_PRE_INC_OBJ:
236 		case ZEND_PRE_DEC_OBJ:
237 		case ZEND_POST_INC_OBJ:
238 		case ZEND_POST_DEC_OBJ:
239 			if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
240 				|| ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
241 				return 1;
242 			}
243 			return 0;
244 		default:
245 			/* For everything we didn't handle, assume a side-effect */
246 			return 1;
247 	}
248 }
249 
add_to_worklists(context * ctx,int var_num,int check)250 static zend_always_inline void add_to_worklists(context *ctx, int var_num, int check) {
251 	zend_ssa_var *var = &ctx->ssa->vars[var_num];
252 	if (var->definition >= 0) {
253 		if (!check || zend_bitset_in(ctx->instr_dead, var->definition)) {
254 			zend_bitset_incl(ctx->instr_worklist, var->definition);
255 		}
256 	} else if (var->definition_phi) {
257 		if (!check || zend_bitset_in(ctx->phi_dead, var_num)) {
258 			zend_bitset_incl(ctx->phi_worklist, var_num);
259 		}
260 	}
261 }
262 
add_to_phi_worklist_no_val(context * ctx,int var_num)263 static inline void add_to_phi_worklist_no_val(context *ctx, int var_num) {
264 	zend_ssa_var *var = &ctx->ssa->vars[var_num];
265 	if (var->definition_phi && zend_bitset_in(ctx->phi_dead, var_num)) {
266 		zend_bitset_incl(ctx->phi_worklist_no_val, var_num);
267 	}
268 }
269 
add_operands_to_worklists(context * ctx,zend_op * opline,zend_ssa_op * ssa_op,int check)270 static zend_always_inline void add_operands_to_worklists(context *ctx, zend_op *opline, zend_ssa_op *ssa_op, int check) {
271 	if (ssa_op->result_use >= 0) {
272 		add_to_worklists(ctx, ssa_op->result_use, check);
273 	}
274 	if (ssa_op->op1_use >= 0) {
275 		if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)) {
276 			add_to_worklists(ctx, ssa_op->op1_use, check);
277 		} else {
278 			add_to_phi_worklist_no_val(ctx, ssa_op->op1_use);
279 		}
280 	}
281 	if (ssa_op->op2_use >= 0) {
282 		if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)) {
283 			add_to_worklists(ctx, ssa_op->op2_use, check);
284 		} else {
285 			add_to_phi_worklist_no_val(ctx, ssa_op->op2_use);
286 		}
287 	}
288 }
289 
add_phi_sources_to_worklists(context * ctx,zend_ssa_phi * phi,int check)290 static zend_always_inline void add_phi_sources_to_worklists(context *ctx, zend_ssa_phi *phi, int check) {
291 	zend_ssa *ssa = ctx->ssa;
292 	int source;
293 	FOREACH_PHI_SOURCE(phi, source) {
294 		add_to_worklists(ctx, source, check);
295 	} FOREACH_PHI_SOURCE_END();
296 }
297 
is_var_dead(context * ctx,int var_num)298 static inline zend_bool is_var_dead(context *ctx, int var_num) {
299 	zend_ssa_var *var = &ctx->ssa->vars[var_num];
300 	if (var->definition_phi) {
301 		return zend_bitset_in(ctx->phi_dead, var_num);
302 	} else if (var->definition >= 0) {
303 		return zend_bitset_in(ctx->instr_dead, var->definition);
304 	} else {
305 		/* Variable has no definition, so either the definition has already been removed (var is
306 		 * dead) or this is one of the implicit variables at the start of the function (for our
307 		 * purposes live) */
308 		return var_num >= ctx->op_array->last_var;
309 	}
310 }
311 
312 // Sometimes we can mark the var as EXT_UNUSED
try_remove_var_def(context * ctx,int free_var,int use_chain,zend_op * opline)313 static zend_bool try_remove_var_def(context *ctx, int free_var, int use_chain, zend_op *opline) {
314 	if (use_chain >= 0) {
315 		return 0;
316 	}
317 	zend_ssa_var *var = &ctx->ssa->vars[free_var];
318 	int def = var->definition;
319 
320 	if (def >= 0) {
321 		zend_ssa_op *def_op = &ctx->ssa->ops[def];
322 
323 		if (def_op->result_def == free_var
324 				&& var->phi_use_chain == NULL
325 				&& var->use_chain == (opline - ctx->op_array->opcodes)) {
326 			zend_op *def_opline = &ctx->op_array->opcodes[def];
327 
328 			switch (def_opline->opcode) {
329 				case ZEND_ASSIGN:
330 				case ZEND_ASSIGN_REF:
331 				case ZEND_ASSIGN_DIM:
332 				case ZEND_ASSIGN_OBJ:
333 				case ZEND_ASSIGN_ADD:
334 				case ZEND_ASSIGN_SUB:
335 				case ZEND_ASSIGN_MUL:
336 				case ZEND_ASSIGN_DIV:
337 				case ZEND_ASSIGN_MOD:
338 				case ZEND_ASSIGN_SL:
339 				case ZEND_ASSIGN_SR:
340 				case ZEND_ASSIGN_CONCAT:
341 				case ZEND_ASSIGN_BW_OR:
342 				case ZEND_ASSIGN_BW_AND:
343 				case ZEND_ASSIGN_BW_XOR:
344 				case ZEND_ASSIGN_POW:
345 				case ZEND_PRE_INC:
346 				case ZEND_PRE_DEC:
347 				case ZEND_PRE_INC_OBJ:
348 				case ZEND_POST_INC_OBJ:
349 				case ZEND_PRE_DEC_OBJ:
350 				case ZEND_POST_DEC_OBJ:
351 				case ZEND_DO_ICALL:
352 				case ZEND_DO_UCALL:
353 				case ZEND_DO_FCALL_BY_NAME:
354 				case ZEND_DO_FCALL:
355 				case ZEND_INCLUDE_OR_EVAL:
356 				case ZEND_YIELD:
357 				case ZEND_YIELD_FROM:
358 				case ZEND_ASSERT_CHECK:
359 					def_opline->result_type = IS_UNUSED;
360 					def_opline->result.var = 0;
361 					def_op->result_def = -1;
362 					var->definition = -1;
363 					return 1;
364 				default:
365 					break;
366 			}
367 		}
368 	}
369 	return 0;
370 }
371 
372 /* Returns whether the instruction has been DCEd */
dce_instr(context * ctx,zend_op * opline,zend_ssa_op * ssa_op)373 static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
374 	zend_ssa *ssa = ctx->ssa;
375 	int free_var = -1;
376 	zend_uchar free_var_type;
377 
378 	if (opline->opcode == ZEND_NOP) {
379 		return 0;
380 	}
381 
382 	/* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */
383 	if (opline->opcode == ZEND_FREE
384 			&& (ssa->var_info[ssa_op->op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
385 			&& !is_var_dead(ctx, ssa_op->op1_use)) {
386 		return 0;
387 	}
388 
389 	if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))&& !is_var_dead(ctx, ssa_op->op1_use)) {
390 		if (!try_remove_var_def(ctx, ssa_op->op1_use, ssa_op->op1_use_chain, opline)) {
391 			if (ssa->var_info[ssa_op->op1_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)
392 				&& opline->opcode != ZEND_CASE) {
393 				free_var = ssa_op->op1_use;
394 				free_var_type = opline->op1_type;
395 			}
396 		}
397 	}
398 	if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op2_use)) {
399 		if (!try_remove_var_def(ctx, ssa_op->op2_use, ssa_op->op2_use_chain, opline)) {
400 			if (ssa->var_info[ssa_op->op2_use].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
401 				if (free_var >= 0) {
402 					// TODO: We can't free two vars. Keep instruction alive.
403 					zend_bitset_excl(ctx->instr_dead, opline - ctx->op_array->opcodes);
404 					return 0;
405 				}
406 				free_var = ssa_op->op2_use;
407 				free_var_type = opline->op2_type;
408 			}
409 		}
410 	}
411 
412 	zend_ssa_rename_defs_of_instr(ctx->ssa, ssa_op);
413 	zend_ssa_remove_instr(ctx->ssa, opline, ssa_op);
414 
415 	if (free_var >= 0) {
416 		opline->opcode = ZEND_FREE;
417 		opline->op1.var = (uintptr_t) ZEND_CALL_VAR_NUM(NULL, ssa->vars[free_var].var);
418 		opline->op1_type = free_var_type;
419 
420 		ssa_op->op1_use = free_var;
421 		ssa_op->op1_use_chain = ssa->vars[free_var].use_chain;
422 		ssa->vars[free_var].use_chain = ssa_op - ssa->ops;
423 		return 0;
424 	}
425 	return 1;
426 }
427 
get_common_phi_source(zend_ssa * ssa,zend_ssa_phi * phi)428 static inline int get_common_phi_source(zend_ssa *ssa, zend_ssa_phi *phi) {
429 	int common_source = -1;
430 	int source;
431 	FOREACH_PHI_SOURCE(phi, source) {
432 		if (common_source == -1) {
433 			common_source = source;
434 		} else if (common_source != source && source != phi->ssa_var) {
435 			return -1;
436 		}
437 	} FOREACH_PHI_SOURCE_END();
438 	ZEND_ASSERT(common_source != -1);
439 	return common_source;
440 }
441 
try_remove_trivial_phi(context * ctx,zend_ssa_phi * phi)442 static void try_remove_trivial_phi(context *ctx, zend_ssa_phi *phi) {
443 	zend_ssa *ssa = ctx->ssa;
444 	if (phi->pi < 0) {
445 		/* Phi assignment with identical source operands */
446 		int common_source = get_common_phi_source(ssa, phi);
447 		if (common_source >= 0) {
448 			zend_ssa_rename_var_uses(ssa, phi->ssa_var, common_source, 1);
449 			zend_ssa_remove_phi(ssa, phi);
450 		}
451 	} else {
452 		/* Pi assignment that is only used in Phi/Pi assignments */
453 		// TODO What if we want to rerun type inference after DCE? Maybe separate this?
454 		/*ZEND_ASSERT(phi->sources[0] != -1);
455 		if (ssa->vars[phi->ssa_var].use_chain < 0) {
456 			zend_ssa_rename_var_uses_keep_types(ssa, phi->ssa_var, phi->sources[0], 1);
457 			zend_ssa_remove_phi(ssa, phi);
458 		}*/
459 	}
460 }
461 
may_break_varargs(const zend_op_array * op_array,const zend_ssa * ssa,const zend_ssa_op * ssa_op)462 static inline zend_bool may_break_varargs(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_op *ssa_op) {
463 	if (ssa_op->op1_def >= 0
464 			&& ssa->vars[ssa_op->op1_def].var < op_array->num_args) {
465 		return 1;
466 	}
467 	if (ssa_op->op2_def >= 0
468 			&& ssa->vars[ssa_op->op2_def].var < op_array->num_args) {
469 		return 1;
470 	}
471 	if (ssa_op->result_def >= 0
472 			&& ssa->vars[ssa_op->result_def].var < op_array->num_args) {
473 		return 1;
474 	}
475 	return 0;
476 }
477 
dce_live_ranges(context * ctx,zend_op_array * op_array,zend_ssa * ssa)478 static void dce_live_ranges(context *ctx, zend_op_array *op_array, zend_ssa *ssa)
479 {
480 	int i = 0;
481 	int j = 0;
482 	zend_live_range *live_range = op_array->live_range;
483 
484 	while (i < op_array->last_live_range) {
485 		if ((live_range->var & ZEND_LIVE_MASK) != ZEND_LIVE_TMPVAR) {
486 			/* keep */
487 			j++;
488 		} else {
489 			uint32_t var = live_range->var & ~ZEND_LIVE_MASK;
490 			uint32_t def = live_range->start - 1;
491 
492 			if ((op_array->opcodes[def].result_type == IS_UNUSED) &&
493 					(UNEXPECTED(op_array->opcodes[def].opcode == ZEND_EXT_STMT) ||
494 					UNEXPECTED(op_array->opcodes[def].opcode == ZEND_EXT_FCALL_END) ||
495 					UNEXPECTED(op_array->opcodes[def].opcode == ZEND_END_SILENCE))) {
496 				def--;
497 			}
498 
499 			if (op_array->opcodes[def].result_type == IS_UNUSED) {
500 				if (op_array->opcodes[def].opcode == ZEND_DO_FCALL) {
501 					/* constructor call */
502 					do {
503 						def--;
504 						if ((op_array->opcodes[def].result_type & (IS_TMP_VAR|IS_VAR))
505 								&& op_array->opcodes[def].result.var == var) {
506 							ZEND_ASSERT(op_array->opcodes[def].opcode == ZEND_NEW);
507 							break;
508 						}
509 					} while (def > 0);
510 				} else if (op_array->opcodes[def].opcode == ZEND_OP_DATA) {
511 					def--;
512 				}
513 			}
514 
515 #if ZEND_DEBUG
516 			ZEND_ASSERT(op_array->opcodes[def].result_type & (IS_TMP_VAR|IS_VAR));
517 			ZEND_ASSERT(op_array->opcodes[def].result.var == var);
518 			ZEND_ASSERT(ssa->ops[def].result_def >= 0);
519 #else
520 			if (!(op_array->opcodes[def].result_type & (IS_TMP_VAR|IS_VAR))
521 					|| op_array->opcodes[def].result.var != var
522 					|| ssa->ops[def].result_def < 0) {
523 				/* TODO: Some wrong live-range? keep it. */
524 				j++;
525 				live_range++;
526 				i++;
527 				continue;
528 			}
529 #endif
530 
531 			var = ssa->ops[def].result_def;
532 
533 			if ((ssa->var_info[var].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
534 					&& !is_var_dead(ctx, var)) {
535 				/* keep */
536 				j++;
537 			} else if (i != j) {
538 				op_array->live_range[j] = *live_range;
539 			}
540 		}
541 
542 		live_range++;
543 		i++;
544 	}
545 	op_array->last_live_range = j;
546 	if (op_array->last_live_range == 0) {
547 		efree(op_array->live_range);
548 		op_array->live_range = NULL;
549 	}
550 }
551 
dce_optimize_op_array(zend_op_array * op_array,zend_ssa * ssa,zend_bool reorder_dtor_effects)552 int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reorder_dtor_effects) {
553 	int i;
554 	zend_ssa_phi *phi;
555 	int removed_ops = 0;
556 
557 	/* DCE of CV operations that changes arguments may affect vararg functions. */
558 	zend_bool has_varargs = (ssa->cfg.flags & ZEND_FUNC_VARARG) != 0;
559 
560 	context ctx;
561 	ctx.ssa = ssa;
562 	ctx.op_array = op_array;
563 	ctx.reorder_dtor_effects = reorder_dtor_effects;
564 
565 	/* We have no dedicated phi vector, so we use the whole ssa var vector instead */
566 	ctx.instr_worklist_len = zend_bitset_len(op_array->last);
567 	ctx.instr_worklist = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
568 	memset(ctx.instr_worklist, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
569 	ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count);
570 	ctx.phi_worklist = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
571 	memset(ctx.phi_worklist, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
572 	ctx.phi_worklist_no_val = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
573 	memset(ctx.phi_worklist_no_val, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
574 
575 	/* Optimistically assume all instructions and phis to be dead */
576 	ctx.instr_dead = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
577 	memset(ctx.instr_dead, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
578 	ctx.phi_dead = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
579 	memset(ctx.phi_dead, 0xff, sizeof(zend_ulong) * ctx.phi_worklist_len);
580 
581 	/* Mark reacable instruction without side effects as dead */
582 	int b = ssa->cfg.blocks_count;
583 	while (b > 0) {
584 		int	op_data = -1;
585 
586 		b--;
587 		zend_basic_block *block = &ssa->cfg.blocks[b];
588 		if (!(block->flags & ZEND_BB_REACHABLE)) {
589 			continue;
590 		}
591 		i = block->start + block->len;
592 		while (i > block->start) {
593 			i--;
594 
595 			if (op_array->opcodes[i].opcode == ZEND_OP_DATA) {
596 				op_data = i;
597 				continue;
598 			}
599 
600 			if (zend_bitset_in(ctx.instr_worklist, i)) {
601 				zend_bitset_excl(ctx.instr_worklist, i);
602 				add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
603 				if (op_data >= 0) {
604 					add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], 0);
605 				}
606 			} else if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects)
607 					|| zend_may_throw(&op_array->opcodes[i], op_array, ssa)
608 					|| (has_varargs && may_break_varargs(op_array, ssa, &ssa->ops[i]))) {
609 				if (op_array->opcodes[i].opcode == ZEND_NEW
610 						&& op_array->opcodes[i+1].opcode == ZEND_DO_FCALL
611 						&& ssa->ops[i].result_def >= 0
612 						&& ssa->vars[ssa->ops[i].result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
613 					zend_bitset_incl(ctx.instr_dead, i);
614 					zend_bitset_incl(ctx.instr_dead, i+1);
615 				} else {
616 					add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 0);
617 					if (op_data >= 0) {
618 						add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], 0);
619 					}
620 				}
621 			} else {
622 				zend_bitset_incl(ctx.instr_dead, i);
623 				if (op_data >= 0) {
624 					zend_bitset_incl(ctx.instr_dead, op_data);
625 				}
626 			}
627 			op_data = -1;
628 		}
629 	}
630 
631 	/* Propagate liveness backwards to all definitions of used vars */
632 	while (!zend_bitset_empty(ctx.instr_worklist, ctx.instr_worklist_len)
633 			|| !zend_bitset_empty(ctx.phi_worklist, ctx.phi_worklist_len)) {
634 		while ((i = zend_bitset_pop_first(ctx.instr_worklist, ctx.instr_worklist_len)) >= 0) {
635 			zend_bitset_excl(ctx.instr_dead, i);
636 			add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], 1);
637 			if (i < op_array->last && op_array->opcodes[i+1].opcode == ZEND_OP_DATA) {
638 				zend_bitset_excl(ctx.instr_dead, i+1);
639 				add_operands_to_worklists(&ctx, &op_array->opcodes[i+1], &ssa->ops[i+1], 1);
640 			}
641 		}
642 		while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
643 			zend_bitset_excl(ctx.phi_dead, i);
644 			zend_bitset_excl(ctx.phi_worklist_no_val, i);
645 			add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi, 1);
646 		}
647 	}
648 
649 	if (op_array->live_range) {
650 		dce_live_ranges(&ctx, op_array, ssa);
651 	}
652 
653 	/* Eliminate dead instructions */
654 	ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) {
655 		removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
656 	} ZEND_BITSET_FOREACH_END();
657 
658 	/* Improper uses don't count as "uses" for the purpose of instruction elimination,
659 	 * but we have to retain phis defining them.
660 	 * Propagate this information backwards, marking any phi with an improperly used
661 	 * target as non-dead. */
662 	while ((i = zend_bitset_pop_first(ctx.phi_worklist_no_val, ctx.phi_worklist_len)) >= 0) {
663 		zend_ssa_phi *phi = ssa->vars[i].definition_phi;
664 		int source;
665 		zend_bitset_excl(ctx.phi_dead, i);
666 		FOREACH_PHI_SOURCE(phi, source) {
667 			add_to_phi_worklist_no_val(&ctx, source);
668 		} FOREACH_PHI_SOURCE_END();
669 	}
670 
671 	/* Now collect the actually dead phis */
672 	FOREACH_PHI(phi) {
673 		if (zend_bitset_in(ctx.phi_dead, phi->ssa_var)) {
674 			zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
675 			zend_ssa_remove_phi(ssa, phi);
676 		} else {
677 			/* Remove trivial phis (phis with identical source operands) */
678 			try_remove_trivial_phi(&ctx, phi);
679 		}
680 	} FOREACH_PHI_END();
681 
682 	return removed_ops;
683 }
684