Coverage Report

Created: 2025-12-14 06:10

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