Coverage Report

Created: 2026-06-02 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/Optimizer/scdf.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine, Sparse Conditional Data Flow Propagation Framework      |
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
   +----------------------------------------------------------------------+
15
*/
16
17
#include "Optimizer/zend_optimizer_internal.h"
18
#include "Optimizer/scdf.h"
19
20
/* This defines a generic framework for sparse conditional dataflow propagation. The algorithm is
21
 * based on "Sparse conditional constant propagation" by Wegman and Zadeck. We're using a
22
 * generalized implementation as described in chapter 8.3 of the SSA book.
23
 *
24
 * Every SSA variable is associated with an element on a finite-height lattice, those value can only
25
 * ever be lowered during the operation of the algorithm. If a value is lowered all instructions and
26
 * phis using that value need to be reconsidered (this is done by adding the variable to a
27
 * worklist). For phi functions the result is computed by applying the meet operation to the
28
 * operands. This continues until a fixed point is reached.
29
 *
30
 * The algorithm is control-flow sensitive: All blocks except the start block are initially assumed
31
 * to be unreachable. When considering a branch instruction, we determine the feasible successors
32
 * based on the current state of the variable lattice. If a new edge becomes feasible we either have
33
 * to mark the successor block executable and consider all instructions in it, or, if the target is
34
 * already executable, we only have to reconsider the phi functions (as we only consider phi
35
 * operands which are associated with a feasible edge).
36
 *
37
 * The generic framework requires the definition of three functions:
38
 * * visit_instr() should recompute the lattice values of all SSA variables defined by an
39
 *   instruction.
40
 * * visit_phi() should recompute the lattice value of the SSA variable defined by the phi. While
41
 *   doing this it should only consider operands for which scfg_is_edge_feasible() returns true.
42
 * * get_feasible_successors() should determine the feasible successors for a branch instruction.
43
 *   Note that this callback only needs to handle conditional branches (with two successors).
44
 */
