/src/php-src/Zend/Optimizer/dce.c
Line | Count | Source |
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 | | |
62 | 29.4k | static inline bool is_bad_mod(const zend_ssa *ssa, int use, int def) { |
63 | 29.4k | if (def < 0) { |
64 | | /* This modification is not tracked by SSA, assume the worst */ |
65 | 5.82k | return true; |
66 | 5.82k | } |
67 | 23.5k | if (ssa->var_info[use].type & MAY_BE_REF) { |
68 | | /* Modification of reference may have side-effect */ |
69 | 9.47k | return true; |
70 | 9.47k | } |
71 | 14.1k | return false; |
72 | 23.5k | } |
73 | | |
74 | | static inline bool may_have_side_effects( |
75 | | const zend_op_array *op_array, const zend_ssa *ssa, |
76 | | const zend_op *opline, const zend_ssa_op *ssa_op, |
77 | 566k | bool reorder_dtor_effects) { |
78 | 566k | switch (opline->opcode) { |
79 | 2.81k | case ZEND_NOP: |
80 | 2.91k | case ZEND_IS_IDENTICAL: |
81 | 2.93k | case ZEND_IS_NOT_IDENTICAL: |
82 | 5.90k | case ZEND_QM_ASSIGN: |
83 | 9.21k | case ZEND_FE_FREE: |
84 | 9.40k | case ZEND_TYPE_CHECK: |
85 | 9.40k | case ZEND_DEFINED: |
86 | 10.5k | case ZEND_ADD: |
87 | 10.9k | case ZEND_SUB: |
88 | 11.4k | case ZEND_MUL: |
89 | 11.4k | case ZEND_POW: |
90 | 11.5k | case ZEND_BW_OR: |
91 | 11.6k | case ZEND_BW_AND: |
92 | 11.9k | case ZEND_BW_XOR: |
93 | 13.0k | case ZEND_CONCAT: |
94 | 13.6k | case ZEND_FAST_CONCAT: |
95 | 13.7k | case ZEND_DIV: |
96 | 13.8k | case ZEND_MOD: |
97 | 14.1k | case ZEND_BOOL_XOR: |
98 | 14.6k | case ZEND_BOOL: |
99 | 14.9k | case ZEND_BOOL_NOT: |
100 | 15.0k | case ZEND_BW_NOT: |
101 | 15.2k | case ZEND_SL: |
102 | 15.3k | case ZEND_SR: |
103 | 15.8k | case ZEND_IS_EQUAL: |
104 | 16.3k | case ZEND_IS_NOT_EQUAL: |
105 | 18.0k | case ZEND_IS_SMALLER: |
106 | 18.1k | case ZEND_IS_SMALLER_OR_EQUAL: |
107 | 18.1k | case ZEND_CASE: |
108 | 18.1k | case ZEND_CASE_STRICT: |
109 | 18.1k | case ZEND_CAST: |
110 | 18.1k | case ZEND_ROPE_INIT: |
111 | 18.1k | case ZEND_ROPE_ADD: |
112 | 18.3k | case ZEND_INIT_ARRAY: |
113 | 18.3k | case ZEND_SPACESHIP: |
114 | 19.0k | case ZEND_STRLEN: |
115 | 19.1k | case ZEND_COUNT: |
116 | 19.1k | case ZEND_GET_TYPE: |
117 | 19.1k | case ZEND_ISSET_ISEMPTY_THIS: |
118 | 19.3k | case ZEND_ISSET_ISEMPTY_DIM_OBJ: |
119 | 19.4k | case ZEND_FETCH_DIM_IS: |
120 | 19.4k | case ZEND_ISSET_ISEMPTY_CV: |
121 | 19.4k | case ZEND_ISSET_ISEMPTY_VAR: |
122 | 19.4k | case ZEND_FETCH_IS: |
123 | 19.4k | case ZEND_IN_ARRAY: |
124 | 19.4k | case ZEND_FUNC_NUM_ARGS: |
125 | 19.4k | case ZEND_FUNC_GET_ARGS: |
126 | 19.4k | case ZEND_ARRAY_KEY_EXISTS: |
127 | 20.2k | case ZEND_COPY_TMP: |
128 | | /* No side effects */ |
129 | 20.2k | return false; |
130 | 15.0k | case ZEND_FREE: |
131 | 15.0k | return opline->extended_value == ZEND_FREE_VOID_CAST; |
132 | 97 | case ZEND_ADD_ARRAY_ELEMENT: |
133 | | /* TODO: We can't free two vars. Keep instruction alive. <?php [0, "$a" => "$b"]; */ |
134 | 97 | if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) { |
135 | 5 | return true; |
136 | 5 | } |
137 | 92 | return false; |
138 | 291 | case ZEND_ROPE_END: |
139 | | /* TODO: Rope dce optimization, see #76446 */ |
140 | 291 | return true; |
141 | 11.7k | case ZEND_JMP: |
142 | 17.4k | case ZEND_JMPZ: |
143 | 24.7k | case ZEND_JMPNZ: |
144 | 25.1k | case ZEND_JMPZ_EX: |
145 | 25.5k | case ZEND_JMPNZ_EX: |
146 | 26.2k | case ZEND_JMP_SET: |
147 | 26.4k | case ZEND_COALESCE: |
148 | 26.9k | case ZEND_ASSERT_CHECK: |
149 | 27.6k | case ZEND_JMP_NULL: |
150 | 27.7k | case ZEND_BIND_INIT_STATIC_OR_JMP: |
151 | 27.7k | case ZEND_JMP_FRAMELESS: |
152 | | /* For our purposes a jumps and branches are side effects. */ |
153 | 27.7k | return true; |
154 | 0 | case ZEND_BEGIN_SILENCE: |
155 | 71.7k | case ZEND_END_SILENCE: |
156 | 103k | case ZEND_ECHO: |
157 | 103k | case ZEND_INCLUDE_OR_EVAL: |
158 | 104k | case ZEND_THROW: |
159 | 104k | case ZEND_MATCH_ERROR: |
160 | 104k | case ZEND_EXT_STMT: |
161 | 104k | case ZEND_EXT_FCALL_BEGIN: |
162 | 104k | case ZEND_EXT_FCALL_END: |
163 | 104k | case ZEND_TICKS: |
164 | 106k | case ZEND_YIELD: |
165 | 106k | case ZEND_VERIFY_NEVER_TYPE: |
166 | | /* Intrinsic side effects */ |
167 | 106k | return true; |
168 | 395 | case ZEND_YIELD_FROM: { |
169 | 395 | uint32_t t1 = OP1_INFO(); |
170 | 395 | if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY && MAY_BE_EMPTY_ONLY(t1)) { |
171 | 14 | return false; |
172 | 14 | } |
173 | 381 | return true; |
174 | 395 | } |
175 | 76.1k | case ZEND_DO_FCALL: |
176 | 76.1k | case ZEND_DO_FCALL_BY_NAME: |
177 | 76.1k | case ZEND_DO_ICALL: |
178 | 80.8k | case ZEND_DO_UCALL: |
179 | 80.8k | case ZEND_FRAMELESS_ICALL_0: |
180 | 80.8k | case ZEND_FRAMELESS_ICALL_1: |
181 | 80.8k | case ZEND_FRAMELESS_ICALL_2: |
182 | 80.8k | case ZEND_FRAMELESS_ICALL_3: |
183 | | /* For now assume all calls have side effects */ |
184 | 80.8k | return true; |
185 | 7.05k | case ZEND_RECV: |
186 | 8.25k | case ZEND_RECV_INIT: |
187 | | /* Even though RECV_INIT can be side-effect free, these cannot be simply dropped |
188 | | * due to the prologue skipping code. */ |
189 | 8.25k | return true; |
190 | 505 | case ZEND_ASSIGN_REF: |
191 | 505 | return true; |
192 | 16.8k | case ZEND_ASSIGN: |
193 | 16.8k | { |
194 | 16.8k | if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)) { |
195 | 6.93k | return true; |
196 | 6.93k | } |
197 | 9.88k | if (!reorder_dtor_effects) { |
198 | 9.88k | if (opline->op2_type != IS_CONST |
199 | 3.90k | && (OP2_INFO() & MAY_HAVE_DTOR) |
200 | 1.09k | && ssa->vars[ssa_op->op2_use].escape_state != ESCAPE_STATE_NO_ESCAPE) { |
201 | | /* DCE might shorten lifetime */ |
202 | 946 | return true; |
203 | 946 | } |
204 | 9.88k | } |
205 | 8.93k | return false; |
206 | 9.88k | } |
207 | 74 | case ZEND_UNSET_VAR: |
208 | 74 | return true; |
209 | 1.43k | case ZEND_UNSET_CV: |
210 | 1.43k | { |
211 | 1.43k | uint32_t t1 = OP1_INFO(); |
212 | 1.43k | if (t1 & MAY_BE_REF) { |
213 | | /* We don't consider uses as the LHS of an assignment as real uses during DCE, so |
214 | | * an unset may be considered dead even if there is a later assignment to the |
215 | | * variable. Removing the unset in this case would not be correct if the variable |
216 | | * is a reference, because unset breaks references. */ |
217 | 853 | return true; |
218 | 853 | } |
219 | 577 | return false; |
220 | 1.43k | } |
221 | 3.05k | case ZEND_PRE_INC: |
222 | 3.29k | case ZEND_POST_INC: |
223 | 3.38k | case ZEND_PRE_DEC: |
224 | 3.49k | case ZEND_POST_DEC: |
225 | 3.49k | return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def); |
226 | 1.03k | case ZEND_ASSIGN_OP: |
227 | 1.03k | return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def) |
228 | 744 | || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE; |
229 | 3.09k | case ZEND_ASSIGN_DIM: |
230 | 7.87k | case ZEND_ASSIGN_OBJ: |
231 | 7.87k | if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def) |
232 | 7.41k | || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) { |
233 | 7.41k | return true; |
234 | 7.41k | } |
235 | 461 | if (!reorder_dtor_effects) { |
236 | 461 | opline++; |
237 | 461 | ssa_op++; |
238 | 461 | if (opline->op1_type != IS_CONST |
239 | 314 | && (OP1_INFO() & MAY_HAVE_DTOR)) { |
240 | | /* DCE might shorten lifetime */ |
241 | 108 | return true; |
242 | 108 | } |
243 | 461 | } |
244 | 353 | return false; |
245 | 138 | case ZEND_PRE_INC_OBJ: |
246 | 182 | case ZEND_PRE_DEC_OBJ: |
247 | 182 | case ZEND_POST_INC_OBJ: |
248 | 182 | case ZEND_POST_DEC_OBJ: |
249 | 182 | if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def) |
250 | 174 | || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) { |
251 | 174 | return true; |
252 | 174 | } |
253 | 8 | return false; |
254 | 374 | case ZEND_BIND_STATIC: |
255 | 374 | if (op_array->static_variables) { |
256 | | /* Implicit and Explicit bind static is effectively prologue of closure so |
257 | | report it has side effects like RECV, RECV_INIT; This allows us to |
258 | | reflect on the closure and discover used variable at runtime */ |
259 | 374 | if ((opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) { |
260 | 186 | return true; |
261 | 186 | } |
262 | | /* Modifies static variables which are observable through reflection */ |
263 | 188 | if ((opline->extended_value & ZEND_BIND_REF) && opline->op2_type != IS_UNUSED) { |
264 | 132 | return true; |
265 | 132 | } |
266 | 188 | } |
267 | 56 | return false; |
268 | 44 | case ZEND_CHECK_VAR: |
269 | 44 | return (OP1_INFO() & MAY_BE_UNDEF) != 0; |
270 | 2 | case ZEND_FE_RESET_R: |
271 | 2 | case ZEND_FE_RESET_RW: |
272 | | /* Model as not having side-effects -- let the side-effect be introduced by |
273 | | * FE_FETCH if the array is not known to be non-empty. */ |
274 | 2 | return (OP1_INFO() & MAY_BE_ANY) != MAY_BE_ARRAY; |
275 | 275k | default: |
276 | | /* For everything we didn't handle, assume a side-effect */ |
277 | 275k | return true; |
278 | 566k | } |
279 | 566k | } |
280 | | |
281 | 1.18M | static zend_always_inline void add_to_worklists(const context *ctx, int var_num, int check) { |
282 | 1.18M | const zend_ssa_var *var = &ctx->ssa->vars[var_num]; |
283 | 1.18M | if (var->definition >= 0) { |
284 | 830k | if (!check || zend_bitset_in(ctx->instr_dead, var->definition)) { |
285 | 743k | zend_bitset_incl(ctx->instr_worklist, var->definition); |
286 | 743k | } |
287 | 830k | } else if (var->definition_phi) { |
288 | 268k | if (!check || zend_bitset_in(ctx->phi_dead, var_num)) { |
289 | 169k | zend_bitset_incl(ctx->phi_worklist, var_num); |
290 | 169k | } |
291 | 268k | } |
292 | 1.18M | } |
293 | | |
294 | 32.6k | static inline void add_to_phi_worklist_no_val(const context *ctx, int var_num) { |
295 | 32.6k | const zend_ssa_var *var = &ctx->ssa->vars[var_num]; |
296 | 32.6k | if (var->definition_phi && zend_bitset_in(ctx->phi_dead, var_num)) { |
297 | 5.33k | zend_bitset_incl(ctx->phi_worklist_no_val, var_num); |
298 | 5.33k | } |
299 | 32.6k | } |
300 | | |
301 | 1.23M | 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) { |
302 | 1.23M | if (ssa_op->result_use >= 0) { |
303 | 14.1k | add_to_worklists(ctx, ssa_op->result_use, check); |
304 | 14.1k | } |
305 | 1.23M | if (ssa_op->op1_use >= 0) { |
306 | 710k | if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use) |
307 | 54.0k | || (opline->opcode == ZEND_ASSIGN |
308 | 688k | && (ssa->var_info[ssa_op->op1_use].type & MAY_BE_REF) != 0)) { |
309 | 688k | add_to_worklists(ctx, ssa_op->op1_use, check); |
310 | 688k | } else { |
311 | 22.1k | add_to_phi_worklist_no_val(ctx, ssa_op->op1_use); |
312 | 22.1k | } |
313 | 710k | } |
314 | 1.23M | if (ssa_op->op2_use >= 0) { |
315 | 208k | if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use) |
316 | 2.98k | || (opline->opcode == ZEND_FE_FETCH_R |
317 | 207k | && (ssa->var_info[ssa_op->op2_use].type & MAY_BE_REF) != 0)) { |
318 | 207k | add_to_worklists(ctx, ssa_op->op2_use, check); |
319 | 207k | } else { |
320 | 1.44k | add_to_phi_worklist_no_val(ctx, ssa_op->op2_use); |
321 | 1.44k | } |
322 | 208k | } |
323 | 1.23M | } |
324 | | |
325 | 155k | static zend_always_inline void add_phi_sources_to_worklists(const context *ctx, zend_ssa_phi *phi, int check) { |
326 | 155k | const zend_ssa *ssa = ctx->ssa; |
327 | 155k | int source; |
328 | 707k | FOREACH_PHI_SOURCE(phi, source) { |
329 | 707k | add_to_worklists(ctx, source, check); |
330 | 707k | } FOREACH_PHI_SOURCE_END(); |
331 | 155k | } |
332 | | |
333 | 13.5k | static inline bool is_var_dead(const context *ctx, int var_num) { |
334 | 13.5k | const zend_ssa_var *var = &ctx->ssa->vars[var_num]; |
335 | 13.5k | if (var->definition_phi) { |
336 | 1.09k | return zend_bitset_in(ctx->phi_dead, var_num); |
337 | 12.4k | } else if (var->definition >= 0) { |
338 | 8.02k | return zend_bitset_in(ctx->instr_dead, var->definition); |
339 | 8.02k | } else { |
340 | | /* Variable has no definition, so either the definition has already been removed (var is |
341 | | * dead) or this is one of the implicit variables at the start of the function (for our |
342 | | * purposes live) */ |
343 | 4.41k | return var_num >= ctx->op_array->last_var; |
344 | 4.41k | } |
345 | 13.5k | } |
346 | | |
347 | | // Sometimes we can mark the var as EXT_UNUSED |
348 | 6.07k | static bool try_remove_var_def(const context *ctx, int free_var, int use_chain, const zend_op *opline) { |
349 | 6.07k | if (use_chain >= 0) { |
350 | 782 | return false; |
351 | 782 | } |
352 | 5.29k | zend_ssa_var *var = &ctx->ssa->vars[free_var]; |
353 | 5.29k | int def = var->definition; |
354 | | |
355 | 5.29k | if (def >= 0) { |
356 | 5.22k | zend_ssa_op *def_op = &ctx->ssa->ops[def]; |
357 | | |
358 | 5.22k | if (def_op->result_def == free_var |
359 | 5.22k | && var->phi_use_chain == NULL |
360 | 5.22k | && var->use_chain == (opline - ctx->op_array->opcodes)) { |
361 | 5.11k | zend_op *def_opline = &ctx->op_array->opcodes[def]; |
362 | | |
363 | 5.11k | switch (def_opline->opcode) { |
364 | 132 | case ZEND_ASSIGN: |
365 | 132 | case ZEND_ASSIGN_REF: |
366 | 143 | case ZEND_ASSIGN_DIM: |
367 | 147 | case ZEND_ASSIGN_OBJ: |
368 | 147 | case ZEND_ASSIGN_OBJ_REF: |
369 | 147 | case ZEND_ASSIGN_STATIC_PROP: |
370 | 147 | case ZEND_ASSIGN_STATIC_PROP_REF: |
371 | 264 | case ZEND_ASSIGN_OP: |
372 | 274 | case ZEND_ASSIGN_DIM_OP: |
373 | 274 | case ZEND_ASSIGN_OBJ_OP: |
374 | 274 | case ZEND_ASSIGN_STATIC_PROP_OP: |
375 | 274 | case ZEND_PRE_INC: |
376 | 278 | case ZEND_PRE_DEC: |
377 | 278 | case ZEND_PRE_INC_OBJ: |
378 | 278 | case ZEND_PRE_DEC_OBJ: |
379 | 278 | case ZEND_DO_ICALL: |
380 | 280 | case ZEND_DO_UCALL: |
381 | 280 | case ZEND_DO_FCALL_BY_NAME: |
382 | 454 | case ZEND_DO_FCALL: |
383 | 454 | case ZEND_INCLUDE_OR_EVAL: |
384 | 454 | case ZEND_YIELD: |
385 | 454 | case ZEND_YIELD_FROM: |
386 | 454 | case ZEND_ASSERT_CHECK: |
387 | 454 | def_opline->result_type = IS_UNUSED; |
388 | 454 | def_opline->result.var = 0; |
389 | 454 | def_op->result_def = -1; |
390 | 454 | var->definition = -1; |
391 | 454 | return true; |
392 | 4.65k | default: |
393 | 4.65k | break; |
394 | 5.11k | } |
395 | 5.11k | } |
396 | 5.22k | } |
397 | 4.83k | return false; |
398 | 5.29k | } |
399 | | |
400 | 48.9k | static zend_always_inline bool may_be_refcounted(uint32_t type) { |
401 | 48.9k | return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0; |
402 | 48.9k | } |
403 | | |
404 | 17.1k | static inline bool is_free_of_live_var(const context *ctx, const zend_op *opline, const zend_ssa_op *ssa_op) { |
405 | 17.1k | switch (opline->opcode) { |
406 | 7.30k | case ZEND_FREE: |
407 | | /* It is always safe to remove FREEs of non-refcounted values, even if they are live. */ |
408 | 7.30k | if ((ctx->ssa->var_info[ssa_op->op1_use].type & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) != 0 |
409 | 7.27k | && !may_be_refcounted(ctx->ssa->var_info[ssa_op->op1_use].type)) { |
410 | 5.45k | return false; |
411 | 5.45k | } |
412 | 1.85k | ZEND_FALLTHROUGH; |
413 | 2.48k | case ZEND_FE_FREE: |
414 | 2.48k | return !is_var_dead(ctx, ssa_op->op1_use); |
415 | 9.23k | default: |
416 | 9.23k | return false; |
417 | 17.1k | } |
418 | 17.1k | } |
419 | | |
420 | | /* Returns whether the instruction has been DCEd */ |
421 | 19.9k | static bool dce_instr(const context *ctx, zend_op *opline, zend_ssa_op *ssa_op) { |
422 | 19.9k | const zend_ssa *ssa = ctx->ssa; |
423 | 19.9k | int free_var = -1; |
424 | 19.9k | uint8_t free_var_type; |
425 | | |
426 | 19.9k | if (opline->opcode == ZEND_NOP) { |
427 | 2.81k | return false; |
428 | 2.81k | } |
429 | | |
430 | | /* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */ |
431 | 17.1k | if (is_free_of_live_var(ctx, opline, ssa_op)) { |
432 | 2.14k | return false; |
433 | 2.14k | } |
434 | | |
435 | 15.0k | if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op1_use)) { |
436 | 4.91k | if (!try_remove_var_def(ctx, ssa_op->op1_use, ssa_op->op1_use_chain, opline)) { |
437 | 4.75k | if (may_be_refcounted(ssa->var_info[ssa_op->op1_use].type) |
438 | 951 | && opline->opcode != ZEND_CASE |
439 | 951 | && opline->opcode != ZEND_CASE_STRICT |
440 | 951 | && opline->opcode != ZEND_COPY_TMP) { |
441 | 169 | free_var = ssa_op->op1_use; |
442 | 169 | free_var_type = opline->op1_type; |
443 | 169 | } |
444 | 4.75k | } |
445 | 4.91k | } |
446 | 15.0k | if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op2_use)) { |
447 | 1.16k | if (!try_remove_var_def(ctx, ssa_op->op2_use, ssa_op->op2_use_chain, opline)) { |
448 | 864 | if (may_be_refcounted(ssa->var_info[ssa_op->op2_use].type)) { |
449 | 264 | if (free_var >= 0) { |
450 | | // TODO: We can't free two vars. Keep instruction alive. |
451 | 70 | zend_bitset_excl(ctx->instr_dead, opline - ctx->op_array->opcodes); |
452 | 70 | return false; |
453 | 70 | } |
454 | 194 | free_var = ssa_op->op2_use; |
455 | 194 | free_var_type = opline->op2_type; |
456 | 194 | } |
457 | 864 | } |
458 | 1.16k | } |
459 | | |
460 | 14.9k | zend_ssa_rename_defs_of_instr(ctx->ssa, ssa_op); |
461 | 14.9k | zend_ssa_remove_instr(ctx->ssa, opline, ssa_op); |
462 | | |
463 | 14.9k | if (free_var >= 0) { |
464 | 293 | opline->opcode = ZEND_FREE; |
465 | 293 | opline->op1.var = EX_NUM_TO_VAR(ssa->vars[free_var].var); |
466 | 293 | opline->op1_type = free_var_type; |
467 | | |
468 | 293 | ssa_op->op1_use = free_var; |
469 | 293 | ssa_op->op1_use_chain = ssa->vars[free_var].use_chain; |
470 | 293 | ssa->vars[free_var].use_chain = ssa_op - ssa->ops; |
471 | 293 | return false; |
472 | 293 | } |
473 | 14.6k | return true; |
474 | 14.9k | } |
475 | | |
476 | 88.7k | static inline int get_common_phi_source(const zend_ssa *ssa, zend_ssa_phi *phi) { |
477 | 88.7k | int common_source = -1; |
478 | 88.7k | int source; |
479 | 446k | FOREACH_PHI_SOURCE(phi, source) { |
480 | 446k | if (source == phi->ssa_var) { |
481 | 1.15k | continue; |
482 | 1.15k | } |
483 | 177k | if (common_source == -1) { |
484 | 88.7k | common_source = source; |
485 | 88.7k | } else if (common_source != source) { |
486 | 88.2k | return -1; |
487 | 88.2k | } |
488 | 177k | } FOREACH_PHI_SOURCE_END(); |
489 | | |
490 | | /* If all sources are phi->ssa_var this phi must be in an unreachable cycle. |
491 | | * We can't easily drop the phi in that case, as we don't have something to replace it with. |
492 | | * Ideally SCCP would eliminate the whole cycle. */ |
493 | 477 | return common_source; |
494 | 88.7k | } |
495 | | |
496 | 127k | static void try_remove_trivial_phi(const context *ctx, zend_ssa_phi *phi) { |
497 | 127k | zend_ssa *ssa = ctx->ssa; |
498 | 127k | if (phi->pi < 0) { |
499 | | /* Phi assignment with identical source operands */ |
500 | 88.7k | int common_source = get_common_phi_source(ssa, phi); |
501 | 88.7k | if (common_source >= 0) { |
502 | 477 | zend_ssa_rename_var_uses(ssa, phi->ssa_var, common_source, 1); |
503 | 477 | zend_ssa_remove_phi(ssa, phi); |
504 | 477 | } |
505 | 88.7k | } else { |
506 | | /* Pi assignment that is only used in Phi/Pi assignments */ |
507 | | // TODO What if we want to rerun type inference after DCE? Maybe separate this? |
508 | | /*ZEND_ASSERT(phi->sources[0] != -1); |
509 | | if (ssa->vars[phi->ssa_var].use_chain < 0) { |
510 | | zend_ssa_rename_var_uses_keep_types(ssa, phi->ssa_var, phi->sources[0], 1); |
511 | | zend_ssa_remove_phi(ssa, phi); |
512 | | }*/ |
513 | 39.1k | } |
514 | 127k | } |
515 | | |
516 | 4 | static inline bool may_break_varargs(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_op *ssa_op) { |
517 | 4 | if (ssa_op->op1_def >= 0 |
518 | 0 | && ssa->vars[ssa_op->op1_def].var < op_array->num_args) { |
519 | 0 | return true; |
520 | 0 | } |
521 | 4 | if (ssa_op->op2_def >= 0 |
522 | 0 | && ssa->vars[ssa_op->op2_def].var < op_array->num_args) { |
523 | 0 | return true; |
524 | 0 | } |
525 | 4 | if (ssa_op->result_def >= 0 |
526 | 2 | && ssa->vars[ssa_op->result_def].var < op_array->num_args) { |
527 | 0 | return true; |
528 | 0 | } |
529 | 4 | return false; |
530 | 4 | } |
531 | | |
532 | 20.1k | static inline bool may_throw_dce_exception(const zend_op *opline) { |
533 | 20.1k | return opline->opcode == ZEND_ADD_ARRAY_ELEMENT && opline->op2_type == IS_UNUSED; |
534 | 20.1k | } |
535 | | |
536 | 61.5k | int dce_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *optimizer_ctx, zend_ssa *ssa, bool reorder_dtor_effects) { |
537 | 61.5k | int i; |
538 | 61.5k | zend_ssa_phi *phi; |
539 | 61.5k | int removed_ops = 0; |
540 | | |
541 | | /* DCE of CV operations that changes arguments may affect vararg functions. */ |
542 | 61.5k | bool has_varargs = (ssa->cfg.flags & ZEND_FUNC_VARARG) != 0; |
543 | | |
544 | 61.5k | context ctx; |
545 | 61.5k | ctx.ssa = ssa; |
546 | 61.5k | ctx.op_array = op_array; |
547 | 61.5k | ctx.reorder_dtor_effects = reorder_dtor_effects; |
548 | | |
549 | 61.5k | void *checkpoint = zend_arena_checkpoint(optimizer_ctx->arena); |
550 | | /* We have no dedicated phi vector, so we use the whole ssa var vector instead */ |
551 | 61.5k | ctx.instr_worklist_len = zend_bitset_len(op_array->last); |
552 | 61.5k | ctx.instr_worklist = zend_arena_calloc(&optimizer_ctx->arena, ctx.instr_worklist_len, sizeof(zend_ulong)); |
553 | 61.5k | ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count); |
554 | 61.5k | ctx.phi_worklist = zend_arena_calloc(&optimizer_ctx->arena, ctx.phi_worklist_len, sizeof(zend_ulong)); |
555 | 61.5k | ctx.phi_worklist_no_val = zend_arena_calloc(&optimizer_ctx->arena, ctx.phi_worklist_len, sizeof(zend_ulong)); |
556 | | |
557 | | /* Optimistically assume all instructions and phis to be dead */ |
558 | 61.5k | ctx.instr_dead = zend_arena_calloc(&optimizer_ctx->arena, ctx.instr_worklist_len, sizeof(zend_ulong)); |
559 | 61.5k | ctx.phi_dead = zend_arena_alloc(&optimizer_ctx->arena, ctx.phi_worklist_len * sizeof(zend_ulong)); |
560 | 61.5k | memset(ctx.phi_dead, 0xff, sizeof(zend_ulong) * ctx.phi_worklist_len); |
561 | | |
562 | | /* Mark non-CV phis as live. Even if the result is unused, we generally cannot remove one |
563 | | * of the producing instructions, as it combines producing the result with control flow. |
564 | | * This can be made more precise if there are any cases where this is not the case. */ |
565 | 372k | FOREACH_PHI(phi) { |
566 | 372k | if (phi->var >= op_array->last_var |
567 | 36.1k | && may_be_refcounted(ssa->var_info[phi->ssa_var].type)) { |
568 | 32.6k | zend_bitset_excl(ctx.phi_dead, phi->ssa_var); |
569 | 32.6k | add_phi_sources_to_worklists(&ctx, phi, 0); |
570 | 32.6k | } |
571 | 372k | } FOREACH_PHI_END(); |
572 | | |
573 | | /* Mark reachable instruction without side effects as dead */ |
574 | 61.5k | int b = ssa->cfg.blocks_count; |
575 | 240k | while (b > 0) { |
576 | 179k | int op_data = -1; |
577 | | |
578 | 179k | b--; |
579 | 179k | const zend_basic_block *block = &ssa->cfg.blocks[b]; |
580 | 179k | if (!(block->flags & ZEND_BB_REACHABLE)) { |
581 | 4.82k | continue; |
582 | 4.82k | } |
583 | 174k | i = block->start + block->len; |
584 | 1.42M | while (i > block->start) { |
585 | 1.25M | i--; |
586 | | |
587 | 1.25M | if (op_array->opcodes[i].opcode == ZEND_OP_DATA) { |
588 | 16.7k | op_data = i; |
589 | 16.7k | continue; |
590 | 16.7k | } |
591 | | |
592 | 1.23M | if (zend_bitset_in(ctx.instr_worklist, i)) { |
593 | 671k | zend_bitset_excl(ctx.instr_worklist, i); |
594 | 671k | add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 0); |
595 | 671k | if (op_data >= 0) { |
596 | 7.27k | add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 0); |
597 | 7.27k | } |
598 | 671k | } else if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects) |
599 | 47.4k | || (zend_may_throw(&op_array->opcodes[i], &ssa->ops[i], op_array, ssa) |
600 | 20.1k | && !may_throw_dce_exception(&op_array->opcodes[i])) |
601 | 538k | || (has_varargs && may_break_varargs(op_array, ssa, &ssa->ops[i]))) { |
602 | 538k | if (op_array->opcodes[i].opcode == ZEND_NEW |
603 | 74 | && op_array->opcodes[i+1].opcode == ZEND_DO_FCALL |
604 | 74 | && ssa->ops[i].result_def >= 0 |
605 | 74 | && ssa->vars[ssa->ops[i].result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) { |
606 | 72 | zend_bitset_incl(ctx.instr_dead, i); |
607 | 72 | zend_bitset_incl(ctx.instr_dead, i+1); |
608 | 538k | } else { |
609 | 538k | add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 0); |
610 | 538k | if (op_data >= 0) { |
611 | 9.42k | add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 0); |
612 | 9.42k | } |
613 | 538k | } |
614 | 538k | } else { |
615 | 27.2k | zend_bitset_incl(ctx.instr_dead, i); |
616 | 27.2k | if (op_data >= 0) { |
617 | 72 | zend_bitset_incl(ctx.instr_dead, op_data); |
618 | 72 | } |
619 | 27.2k | } |
620 | 1.23M | op_data = -1; |
621 | 1.23M | } |
622 | 174k | } |
623 | | |
624 | | /* Propagate liveness backwards to all definitions of used vars */ |
625 | 72.0k | while (!zend_bitset_empty(ctx.instr_worklist, ctx.instr_worklist_len) |
626 | 69.9k | || !zend_bitset_empty(ctx.phi_worklist, ctx.phi_worklist_len)) { |
627 | 18.0k | while ((i = zend_bitset_pop_first(ctx.instr_worklist, ctx.instr_worklist_len)) >= 0) { |
628 | 7.57k | zend_bitset_excl(ctx.instr_dead, i); |
629 | 7.57k | add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 1); |
630 | 7.57k | if (i < op_array->last |
631 | 7.57k | && (op_array->opcodes[i+1].opcode == ZEND_OP_DATA |
632 | 7.54k | || (op_array->opcodes[i].opcode == ZEND_NEW |
633 | 35 | && op_array->opcodes[i+1].opcode == ZEND_DO_FCALL))) { |
634 | 35 | zend_bitset_excl(ctx.instr_dead, i+1); |
635 | 35 | add_operands_to_worklists(&ctx, &op_array->opcodes[i+1], &ssa->ops[i+1], ssa, 1); |
636 | 35 | } |
637 | 7.57k | } |
638 | 133k | while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) { |
639 | 123k | zend_bitset_excl(ctx.phi_dead, i); |
640 | 123k | zend_bitset_excl(ctx.phi_worklist_no_val, i); |
641 | 123k | add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi, 1); |
642 | 123k | } |
643 | 10.4k | } |
644 | | |
645 | | /* Eliminate dead instructions */ |
646 | 269k | ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) { |
647 | 19.9k | removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]); |
648 | 19.9k | } ZEND_BITSET_FOREACH_END(); |
649 | | |
650 | | /* Improper uses don't count as "uses" for the purpose of instruction elimination, |
651 | | * but we have to retain phis defining them. |
652 | | * Propagate this information backwards, marking any phi with an improperly used |
653 | | * target as non-dead. */ |
654 | 66.1k | while ((i = zend_bitset_pop_first(ctx.phi_worklist_no_val, ctx.phi_worklist_len)) >= 0) { |
655 | 4.55k | zend_ssa_phi *phi = ssa->vars[i].definition_phi; |
656 | 4.55k | int source; |
657 | 4.55k | zend_bitset_excl(ctx.phi_dead, i); |
658 | 22.7k | FOREACH_PHI_SOURCE(phi, source) { |
659 | 22.7k | add_to_phi_worklist_no_val(&ctx, source); |
660 | 22.7k | } FOREACH_PHI_SOURCE_END(); |
661 | 4.55k | } |
662 | | |
663 | | /* Now collect the actually dead phis */ |
664 | 372k | FOREACH_PHI(phi) { |
665 | 372k | if (zend_bitset_in(ctx.phi_dead, phi->ssa_var)) { |
666 | 3.62k | zend_ssa_remove_uses_of_var(ssa, phi->ssa_var); |
667 | 3.62k | zend_ssa_remove_phi(ssa, phi); |
668 | 127k | } else { |
669 | | /* Remove trivial phis (phis with identical source operands) */ |
670 | 127k | try_remove_trivial_phi(&ctx, phi); |
671 | 127k | } |
672 | 372k | } FOREACH_PHI_END(); |
673 | | |
674 | 61.5k | zend_arena_release(&optimizer_ctx->arena, checkpoint); |
675 | | |
676 | 61.5k | return removed_ops; |
677 | 61.5k | } |