Coverage Report

Created: 2026-06-13 07:01

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 © 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
38.3k
static inline bool is_bad_mod(const zend_ssa *ssa, int use, int def) {
61
38.3k
  if (def < 0) {
62
    /* This modification is not tracked by SSA, assume the worst */
63
7.15k
    return true;
64
7.15k
  }
65
31.1k
  if (ssa->var_info[use].type & MAY_BE_REF) {
66
    /* Modification of reference may have side-effect */
67
11.9k
    return true;
68
11.9k
  }
69
19.2k
  return false;
70
31.1k
}
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
684k
    bool reorder_dtor_effects) {
76
684k
  switch (opline->opcode) {
77
3.32k
    case ZEND_NOP:
78
3.47k
    case ZEND_IS_IDENTICAL:
79
3.51k
    case ZEND_IS_NOT_IDENTICAL:
80
7.63k
    case ZEND_QM_ASSIGN:
81
11.6k
    case ZEND_FE_FREE:
82
12.0k
    case ZEND_TYPE_CHECK:
83
12.0k
    case ZEND_DEFINED:
84
13.2k
    case ZEND_ADD:
85
13.8k
    case ZEND_SUB:
86
14.4k
    case ZEND_MUL:
87
14.4k
    case ZEND_POW:
88
14.5k
    case ZEND_BW_OR:
89
14.7k
    case ZEND_BW_AND:
90
15.0k
    case ZEND_BW_XOR:
91
16.3k
    case ZEND_CONCAT:
92
17.3k
    case ZEND_FAST_CONCAT:
93
17.4k
    case ZEND_DIV:
94
17.6k
    case ZEND_MOD:
95
18.0k
    case ZEND_BOOL_XOR:
96
18.5k
    case ZEND_BOOL:
97
19.0k
    case ZEND_BOOL_NOT:
98
20.0k
    case ZEND_BW_NOT:
99
20.2k
    case ZEND_SL:
100
20.4k
    case ZEND_SR:
101
20.9k
    case ZEND_IS_EQUAL:
102
21.7k
    case ZEND_IS_NOT_EQUAL:
103
24.1k
    case ZEND_IS_SMALLER:
104
24.2k
    case ZEND_IS_SMALLER_OR_EQUAL:
105
24.2k
    case ZEND_CASE:
106
24.2k
    case ZEND_CASE_STRICT:
107
24.4k
    case ZEND_CAST:
108
24.4k
    case ZEND_ROPE_INIT:
109
24.4k
    case ZEND_ROPE_ADD:
110
24.5k
    case ZEND_INIT_ARRAY:
111
24.5k
    case ZEND_SPACESHIP:
112
25.7k
    case ZEND_STRLEN:
113
25.8k
    case ZEND_COUNT:
114
25.8k
    case ZEND_GET_TYPE:
115
25.8k
    case ZEND_ISSET_ISEMPTY_THIS:
116
26.0k
    case ZEND_ISSET_ISEMPTY_DIM_OBJ:
117
26.1k
    case ZEND_FETCH_DIM_IS:
118
26.2k
    case ZEND_ISSET_ISEMPTY_CV:
119
26.2k
    case ZEND_ISSET_ISEMPTY_VAR:
120
26.2k
    case ZEND_FETCH_IS:
121
26.2k
    case ZEND_IN_ARRAY:
122
26.2k
    case ZEND_FUNC_NUM_ARGS:
123
26.2k
    case ZEND_FUNC_GET_ARGS:
124
26.2k
    case ZEND_ARRAY_KEY_EXISTS:
125
27.3k
    case ZEND_COPY_TMP:
126
      /* No side effects */
127
27.3k
      return false;
128
20.6k
    case ZEND_FREE:
129
20.6k
      return opline->extended_value == ZEND_FREE_VOID_CAST;
130
122
    case ZEND_ADD_ARRAY_ELEMENT:
131
      /* TODO: We can't free two vars. Keep instruction alive. <?php [0, "$a" => "$b"]; */
132
122
      if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) {
133
5
        return true;
134
5
      }
135
117
      return false;
136
529
    case ZEND_ROPE_END:
137
      /* TODO: Rope dce optimization, see #76446 */
138
529
      return true;
139
15.2k
    case ZEND_JMP:
140
22.9k
    case ZEND_JMPZ:
141
31.8k
    case ZEND_JMPNZ:
142
32.2k
    case ZEND_JMPZ_EX:
143
32.8k
    case ZEND_JMPNZ_EX:
144
33.8k
    case ZEND_JMP_SET:
145
34.0k
    case ZEND_COALESCE:
146
34.9k
    case ZEND_ASSERT_CHECK:
147
36.6k
    case ZEND_JMP_NULL:
148
36.7k
    case ZEND_BIND_INIT_STATIC_OR_JMP:
149
36.7k
    case ZEND_JMP_FRAMELESS:
150
      /* For our purposes a jumps and branches are side effects. */
151
36.7k
      return true;
152
0
    case ZEND_BEGIN_SILENCE:
153
76.7k
    case ZEND_END_SILENCE:
154
114k
    case ZEND_ECHO:
155
114k
    case ZEND_INCLUDE_OR_EVAL:
156
115k
    case ZEND_THROW:
157
116k
    case ZEND_MATCH_ERROR:
158
116k
    case ZEND_EXT_STMT:
159
116k
    case ZEND_EXT_FCALL_BEGIN:
160
116k
    case ZEND_EXT_FCALL_END:
161
116k
    case ZEND_TICKS:
162
117k
    case ZEND_YIELD:
163
117k
    case ZEND_VERIFY_NEVER_TYPE:
164
      /* Intrinsic side effects */
165
117k
      return true;
166
489
    case ZEND_YIELD_FROM: {
167
489
      uint32_t t1 = OP1_INFO();
168
489
      if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY && MAY_BE_EMPTY_ONLY(t1)) {
169
14
        return false;
170
14
      }
171
475
      return true;
172
489
    }
