Browse Source

Initial integration of Sparse Conditional Constant Propagation (SCCP), originally developed in https://github.com/nikic/php-src/tree/opt, into DFA optimization pass.

pull/2622/head
Dmitry Stogov 8 years ago
parent
commit
4e8fa2c3dd
  1. 8
      Zend/zend_bitset.h
  2. 6
      ext/opcache/Optimizer/dfa_pass.c
  3. 1318
      ext/opcache/Optimizer/sccp.c
  4. 266
      ext/opcache/Optimizer/scdf.c
  5. 99
      ext/opcache/Optimizer/scdf.h
  6. 29
      ext/opcache/Optimizer/zend_optimizer.c
  7. 5
      ext/opcache/Optimizer/zend_optimizer_internal.h
  8. 251
      ext/opcache/Optimizer/zend_ssa.c
  9. 66
      ext/opcache/Optimizer/zend_ssa.h
  10. 2
      ext/opcache/config.m4

8
Zend/zend_bitset.h

@ -245,6 +245,14 @@ static inline int zend_bitset_last(zend_bitset set, uint32_t len)
} \
} while (0)
static inline int zend_bitset_pop_first(zend_bitset set, uint32_t len) {
int i = zend_bitset_first(set, len);
if (i >= 0) {
zend_bitset_excl(set, i);
}
return i;
}
#endif /* _ZEND_BITSET_H_ */
/*

6
ext/opcache/Optimizer/dfa_pass.c

@ -319,7 +319,7 @@ static zend_bool opline_supports_assign_contraction(
return 1;
}
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa)
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map)
{
if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) {
zend_dump_op_array(op_array, ZEND_DUMP_SSA, "before dfa pass", ssa);
@ -332,6 +332,8 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
zend_op *opline;
zval tmp;
sccp_optimize_op_array(op_array, ssa, call_map);
for (v = op_array->last_var; v < ssa->vars_count; v++) {
op_1 = ssa->vars[v].definition;
@ -598,7 +600,7 @@ void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx)
return;
}
zend_dfa_optimize_op_array(op_array, ctx, &ssa);
zend_dfa_optimize_op_array(op_array, ctx, &ssa, NULL);
/* Destroy SSA */
zend_arena_release(&ctx->arena, checkpoint);

1318
ext/opcache/Optimizer/sccp.c
File diff suppressed because it is too large
View File

266
ext/opcache/Optimizer/scdf.c

