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