173
88.6k
    case ZEND_DO_FCALL:
174
88.7k
    case ZEND_DO_FCALL_BY_NAME:
175
88.7k
    case ZEND_DO_ICALL:
176
94.7k
    case ZEND_DO_UCALL:
177
94.7k
    case ZEND_FRAMELESS_ICALL_0:
178
94.7k
    case ZEND_FRAMELESS_ICALL_1:
179
94.7k
    case ZEND_FRAMELESS_ICALL_2:
180
94.7k
    case ZEND_FRAMELESS_ICALL_3:
181
      /* For now assume all calls have side effects */
182
94.7k
      return true;
183
8.30k
    case ZEND_RECV:
184
10.0k
    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
10.0k
      return true;
188
668
    case ZEND_ASSIGN_REF:
189
668
      return true;
190
21.9k
    case ZEND_ASSIGN:
191
21.9k
    {
192
21.9k
      if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)) {
193
8.52k
        return true;
194
8.52k
      }
195
13.4k
      if (!reorder_dtor_effects) {
196
13.4k
        if (opline->op2_type != IS_CONST
197
5.35k
          && (OP2_INFO() & MAY_HAVE_DTOR)
198
1.96k
          && ssa->vars[ssa_op->op2_use].escape_state != ESCAPE_STATE_NO_ESCAPE) {
199
          /* DCE might shorten lifetime */
200
1.75k
          return true;
201
1.75k
        }
202
13.4k
      }
203
11.6k
      return false;
204
13.4k
    }
205
96
    case ZEND_UNSET_VAR:
206
96
      return true;
207
2.26k
    case ZEND_UNSET_CV:
208
2.26k
    {
209
2.26k
      uint32_t t1 = OP1_INFO();
210
2.26k
      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
1.04k
        return true;
216
1.04k
      }
217
1.22k
      return false;
218
2.26k
    }
219
4.03k
    case ZEND_PRE_INC:
220
4.41k
    case ZEND_POST_INC:
221
4.60k
    case ZEND_PRE_DEC:
222
4.77k
    case ZEND_POST_DEC:
223
4.77k
      return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def);
224
1.43k
    case ZEND_ASSIGN_OP:
225
1.43k
      return is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
226
1.04k
        || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE;
227
4.11k
    case ZEND_ASSIGN_DIM:
228
9.97k
    case ZEND_ASSIGN_OBJ:
229
9.97k
      if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
230
9.42k
        || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
231
9.42k
        return true;
232
9.42k
      }
233
556
      if (!reorder_dtor_effects) {
234
556
        opline++;
235
556
        ssa_op++;
236
556
        if (opline->op1_type != IS_CONST
237
375
          && (OP1_INFO() & MAY_HAVE_DTOR)) {
238
          /* DCE might shorten lifetime */
239
122
          return true;
240
122
        }
241
556
      }