@ -0,0 +1,266 @@
/*
+----------------------------------------------------------------------+
| Zend Engine, Call Graph |
+----------------------------------------------------------------------+
| Copyright (c) 1998-2017 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
static void mark_edge_feasible(scdf_ctx *ctx, int from, const int *to_ptr, int suc_num) {
int to = *to_ptr;
if (suc_num < 2) {
int edge = from * 2 + suc_num;
if (zend_bitset_in(ctx->feasible_edges, edge)) {
/* We already handled this edge */
return;
}
DEBUG_PRINT("Marking edge %d->%d (successor %d) feasible\n", from, to, suc_num);
zend_bitset_incl(ctx->feasible_edges, edge);
} else {
if (!ctx->feasible_edges_ht) {
ALLOC_HASHTABLE(ctx->feasible_edges_ht);
zend_hash_init(ctx->feasible_edges_ht, 0, NULL, NULL, 0);
}
if (!zend_hash_index_add_empty_element(
ctx->feasible_edges_ht, (zend_long) (intptr_t) to_ptr)) {
/* We already handled this edge */
return;
}
DEBUG_PRINT("Marking edge %d->%d (successor %d) feasible\n", from, to, suc_num);
}
if (!zend_bitset_in(ctx->executable_blocks, to)) {
if (!zend_bitset_in(ctx->block_worklist, to)) {
DEBUG_PRINT("Adding block %d to worklist\n", to);
}
zend_bitset_incl(ctx->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 = &ctx->ssa->blocks[to];
zend_ssa_phi *phi;
for (phi = ssa_block->phis; phi; phi = phi->next) {
ctx->handlers.visit_phi(ctx, ctx->ctx, phi);
}
}
}
/* Returns whether there is a successor */
static inline zend_bool get_feasible_successors(
scdf_ctx *ctx, zend_basic_block *block,
zend_op *opline, zend_ssa_op *ssa_op, zend_bool *suc) {
/* Terminal block without successors */
if (block->successors_count == 0) {
return 0;
}
/* Unconditional jump */
if (block->successors_count == 1) {
suc[0] = 1;
return 1;
}
return ctx->handlers.get_feasible_successors(ctx, ctx->ctx, block, opline, ssa_op, suc);
}
static void handle_instr(scdf_ctx *ctx, int block_num, zend_op *opline, zend_ssa_op *ssa_op) {
zend_basic_block *block = &ctx->ssa->cfg.blocks[block_num];
ctx->handlers.visit_instr(ctx, ctx->ctx, opline, ssa_op);
if (opline - ctx->op_array->opcodes == block->start + block->len - 1) {
if (opline->opcode == ZEND_SWITCH_LONG || opline->opcode == ZEND_SWITCH_STRING) {
// TODO For now consider all edges feasible
int s;
for (s = 0; s < block->successors_count; s++) {
mark_edge_feasible(ctx, block_num, &block->successors[s], s);
}
} else {
zend_bool suc[2] = {0};
if (get_feasible_successors(ctx, block, opline, ssa_op, suc)) {
if (suc[0]) {
mark_edge_feasible(ctx, block_num, &block->successors[0], 0);
}
if (suc[1]) {
mark_edge_feasible(ctx, block_num, &block->successors[1], 1);
}
}
}
}
}
void scdf_init(scdf_ctx *ctx, zend_op_array *op_array, zend_ssa *ssa, void *extra_ctx) {
zend_ulong *bitsets;
ctx->op_array = op_array;
ctx->ssa = ssa;
ctx->ctx = extra_ctx;
ctx->instr_worklist_len = zend_bitset_len(op_array->last);
ctx->phi_var_worklist_len = zend_bitset_len(ssa->vars_count);
ctx->block_worklist_len = zend_bitset_len(ssa->cfg.blocks_count);
bitsets = safe_emalloc(
ctx->instr_worklist_len + ctx->phi_var_worklist_len + 4 * ctx->block_worklist_len,
sizeof(zend_ulong), 0);
ctx->instr_worklist = bitsets;
ctx->phi_var_worklist = ctx->instr_worklist + ctx->instr_worklist_len;
ctx->block_worklist = ctx->phi_var_worklist + ctx->phi_var_worklist_len;
ctx->executable_blocks = ctx->block_worklist + ctx->block_worklist_len;
ctx->feasible_edges = ctx->executable_blocks + ctx->block_worklist_len;
ctx->feasible_edges_ht = NULL;
zend_bitset_clear(ctx->instr_worklist, ctx->instr_worklist_len);
zend_bitset_clear(ctx->phi_var_worklist, ctx->phi_var_worklist_len);
zend_bitset_clear(ctx->block_worklist, ctx->block_worklist_len);
zend_bitset_clear(ctx->executable_blocks, ctx->block_worklist_len);
zend_bitset_clear(ctx->feasible_edges, ctx->block_worklist_len * 2);
zend_bitset_incl(ctx->block_worklist, 0);
zend_bitset_incl(ctx->executable_blocks, 0);
}
void scdf_free(scdf_ctx *ctx) {
if (ctx->feasible_edges_ht) {
zend_hash_destroy(ctx->feasible_edges_ht);
efree(ctx->feasible_edges_ht);
}
efree(ctx->instr_worklist);
}
void scdf_solve(scdf_ctx *ctx, const char *name) {
zend_ssa *ssa = ctx->ssa;
DEBUG_PRINT("Start SCDF solve (%s)\n", name);
while (!zend_bitset_empty(ctx->instr_worklist, ctx->instr_worklist_len)
|| !zend_bitset_empty(ctx->phi_var_worklist, ctx->phi_var_worklist_len)
|| !zend_bitset_empty(ctx->block_worklist, ctx->block_worklist_len)
) {
int i;
while ((i = zend_bitset_pop_first(ctx->phi_var_worklist, ctx->phi_var_worklist_len)) >= 0) {
zend_ssa_phi *phi = ssa->vars[i].definition_phi;
ZEND_ASSERT(phi);
if (zend_bitset_in(ctx->executable_blocks, phi->block)) {
ctx->handlers.visit_phi(ctx, ctx->ctx, phi);
}
}
while ((i = zend_bitset_pop_first(ctx->instr_worklist, ctx->instr_worklist_len)) >= 0) {
int block_num = ssa->cfg.map[i];
if (zend_bitset_in(ctx->executable_blocks, block_num)) {
handle_instr(ctx, block_num, &ctx->op_array->opcodes[i], &ssa->ops[i]);
}
}
while ((i = zend_bitset_pop_first(ctx->block_worklist, ctx->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(ctx->executable_blocks, i);
{
zend_ssa_phi *phi;
for (phi = ssa_block->phis; phi; phi = phi->next) {
zend_bitset_excl(ctx->phi_var_worklist, phi->ssa_var);
ctx->handlers.visit_phi(ctx, ctx->ctx, phi);
}
}
{
int j, end = block->start + block->len;
for (j = block->start; j < end; j++) {
zend_bitset_excl(ctx->instr_worklist, j);
handle_instr(ctx, i, &ctx->op_array->opcodes[j], &ssa->ops[j]);
}
}
if (block->len == 0) {
/* Zero length blocks don't have a last instruction that would normally do this */
mark_edge_feasible(ctx, i, &block->successors[0], 0);
}
}
}
}
/* 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_live_range(scdf_ctx *scdf, uint32_t block) {
uint32_t i;
const zend_op_array *op_array = scdf->op_array;
const zend_cfg *cfg = &scdf->ssa->cfg;
for (i = 0; i < op_array->last_live_range; i++) {
zend_live_range *live_range = &op_array->live_range[i];
uint32_t start_block = cfg->map[live_range->start];
uint32_t end_block = cfg->map[live_range->end];
if (end_block == block && start_block != block
&& zend_bitset_in(scdf->executable_blocks, start_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. */
void scdf_remove_unreachable_blocks(scdf_ctx *scdf) {
zend_ssa *ssa = scdf->ssa;
int i;
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_live_range(scdf, i)) {
zend_ssa_remove_block(scdf->op_array, ssa, i);
}
}
}

