xref: /PHP-7.4/ext/opcache/Optimizer/dce.c (revision 29fa4d20)
1 /*
2    +----------------------------------------------------------------------+
3    | Zend Engine, DCE - Dead Code Elimination                             |
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: 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 optimization, 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_OP:
205 			return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
206 				|| ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE;
207 		case ZEND_ASSIGN_DIM:
208 		case ZEND_ASSIGN_OBJ:
209 			if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
210 				|| ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
211 				return 1;
212 			}
213 			if (!reorder_dtor_effects) {
214 				opline++;
215 				ssa_op++;
216 				if (opline->op1_type != IS_CONST
217 					&& (OP1_INFO() & MAY_HAVE_DTOR)) {
218 					/* DCE might shorten lifetime */
219 					return 1;
220 				}
221 			}
222 			return 0;
223 		case ZEND_PRE_INC_OBJ:
224 		case ZEND_PRE_DEC_OBJ:
225 		case ZEND_POST_INC_OBJ:
226 		case ZEND_POST_DEC_OBJ:
227 			if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
228 				|| ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
229 				return 1;
230 			}
231 			return 0;
232 		case ZEND_BIND_STATIC:
233 			if (op_array->static_variables
234 			 && (opline->extended_value & ZEND_BIND_REF) != 0) {
235 				zval *value =
236 					(zval*)((char*)op_array->static_variables->arData +
237 						(opline->extended_value & ~ZEND_BIND_REF));
238 				if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
239 					/* AST may contain undefined constants */
240 					return 1;
241 				}
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,zend_ssa * ssa,int check)270 static zend_always_inline void add_operands_to_worklists(context *ctx, zend_op *opline, zend_ssa_op *ssa_op, zend_ssa *ssa, 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 		 || (opline->opcode == ZEND_ASSIGN
277 		  && (ssa->var_info[ssa_op->op1_use].type & MAY_BE_REF) != 0)) {
278 			add_to_worklists(ctx, ssa_op->op1_use, check);
279 		} else {
280 			add_to_phi_worklist_no_val(ctx, ssa_op->op1_use);
281 		}
282 	}
283 	if (ssa_op->op2_use >= 0) {
284 		if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)
285 		 || (opline->opcode == ZEND_FE_FETCH_R
286 		  && (ssa->var_info[ssa_op->op2_use].type & MAY_BE_REF) != 0)) {
287 			add_to_worklists(ctx, ssa_op->op2_use, check);
288 		} else {
289 			add_to_phi_worklist_no_val(ctx, ssa_op->op2_use);
290 		}
291 	}
292 }
293 
add_phi_sources_to_worklists(context * ctx,zend_ssa_phi * phi,int check)294 static zend_always_inline void add_phi_sources_to_worklists(context *ctx, zend_ssa_phi *phi, int check) {
295 	zend_ssa *ssa = ctx->ssa;
296 	int source;
297 	FOREACH_PHI_SOURCE(phi, source) {
298 		add_to_worklists(ctx, source, check);
299 	} FOREACH_PHI_SOURCE_END();
300 }
301 
is_var_dead(context * ctx,int var_num)302 static inline zend_bool is_var_dead(context *ctx, int var_num) {
303 	zend_ssa_var *var = &ctx->ssa->vars[var_num];
304 	if (var->definition_phi) {
305 		return zend_bitset_in(ctx->phi_dead, var_num);
306 	} else if (var->definition >= 0) {
307 		return zend_bitset_in(ctx->instr_dead, var->definition);
308 	} else {
309 		/* Variable has no definition, so either the definition has already been removed (var is
310 		 * dead) or this is one of the implicit variables at the start of the function (for our
311 		 * purposes live) */
312 		return var_num >= ctx->op_array->last_var;
313 	}
314 }
315 
316 // Sometimes we can mark the var as EXT_UNUSED
try_remove_var_def(context * ctx,int free_var,int use_chain,zend_op * opline)317 static zend_bool try_remove_var_def(context *ctx, int free_var, int use_chain, zend_op *opline) {
318 	if (use_chain >= 0) {
319 		return 0;
320 	}
321 	zend_ssa_var *var = &ctx->ssa->vars[free_var];
322 	int def = var->definition;
323 
324 	if (def >= 0) {
325 		zend_ssa_op *def_op = &ctx->ssa->ops[def];
326 
327 		if (def_op->result_def == free_var
328 				&& var->phi_use_chain == NULL
329 				&& var->use_chain == (opline - ctx->op_array->opcodes)) {
330 			zend_op *def_opline = &ctx->op_array->opcodes[def];
331 
332 			switch (def_opline->opcode) {
333 				case ZEND_ASSIGN:
334 				case ZEND_ASSIGN_REF:
335 				case ZEND_ASSIGN_DIM:
336 				case ZEND_ASSIGN_OBJ:
337 				case ZEND_ASSIGN_OBJ_REF:
338 				case ZEND_ASSIGN_STATIC_PROP:
339 				case ZEND_ASSIGN_STATIC_PROP_REF:
340 				case ZEND_ASSIGN_OP:
341 				case ZEND_ASSIGN_DIM_OP:
342 				case ZEND_ASSIGN_OBJ_OP:
343 				case ZEND_ASSIGN_STATIC_PROP_OP:
344 				case ZEND_PRE_INC:
345 				case ZEND_PRE_DEC:
346 				case ZEND_PRE_INC_OBJ:
347 				case ZEND_POST_INC_OBJ:
348 				case ZEND_PRE_DEC_OBJ:
349 				case ZEND_POST_DEC_OBJ:
350 				case ZEND_DO_ICALL:
351 				case ZEND_DO_UCALL:
352 				case ZEND_DO_FCALL_BY_NAME:
353 				case ZEND_DO_FCALL:
354 				case ZEND_INCLUDE_OR_EVAL:
355 				case ZEND_YIELD:
356 				case ZEND_YIELD_FROM:
357 				case ZEND_ASSERT_CHECK:
358 					def_opline->result_type = IS_UNUSED;
359 					def_opline->result.var = 0;
360 					def_op->result_def = -1;
361 					var->definition = -1;
362 					return 1;
363 				default:
364 					break;
365 			}
366 		}
367 	}
368 	return 0;
369 }
370 
may_be_refcounted(uint32_t type)371 static zend_always_inline zend_bool may_be_refcounted(uint32_t type) {
372 	return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0;
373 }
374 
375 /* Returns whether the instruction has been DCEd */
dce_instr(context * ctx,zend_op * opline,zend_ssa_op * ssa_op)376 static zend_bool dce_instr(context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
377 	zend_ssa *ssa = ctx->ssa;
378 	int free_var = -1;
379 	zend_uchar free_var_type;
380 
381 	if (opline->opcode == ZEND_NOP) {
382 		return 0;
383 	}
384 
385 	/* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */
386 	if (opline->opcode == ZEND_FREE && may_be_refcounted(ssa->var_info[ssa_op->op1_use].type)
387 			&& !is_var_dead(ctx, ssa_op->op1_use)) {
388 		return 0;
389 	}
390 
391 	if ((opline->op1_type & (IS_VAR|IS_TMP_VAR))&& !is_var_dead(ctx, ssa_op->op1_use)) {
392 		if (!try_remove_var_def(ctx, ssa_op->op1_use, ssa_op->op1_use_chain, opline)) {
393 			if (may_be_refcounted(ssa->var_info[ssa_op->op1_use].type)
394 					&& opline->opcode != ZEND_CASE) {
395 				free_var = ssa_op->op1_use;
396 				free_var_type = opline->op1_type;
397 			}
398 		}
399 	}
400 	if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op2_use)) {
401 		if (!try_remove_var_def(ctx, ssa_op->op2_use, ssa_op->op2_use_chain, opline)) {
402 			if (may_be_refcounted(ssa->var_info[ssa_op->op2_use].type)) {
403 				if (free_var >= 0) {
404 					// TODO: We can't free two vars. Keep instruction alive.
405 					zend_bitset_excl(ctx->instr_dead, opline - ctx->op_array->opcodes);
406 					return 0;
407 				}
408 				free_var = ssa_op->op2_use;
409 				free_var_type = opline->op2_type;
410 			}
411 		}
412 	}
413 
414 	zend_ssa_rename_defs_of_instr(ctx->ssa, ssa_op);
415 	zend_ssa_remove_instr(ctx->ssa, opline, ssa_op);
416 
417 	if (free_var >= 0) {
418 		opline->opcode = ZEND_FREE;
419 		opline->op1.var = (uintptr_t) ZEND_CALL_VAR_NUM(NULL, ssa->vars[free_var].var);
420 		opline->op1_type = free_var_type;
421 
422 		ssa_op->op1_use = free_var;
423 		ssa_op->op1_use_chain = ssa->vars[free_var].use_chain;
424 		ssa->vars[free_var].use_chain = ssa_op - ssa->ops;
425 		return 0;
426 	}
427 	return 1;
428 }
429 
get_common_phi_source(zend_ssa * ssa,zend_ssa_phi * phi)430 static inline int get_common_phi_source(zend_ssa *ssa, zend_ssa_phi *phi) {
431 	int common_source = -1;
432 	int source;
433 	FOREACH_PHI_SOURCE(phi, source) {
434 		if (common_source == -1) {
435 			common_source = source;
436 		} else if (common_source != source && source != phi->ssa_var) {
437 			return -1;
438 		}
439 	} FOREACH_PHI_SOURCE_END();
440 	ZEND_ASSERT(common_source != -1);
441 	return common_source;
442 }
443 
try_remove_trivial_phi(context * ctx,zend_ssa_phi * phi)444 static void try_remove_trivial_phi(context *ctx, zend_ssa_phi *phi) {
445 	zend_ssa *ssa = ctx->ssa;
446 	if (phi->pi < 0) {
447 		/* Phi assignment with identical source operands */
448 		int common_source = get_common_phi_source(ssa, phi);
449 		if (common_source >= 0) {
450 			zend_ssa_rename_var_uses(ssa, phi->ssa_var, common_source, 1);
451 			zend_ssa_remove_phi(ssa, phi);
452 		}
453 	} else {
454 		/* Pi assignment that is only used in Phi/Pi assignments */
455 		// TODO What if we want to rerun type inference after DCE? Maybe separate this?
456 		/*ZEND_ASSERT(phi->sources[0] != -1);
457 		if (ssa->vars[phi->ssa_var].use_chain < 0) {
458 			zend_ssa_rename_var_uses_keep_types(ssa, phi->ssa_var, phi->sources[0], 1);
459 			zend_ssa_remove_phi(ssa, phi);
460 		}*/
461 	}
462 }
463 
may_break_varargs(const zend_op_array * op_array,const zend_ssa * ssa,const zend_ssa_op * ssa_op)464 static inline zend_bool may_break_varargs(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_op *ssa_op) {
465 	if (ssa_op->op1_def >= 0
466 			&& ssa->vars[ssa_op->op1_def].var < op_array->num_args) {
467 		return 1;
468 	}
469 	if (ssa_op->op2_def >= 0
470 			&& ssa->vars[ssa_op->op2_def].var < op_array->num_args) {
471 		return 1;
472 	}
473 	if (ssa_op->result_def >= 0
474 			&& ssa->vars[ssa_op->result_def].var < op_array->num_args) {
475 		return 1;
476 	}
477 	return 0;
478 }
479 
dce_optimize_op_array(zend_op_array * op_array,zend_ssa * ssa,zend_bool reorder_dtor_effects)480 int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reorder_dtor_effects) {
481 	int i;
482 	zend_ssa_phi *phi;
483 	int removed_ops = 0;
484 
485 	/* DCE of CV operations that changes arguments may affect vararg functions. */
486 	zend_bool has_varargs = (ssa->cfg.flags & ZEND_FUNC_VARARG) != 0;
487 
488 	context ctx;
489 	ctx.ssa = ssa;
490 	ctx.op_array = op_array;
491 	ctx.reorder_dtor_effects = reorder_dtor_effects;
492 
493 	/* We have no dedicated phi vector, so we use the whole ssa var vector instead */
494 	ctx.instr_worklist_len = zend_bitset_len(op_array->last);
495 	ctx.instr_worklist = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
496 	memset(ctx.instr_worklist, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
497 	ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count);
498 	ctx.phi_worklist = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
499 	memset(ctx.phi_worklist, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
500 	ctx.phi_worklist_no_val = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
501 	memset(ctx.phi_worklist_no_val, 0, sizeof(zend_ulong) * ctx.phi_worklist_len);
502 
503 	/* Optimistically assume all instructions and phis to be dead */
504 	ctx.instr_dead = alloca(sizeof(zend_ulong) * ctx.instr_worklist_len);
505 	memset(ctx.instr_dead, 0, sizeof(zend_ulong) * ctx.instr_worklist_len);
506 	ctx.phi_dead = alloca(sizeof(zend_ulong) * ctx.phi_worklist_len);
507 	memset(ctx.phi_dead, 0xff, sizeof(zend_ulong) * ctx.phi_worklist_len);
508 
509 	/* Mark non-CV phis as live. Even if the result is unused, we generally cannot remove one
510 	 * of the producing instructions, as it combines producing the result with control flow.
511 	 * This can be made more precise if there are any cases where this is not the case. */
512 	FOREACH_PHI(phi) {
513 		if (phi->var >= op_array->last_var
514 				&& may_be_refcounted(ssa->var_info[phi->ssa_var].type)) {
515 			zend_bitset_excl(ctx.phi_dead, phi->ssa_var);
516 			add_phi_sources_to_worklists(&ctx, phi, 0);
517 		}
518 	} FOREACH_PHI_END();
519 
520 	/* Mark reacable instruction without side effects as dead */
521 	int b = ssa->cfg.blocks_count;
522 	while (b > 0) {
523 		int	op_data = -1;
524 
525 		b--;
526 		zend_basic_block *block = &ssa->cfg.blocks[b];
527 		if (!(block->flags & ZEND_BB_REACHABLE)) {
528 			continue;
529 		}
530 		i = block->start + block->len;
531 		while (i > block->start) {
532 			i--;
533 
534 			if (op_array->opcodes[i].opcode == ZEND_OP_DATA) {
535 				op_data = i;
536 				continue;
537 			}
538 
539 			if (zend_bitset_in(ctx.instr_worklist, i)) {
540 				zend_bitset_excl(ctx.instr_worklist, i);
541 				add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 0);
542 				if (op_data >= 0) {
543 					add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 0);
544 				}
545 			} else if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects)
546 					|| zend_may_throw(&op_array->opcodes[i], op_array, ssa)
547 					|| (has_varargs && may_break_varargs(op_array, ssa, &ssa->ops[i]))) {
548 				if (op_array->opcodes[i].opcode == ZEND_NEW
549 						&& op_array->opcodes[i+1].opcode == ZEND_DO_FCALL
550 						&& ssa->ops[i].result_def >= 0
551 						&& ssa->vars[ssa->ops[i].result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
552 					zend_bitset_incl(ctx.instr_dead, i);
553 					zend_bitset_incl(ctx.instr_dead, i+1);
554 				} else {
555 					add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 0);
556 					if (op_data >= 0) {
557 						add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 0);
558 					}
559 				}
560 			} else {
561 				zend_bitset_incl(ctx.instr_dead, i);
562 				if (op_data >= 0) {
563 					zend_bitset_incl(ctx.instr_dead, op_data);
564 				}
565 			}
566 			op_data = -1;
567 		}
568 	}
569 
570 	/* Propagate liveness backwards to all definitions of used vars */
571 	while (!zend_bitset_empty(ctx.instr_worklist, ctx.instr_worklist_len)
572 			|| !zend_bitset_empty(ctx.phi_worklist, ctx.phi_worklist_len)) {
573 		while ((i = zend_bitset_pop_first(ctx.instr_worklist, ctx.instr_worklist_len)) >= 0) {
574 			zend_bitset_excl(ctx.instr_dead, i);
575 			add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 1);
576 			if (i < op_array->last && op_array->opcodes[i+1].opcode == ZEND_OP_DATA) {
577 				zend_bitset_excl(ctx.instr_dead, i+1);
578 				add_operands_to_worklists(&ctx, &op_array->opcodes[i+1], &ssa->ops[i+1], ssa, 1);
579 			}
580 		}
581 		while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
582 			zend_bitset_excl(ctx.phi_dead, i);
583 			zend_bitset_excl(ctx.phi_worklist_no_val, i);
584 			add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi, 1);
585 		}
586 	}
587 
588 	/* Eliminate dead instructions */
589 	ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) {
590 		removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
591 	} ZEND_BITSET_FOREACH_END();
592 
593 	/* Improper uses don't count as "uses" for the purpose of instruction elimination,
594 	 * but we have to retain phis defining them.
595 	 * Propagate this information backwards, marking any phi with an improperly used
596 	 * target as non-dead. */
597 	while ((i = zend_bitset_pop_first(ctx.phi_worklist_no_val, ctx.phi_worklist_len)) >= 0) {
598 		zend_ssa_phi *phi = ssa->vars[i].definition_phi;
599 		int source;
600 		zend_bitset_excl(ctx.phi_dead, i);
601 		FOREACH_PHI_SOURCE(phi, source) {
602 			add_to_phi_worklist_no_val(&ctx, source);
603 		} FOREACH_PHI_SOURCE_END();
604 	}
605 
606 	/* Now collect the actually dead phis */
607 	FOREACH_PHI(phi) {
608 		if (zend_bitset_in(ctx.phi_dead, phi->ssa_var)) {
609 			zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
610 			zend_ssa_remove_phi(ssa, phi);
611 		} else {
612 			/* Remove trivial phis (phis with identical source operands) */
613 			try_remove_trivial_phi(&ctx, phi);
614 		}
615 	} FOREACH_PHI_END();
616 
617 	return removed_ops;
618 }
619