242
434
      return false;
243
167
    case ZEND_PRE_INC_OBJ:
244
213
    case ZEND_PRE_DEC_OBJ:
245
213
    case ZEND_POST_INC_OBJ:
246
213
    case ZEND_POST_DEC_OBJ:
247
213
      if (is_bad_mod(ssa, ssa_op->op1_use, ssa_op->op1_def)
248
201
        || ssa->vars[ssa_op->op1_def].escape_state != ESCAPE_STATE_NO_ESCAPE) {
249
201
        return true;
250
201
      }
251
12
      return false;
252
541
    case ZEND_BIND_STATIC:
253
541
      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
541
        if ((opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) {
258
256
          return true;
259
256
        }
260
        /* Modifies static variables which are observable through reflection */
261
285
        if ((opline->extended_value & ZEND_BIND_REF) && opline->op2_type != IS_UNUSED) {
262
169
          return true;
263
169
        }
264
285
      }
265
116
      return false;
266
64
    case ZEND_CHECK_VAR:
267
64
      return (OP1_INFO() & MAY_BE_UNDEF) != 0;
268
4
    case ZEND_FE_RESET_R:
269
4
    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
4
      return (OP1_INFO() & MAY_BE_ANY) != MAY_BE_ARRAY;
273
333k
    default:
274
      /* For everything we didn't handle, assume a side-effect */
275
333k
      return true;
276
684k
  }
277
684k
}
278
279
1.33M
static zend_always_inline void add_to_worklists(const context *ctx, int var_num, int check) {
280
1.33M
  const zend_ssa_var *var = &ctx->ssa->vars[var_num];
281
1.33M
  if (var->definition >= 0) {
282
949k
    if (!check || zend_bitset_in(ctx->instr_dead, var->definition)) {
283
858k
      zend_bitset_incl(ctx->instr_worklist, var->definition);
284
858k
    }
285
949k
  } else if (var->definition_phi) {
286
273k
    if (!check || zend_bitset_in(ctx->phi_dead, var_num)) {
287
175k
      zend_bitset_incl(ctx->phi_worklist, var_num);
288
175k
    }
289
273k
  }
290
1.33M
}
291
292
43.1k
static inline void add_to_phi_worklist_no_val(const context *ctx, int var_num) {
293
43.1k
  const zend_ssa_var *var = &ctx->ssa->vars[var_num];
294
43.1k
  if (var->definition_phi && zend_bitset_in(ctx->phi_dead, var_num)) {
295
6.89k
    zend_bitset_incl(ctx->phi_worklist_no_val, var_num);
296
6.89k
  }
297
43.1k
}
298
299
1.44M
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.44M
  if (ssa_op->result_use >= 0) {
301
15.7k
    add_to_worklists(ctx, ssa_op->result_use, check);
302
15.7k
  }
303
1.44M
  if (ssa_op->op1_use >= 0) {
304
823k
    if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op1_use)
305
68.4k
     || (opline->opcode == ZEND_ASSIGN
306
793k
      && (ssa->var_info[ssa_op->op1_use].type & MAY_BE_REF) != 0)) {
307
793k
      add_to_worklists(ctx, ssa_op->op1_use, check);
308
793k
    } else {
309
29.8k
      add_to_phi_worklist_no_val(ctx, ssa_op->op1_use);
310
29.8k
    }
311
823k
  }
312
1.44M
  if (ssa_op->op2_use >= 0) {
313
243k
    if (!zend_ssa_is_no_val_use(opline, ssa_op, ssa_op->op2_use)
314
3.59k
     || (opline->opcode == ZEND_FE_FETCH_R
315
241k
      && (ssa->var_info[ssa_op->op2_use].type & MAY_BE_REF) != 0)) {
316
241k
      add_to_worklists(ctx, ssa_op->op2_use, check);
317
241k
    } else {
318
1.72k
      add_to_phi_worklist_no_val(ctx, ssa_op->op2_use);
319
1.72k
    }
320
243k
  }