99
ext/opcache/Optimizer/scdf.h

@ -0,0 +1,99 @@
/*
+----------------------------------------------------------------------+
| Zend Engine, Call Graph |
+----------------------------------------------------------------------+
| Copyright (c) 1998-2017 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> |
+----------------------------------------------------------------------+
*/
#ifndef _SCDF_H
#define _SCDF_H
#include "zend_bitset.h"
typedef struct _scdf_ctx {
void *ctx;
zend_op_array *op_array;
zend_ssa *ssa;
zend_bitset instr_worklist;
/* Represent phi-instructions through the defining var */
zend_bitset phi_var_worklist;
zend_bitset block_worklist;
zend_bitset executable_blocks;
/* Edge encoding: 2 bits per block, one for each successor */
zend_bitset feasible_edges;
/* If there are more than two successors, an HT is used instead */
HashTable *feasible_edges_ht;
uint32_t instr_worklist_len;
uint32_t phi_var_worklist_len;
uint32_t block_worklist_len;
struct {
void (*visit_instr)(
struct _scdf_ctx *scdf, void *ctx, zend_op *opline, zend_ssa_op *ssa_op);
void (*visit_phi)(
struct _scdf_ctx *scdf, void *ctx, zend_ssa_phi *phi);
zend_bool (*get_feasible_successors)(
struct _scdf_ctx *scdf, void *ctx, zend_basic_block *block,
zend_op *opline, zend_ssa_op *ssa_op, zend_bool *suc);
} handlers;
} scdf_ctx;
void scdf_init(scdf_ctx *scdf, zend_op_array *op_array, zend_ssa *ssa, void *ctx);
void scdf_solve(scdf_ctx *scdf, const char *name);
void scdf_free(scdf_ctx *scdf);
void scdf_remove_unreachable_blocks(scdf_ctx *scdf);
/* Add uses to worklist */
static inline void scdf_add_to_worklist(scdf_ctx *scdf, int var_num) {
zend_ssa *ssa = scdf->ssa;
zend_ssa_var *var = &ssa->vars[var_num];
int use;
zend_ssa_phi *phi;
FOREACH_USE(var, use) {
zend_bitset_incl(scdf->instr_worklist, use);
} FOREACH_USE_END();
FOREACH_PHI_USE(var, phi) {
zend_bitset_incl(scdf->phi_var_worklist, phi->ssa_var);
} FOREACH_PHI_USE_END();
}
/* This should usually not be necessary, however it's used for type narrowing. */
static inline void scdf_add_def_to_worklist(scdf_ctx *scdf, int var_num) {
zend_ssa_var *var = &scdf->ssa->vars[var_num];
if (var->definition >= 0) {
zend_bitset_incl(scdf->instr_worklist, var->definition);
} else if (var->definition_phi) {
zend_bitset_incl(scdf->phi_var_worklist, var_num);
}
}
static inline zend_bool scdf_is_edge_feasible(scdf_ctx *scdf, int from, int to) {
zend_basic_block *block = &scdf->ssa->cfg.blocks[from];
int s;
for (s = 0; s < block->successors_count; s++) {
if (block->successors[s] == to) {
if (s < 2) {
return zend_bitset_in(scdf->feasible_edges, 2 * from + s);
} else {
return scdf->feasible_edges_ht
&& zend_hash_index_exists(scdf->feasible_edges_ht,
(zend_long) (intptr_t) &block->successors[s]);
}
}
}
ZEND_ASSERT(0);
}
#endif

