Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}