45
46
#if 0
47
#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)
48
#else
49
#define DEBUG_PRINT(...)
50
#endif
51
52
100k
void scdf_mark_edge_feasible(scdf_ctx *scdf, int from, int to) {
53
100k
  uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
54
55
100k
  if (zend_bitset_in(scdf->feasible_edges, edge)) {
56
    /* We already handled this edge */
57
1.76k
    return;
58
1.76k
  }
59
60
99.1k
  DEBUG_PRINT("Marking edge %d->%d feasible\n", from, to);
61
99.1k
  zend_bitset_incl(scdf->feasible_edges, edge);
62
63
99.1k
  if (!zend_bitset_in(scdf->executable_blocks, to)) {
64
93.7k
    if (!zend_bitset_in(scdf->block_worklist, to)) {
65
67.6k
      DEBUG_PRINT("Adding block %d to worklist\n", to);
66
67.6k
    }
67
93.7k
    zend_bitset_incl(scdf->block_worklist, to);
68
93.7k
  } else {
69
    /* Block is already executable, only a new edge became feasible.
70
     * Reevaluate phi nodes to account for changed source operands. */
71
5.46k
    const zend_ssa_block *ssa_block = &scdf->ssa->blocks[to];
72
20.6k
    for (const zend_ssa_phi *phi = ssa_block->phis; phi; phi = phi->next) {
73
15.1k
      zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
74
15.1k
      scdf->handlers.visit_phi(scdf, phi);
75
15.1k
    }
76
5.46k
  }
77
99.1k
}
78
79
37.1k
void scdf_init(zend_optimizer_ctx *ctx, scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa) {
80
37.1k
  scdf->op_array = op_array;
81
37.1k
  scdf->ssa = ssa;
82
83
37.1k
  scdf->instr_worklist_len = zend_bitset_len(op_array->last);
84
37.1k
  scdf->phi_var_worklist_len = zend_bitset_len(ssa->vars_count);
85
37.1k
  scdf->block_worklist_len = zend_bitset_len(ssa->cfg.blocks_count);
86
87
37.1k
  scdf->instr_worklist = zend_arena_calloc(&ctx->arena,
88
37.1k
    scdf->instr_worklist_len + scdf->phi_var_worklist_len + 2 * scdf->block_worklist_len + zend_bitset_len(ssa->cfg.edges_count),
89
37.1k
    sizeof(zend_ulong));
90
91
37.1k
  scdf->phi_var_worklist = scdf->instr_worklist + scdf->instr_worklist_len;
92
37.1k
  scdf->block_worklist = scdf->phi_var_worklist + scdf->phi_var_worklist_len;
93
37.1k
  scdf->executable_blocks = scdf->block_worklist + scdf->block_worklist_len;
94
37.1k
  scdf->feasible_edges = scdf->executable_blocks + scdf->block_worklist_len;
95
96
37.1k
  zend_bitset_incl(scdf->block_worklist, 0);
97
37.1k
  zend_bitset_incl(scdf->executable_blocks, 0);
98
37.1k
}
99
100
37.1k
void scdf_solve(scdf_ctx *scdf, const char *name) {
101
37.1k
  const zend_ssa *ssa = scdf->ssa;
102
37.1k
  DEBUG_PRINT("Start SCDF solve (%s)\n", name);
103
76.0k
  while (!zend_bitset_empty(scdf->instr_worklist, scdf->instr_worklist_len)
104
74.9k
    || !zend_bitset_empty(scdf->phi_var_worklist, scdf->phi_var_worklist_len)
105
74.3k
    || !zend_bitset_empty(scdf->block_worklist, scdf->block_worklist_len)
106
38.9k
  ) {
107
38.9k
    int i;
108
42.0k
    while ((i = zend_bitset_pop_first(scdf->phi_var_worklist, scdf->phi_var_worklist_len)) >= 0) {
109
3.09k
      const zend_ssa_phi *phi = ssa->vars[i].definition_phi;
110
3.09k
      ZEND_ASSERT(phi);
111
3.09k
      if (zend_bitset_in(scdf->executable_blocks, phi->block)) {
112
2.63k
        scdf->handlers.visit_phi(scdf, phi);
113
2.63k
      }
114
3.09k
    }
115
116
45.3k
    while ((i = zend_bitset_pop_first(scdf->instr_worklist, scdf->instr_worklist_len)) >= 0) {
117
6.47k
      int block_num = ssa->cfg.map[i];
118
6.47k
      if (zend_bitset_in(scdf->executable_blocks, block_num)) {
119
4.97k
        zend_basic_block *block = &ssa->cfg.blocks[block_num];
120
4.97k
        zend_op *opline = &scdf->op_array->opcodes[i];
121
4.97k
        zend_ssa_op *ssa_op = &ssa->ops[i];
122
4.97k
        if (opline->opcode == ZEND_OP_DATA) {
123
25
          opline--;
124
25
          ssa_op--;
125
25
        }
126
4.97k
        scdf->handlers.visit_instr(scdf, opline, ssa_op);
127
4.97k
        if (i == block->start + block->len - 1) {
128
1.66k
          if (block->successors_count == 1) {
129
687
            scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
130
976
          } else if (block->successors_count > 1) {
131
972
            scdf->handlers.mark_feasible_successors(scdf, block_num, block, opline, ssa_op);
132
972
          }
133
1.66k
        }
134
4.97k
      }
135
6.47k
    }
136
137
143k
    while ((i = zend_bitset_pop_first(scdf->block_worklist, scdf->block_worklist_len)) >= 0) {
138
      /* This block is now live. Interpret phis and instructions in it. */
139
104k
      zend_basic_block *block = &ssa->cfg.blocks[i];
140
104k
      const zend_ssa_block *ssa_block = &ssa->blocks[i];
141
142
104k
      DEBUG_PRINT("Pop block %d from worklist\n", i);
143
104k
      zend_bitset_incl(scdf->executable_blocks, i);
144
145
184k
      for (const zend_ssa_phi *phi = ssa_block->phis; phi; phi = phi->next) {
146
79.9k
        zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
147
79.9k
        scdf->handlers.visit_phi(scdf, phi);
148
79.9k
      }
149
150
104k
      if (block->len == 0) {
151
        /* Zero length blocks don't have a last instruction that would normally do this */
152
83
        scdf_mark_edge_feasible(scdf, i, block->successors[0]);
153
104k
      } else {
154
104k
        zend_op *opline = NULL;
155
104k
        uint32_t j, end = block->start + block->len;
156
826k
        for (j = block->start; j < end; j++) {
157
722k
          opline = &scdf->op_array->opcodes[j];
158
722k
          zend_bitset_excl(scdf->instr_worklist, j);
159
722k
          if (opline->opcode != ZEND_OP_DATA) {
160
711k
            scdf->handlers.visit_instr(scdf, opline, &ssa->ops[j]);
161
711k
          }
162
722k
        }
163
104k
        if (block->successors_count == 1) {
164
33.4k
          scdf_mark_edge_feasible(scdf, i, block->successors[0]);
165
71.2k
        } else if (block->successors_count > 1) {
166
33.4k
          ZEND_ASSERT(opline && "Should have opline in non-empty block");
167
33.4k
          if (opline->opcode == ZEND_OP_DATA) {
168
0
            opline--;
169
0
            j--;
170
0
          }
171
33.4k
          scdf->handlers.mark_feasible_successors(scdf, i, block, opline, &ssa->ops[j-1]);
172
33.4k
        }
173
104k
      }
174
104k
    }
175
38.9k
  }
176
37.1k
}
177
178
/* If a live range starts in a reachable block and ends in an unreachable block, we should
179
 * not eliminate the latter. While it cannot be reached, the FREE opcode of the loop var
180
 * is necessary for the correctness of temporary compaction. */