29
ext/opcache/Optimizer/zend_optimizer.c

@ -53,6 +53,25 @@ void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, zval *name, zval*
zend_hash_add(ctx->constants, Z_STR_P(name), &val);
}
zend_uchar zend_compound_assign_to_binary_op(zend_uchar opcode)
{
switch (opcode) {
case ZEND_ASSIGN_ADD: return ZEND_ADD;
case ZEND_ASSIGN_SUB: return ZEND_SUB;
case ZEND_ASSIGN_MUL: return ZEND_MUL;
case ZEND_ASSIGN_DIV: return ZEND_DIV;
case ZEND_ASSIGN_MOD: return ZEND_MOD;
case ZEND_ASSIGN_SL: return ZEND_SL;
case ZEND_ASSIGN_SR: return ZEND_SR;
case ZEND_ASSIGN_CONCAT: return ZEND_CONCAT;
case ZEND_ASSIGN_BW_OR: return ZEND_BW_OR;
case ZEND_ASSIGN_BW_AND: return ZEND_BW_AND;
case ZEND_ASSIGN_BW_XOR: return ZEND_BW_XOR;
case ZEND_ASSIGN_POW: return ZEND_POW;
EMPTY_SWITCH_DEFAULT_CASE()
}
}
int zend_optimizer_eval_binary_op(zval *result, zend_uchar opcode, zval *op1, zval *op2) /* {{{ */
{
binary_op_type binary_op = get_binary_op(opcode);
@ -242,6 +261,14 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array,
MAKE_NOP(opline);
zval_ptr_dtor_nogc(val);
return 1;
case ZEND_SEND_VAR_EX:
case ZEND_FETCH_DIM_W:
case ZEND_FETCH_DIM_RW:
case ZEND_FETCH_DIM_FUNC_ARG:
case ZEND_FETCH_DIM_UNSET:
case ZEND_ASSIGN_DIM:
case ZEND_RETURN_BY_REF:
return 0;
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_CATCH:
case ZEND_FETCH_CONSTANT:
@ -1172,7 +1199,7 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
for (i = 0; i < call_graph.op_arrays_count; i++) {
func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (func_info) {
zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa);
zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, func_info->call_map);
}
}

5
ext/opcache/Optimizer/zend_optimizer_internal.h

@ -23,6 +23,7 @@
#define ZEND_OPTIMIZER_INTERNAL_H
#include "zend_ssa.h"
#include "zend_func_info.h"
#define ZEND_OP1_LITERAL(opline) (op_array)->literals[(opline)->op1.constant]
#define ZEND_OP1_JMP_ADDR(opline) OP_JMP_ADDR(opline, (opline)->op1)
@ -98,7 +99,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx);
void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx);
void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx);
int zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *flags);
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa);
void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map);
void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx);
void zend_optimizer_nop_removal(zend_op_array *op_array);
void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx);
@ -108,5 +109,7 @@ zend_function *zend_optimizer_get_called_func(
uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args);
void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, zend_op *opline);
void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_t *shiftlist);
zend_uchar zend_compound_assign_to_binary_op(zend_uchar opcode);
void sccp_optimize_op_array(zend_op_array *op_arrya, zend_ssa *ssa, zend_call_info **call_map);
#endif