321
1.44M
}
322
323
159k
static zend_always_inline void add_phi_sources_to_worklists(const context *ctx, zend_ssa_phi *phi, int check) {
324
159k
  const zend_ssa *ssa = ctx->ssa;
325
159k
  int source;
326
725k
  FOREACH_PHI_SOURCE(phi, source) {
327
725k
    add_to_worklists(ctx, source, check);
328
725k
  } FOREACH_PHI_SOURCE_END();
329
159k
}
330
331
19.4k
static inline bool is_var_dead(const context *ctx, int var_num) {
332
19.4k
  const zend_ssa_var *var = &ctx->ssa->vars[var_num];
333
19.4k
  if (var->definition_phi) {
334
1.34k
    return zend_bitset_in(ctx->phi_dead, var_num);
335
18.1k
  } else if (var->definition >= 0) {
336
11.0k
    return zend_bitset_in(ctx->instr_dead, var->definition);
337
11.0k
  } 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
7.06k
    return var_num >= ctx->op_array->last_var;
342
7.06k
  }
343
19.4k
}
344
345
// Sometimes we can mark the var as EXT_UNUSED
346
8.79k
static bool try_remove_var_def(const context *ctx, int free_var, int use_chain, const zend_op *opline) {
347
8.79k
  if (use_chain >= 0) {
348
1.13k
    return false;
349
1.13k
  }
350
7.65k
  zend_ssa_var *var = &ctx->ssa->vars[free_var];
351
7.65k
  int def = var->definition;
352
353
7.65k
  if (def >= 0) {
354
7.59k
    zend_ssa_op *def_op = &ctx->ssa->ops[def];
355
356
7.59k
    if (def_op->result_def == free_var
357
7.59k
        && var->phi_use_chain == NULL
358
7.59k
        && var->use_chain == (opline - ctx->op_array->opcodes)) {
359
7.43k
      zend_op *def_opline = &ctx->op_array->opcodes[def];
360
361
7.43k
      switch (def_opline->opcode) {
362
201
        case ZEND_ASSIGN:
363
201
        case ZEND_ASSIGN_REF:
364
227
        case ZEND_ASSIGN_DIM:
365
231
        case ZEND_ASSIGN_OBJ:
366
231
        case ZEND_ASSIGN_OBJ_REF:
367
231
        case ZEND_ASSIGN_STATIC_PROP:
368
231
        case ZEND_ASSIGN_STATIC_PROP_REF:
369
396
        case ZEND_ASSIGN_OP:
370
406
        case ZEND_ASSIGN_DIM_OP:
371
406
        case ZEND_ASSIGN_OBJ_OP:
372
406
        case ZEND_ASSIGN_STATIC_PROP_OP:
373
410
        case ZEND_PRE_INC:
374
416
        case ZEND_PRE_DEC:
375
416
        case ZEND_PRE_INC_OBJ:
376
416
        case ZEND_PRE_DEC_OBJ:
377
416
        case ZEND_DO_ICALL:
378
418
        case ZEND_DO_UCALL:
379
418
        case ZEND_DO_FCALL_BY_NAME:
380
630
        case ZEND_DO_FCALL:
381
630
        case ZEND_INCLUDE_OR_EVAL:
382
630
        case ZEND_YIELD:
383
630
        case ZEND_YIELD_FROM:
384
630
        case ZEND_ASSERT_CHECK:
385
630
          def_opline->result_type = IS_UNUSED;
386
630
          def_opline->result.var = 0;
387
630
          def_op->result_def = -1;
388
630
          var->definition = -1;
389
630
          return true;
390
6.80k
        default:
391
6.80k
          break;
392
7.43k
      }
393
7.43k
    }
394
7.59k
  }
395
7.02k
  return false;
396
7.65k
}
397
398
54.9k
static zend_always_inline bool may_be_refcounted(uint32_t type) {
399
54.9k
  return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0;
400
54.9k
}
401
402
24.1k
static inline bool is_free_of_live_var(const context *ctx, const zend_op *opline, const zend_ssa_op *ssa_op) {
403
24.1k
  switch (opline->opcode) {
404
10.1k
    case ZEND_FREE:
405
      /* It is always safe to remove FREEs of non-refcounted values, even if they are live. */
406
10.1k
      if ((ctx->ssa->var_info[ssa_op->op1_use].type & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) != 0
407
10.1k
       && !may_be_refcounted(ctx->ssa->var_info[ssa_op->op1_use].type)) {
408
7.84k
        return false;
409
7.84k
      }
410
2.32k
      ZEND_FALLTHROUGH;
411
3.09k
    case ZEND_FE_FREE:
412
3.09k
      return !is_var_dead(ctx, ssa_op->op1_use);
413
13.1k
    default:
414
13.1k
      return false;
415
24.1k
  }
416
24.1k
}
417
418
/* Returns whether the instruction has been DCEd */
419
27.4k
static bool dce_instr(const context *ctx, zend_op *opline, zend_ssa_op *ssa_op) {
420
27.4k
  const zend_ssa *ssa = ctx->ssa;
421
27.4k
  int free_var = -1;
422
27.4k
  uint8_t free_var_type;
423
424
27.4k
  if (opline->opcode == ZEND_NOP) {
425
3.32k
    return false;
426
3.32k
  }
427
428
  /* We mark FREEs as dead, but they're only really dead if the destroyed var is dead */
429
24.1k
  if (is_free_of_live_var(ctx, opline, ssa_op)) {
430
2.45k
    return false;
431
2.45k
  }
432
433
21.6k
  if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op1_use)) {
434
7.38k
    if (!try_remove_var_def(ctx, ssa_op->op1_use, ssa_op->op1_use_chain, opline)) {
435
7.15k
      if (may_be_refcounted(ssa->var_info[ssa_op->op1_use].type)
436
1.63k
          && opline->opcode != ZEND_CASE
437
1.63k
          && opline->opcode != ZEND_CASE_STRICT
438
1.63k
          && opline->opcode != ZEND_COPY_TMP) {
439
493
        free_var = ssa_op->op1_use;
440
493
        free_var_type = opline->op1_type;
441
493
      }
442
7.15k
    }
443
7.38k
  }
