mirror of
https://github.com/php/php-src.git
synced 2025-01-10 04:54:47 +08:00
232 lines
9.5 KiB
C
232 lines
9.5 KiB
C
/*
|
|
+----------------------------------------------------------------------+
|
|
| Zend Engine, Sparse Conditional Data Flow Propagation Framework |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Nikita Popov <nikic@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "ZendAccelerator.h"
|
|
#include "Optimizer/zend_optimizer_internal.h"
|
|
#include "Optimizer/scdf.h"
|
|
|
|
/* This defines a generic framework for sparse conditional dataflow propagation. The algorithm is
|
|
* based on "Sparse conditional constant propagation" by Wegman and Zadeck. We're using a
|
|
* generalized implementation as described in chapter 8.3 of the SSA book.
|
|
*
|
|
* Every SSA variable is associated with an element on a finite-height lattice, those value can only
|
|
* ever be lowered during the operation of the algorithm. If a value is lowered all instructions and
|
|
* phis using that value need to be reconsidered (this is done by adding the variable to a
|
|
* worklist). For phi functions the result is computed by applying the meet operation to the
|
|
* operands. This continues until a fixed point is reached.
|
|
*
|
|
* The algorithm is control-flow sensitive: All blocks except the start block are initially assumed
|
|
* to be unreachable. When considering a branch instruction, we determine the feasible successors
|
|
* based on the current state of the variable lattice. If a new edge becomes feasible we either have
|
|
* to mark the successor block executable and consider all instructions in it, or, if the target is
|
|
* already executable, we only have to reconsider the phi functions (as we only consider phi
|
|
* operands which are associated with a feasible edge).
|
|
*
|
|
* The generic framework requires the definition of three functions:
|
|
* * visit_instr() should recompute the lattice values of all SSA variables defined by an
|
|
* instruction.
|
|
* * visit_phi() should recompute the lattice value of the SSA variable defined by the phi. While
|
|
* doing this it should only consider operands for which scfg_is_edge_feasible() returns true.
|
|
* * get_feasible_successors() should determine the feasible successors for a branch instruction.
|
|
* Note that this callback only needs to handle conditional branches (with two successors).
|
|
*/
|
|
|
|
#if 0
|
|
#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)
|
|
#else
|
|
#define DEBUG_PRINT(...)
|
|
#endif
|
|
|
|
void scdf_mark_edge_feasible(scdf_ctx *scdf, int from, int to) {
|
|
uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
|
|
|
|
if (zend_bitset_in(scdf->feasible_edges, edge)) {
|
|
/* We already handled this edge */
|
|
return;
|
|
}
|
|
|
|
DEBUG_PRINT("Marking edge %d->%d feasible\n", from, to);
|
|
zend_bitset_incl(scdf->feasible_edges, edge);
|
|
|
|
if (!zend_bitset_in(scdf->executable_blocks, to)) {
|
|
if (!zend_bitset_in(scdf->block_worklist, to)) {
|
|
DEBUG_PRINT("Adding block %d to worklist\n", to);
|
|
}
|
|
zend_bitset_incl(scdf->block_worklist, to);
|
|
} else {
|
|
/* Block is already executable, only a new edge became feasible.
|
|
* Reevaluate phi nodes to account for changed source operands. */
|
|
zend_ssa_block *ssa_block = &scdf->ssa->blocks[to];
|
|
zend_ssa_phi *phi;
|
|
for (phi = ssa_block->phis; phi; phi = phi->next) {
|
|
zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
|
|
scdf->handlers.visit_phi(scdf, phi);
|
|
}
|
|
}
|
|
}
|
|
|
|
void scdf_init(zend_optimizer_ctx *ctx, scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa) {
|
|
scdf->op_array = op_array;
|
|
scdf->ssa = ssa;
|
|
|
|
scdf->instr_worklist_len = zend_bitset_len(op_array->last);
|
|
scdf->phi_var_worklist_len = zend_bitset_len(ssa->vars_count);
|
|
scdf->block_worklist_len = zend_bitset_len(ssa->cfg.blocks_count);
|
|
|
|
scdf->instr_worklist = zend_arena_calloc(&ctx->arena,
|
|
scdf->instr_worklist_len + scdf->phi_var_worklist_len + 2 * scdf->block_worklist_len + zend_bitset_len(ssa->cfg.edges_count),
|
|
sizeof(zend_ulong));
|
|
|
|
scdf->phi_var_worklist = scdf->instr_worklist + scdf->instr_worklist_len;
|
|
scdf->block_worklist = scdf->phi_var_worklist + scdf->phi_var_worklist_len;
|
|
scdf->executable_blocks = scdf->block_worklist + scdf->block_worklist_len;
|
|
scdf->feasible_edges = scdf->executable_blocks + scdf->block_worklist_len;
|
|
|
|
zend_bitset_incl(scdf->block_worklist, 0);
|
|
zend_bitset_incl(scdf->executable_blocks, 0);
|
|
}
|
|
|
|
void scdf_solve(scdf_ctx *scdf, const char *name) {
|
|
zend_ssa *ssa = scdf->ssa;
|
|
DEBUG_PRINT("Start SCDF solve (%s)\n", name);
|
|
while (!zend_bitset_empty(scdf->instr_worklist, scdf->instr_worklist_len)
|
|
|| !zend_bitset_empty(scdf->phi_var_worklist, scdf->phi_var_worklist_len)
|
|
|| !zend_bitset_empty(scdf->block_worklist, scdf->block_worklist_len)
|
|
) {
|
|
int i;
|
|
while ((i = zend_bitset_pop_first(scdf->phi_var_worklist, scdf->phi_var_worklist_len)) >= 0) {
|
|
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
|
|
ZEND_ASSERT(phi);
|
|
if (zend_bitset_in(scdf->executable_blocks, phi->block)) {
|
|
scdf->handlers.visit_phi(scdf, phi);
|
|
}
|
|
}
|
|
|
|
while ((i = zend_bitset_pop_first(scdf->instr_worklist, scdf->instr_worklist_len)) >= 0) {
|
|
int block_num = ssa->cfg.map[i];
|
|
if (zend_bitset_in(scdf->executable_blocks, block_num)) {
|
|
zend_basic_block *block = &ssa->cfg.blocks[block_num];
|
|
zend_op *opline = &scdf->op_array->opcodes[i];
|
|
zend_ssa_op *ssa_op = &ssa->ops[i];
|
|
if (opline->opcode == ZEND_OP_DATA) {
|
|
opline--;
|
|
ssa_op--;
|
|
}
|
|
scdf->handlers.visit_instr(scdf, opline, ssa_op);
|
|
if (i == block->start + block->len - 1) {
|
|
if (block->successors_count == 1) {
|
|
scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
|
|
} else if (block->successors_count > 1) {
|
|
scdf->handlers.mark_feasible_successors(scdf, block_num, block, opline, ssa_op);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while ((i = zend_bitset_pop_first(scdf->block_worklist, scdf->block_worklist_len)) >= 0) {
|
|
/* This block is now live. Interpret phis and instructions in it. */
|
|
zend_basic_block *block = &ssa->cfg.blocks[i];
|
|
zend_ssa_block *ssa_block = &ssa->blocks[i];
|
|
|
|
DEBUG_PRINT("Pop block %d from worklist\n", i);
|
|
zend_bitset_incl(scdf->executable_blocks, i);
|
|
|
|
{
|
|
zend_ssa_phi *phi;
|
|
for (phi = ssa_block->phis; phi; phi = phi->next) {
|
|
zend_bitset_excl(scdf->phi_var_worklist, phi->ssa_var);
|
|
scdf->handlers.visit_phi(scdf, phi);
|
|
}
|
|
}
|
|
|
|
if (block->len == 0) {
|
|
/* Zero length blocks don't have a last instruction that would normally do this */
|
|
scdf_mark_edge_feasible(scdf, i, block->successors[0]);
|
|
} else {
|
|
zend_op *opline = NULL;
|
|
int j, end = block->start + block->len;
|
|
for (j = block->start; j < end; j++) {
|
|
opline = &scdf->op_array->opcodes[j];
|
|
zend_bitset_excl(scdf->instr_worklist, j);
|
|
if (opline->opcode != ZEND_OP_DATA) {
|
|
scdf->handlers.visit_instr(scdf, opline, &ssa->ops[j]);
|
|
}
|
|
}
|
|
if (block->successors_count == 1) {
|
|
scdf_mark_edge_feasible(scdf, i, block->successors[0]);
|
|
} else if (block->successors_count > 1) {
|
|
ZEND_ASSERT(opline && "Should have opline in non-empty block");
|
|
if (opline->opcode == ZEND_OP_DATA) {
|
|
opline--;
|
|
j--;
|
|
}
|
|
scdf->handlers.mark_feasible_successors(scdf, i, block, opline, &ssa->ops[j-1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If a live range starts in a reachable block and ends in an unreachable block, we should
|
|
* not eliminate the latter. While it cannot be reached, the FREE opcode of the loop var
|
|
* is necessary for the correctness of temporary compaction. */
|
|
static zend_bool kept_alive_by_loop_var_free(scdf_ctx *scdf, uint32_t block_idx) {
|
|
uint32_t i;
|
|
const zend_op_array *op_array = scdf->op_array;
|
|
const zend_cfg *cfg = &scdf->ssa->cfg;
|
|
const zend_basic_block *block = &cfg->blocks[block_idx];
|
|
if (!(cfg->flags & ZEND_FUNC_FREE_LOOP_VAR)) {
|
|
return 0;
|
|
}
|
|
for (i = block->start; i < block->start + block->len; i++) {
|
|
zend_op *opline = &op_array->opcodes[i];
|
|
if (opline->opcode == ZEND_FE_FREE ||
|
|
(opline->opcode == ZEND_FREE && opline->extended_value == ZEND_FREE_SWITCH)) {
|
|
int ssa_var = scdf->ssa->ops[i].op1_use;
|
|
if (ssa_var >= 0) {
|
|
int op_num = scdf->ssa->vars[ssa_var].definition;
|
|
uint32_t def_block;
|
|
ZEND_ASSERT(op_num >= 0);
|
|
def_block = cfg->map[op_num];
|
|
if (zend_bitset_in(scdf->executable_blocks, def_block)) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Removes unreachable blocks. This will remove both the instructions (and phis) in the
|
|
* blocks, as well as remove them from the successor / predecessor lists and mark them
|
|
* unreachable. Blocks already marked unreachable are not removed. */
|
|
int scdf_remove_unreachable_blocks(scdf_ctx *scdf) {
|
|
zend_ssa *ssa = scdf->ssa;
|
|
int i;
|
|
int removed_ops = 0;
|
|
for (i = 0; i < ssa->cfg.blocks_count; i++) {
|
|
if (!zend_bitset_in(scdf->executable_blocks, i)
|
|
&& (ssa->cfg.blocks[i].flags & ZEND_BB_REACHABLE)
|
|
&& !kept_alive_by_loop_var_free(scdf, i)) {
|
|
removed_ops += ssa->cfg.blocks[i].len;
|
|
zend_ssa_remove_block(scdf->op_array, ssa, i);
|
|
}
|
|
}
|
|
return removed_ops;
|
|
}
|