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