444
21.6k
  if ((opline->op2_type & (IS_VAR|IS_TMP_VAR)) && !is_var_dead(ctx, ssa_op->op2_use)) {
445
1.41k
    if (!try_remove_var_def(ctx, ssa_op->op2_use, ssa_op->op2_use_chain, opline)) {
446
1.01k
      if (may_be_refcounted(ssa->var_info[ssa_op->op2_use].type)) {
447
354
        if (free_var >= 0) {
448
          // TODO: We can't free two vars. Keep instruction alive.
449
108
          zend_bitset_excl(ctx->instr_dead, opline - ctx->op_array->opcodes);
450
108
          return false;
451
108
        }
452
246
        free_var = ssa_op->op2_use;
453
246
        free_var_type = opline->op2_type;
454
246
      }
455
1.01k
    }
456
1.41k
  }
457
458
21.5k
  zend_ssa_rename_defs_of_instr(ctx->ssa, ssa_op);
459
21.5k
  zend_ssa_remove_instr(ctx->ssa, opline, ssa_op);
460
461
21.5k
  if (free_var >= 0) {
462
631
    opline->opcode = ZEND_FREE;
463
631
    opline->op1.var = EX_NUM_TO_VAR(ssa->vars[free_var].var);
464
631
    opline->op1_type = free_var_type;
465
466
631
    ssa_op->op1_use = free_var;
467
631
    ssa_op->op1_use_chain = ssa->vars[free_var].use_chain;
468
631
    ssa->vars[free_var].use_chain = ssa_op - ssa->ops;
469
631
    return false;
470
631
  }
471
20.9k
  return true;
472
21.5k
}
473
474
93.8k
static inline int get_common_phi_source(const zend_ssa *ssa, zend_ssa_phi *phi) {
475
93.8k
  int common_source = -1;
476
93.8k
  int source;
477
472k
  FOREACH_PHI_SOURCE(phi, source) {
478
472k
    if (source == phi->ssa_var) {
479
1.40k
      continue;
480
1.40k
    }
481
188k
    if (common_source == -1) {
482
93.8k
      common_source = source;
483
94.2k
    } else if (common_source != source) {
484
93.3k
      return -1;
485
93.3k
    }
486
188k
  } 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
537
  return common_source;
492
93.8k
}
493
494
133k
static void try_remove_trivial_phi(const context *ctx, zend_ssa_phi *phi) {
495
133k
  zend_ssa *ssa = ctx->ssa;
496
133k
  if (phi->pi < 0) {
497
    /* Phi assignment with identical source operands */
498
93.8k
    int common_source = get_common_phi_source(ssa, phi);
499
93.8k
    if (common_source >= 0) {
500
537
      zend_ssa_rename_var_uses(ssa, phi->ssa_var, common_source, 1);
501
537
      zend_ssa_remove_phi(ssa, phi);
502
537
    }
503
93.8k
  } 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
40.1k
  }
