Coverage Report

Created: 2026-06-02 06:40

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
185k
void scdf_mark_edge_feasible(scdf_ctx *scdf, int from, int to) {
53
185k
  uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
54
55
185k
  if (zend_bitset_in(scdf->feasible_edges, edge)) {
56
    /* We already handled this edge */
57
3.94k
    return;
58
3.94k
  }
59
60
181k
  DEBUG_PRINT("Marking edge %d->%d feasible\n", from, to);
61
181k
  zend_bitset_incl(scdf->feasible_edges, edge);
62
63
181k
  if (!zend_bitset_in(scdf->executable_blocks, to)) {
64
168k
    if (!zend_bitset_in(scdf->block_worklist, to)) {
65
123k
      DEBUG_PRINT("Adding block %d to worklist\n", to);
66
123k
    }
67
168k
    zend_bitset_incl(scdf->block_worklist, to);
68
168k
  } else {
69
    /* Block is already executable, only a new edge became feasible.
70
     * Reevaluate phi nodes to account for changed source operands. */
71
12.2k
    const zend_ssa_block *ssa_block = &scdf->ssa->blocks[to];
72
44.2k
    for (const zend_ssa_phi *phi = ssa_block->phis; phi; phi = phi->next) {
73
32.0k
      zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
74
32.0k
      scdf->handlers.visit_phi(scdf, phi);
75
32.0k
    }
76
12.2k
  }
77
181k
}
78
79
71.2k
void scdf_init(zend_optimizer_ctx *ctx, scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa) {
80
71.2k
  scdf->op_array = op_array;
81
71.2k
  scdf->ssa = ssa;
82
83
71.2k
  scdf->instr_worklist_len = zend_bitset_len(op_array->last);
84
71.2k
  scdf->phi_var_worklist_len = zend_bitset_len(ssa->vars_count);
85
71.2k
  scdf->block_worklist_len = zend_bitset_len(ssa->cfg.blocks_count);
86
87
71.2k
  scdf->instr_worklist = zend_arena_calloc(&ctx->arena,
88
71.2k
    scdf->instr_worklist_len + scdf->phi_var_worklist_len + 2 * scdf->block_worklist_len + zend_bitset_len(ssa->cfg.edges_count),
89
71.2k
    sizeof(zend_ulong));
90
91
71.2k
  scdf->phi_var_worklist = scdf->instr_worklist + scdf->instr_worklist_len;
92
71.2k
  scdf->block_worklist = scdf->phi_var_worklist + scdf->phi_var_worklist_len;
93
71.2k
  scdf->executable_blocks = scdf->block_worklist + scdf->block_worklist_len;
94
71.2k
  scdf->feasible_edges = scdf->executable_blocks + scdf->block_worklist_len;
95
96
71.2k
  zend_bitset_incl(scdf->block_worklist, 0);
97
71.2k
  zend_bitset_incl(scdf->executable_blocks, 0);
98
71.2k
}
99
100
71.2k
void scdf_solve(scdf_ctx *scdf, const char *name) {
101
71.2k
  const zend_ssa *ssa = scdf->ssa;
102
71.2k
  DEBUG_PRINT("Start SCDF solve (%s)\n", name);
103
146k
  while (!zend_bitset_empty(scdf->instr_worklist, scdf->instr_worklist_len)
104
143k
    || !zend_bitset_empty(scdf->phi_var_worklist, scdf->phi_var_worklist_len)
105
142k
    || !zend_bitset_empty(scdf->block_worklist, scdf->block_worklist_len)
106
75.1k
  ) {
107
75.1k
    int i;
108
81.6k
    while ((i = zend_bitset_pop_first(scdf->phi_var_worklist, scdf->phi_var_worklist_len)) >= 0) {
109
6.46k
      const zend_ssa_phi *phi = ssa->vars[i].definition_phi;
110
6.46k
      ZEND_ASSERT(phi);
111
6.46k
      if (zend_bitset_in(scdf->executable_blocks, phi->block)) {
112
5.42k
        scdf->handlers.visit_phi(scdf, phi);
113
5.42k
      }
114
6.46k
    }
115
116
88.8k
    while ((i = zend_bitset_pop_first(scdf->instr_worklist, scdf->instr_worklist_len)) >= 0) {
117
13.6k
      int block_num = ssa->cfg.map[i];
118
13.6k
      if (zend_bitset_in(scdf->executable_blocks, block_num)) {
119
10.7k
        zend_basic_block *block = &ssa->cfg.blocks[block_num];
120
10.7k
        zend_op *opline = &scdf->op_array->opcodes[i];
121
10.7k
        zend_ssa_op *ssa_op = &ssa->ops[i];
122
10.7k
        if (opline->opcode == ZEND_OP_DATA) {
123
47
          opline--;
124
47
          ssa_op--;
125
47
        }
126
10.7k
        scdf->handlers.visit_instr(scdf, opline, ssa_op);
127
10.7k
        if (i == block->start + block->len - 1) {
128
3.75k
          if (block->successors_count == 1) {
129
1.58k
            scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
130
2.17k
          } else if (block->successors_count > 1) {
131
2.16k
            scdf->handlers.mark_feasible_successors(scdf, block_num, block, opline, ssa_op);
132
2.16k
          }
133
3.75k
        }
134
10.7k
      }
135
13.6k
    }
136
137
270k
    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
195k
      zend_basic_block *block = &ssa->cfg.blocks[i];
140
195k
      const zend_ssa_block *ssa_block = &ssa->blocks[i];
141
142
195k
      DEBUG_PRINT("Pop block %d from worklist\n", i);
143
195k
      zend_bitset_incl(scdf->executable_blocks, i);
144
145
335k
      for (const zend_ssa_phi *phi = ssa_block->phis; phi; phi = phi->next) {
146
139k
        zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
147
139k
        scdf->handlers.visit_phi(scdf, phi);
148
139k
      }
149
150
195k
      if (block->len == 0) {
151
        /* Zero length blocks don't have a last instruction that would normally do this */
152
110
        scdf_mark_edge_feasible(scdf, i, block->successors[0]);
153
195k
      } else {
154
195k
        zend_op *opline = NULL;
155
195k
        uint32_t j, end = block->start + block->len;
156
1.63M
        for (j = block->start; j < end; j++) {
157
1.44M
          opline = &scdf->op_array->opcodes[j];
158
1.44M
          zend_bitset_excl(scdf->instr_worklist, j);
159
1.44M
          if (opline->opcode != ZEND_OP_DATA) {
160
1.41M
            scdf->handlers.visit_instr(scdf, opline, &ssa->ops[j]);
161
1.41M
          }
162
1.44M
        }
163
195k
        if (block->successors_count == 1) {
164
61.4k
          scdf_mark_edge_feasible(scdf, i, block->successors[0]);
165
133k
        } else if (block->successors_count > 1) {
166
60.8k
          ZEND_ASSERT(opline && "Should have opline in non-empty block");
167
60.8k
          if (opline->opcode == ZEND_OP_DATA) {
168
0
            opline--;
169
0
            j--;
170
0
          }
171
60.8k
          scdf->handlers.mark_feasible_successors(scdf, i, block, opline, &ssa->ops[j-1]);
172
60.8k
        }
173
195k
      }
174
195k
    }
175
75.1k
  }