251
ext/opcache/Optimizer/zend_ssa.c

@ -13,6 +13,7 @@
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov <dmitry@zend.com> |
| Nikita Popov <nikic@php.net> |
+----------------------------------------------------------------------+
*/
@ -1145,6 +1146,256 @@ int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var) /* {{{ */
}
/* }}} */
void zend_ssa_remove_instr(zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op) /* {{{ */
{
if (ssa_op->result_use >= 0) {
zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->result_use);
ssa_op->result_use = -1;
ssa_op->res_use_chain = -1;
}
if (ssa_op->op1_use >= 0) {
zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->op1_use);
ssa_op->op1_use = -1;
ssa_op->op1_use_chain = -1;
}
if (ssa_op->op2_use >= 0) {
zend_ssa_unlink_use_chain(ssa, ssa_op - ssa->ops, ssa_op->op2_use);
ssa_op->op2_use = -1;
ssa_op->op2_use_chain = -1;
}
/* We let the caller make sure that all defs are gone */
ZEND_ASSERT(ssa_op->result_def == -1);
ZEND_ASSERT(ssa_op->op1_def == -1);
ZEND_ASSERT(ssa_op->op2_def == -1);
MAKE_NOP(opline);
}
/* }}} */
static inline zend_ssa_phi **zend_ssa_next_use_phi_ptr(zend_ssa *ssa, int var, zend_ssa_phi *p) /* {{{ */
{
if (p->pi >= 0) {
return &p->use_chains[0];
} else {
int j;
for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
if (p->sources[j] == var) {
return &p->use_chains[j];
}
}
}
ZEND_ASSERT(0);
return NULL;
}
/* }}} */
/* May be called even if source is not used in the phi (useful when removing uses in a phi
* with multiple identical operands) */
static inline void zend_ssa_remove_use_of_phi_source(zend_ssa *ssa, zend_ssa_phi *phi, int source) /* {{{ */
{
zend_ssa_phi **cur = &ssa->vars[source].phi_use_chain;
while (*cur && *cur != phi) {
cur = zend_ssa_next_use_phi_ptr(ssa, source, *cur);
}
if (*cur) {
*cur = zend_ssa_next_use_phi(ssa, source, *cur);
}
}
/* }}} */
static void zend_ssa_remove_uses_of_phi_sources(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
{
int source;
FOREACH_PHI_SOURCE(phi, source) {
zend_ssa_remove_use_of_phi_source(ssa, phi, source);
} FOREACH_PHI_SOURCE_END();
}
/* }}} */
static void zend_ssa_remove_phi_from_block(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
{
zend_ssa_block *block = &ssa->blocks[phi->block];
zend_ssa_phi **cur = &block->phis;
while (*cur != phi) {
ZEND_ASSERT(*cur != NULL);
cur = &(*cur)->next;
}
*cur = (*cur)->next;
}
/* }}} */
static inline void zend_ssa_remove_defs_of_instr(zend_ssa *ssa, zend_ssa_op *ssa_op) /* {{{ */
{
if (ssa_op->op1_def >= 0) {
zend_ssa_remove_uses_of_var(ssa, ssa_op->op1_def);
zend_ssa_remove_op1_def(ssa, ssa_op);
}
if (ssa_op->op2_def >= 0) {
zend_ssa_remove_uses_of_var(ssa, ssa_op->op2_def);
zend_ssa_remove_op2_def(ssa, ssa_op);
}
if (ssa_op->result_def >= 0) {
zend_ssa_remove_uses_of_var(ssa, ssa_op->result_def);
zend_ssa_remove_result_def(ssa, ssa_op);
}
}
/* }}} */
static inline void zend_ssa_remove_phi_source(zend_ssa *ssa, zend_ssa_phi *phi, int i) /* {{{ */
{
int j, var_num = phi->sources[i];
/* Check if they same var is used in a different phi operand as well, in this case we don't
* need to adjust the use chain (but may have to move the next pointer). */
for (j = 0; j < ssa->cfg.blocks[phi->block].predecessors_count; j++) {
if (phi->sources[j] == var_num) {
if (j < i) {
phi->sources[i] = -1;
return;
}
if (j > i) {
phi->use_chains[j] = phi->use_chains[i];
phi->use_chains[i] = NULL;
phi->sources[i] = -1;
return;
}
}
}
/* Variable only used in one operand, remove the phi from the use chain. */
zend_ssa_remove_use_of_phi_source(ssa, phi, var_num);
phi->sources[i] = -1;
phi->use_chains[i] = NULL;
}
/* }}} */
void zend_ssa_remove_phi(zend_ssa *ssa, zend_ssa_phi *phi) /* {{{ */
{
ZEND_ASSERT(phi->ssa_var >= 0);
ZEND_ASSERT(ssa->vars[phi->ssa_var].use_chain < 0
&& ssa->vars[phi->ssa_var].phi_use_chain == NULL);
zend_ssa_remove_uses_of_phi_sources(ssa, phi);
zend_ssa_remove_phi_from_block(ssa, phi);
ssa->vars[phi->ssa_var].definition_phi = NULL;
phi->ssa_var = -1;
}
/* }}} */
void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num) /* {{{ */
{
zend_ssa_var *var = &ssa->vars[var_num];
zend_ssa_phi *phi;
int use;
FOREACH_PHI_USE(var, phi) {
int i, end = NUM_PHI_SOURCES(phi);
for (i = 0; i < end; i++) {
if (phi->sources[i] == var_num) {
phi->sources[i] = -1;
phi->use_chains[i] = NULL;
}
}
} FOREACH_PHI_USE_END();
var->phi_use_chain = NULL;
FOREACH_USE(var, use) {
zend_ssa_op *ssa_op = &ssa->ops[use];
if (ssa_op->op1_use == var_num) {
ssa_op->op1_use = -1;
ssa_op->op1_use_chain = -1;
}
if (ssa_op->op2_use == var_num) {
ssa_op->op2_use = -1;
ssa_op->op2_use_chain = -1;
}
if (ssa_op->result_use == var_num) {
ssa_op->result_use = -1;
ssa_op->res_use_chain = -1;
}
} FOREACH_USE_END();
var->use_chain = -1;
}
/* }}} */
void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int i) /* {{{ */
{
zend_basic_block *block = &ssa->cfg.blocks[i];
zend_ssa_block *ssa_block = &ssa->blocks[i];
int *predecessors;
zend_ssa_phi *phi;
int j, s;
block->flags &= ~ZEND_BB_REACHABLE;
/* Removes phis in this block */
for (phi = ssa_block->phis; phi; phi = phi->next) {
zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
zend_ssa_remove_phi(ssa, phi);
}
/* Remove instructions in this block */
for (j = block->start; j < block->start + block->len; j++) {
if (op_array->opcodes[j].opcode == ZEND_NOP) {
continue;
}
zend_ssa_remove_defs_of_instr(ssa, &ssa->ops[j]);
zend_ssa_remove_instr(ssa, &op_array->opcodes[j], &ssa->ops[j]);
}
for (s = 0; s < block->successors_count; s++) {
zend_basic_block *next_block = &ssa->cfg.blocks[block->successors[s]];
zend_ssa_block *next_ssa_block = &ssa->blocks[block->successors[s]];
zend_ssa_phi *phi;
/* Find at which predecessor offset this block is referenced */
int pred_offset = -1;
predecessors = &ssa->cfg.predecessors[next_block->predecessor_offset];
for (j = 0; j < next_block->predecessors_count; j++) {
if (predecessors[j] == i) {
pred_offset = j;
break;
}
}
ZEND_ASSERT(pred_offset != -1);
/* For phis in successor blocks, remove the operands associated with this block */
for (phi = next_ssa_block->phis; phi; phi = phi->next) {
if (phi->pi >= 0) {
if (phi->pi == i) {
zend_ssa_remove_uses_of_var(ssa, phi->ssa_var);
zend_ssa_remove_phi(ssa, phi);
}
} else {
if (phi->sources[pred_offset] >= 0) {
zend_ssa_remove_phi_source(ssa, phi, pred_offset);
}
}
}
/* Remove this predecessor */
predecessors[pred_offset] = -1;
}
/* Remove successors of predecessors */
predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
for (j = 0; j < block->predecessors_count; j++) {
if (predecessors[j] >= 0) {
zend_basic_block *prev_block = &ssa->cfg.blocks[predecessors[j]];
for (s = 0; s < prev_block->successors_count; s++) {
if (prev_block->successors[s] == i) {
memmove(prev_block->successors + s,
prev_block->successors + s + 1,
sizeof(int) * (prev_block->successors_count - s - 1));
prev_block->successors_count--;
s--;
}
}
}
}
}
/* }}} */
/*
* Local variables:
* tab-width: 4

66
ext/opcache/Optimizer/zend_ssa.h

@ -131,6 +131,40 @@ int zend_build_ssa(zend_arena **arena, const zend_script *script, const zend_op_
int zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_array *op_array, zend_ssa *ssa);
int zend_ssa_unlink_use_chain(zend_ssa *ssa, int op, int var);
void zend_ssa_remove_instr(zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op);
void zend_ssa_remove_phi(zend_ssa *ssa, zend_ssa_phi *phi);
void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num);
void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int b);
static zend_always_inline void _zend_ssa_remove_def(zend_ssa_var *var)
{
ZEND_ASSERT(var->definition >= 0);
ZEND_ASSERT(var->use_chain < 0);
ZEND_ASSERT(!var->phi_use_chain);
var->definition = -1;
}
static zend_always_inline void zend_ssa_remove_result_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
{
zend_ssa_var *var = &ssa->vars[ssa_op->result_def];
_zend_ssa_remove_def(var);
ssa_op->result_def = -1;
}
static zend_always_inline void zend_ssa_remove_op1_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
{
zend_ssa_var *var = &ssa->vars[ssa_op->op1_def];
_zend_ssa_remove_def(var);
ssa_op->op1_def = -1;
}
static zend_always_inline void zend_ssa_remove_op2_def(zend_ssa *ssa, zend_ssa_op *ssa_op)
{
zend_ssa_var *var = &ssa->vars[ssa_op->op2_def];
_zend_ssa_remove_def(var);
ssa_op->op2_def = -1;
}
END_EXTERN_C()
static zend_always_inline int zend_ssa_next_use(const zend_ssa_op *ssa_op, int var, int use)
@ -172,6 +206,38 @@ static zend_always_inline zend_bool zend_ssa_is_no_val_use(const zend_op *opline
return 0;
}
#define NUM_PHI_SOURCES(phi) \
((phi)->pi >= 0 ? 1 : (ssa->cfg.blocks[(phi)->block].predecessors_count))
/* FOREACH_USE and FOREACH_PHI_USE explicitly support "continue"
* and changing the use chain of the current element */
#define FOREACH_USE(var, use) do { \
int _var_num = (var) - ssa->vars, next; \
for (use = (var)->use_chain; use >= 0; use = next) { \
next = zend_ssa_next_use(ssa->ops, _var_num, use);
#define FOREACH_USE_END() \
} \
} while (0)
#define FOREACH_PHI_USE(var, phi) do { \
int _var_num = (var) - ssa->vars; \
zend_ssa_phi *next_phi; \
for (phi = (var)->phi_use_chain; phi; phi = next_phi) { \
next_phi = zend_ssa_next_use_phi(ssa, _var_num, phi);
#define FOREACH_PHI_USE_END() \
} \
} while (0)
#define FOREACH_PHI_SOURCE(phi, source) do { \
zend_ssa_phi *_phi = (phi); \
int _i, _end = NUM_PHI_SOURCES(phi); \
for (_i = 0; _i < _end; _i++) { \
if (_phi->sources[_i] < 0) continue; \
source = _phi->sources[_i];
#define FOREACH_PHI_SOURCE_END() \
} \
} while (0)
#endif /* ZEND_SSA_H */
/*

2
ext/opcache/config.m4

@ -410,6 +410,8 @@ fi
Optimizer/zend_inference.c \
Optimizer/zend_func_info.c \
Optimizer/zend_call_graph.c \
Optimizer/sccp.c \
Optimizer/scdf.c \
Optimizer/zend_dump.c,
shared,,-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1,,yes)

Loading…
Cancel
Save