512
133k
}
513
514
16
static inline bool may_break_varargs(const zend_op_array *op_array, const zend_ssa *ssa, const zend_ssa_op *ssa_op) {
515
16
  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
16
  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
16
  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
16
  return false;
528
16
}
529
530
27.6k
static inline bool may_throw_dce_exception(const zend_op *opline) {
531
27.6k
  return opline->opcode == ZEND_ADD_ARRAY_ELEMENT && opline->op2_type == IS_UNUSED;
532
27.6k
}
533
534
72.4k
int dce_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *optimizer_ctx, zend_ssa *ssa, bool reorder_dtor_effects) {
535
72.4k
  int i;
536
72.4k
  zend_ssa_phi *phi;
537
72.4k
  int removed_ops = 0;
538
539
  /* DCE of CV operations that changes arguments may affect vararg functions. */
540
72.4k
  bool has_varargs = (ssa->cfg.flags & ZEND_FUNC_VARARG) != 0;
541
542
72.4k
  context ctx;
543
72.4k
  ctx.ssa = ssa;
544
72.4k
  ctx.op_array = op_array;
545
72.4k
  ctx.reorder_dtor_effects = reorder_dtor_effects;
546
547
72.4k
  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
72.4k
  ctx.instr_worklist_len = zend_bitset_len(op_array->last);
550
72.4k
  ctx.instr_worklist = zend_arena_calloc(&optimizer_ctx->arena, ctx.instr_worklist_len, sizeof(zend_ulong));
551
72.4k
  ctx.phi_worklist_len = zend_bitset_len(ssa->vars_count);
552
72.4k
  ctx.phi_worklist = zend_arena_calloc(&optimizer_ctx->arena, ctx.phi_worklist_len, sizeof(zend_ulong));
553
72.4k
  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
72.4k
  ctx.instr_dead = zend_arena_calloc(&optimizer_ctx->arena, ctx.instr_worklist_len, sizeof(zend_ulong));
557
72.4k
  ctx.phi_dead = zend_arena_alloc(&optimizer_ctx->arena, ctx.phi_worklist_len * sizeof(zend_ulong));
558
72.4k
  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
414k
  FOREACH_PHI(phi) {
564
414k
    if (phi->var >= op_array->last_var
565
36.6k
        && may_be_refcounted(ssa->var_info[phi->ssa_var].type)) {
566
31.2k
      zend_bitset_excl(ctx.phi_dead, phi->ssa_var);
567
31.2k
      add_phi_sources_to_worklists(&ctx, phi, 0);
568
31.2k
    }
569
414k
  } FOREACH_PHI_END();
570
571
  /* Mark reachable instruction without side effects as dead */
572
72.4k
  uint32_t b = ssa->cfg.blocks_count;
573
274k
  while (b > 0) {
574
202k
    int op_data = -1;
575
576
202k
    b--;
577
202k
    const zend_basic_block *block = &ssa->cfg.blocks[b];
578
202k
    if (!(block->flags & ZEND_BB_REACHABLE)) {
579
5.46k
      continue;
580
5.46k
    }
581
196k
    i = block->start + block->len;
582
1.66M
    while (i > block->start) {
583
1.47M
      i--;
584
585
1.47M
      if (op_array->opcodes[i].opcode == ZEND_OP_DATA) {
586
23.4k
        op_data = i;
587
23.4k
        continue;
588
23.4k
      }
589
590
1.44M
      if (zend_bitset_in(ctx.instr_worklist, i)) {
591
764k
        zend_bitset_excl(ctx.instr_worklist, i);
592
764k
        add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 0);
593
764k
        if (op_data >= 0) {
594
11.2k
          add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 0);
595
11.2k
        }
596
764k
      } else if (may_have_side_effects(op_array, ssa, &op_array->opcodes[i], &ssa->ops[i], ctx.reorder_dtor_effects)
597
64.5k
          || (zend_may_throw(&op_array->opcodes[i], &ssa->ops[i], op_array, ssa)
598
27.6k
            && !may_throw_dce_exception(&op_array->opcodes[i]))
599
647k
          || (has_varargs && may_break_varargs(op_array, ssa, &ssa->ops[i]))) {
600
647k
        if (op_array->opcodes[i].opcode == ZEND_NEW
601
114
            && op_array->opcodes[i+1].opcode == ZEND_DO_FCALL
602
114
            && ssa->ops[i].result_def >= 0
603
114
            && ssa->vars[ssa->ops[i].result_def].escape_state == ESCAPE_STATE_NO_ESCAPE) {
604
104
          zend_bitset_incl(ctx.instr_dead, i);
605
104
          zend_bitset_incl(ctx.instr_dead, i+1);
606
647k
        } else {
607
647k
          add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 0);
608
647k
          if (op_data >= 0) {
609
12.0k
            add_operands_to_worklists(&ctx, &op_array->opcodes[op_data], &ssa->ops[op_data], ssa, 0);
610
12.0k
          }
611
647k
        }
612
647k
      } else {
613
36.9k
        zend_bitset_incl(ctx.instr_dead, i);
614
36.9k
        if (op_data >= 0) {
615
84
          zend_bitset_incl(ctx.instr_dead, op_data);
616
84
        }
617
36.9k
      }