176
71.2k
}
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
961
    const scdf_ctx *scdf, const zend_op *opline, const zend_ssa_op *ssa_op) {
183
961
  if (!zend_optimizer_is_loop_var_free(opline)) {
184
918
    return false;
185
918
  }
186
187
43
  int var = ssa_op->op1_use;
188
43
  if (var < 0) {
189
11
    return false;
190
11
  }
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
43
}
201
202
4.25k
static bool kept_alive_by_loop_var_free(const scdf_ctx *scdf, const zend_basic_block *block) {
203
4.25k
  const zend_op_array *op_array = scdf->op_array;
204
4.25k
  const zend_cfg *cfg = &scdf->ssa->cfg;
205
4.25k
  if (!(cfg->flags & ZEND_FUNC_FREE_LOOP_VAR)) {
206
3.88k
    return false;
207
3.88k
  }
208
209
1.27k
  for (uint32_t i = block->start; i < block->start + block->len; i++) {
210
919
    if (is_live_loop_var_free(scdf, &op_array->opcodes[i], &scdf->ssa->ops[i])) {
211
16
      return true;
212
16
    }
213
919
  }
214
355
  return false;
215
371
}
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
71.2k
uint32_t scdf_remove_unreachable_blocks(const scdf_ctx *scdf) {
254
71.2k
  zend_ssa *ssa = scdf->ssa;
255
71.2k
  uint32_t removed_ops = 0;
256
270k
  for (uint32_t i = 0; i < ssa->cfg.blocks_count; i++) {
257
199k
    const zend_basic_block *block = &ssa->cfg.blocks[i];
258
199k
    if (!zend_bitset_in(scdf->executable_blocks, i) && (block->flags & ZEND_BB_REACHABLE)) {
259
4.25k
      if (!kept_alive_by_loop_var_free(scdf, block)) {
260
4.24k
        removed_ops += block->len;
261
4.24k
        zend_ssa_remove_block(scdf->op_array, ssa, i);
262
4.24k
      } else {
263
16
        removed_ops += cleanup_loop_var_free_block(scdf, block);
264
16
      }
265
4.25k
    }
266
199k
  }
267
71.2k
  return removed_ops;
268
71.2k
}