181
static bool is_live_loop_var_free(
182
507
    const scdf_ctx *scdf, const zend_op *opline, const zend_ssa_op *ssa_op) {
183
507
  if (!zend_optimizer_is_loop_var_free(opline)) {
184
473
    return false;
185
473
  }
186
187
34
  int var = ssa_op->op1_use;
188
34
  if (var < 0) {
189
2
    return false;
190
2
  }
191
192
32
  const zend_ssa_var *ssa_var = &scdf->ssa->vars[var];
193
32
  uint32_t def_block;
194
32
  if (ssa_var->definition >= 0) {
195
12
    def_block = scdf->ssa->cfg.map[ssa_var->definition];
196
20
  } else {
197
20
    def_block = ssa_var->definition_phi->block;
198
20
  }
199
32
  return zend_bitset_in(scdf->executable_blocks, def_block);
200
34
}
201
202
1.76k
static bool kept_alive_by_loop_var_free(const scdf_ctx *scdf, const zend_basic_block *block) {
203
1.76k
  const zend_op_array *op_array = scdf->op_array;
204
1.76k
  const zend_cfg *cfg = &scdf->ssa->cfg;
205
1.76k
  if (!(cfg->flags & ZEND_FUNC_FREE_LOOP_VAR)) {
206
1.58k
    return false;
207
1.58k
  }
208
209
628
  for (uint32_t i = block->start; i < block->start + block->len; i++) {
210
465
    if (is_live_loop_var_free(scdf, &op_array->opcodes[i], &scdf->ssa->ops[i])) {
211
16
      return true;
212
16
    }
213
465
  }
214
163
  return false;
215
179
}
216
217
16
static uint32_t cleanup_loop_var_free_block(const scdf_ctx *scdf, const zend_basic_block *block) {
218
16
  zend_ssa *ssa = scdf->ssa;
219
16
  const zend_op_array *op_array = scdf->op_array;
220
16
  const zend_cfg *cfg = &ssa->cfg;
221
16
  int block_num = block - cfg->blocks;
222
16
  uint32_t removed_ops = 0;
223
224
  /* Removes phi nodes */
225
20
  for (zend_ssa_phi *phi = ssa->blocks[block_num].phis; phi; phi = phi->next) {
226
4
    zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
227
4
    zend_ssa_remove_phi(ssa, phi);
228
4
  }
229
230
58
  for (uint32_t i = block->start; i < block->start + block->len; i++) {
231
42
    zend_op *opline = &op_array->opcodes[i];
232
42
    zend_ssa_op *ssa_op = &scdf->ssa->ops[i];
233
42
    if (opline->opcode == ZEND_NOP
234
42
     || is_live_loop_var_free(scdf, opline, ssa_op)) {
235
16
      continue;
236
16
    }
237
238
    /* While we have to preserve the loop var free, we can still remove other instructions
239
     * in the block. */
240
26
    zend_ssa_remove_defs_of_instr(ssa, ssa_op);
241
26
    zend_ssa_remove_instr(ssa, opline, ssa_op);
242
26
    removed_ops++;
243
26
  }
244
245
16
  zend_ssa_remove_block_from_cfg(ssa, block_num);
246
247
16
  return removed_ops;
248
16
}
249
250
/* Removes unreachable blocks. This will remove both the instructions (and phis) in the
251
 * blocks, as well as remove them from the successor / predecessor lists and mark them
252
 * unreachable. Blocks already marked unreachable are not removed. */
253
37.1k
uint32_t scdf_remove_unreachable_blocks(const scdf_ctx *scdf) {
254
37.1k
  zend_ssa *ssa = scdf->ssa;
255
37.1k
  uint32_t removed_ops = 0;
256
143k
  for (uint32_t i = 0; i < ssa->cfg.blocks_count; i++) {
257
106k
    const zend_basic_block *block = &ssa->cfg.blocks[i];
258
106k
    if (!zend_bitset_in(scdf->executable_blocks, i) && (block->flags & ZEND_BB_REACHABLE)) {
259
1.76k
      if (!kept_alive_by_loop_var_free(scdf, block)) {
260
1.75k
        removed_ops += block->len;
261
1.75k
        zend_ssa_remove_block(scdf->op_array, ssa, i);
262
1.75k
      } else {
263
16
        removed_ops += cleanup_loop_var_free_block(scdf, block);
264
16
      }
265
1.76k
    }
266
106k
  }
267
37.1k
  return removed_ops;
268
37.1k
}