618
1.44M
      op_data = -1;
619
1.44M
    }
620
196k
  }
621
622
  /* Propagate liveness backwards to all definitions of used vars */
623
85.9k
  while (!zend_bitset_empty(ctx.instr_worklist, ctx.instr_worklist_len)
624
83.1k
      || !zend_bitset_empty(ctx.phi_worklist, ctx.phi_worklist_len)) {
625
23.3k
    while ((i = zend_bitset_pop_first(ctx.instr_worklist, ctx.instr_worklist_len)) >= 0) {
626
9.90k
      zend_bitset_excl(ctx.instr_dead, i);
627
9.90k
      add_operands_to_worklists(&ctx, &op_array->opcodes[i], &ssa->ops[i], ssa, 1);
628
9.90k
      if (i < op_array->last
629
9.90k
       && (op_array->opcodes[i+1].opcode == ZEND_OP_DATA
630
9.86k
        || (op_array->opcodes[i].opcode == ZEND_NEW
631
55
         && op_array->opcodes[i+1].opcode == ZEND_DO_FCALL))) {
632
55
        zend_bitset_excl(ctx.instr_dead, i+1);
633
55
        add_operands_to_worklists(&ctx, &op_array->opcodes[i+1], &ssa->ops[i+1], ssa, 1);
634
55
      }
635
9.90k
    }
636
141k
    while ((i = zend_bitset_pop_first(ctx.phi_worklist, ctx.phi_worklist_len)) >= 0) {
637
128k
      zend_bitset_excl(ctx.phi_dead, i);
638
128k
      zend_bitset_excl(ctx.phi_worklist_no_val, i);
639
128k
      add_phi_sources_to_worklists(&ctx, ssa->vars[i].definition_phi, 1);
640
128k
    }
641
13.4k
  }
642
643
  /* Eliminate dead instructions */
644
337k
  ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) {
645
27.4k
    removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]);
646
27.4k
  } 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
78.2k
  while ((i = zend_bitset_pop_first(ctx.phi_worklist_no_val, ctx.phi_worklist_len)) >= 0) {
653
5.77k
    zend_ssa_phi *phi = ssa->vars[i].definition_phi;
654
5.77k
    int source;
655
5.77k
    zend_bitset_excl(ctx.phi_dead, i);
656
28.8k
    FOREACH_PHI_SOURCE(phi, source) {
657
28.8k
      add_to_phi_worklist_no_val(&ctx, source);
658
28.8k
    } FOREACH_PHI_SOURCE_END();
659
5.77k
  }
660
661
  /* Now collect the actually dead phis */
662
414k
  FOREACH_PHI(phi) {
663
414k
    if (zend_bitset_in(ctx.phi_dead, phi->ssa_var)) {
664
6.02k
      zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
665
6.02k
      zend_ssa_remove_phi(ssa, phi);
666
133k
    } else {
667
      /* Remove trivial phis (phis with identical source operands) */
668
133k
      try_remove_trivial_phi(&ctx, phi);
669
133k
    }
670
414k
  } FOREACH_PHI_END();
671
672
72.4k
  zend_arena_release(&optimizer_ctx->arena, checkpoint);
673
674
72.4k
  return removed_ops;
675
72.4k
}