Coverage Report

Created: 2025-12-31 07:28

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