Implement single-pass live range calculation

Instead of interleaving creation of live-ranges with the main
compiler code, compute them in a separate pass over the opcodes
as part of pass_two. Additionally, do not keep live ranges
synchronized during optimization in opcache and instead use the
same mechanism to recompute them after optimization.
This commit is contained in:
Nikita Popov 2019-01-17 16:07:17 +01:00
parent 276d3a7d15
commit 3269e88468
18 changed files with 301 additions and 655 deletions

View file

@ -56,10 +56,7 @@ typedef struct _zend_loop_var {
zend_uchar opcode;
zend_uchar var_type;
uint32_t var_num;
union {
uint32_t try_catch_offset;
uint32_t live_range_offset;
} u;
} zend_loop_var;
static inline uint32_t zend_alloc_cache_slots(unsigned count) {
@ -625,33 +622,6 @@ void zend_stop_lexing(void)
LANG_SCNG(yy_cursor) = LANG_SCNG(yy_limit);
}
static uint32_t zend_start_live_range(uint32_t start) /* {{{ */
{
zend_op_array *op_array = CG(active_op_array);
zend_live_range *range;
op_array->last_live_range++;
op_array->live_range = erealloc(op_array->live_range, sizeof(zend_live_range) * op_array->last_live_range);
range = op_array->live_range + op_array->last_live_range - 1;
range->start = start;
return op_array->last_live_range - 1;
}
/* }}} */
static void zend_end_live_range(uint32_t offset, uint32_t end, uint32_t kind, uint32_t var) /* {{{ */
{
zend_op_array *op_array = CG(active_op_array);
zend_live_range *range = op_array->live_range + offset;
if (range->start == end && offset == (uint32_t)op_array->last_live_range - 1) {
op_array->last_live_range--;
} else {
range->end = end;
range->var = (var * sizeof(zval)) | kind;
}
}
/* }}} */
static inline void zend_begin_loop(
zend_uchar free_opcode, const znode *loop_var, zend_bool is_switch) /* {{{ */
{
@ -670,7 +640,6 @@ static inline void zend_begin_loop(
info.opcode = free_opcode;
info.var_type = loop_var->op_type;
info.var_num = loop_var->u.op.var;
info.u.live_range_offset = zend_start_live_range(start);
brk_cont_element->start = start;
} else {
info.opcode = ZEND_NOP;
@ -692,13 +661,6 @@ static inline void zend_end_loop(int cont_addr, const znode *var_node) /* {{{ */
brk_cont_element->brk = end;
CG(context).current_brk_cont = brk_cont_element->parent;
if (brk_cont_element->start != -1) {
zend_loop_var *loop_var = zend_stack_top(&CG(loop_var_stack));
zend_end_live_range(loop_var->u.live_range_offset, end,
loop_var->opcode == ZEND_FE_FREE ? ZEND_LIVE_LOOP : ZEND_LIVE_TMPVAR,
var_node->u.op.var);
}
zend_stack_del_top(&CG(loop_var_stack));
}
/* }}} */
@ -1809,172 +1771,6 @@ static inline void zend_make_tmp_result(znode *result, zend_op *opline) /* {{{ *
}
/* }}} */
static void zend_find_live_range(zend_op *opline, zend_uchar type, uint32_t var) /* {{{ */
{
zend_op *def = opline;
while (def != CG(active_op_array)->opcodes) {
def--;
if (def->result_type == type && def->result.var == var) {
if (def->opcode == ZEND_ADD_ARRAY_ELEMENT ||
def->opcode == ZEND_ROPE_ADD) {
/* not a real definition */
continue;
} else if (def->opcode == ZEND_JMPZ_EX ||
def->opcode == ZEND_JMPNZ_EX ||
def->opcode == ZEND_BOOL ||
def->opcode == ZEND_BOOL_NOT) {
/* result IS_BOOL, it doesn't have to be destroyed */
break;
} else if (def->opcode == ZEND_DECLARE_ANON_CLASS ||
def->opcode == ZEND_DECLARE_ANON_INHERITED_CLASS) {
/* classes don't have to be destroyed */
break;
} else if (def->opcode == ZEND_FAST_CALL) {
/* fast_calls don't have to be destroyed */
break;
} else if (def->opcode == ZEND_NEW) {
/* Objects created via ZEND_NEW are only fully initialized
* after the DO_FCALL (constructor call) */
int level = 0;
while (def + 1 != opline) {
def++;
if (def->opcode == ZEND_DO_FCALL) {
if (level == 0) {
break;
}
level--;
} else {
switch(def->opcode) {
case ZEND_INIT_FCALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_INIT_USER_CALL:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_NEW:
level++;
break;
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
level--;
break;
}
}
}
if (def + 1 == opline) {
break;
}
}
zend_end_live_range(
zend_start_live_range(
def + 1 - CG(active_op_array)->opcodes),
opline - CG(active_op_array)->opcodes,
ZEND_LIVE_TMPVAR, var);
break;
}
}
}
/* }}} */
static zend_always_inline int zend_is_def_range(zend_op *opline, zend_uchar type, uint32_t var) /* {{{ */
{
while (1) {
if (opline->result_type == type && opline->result.var == var) {
return opline->opcode != ZEND_ADD_ARRAY_ELEMENT &&
opline->opcode != ZEND_ROPE_ADD;
} else if (opline->opcode == ZEND_OP_DATA) {
return (opline-1)->result_type == type &&
(opline-1)->result.var == var;
} else if (opline->opcode == ZEND_END_SILENCE ||
opline->opcode == ZEND_NOP ||
opline->opcode == ZEND_EXT_NOP ||
opline->opcode == ZEND_EXT_STMT ||
opline->opcode == ZEND_EXT_FCALL_BEGIN ||
opline->opcode == ZEND_EXT_FCALL_END ||
opline->opcode == ZEND_TICKS) {
opline--;
} else {
return 0;
}
}
}
/* }}} */
static void zend_check_live_ranges_op1(zend_op *opline) /* {{{ */
{
if (!zend_is_def_range(opline - 1, opline->op1_type, opline->op1.var)) {
if (opline->opcode == ZEND_OP_DATA) {
if (!zend_is_def_range(opline - 2, opline->op1_type, opline->op1.var)) {
zend_find_live_range(opline - 1, opline->op1_type, opline->op1.var);
}
} else if (opline->opcode == ZEND_INIT_STATIC_METHOD_CALL ||
opline->opcode == ZEND_NEW ||
opline->opcode == ZEND_FETCH_CLASS_CONSTANT) {
/* classes don't have to be destroyed */
} else if (opline->opcode == ZEND_FAST_RET) {
/* fast_calls don't have to be destroyed */
} else if (opline->opcode == ZEND_CASE ||
opline->opcode == ZEND_SWITCH_LONG ||
opline->opcode == ZEND_SWITCH_STRING ||
opline->opcode == ZEND_FE_FETCH_R ||
opline->opcode == ZEND_FE_FETCH_RW ||
opline->opcode == ZEND_FE_FREE ||
opline->opcode == ZEND_ROPE_ADD ||
opline->opcode == ZEND_ROPE_END ||
opline->opcode == ZEND_END_SILENCE ||
opline->opcode == ZEND_FETCH_LIST_R ||
opline->opcode == ZEND_FETCH_LIST_W ||
opline->opcode == ZEND_VERIFY_RETURN_TYPE ||
opline->opcode == ZEND_BIND_LEXICAL) {
/* these opcodes are handled separately */
} else {
zend_find_live_range(opline, opline->op1_type, opline->op1.var);
}
}
}
/* }}} */
static void zend_check_live_ranges_op2(zend_op *opline) /* {{{ */
{
if (!zend_is_def_range(opline - 1, opline->op2_type, opline->op2.var)) {
if (opline->opcode == ZEND_OP_DATA) {
if (!zend_is_def_range(opline - 2, opline->op2_type, opline->op2.var)) {
zend_find_live_range(opline-1, opline->op2_type, opline->op2.var);
}
} else if (opline->opcode == ZEND_FETCH_STATIC_PROP_R ||
opline->opcode == ZEND_FETCH_STATIC_PROP_W ||
opline->opcode == ZEND_FETCH_STATIC_PROP_RW ||
opline->opcode == ZEND_FETCH_STATIC_PROP_IS ||
opline->opcode == ZEND_FETCH_STATIC_PROP_FUNC_ARG ||
opline->opcode == ZEND_FETCH_STATIC_PROP_UNSET ||
opline->opcode == ZEND_UNSET_STATIC_PROP ||
opline->opcode == ZEND_ISSET_ISEMPTY_STATIC_PROP ||
opline->opcode == ZEND_INSTANCEOF) {
/* classes don't have to be destroyed */
} else {
zend_find_live_range(opline, opline->op2_type, opline->op2.var);
}
}
}
/* }}} */
static void zend_check_live_ranges(zend_op *opline) /* {{{ */
{
if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
zend_check_live_ranges_op1(opline);
}
if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {
zend_check_live_ranges_op2(opline);
}
}
/* }}} */
static zend_op *zend_emit_op(znode *result, zend_uchar opcode, znode *op1, znode *op2) /* {{{ */
{
zend_op *opline = get_next_op();
@ -1982,16 +1778,10 @@ static zend_op *zend_emit_op(znode *result, zend_uchar opcode, znode *op1, znode
if (op1 != NULL) {
SET_NODE(opline->op1, op1);
if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
zend_check_live_ranges_op1(opline);
}
}
if (op2 != NULL) {
SET_NODE(opline->op2, op2);
if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {
zend_check_live_ranges_op2(opline);
}
}
if (result) {
@ -2008,16 +1798,10 @@ static zend_op *zend_emit_op_tmp(znode *result, zend_uchar opcode, znode *op1, z
if (op1 != NULL) {
SET_NODE(opline->op1, op1);
if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) {
zend_check_live_ranges_op1(opline);
}
}
if (op2 != NULL) {
SET_NODE(opline->op2, op2);
if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) {
zend_check_live_ranges_op2(opline);
}
}
if (result) {
@ -2166,7 +1950,6 @@ static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */
for (i = offset; i < count; ++i) {
opline = get_next_op();
memcpy(opline, &oplines[i], sizeof(zend_op));
zend_check_live_ranges(opline);
}
CG(delayed_oplines_stack).top = offset;
return opline;
@ -4099,7 +3882,6 @@ void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type) /* {{
}
SET_NODE(opline->op2, &method_node);
}
zend_check_live_ranges(opline);
/* Check if we already know which method we're calling */
if (opline->op2_type == IS_CONST) {
@ -4321,7 +4103,7 @@ static int zend_handle_loops_and_finally_ex(zend_long depth, znode *return_value
if (return_value) {
SET_NODE(opline->op2, return_value);
}
opline->op1.num = loop_var->u.try_catch_offset;
opline->op1.num = loop_var->try_catch_offset;
} else if (loop_var->opcode == ZEND_DISCARD_EXCEPTION) {
zend_op *opline = get_next_op();
opline->opcode = ZEND_DISCARD_EXCEPTION;
@ -5043,10 +4825,8 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
zend_end_loop(get_next_op_number(), &expr_node);
if (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) {
/* don't use emit_op() to prevent automatic live-range construction */
opline = get_next_op();
opline->opcode = ZEND_FREE;
SET_NODE(opline->op1, &expr_node);
opline = zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL);
opline->extended_value = ZEND_FREE_SWITCH;
} else if (expr_node.op_type == IS_CONST) {
zval_ptr_dtor_nogc(&expr_node.u.constant);
}
@ -5096,7 +4876,7 @@ void zend_compile_try(zend_ast *ast) /* {{{ */
fast_call.opcode = ZEND_FAST_CALL;
fast_call.var_type = IS_TMP_VAR;
fast_call.var_num = CG(context).fast_call_var;
fast_call.u.try_catch_offset = try_catch_offset;
fast_call.try_catch_offset = try_catch_offset;
zend_stack_push(&CG(loop_var_stack), &fast_call);
}
@ -7631,9 +7411,7 @@ void zend_compile_silence(znode *result, zend_ast *ast) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
znode silence_node;
uint32_t range;
range = zend_start_live_range(get_next_op_number());
zend_emit_op_tmp(&silence_node, ZEND_BEGIN_SILENCE, NULL, NULL);
if (expr_ast->kind == ZEND_AST_VAR) {
@ -7644,11 +7422,6 @@ void zend_compile_silence(znode *result, zend_ast *ast) /* {{{ */
zend_compile_expr(result, expr_ast);
}
/* Store BEGIN_SILENCE/END_SILENCE pair to restore previous
* EG(error_reporting) value on exception */
zend_end_live_range(range, get_next_op_number(),
ZEND_LIVE_SILENCE, silence_node.u.op.var);
zend_emit_op(NULL, ZEND_END_SILENCE, &silence_node, NULL);
}
/* }}} */
@ -7940,7 +7713,6 @@ static void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
GET_NODE(result, opline->result);
} else {
uint32_t var;
uint32_t range = zend_start_live_range(rope_init_lineno);
init_opline->extended_value = j;
opline->opcode = ZEND_ROPE_END;
@ -7955,9 +7727,6 @@ static void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
i--;
}
zend_end_live_range(range, opline - CG(active_op_array)->opcodes,
ZEND_LIVE_ROPE, var);
/* Update all the previous opcodes to use the same variable */
while (opline != init_opline) {
opline--;

View file

@ -806,6 +806,10 @@ static zend_always_inline const char *zend_get_unmangled_property_name(const zen
#define ZEND_FUNCTION_DTOR zend_function_dtor
#define ZEND_CLASS_DTOR destroy_zend_class
typedef zend_bool (*zend_needs_live_range_cb)(zend_op_array *op_array, zend_op *opline);
ZEND_API void zend_recalc_live_ranges(
zend_op_array *op_array, zend_needs_live_range_cb needs_live_range);
ZEND_API int pass_two(zend_op_array *op_array);
ZEND_API zend_bool zend_is_compiling(void);
ZEND_API char *zend_make_compiled_string_description(const char *name);
@ -909,6 +913,7 @@ void zend_assert_valid_class_name(const zend_string *const_name);
#define ZEND_LAST_CATCH (1<<0)
#define ZEND_FREE_ON_RETURN (1<<0)
#define ZEND_FREE_SWITCH (1<<1)
#define ZEND_SEND_BY_VAL 0
#define ZEND_SEND_BY_REF 1

View file

@ -547,6 +547,123 @@ static uint32_t zend_get_brk_cont_target(const zend_op_array *op_array, const ze
return opline->opcode == ZEND_BRK ? jmp_to->brk : jmp_to->cont;
}
static void emit_live_range(
zend_op_array *op_array, uint32_t var_num, uint32_t start, uint32_t end,
zend_needs_live_range_cb needs_live_range) {
zend_op *def_opline = &op_array->opcodes[start], *orig_def_opline = def_opline;
zend_op *use_opline = &op_array->opcodes[end];
zend_live_range *range;
uint32_t kind = ZEND_LIVE_TMPVAR;
switch (def_opline->opcode) {
/* These should never be the first def. */
case ZEND_ADD_ARRAY_ELEMENT:
case ZEND_ROPE_ADD:
ZEND_ASSERT(0);
return;
/* Result is boolean, it doesn't have to be destroyed. */
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_BOOL:
case ZEND_BOOL_NOT:
/* Classes don't have to be destroyed. */
case ZEND_FETCH_CLASS:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:
/* FAST_CALLs don't have to be destroyed. */
case ZEND_FAST_CALL:
return;
case ZEND_BEGIN_SILENCE:
kind = ZEND_LIVE_SILENCE;
break;
case ZEND_ROPE_INIT:
kind = ZEND_LIVE_ROPE;
/* ROPE live ranges include the generating opcode. */
def_opline--;
break;
case ZEND_FE_RESET_R:
case ZEND_FE_RESET_RW:
kind = ZEND_LIVE_LOOP;
break;
/* Objects created via ZEND_NEW are only fully initialized
* after the DO_FCALL (constructor call). */
case ZEND_NEW: {
int level = 0;
while (def_opline + 1 < use_opline) {
def_opline++;
if (def_opline->opcode == ZEND_DO_FCALL) {
if (level == 0) {
break;
}
level--;
} else {
switch (def_opline->opcode) {
case ZEND_INIT_FCALL:
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_INIT_USER_CALL:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_NEW:
level++;
break;
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
level--;
break;
}
}
}
break;
}
}
start = def_opline + 1 - op_array->opcodes;
if (start == end) {
/* Trivial live-range, no need to store it. */
return;
}
/* Check hook to determine whether a live range is necessary, e.g. based on type info. */
if (needs_live_range && !needs_live_range(op_array, orig_def_opline)) {
return;
}
op_array->last_live_range++;
op_array->live_range = erealloc(op_array->live_range,
sizeof(zend_live_range) * op_array->last_live_range);
ZEND_ASSERT(start < end);
range = &op_array->live_range[op_array->last_live_range - 1];
range->var = (uint32_t) (intptr_t) ZEND_CALL_VAR_NUM(NULL, op_array->last_var + var_num);
range->var |= kind;
range->start = start;
range->end = end;
}
static zend_bool is_fake_def(zend_op *opline) {
/* These opcodes only modify the result, not create it. */
return opline->opcode == ZEND_ROPE_ADD
|| opline->opcode == ZEND_ADD_ARRAY_ELEMENT;
}
static zend_bool keeps_op1_alive(zend_op *opline) {
/* These opcodes don't consume their OP1 operand,
* it is later freed by something else. */
return opline->opcode == ZEND_CASE
|| opline->opcode == ZEND_SWITCH_LONG
|| opline->opcode == ZEND_SWITCH_STRING
|| opline->opcode == ZEND_FE_FETCH_R
|| opline->opcode == ZEND_FE_FETCH_RW
|| opline->opcode == ZEND_FETCH_LIST_R
|| opline->opcode == ZEND_FETCH_LIST_W
|| opline->opcode == ZEND_VERIFY_RETURN_TYPE
|| opline->opcode == ZEND_BIND_LEXICAL
|| opline->opcode == ZEND_ROPE_ADD;
}
/* Live ranges must be sorted by increasing start opline */
static int cmp_live_range(const zend_live_range *a, const zend_live_range *b) {
return a->start - b->start;
@ -556,11 +673,73 @@ static void swap_live_range(zend_live_range *a, zend_live_range *b) {
*a = *b;
*b = tmp;
}
static void zend_sort_live_ranges(zend_op_array *op_array) {
static void zend_calc_live_ranges(
zend_op_array *op_array, zend_needs_live_range_cb needs_live_range) {
zend_op *opline = &op_array->opcodes[op_array->last - 1];
zend_op *begin = op_array->opcodes;
ALLOCA_FLAG(use_heap)
uint32_t var_offset = op_array->last_var;
uint32_t *last_use = do_alloca(sizeof(uint32_t) * op_array->T, use_heap);
memset(last_use, -1, sizeof(uint32_t) * op_array->T);
ZEND_ASSERT(!op_array->live_range);
for (; opline >= begin; opline--) {
uint32_t opnum = opline - begin;
if (opline->opcode == ZEND_OP_DATA) {
/* OP_DATA is really part of the previous opcode. */
opnum--;
}
if ((opline->result_type & (IS_TMP_VAR|IS_VAR)) && !is_fake_def(opline)) {
uint32_t var_num = EX_VAR_TO_NUM(opline->result.var) - var_offset;
/* Defs without uses can occur for two reasons: Either because the result is
* genuinely unused (e.g. omitted FREE opcode for an unused boolean result), or
* because there are multiple defining opcodes (e.g. JMPZ_EX and QM_ASSIGN), in
* which case the last one starts the live range. As such, we can simply ignore
* missing uses here. */
if (last_use[var_num] != (uint32_t) -1) {
emit_live_range(op_array, var_num, opnum, last_use[var_num], needs_live_range);
last_use[var_num] = (uint32_t) -1;
}
}
if ((opline->op1_type & (IS_TMP_VAR|IS_VAR)) && !keeps_op1_alive(opline)) {
uint32_t var_num = EX_VAR_TO_NUM(opline->op1.var) - var_offset;
if (last_use[var_num] == (uint32_t) -1) {
last_use[var_num] = opnum;
}
}
if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
uint32_t var_num = EX_VAR_TO_NUM(opline->op2.var) - var_offset;
if (last_use[var_num] == (uint32_t) -1) {
last_use[var_num] = opnum;
}
}
}
if (op_array->live_range) {
zend_sort(op_array->live_range, op_array->last_live_range, sizeof(zend_live_range),
(compare_func_t) cmp_live_range, (swap_func_t) swap_live_range);
}
free_alloca(last_use, use_heap);
}
ZEND_API void zend_recalc_live_ranges(
zend_op_array *op_array, zend_needs_live_range_cb needs_live_range) {
/* We assume that we never create live-ranges where there were none before. */
if (!op_array->live_range) {
return;
}
efree(op_array->live_range);
op_array->live_range = NULL;
op_array->last_live_range = 0;
zend_calc_live_ranges(op_array, needs_live_range);
}
ZEND_API int pass_two(zend_op_array *op_array)
{
zend_op *opline, *end;
@ -726,16 +905,7 @@ ZEND_API int pass_two(zend_op_array *op_array)
opline++;
}
if (op_array->live_range) {
int i;
zend_sort_live_ranges(op_array);
for (i = 0; i < op_array->last_live_range; i++) {
op_array->live_range[i].var =
(uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + (op_array->live_range[i].var / sizeof(zval))) |
(op_array->live_range[i].var & ZEND_LIVE_MASK);
}
}
zend_calc_live_ranges(op_array, NULL);
return 0;
}

View file

@ -197,7 +197,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
) {
znode_op op1 = opline->op1;
if (opline->opcode == ZEND_VERIFY_RETURN_TYPE) {
zend_optimizer_remove_live_range(op_array, op1.var);
COPY_NODE(opline->result, opline->op1);
COPY_NODE(opline->op1, src->op1);
VAR_SOURCE(op1) = NULL;
@ -207,7 +206,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
zval c;
ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src));
if (zend_optimizer_update_op1_const(op_array, opline, &c)) {
zend_optimizer_remove_live_range(op_array, op1.var);
VAR_SOURCE(op1) = NULL;
literal_dtor(&ZEND_OP1_LITERAL(src));
MAKE_NOP(src);
@ -277,7 +275,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
ZVAL_COPY(&c, &ZEND_OP1_LITERAL(src));
if (zend_optimizer_update_op2_const(op_array, opline, &c)) {
zend_optimizer_remove_live_range(op_array, op2.var);
VAR_SOURCE(op2) = NULL;
literal_dtor(&ZEND_OP1_LITERAL(src));
MAKE_NOP(src);
@ -295,7 +292,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
src->opcode == ZEND_CAST &&
src->extended_value == IS_STRING) {
/* T = CAST(X, String), ECHO(T) => NOP, ECHO(X) */
zend_optimizer_remove_live_range(op_array, opline->op1.var);
VAR_SOURCE(opline->op1) = NULL;
COPY_NODE(opline->op1, src->op1);
MAKE_NOP(src);
@ -731,7 +727,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
src->opcode == ZEND_CAST &&
src->extended_value == IS_STRING) {
/* convert T1 = CAST(STRING, X), T2 = CONCAT(T1, Y) to T2 = CONCAT(X,Y) */
zend_optimizer_remove_live_range(op_array, opline->op1.var);
VAR_SOURCE(opline->op1) = NULL;
COPY_NODE(opline->op1, src->op1);
MAKE_NOP(src);
@ -744,7 +739,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
src->opcode == ZEND_CAST &&
src->extended_value == IS_STRING) {
/* convert T1 = CAST(STRING, X), T2 = CONCAT(Y, T1) to T2 = CONCAT(Y,X) */
zend_optimizer_remove_live_range(op_array, opline->op2.var);
zend_op *src = VAR_SOURCE(opline->op2);
VAR_SOURCE(opline->op2) = NULL;
COPY_NODE(opline->op2, src->op1);
@ -1085,43 +1079,6 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
free_alloca(map, use_heap);
}
/* adjust loop jump targets & remove unused live range entries */
if (op_array->last_live_range) {
int i, j;
for (i = 0, j = 0; i < op_array->last_live_range; i++) {
if (op_array->live_range[i].var == (uint32_t)-1) {
/* this live range already removed */
continue;
}
if (!(blocks[cfg->map[op_array->live_range[i].start]].flags & ZEND_BB_REACHABLE)) {
ZEND_ASSERT(!(blocks[cfg->map[op_array->live_range[i].end]].flags & ZEND_BB_REACHABLE));
} else {
uint32_t start_op = blocks[cfg->map[op_array->live_range[i].start]].start;
uint32_t end_op = blocks[cfg->map[op_array->live_range[i].end]].start;
if (start_op == end_op) {
/* skip empty live range */
continue;
}
op_array->live_range[i].start = start_op;
op_array->live_range[i].end = end_op;
if (i != j) {
op_array->live_range[j] = op_array->live_range[i];
}
j++;
}
}
if (i != j) {
op_array->last_live_range = j;
if (j == 0) {
efree(op_array->live_range);
op_array->live_range = NULL;
}
}
}
/* adjust early binding list */
if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) {
ZEND_ASSERT(op_array == &ctx->script->main_op_array);
@ -1943,7 +1900,7 @@ void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)
/* Build CFG */
checkpoint = zend_arena_checkpoint(ctx->arena);
if (zend_build_cfg(&ctx->arena, op_array, ZEND_CFG_SPLIT_AT_LIVE_RANGES, &cfg) != SUCCESS) {
if (zend_build_cfg(&ctx->arena, op_array, 0, &cfg) != SUCCESS) {
zend_arena_release(&ctx->arena, checkpoint);
return;
}

View file

@ -95,15 +95,6 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) {
}
}
/* Update TMP references in live ranges */
if (op_array->live_range) {
for (i = 0; i < op_array->last_live_range; i++) {
op_array->live_range[i].var =
(op_array->live_range[i].var & ZEND_LIVE_MASK) |
NUM_VAR(vars_map[VAR_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK)]);
}
}
/* Update CV name table */
if (num_cvs != op_array->last_var) {
if (num_cvs) {

View file

@ -478,79 +478,6 @@ static inline zend_bool may_break_varargs(const zend_op_array *op_array, const z
return 0;
}
static void dce_live_ranges(context *ctx, zend_op_array *op_array, zend_ssa *ssa)
{
int i = 0;
int j = 0;
zend_live_range *live_range = op_array->live_range;
while (i < op_array->last_live_range) {
if ((live_range->var & ZEND_LIVE_MASK) != ZEND_LIVE_TMPVAR) {
/* keep */
j++;
} else {
uint32_t var = live_range->var & ~ZEND_LIVE_MASK;
uint32_t def = live_range->start - 1;
if ((op_array->opcodes[def].result_type == IS_UNUSED) &&
(UNEXPECTED(op_array->opcodes[def].opcode == ZEND_EXT_STMT) ||
UNEXPECTED(op_array->opcodes[def].opcode == ZEND_EXT_FCALL_END))) {
def--;
}
if (op_array->opcodes[def].result_type == IS_UNUSED) {
if (op_array->opcodes[def].opcode == ZEND_DO_FCALL) {
/* constructor call */
do {
def--;
if ((op_array->opcodes[def].result_type & (IS_TMP_VAR|IS_VAR))
&& op_array->opcodes[def].result.var == var) {
ZEND_ASSERT(op_array->opcodes[def].opcode == ZEND_NEW);
break;
}
} while (def > 0);
} else if (op_array->opcodes[def].opcode == ZEND_OP_DATA) {
def--;
}
}
#if ZEND_DEBUG
ZEND_ASSERT(op_array->opcodes[def].result_type & (IS_TMP_VAR|IS_VAR));
ZEND_ASSERT(op_array->opcodes[def].result.var == var);
ZEND_ASSERT(ssa->ops[def].result_def >= 0);
#else
if (!(op_array->opcodes[def].result_type & (IS_TMP_VAR|IS_VAR))
|| op_array->opcodes[def].result.var != var
|| ssa->ops[def].result_def < 0) {
/* TODO: Some wrong live-range? keep it. */
j++;
live_range++;
i++;
continue;
}
#endif
var = ssa->ops[def].result_def;
if ((ssa->var_info[var].type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))
&& !is_var_dead(ctx, var)) {
/* keep */
j++;
} else if (i != j) {
op_array->live_range[j] = *live_range;
}
}
live_range++;
i++;
}
op_array->last_live_range = j;
if (op_array->last_live_range == 0) {
efree(op_array->live_range);
op_array->live_range = NULL;
}
}
int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reorder_dtor_effects) {
int i;
zend_ssa_phi *phi;
@ -648,10 +575,6 @@ int dce_optimize_op_array(zend_op_array *op_array, zend_ssa *ssa, zend_bool reor
}
}
if (op_array->live_range) {
dce_live_ranges(&ctx, op_array, ssa);
}
/* Eliminate dead instructions */
ZEND_BITSET_FOREACH(ctx.instr_dead, ctx.instr_worklist_len, i) {
removed_ops += dce_instr(&ctx, &op_array->opcodes[i], &ssa->ops[i]);

View file

@ -247,12 +247,6 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa, zend_op
}
}
/* update brk/cont array */
for (j = 0; j < op_array->last_live_range; j++) {
op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start];
op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end];
}
/* update try/catch array */
for (j = 0; j < op_array->last_try_catch; j++) {
op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op];
@ -852,9 +846,6 @@ optimize_jmpnz:
take_successor_1(ssa, block_num, block);
goto optimize_nop;
} else {
if (opline->result_type & (IS_TMP_VAR|IS_VAR)) {
zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition);
}
opline->opcode = ZEND_JMP;
opline->result_type = IS_UNUSED;
zend_ssa_remove_result_def(ssa, ssa_op);

View file

@ -81,12 +81,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array, zend_optimizer_ctx *ctx
zend_optimizer_shift_jump(op_array, opline, shiftlist);
}
/* update brk/cont array */
for (j = 0; j < op_array->last_live_range; j++) {
op_array->live_range[j].start -= shiftlist[op_array->live_range[j].start];
op_array->live_range[j].end -= shiftlist[op_array->live_range[j].end];
}
/* update try/catch array */
for (j = 0; j < op_array->last_try_catch; j++) {
op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op];

View file

@ -182,14 +182,6 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c
opline--;
}
if (op_array->live_range) {
for (i = 0; i < op_array->last_live_range; i++) {
op_array->live_range[i].var =
NUM_VAR(map_T[VAR_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK) - offset] + offset) |
(op_array->live_range[i].var & ZEND_LIVE_MASK);
}
}
zend_arena_release(&ctx->arena, checkpoint);
op_array->T = max + 1;
}

View file

@ -340,7 +340,6 @@ static zend_bool try_replace_op2(
ZEND_ASSERT(ssa_op->result_def == (ssa_op + 1)->op2_use);
if (zend_optimizer_update_op2_const(ctx->scdf.op_array, opline + 1, &zv)) {
zend_ssa_op *next_op = ssa_op + 1;
zend_optimizer_remove_live_range_ex(ctx->scdf.op_array, opline->result.var, ssa_op - ctx->scdf.ssa->ops);
zend_ssa_unlink_use_chain(ctx->scdf.ssa, next_op - ctx->scdf.ssa->ops, next_op->op2_use);
next_op->op2_use = -1;
next_op->op2_use_chain = -1;
@ -2128,7 +2127,6 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
uint32_t old_var = opline->result.var;
ssa_op->result_def = -1;
zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition);
if (opline->opcode == ZEND_DO_ICALL) {
removed_ops = remove_call(ctx, opline, ssa_op) - 1;
} else {
@ -2143,9 +2141,6 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
}
return 0;
} else {
if (opline->result_type & (IS_TMP_VAR|IS_VAR)) {
zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition);
}
zend_ssa_remove_result_def(ssa, ssa_op);
if (opline->opcode == ZEND_DO_ICALL) {
removed_ops = remove_call(ctx, opline, ssa_op);
@ -2199,9 +2194,6 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
if (ssa_op->result_def >= 0) {
if (ssa->vars[ssa_op->result_def].use_chain < 0
&& ssa->vars[ssa_op->result_def].phi_use_chain == NULL) {
if (opline->result_type & (IS_TMP_VAR|IS_VAR)) {
zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition);
}
zend_ssa_remove_result_def(ssa, ssa_op);
opline->result_type = IS_UNUSED;
} else if (opline->opcode != ZEND_PRE_INC &&

View file

@ -184,20 +184,24 @@ void scdf_solve(scdf_ctx *scdf, const char *name) {
/* 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) {
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;
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)) {
const zend_basic_block *block = &cfg->blocks[block_idx];
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)) {
zend_op *def_opline = zend_optimizer_get_loop_var_def(op_array, opline);
if (def_opline) {
uint32_t def_block = cfg->map[def_opline - op_array->opcodes];
if (zend_bitset_in(scdf->executable_blocks, def_block)) {
return 1;
}
}
}
}
return 0;
}
@ -212,7 +216,7 @@ int scdf_remove_unreachable_blocks(scdf_ctx *scdf) {
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)) {
&& !kept_alive_by_loop_var_free(scdf, i)) {
removed_ops += ssa->cfg.blocks[i].len;
zend_ssa_remove_block(scdf->op_array, ssa, i);
}

View file

@ -118,49 +118,6 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *
do {
changed = 0;
/* Add live range paths */
for (j = 0; j < op_array->last_live_range; j++) {
zend_live_range *live_range = &op_array->live_range[j];
if (live_range->var == (uint32_t)-1) {
/* this live range already removed */
continue;
}
b = blocks + block_map[live_range->start];
if (b->flags & ZEND_BB_REACHABLE) {
while (b->len > 0 && op_array->opcodes[b->start].opcode == ZEND_NOP) {
/* check if NOP breaks incorrect smart branch */
if (b->len == 2
&& (op_array->opcodes[b->start + 1].opcode == ZEND_JMPZ
|| op_array->opcodes[b->start + 1].opcode == ZEND_JMPNZ)
&& (op_array->opcodes[b->start + 1].op1_type & (IS_CV|IS_CONST))
&& b->start > 0
&& zend_is_smart_branch(op_array->opcodes + b->start - 1)) {
break;
}
b->start++;
b->len--;
}
if (b->len == 0 && (uint32_t)b->successors[0] == block_map[live_range->end]) {
/* mark as removed (empty live range) */
live_range->var = (uint32_t)-1;
continue;
}
b->flags |= ZEND_BB_GEN_VAR;
b = blocks + block_map[live_range->end];
b->flags |= ZEND_BB_KILL_VAR;
if (!(b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE))) {
if ((cfg->flags & ZEND_CFG_SPLIT_AT_LIVE_RANGES)) {
changed = 1;
zend_mark_reachable(op_array->opcodes, cfg, b);
} else {
b->flags |= ZEND_BB_UNREACHABLE_FREE;
}
}
} else {
ZEND_ASSERT(!(blocks[block_map[live_range->end]].flags & ZEND_BB_REACHABLE));
}
}
/* Add exception paths */
for (j = 0; j < op_array->last_try_catch; j++) {
@ -236,6 +193,29 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *
}
}
} while (changed);
/* Mark blocks that are unreachable, but free a loop var created in a reachable block. */
for (b = blocks; b < blocks + cfg->blocks_count; b++) {
if (b->flags & ZEND_BB_REACHABLE) {
continue;
}
for (j = b->start; j < b->start + b->len; j++) {
zend_op *opline = &op_array->opcodes[j];
if (opline->opcode == ZEND_FE_FREE ||
(opline->opcode == ZEND_FREE && opline->extended_value == ZEND_FREE_SWITCH)
) {
zend_op *def_opline = zend_optimizer_get_loop_var_def(op_array, opline);
if (def_opline) {
uint32_t def_block = block_map[def_opline - op_array->opcodes];
if (blocks[def_block].flags & ZEND_BB_REACHABLE) {
b->flags |= ZEND_BB_UNREACHABLE_FREE;
break;
}
}
}
}
}
}
}
/* }}} */
@ -293,7 +273,7 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
zval *zv;
zend_bool extra_entry_block = 0;
cfg->flags = build_flags & (ZEND_CFG_SPLIT_AT_LIVE_RANGES|ZEND_CFG_STACKLESS|ZEND_CFG_RECV_ENTRY);
cfg->flags = build_flags & (ZEND_CFG_STACKLESS|ZEND_CFG_RECV_ENTRY);
cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t));
@ -446,13 +426,6 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
extra_entry_block = 1;
}
if ((cfg->flags & ZEND_CFG_SPLIT_AT_LIVE_RANGES)) {
for (j = 0; j < op_array->last_live_range; j++) {
BB_START(op_array->live_range[j].start);
BB_START(op_array->live_range[j].end);
}
}
if (op_array->last_try_catch) {
for (j = 0; j < op_array->last_try_catch; j++) {
BB_START(op_array->try_catch_array[j].try_op);

View file

@ -29,8 +29,6 @@
#define ZEND_BB_CATCH (1<<6) /* start of catch block */
#define ZEND_BB_FINALLY (1<<7) /* start of finally block */
#define ZEND_BB_FINALLY_END (1<<8) /* end of finally block */
#define ZEND_BB_GEN_VAR (1<<9) /* start of live range */
#define ZEND_BB_KILL_VAR (1<<10) /* end of live range */
#define ZEND_BB_UNREACHABLE_FREE (1<<11) /* unreachable loop free */
#define ZEND_BB_RECV_ENTRY (1<<12) /* RECV entry */
@ -39,7 +37,7 @@
#define ZEND_BB_REACHABLE (1<<31)
#define ZEND_BB_PROTECTED (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY|ZEND_BB_TRY|ZEND_BB_CATCH|ZEND_BB_FINALLY|ZEND_BB_FINALLY_END|ZEND_BB_GEN_VAR|ZEND_BB_KILL_VAR)
#define ZEND_BB_PROTECTED (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY|ZEND_BB_TRY|ZEND_BB_CATCH|ZEND_BB_FINALLY|ZEND_BB_FINALLY_END|ZEND_BB_UNREACHABLE_FREE)
typedef struct _zend_basic_block {
int *successors; /* successor block indices */
@ -99,7 +97,6 @@ typedef struct _zend_cfg {
#define ZEND_SSA_DEBUG_LIVENESS (1<<29)
#define ZEND_SSA_DEBUG_PHI_PLACEMENT (1<<28)
#define ZEND_SSA_RC_INFERENCE (1<<27)
#define ZEND_CFG_SPLIT_AT_LIVE_RANGES (1<<26)
#define ZEND_CFG_NO_ENTRY_PREDECESSORS (1<<25)
#define ZEND_CFG_RECV_ENTRY (1<<24)
#define ZEND_CALL_TREE (1<<23)

View file

@ -759,15 +759,12 @@ static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags
if (b->flags & ZEND_BB_FINALLY_END) {
fprintf(stderr, " finally_end");
}
if (b->flags & ZEND_BB_GEN_VAR) {
fprintf(stderr, " gen_var");
}
if (b->flags & ZEND_BB_KILL_VAR) {
fprintf(stderr, " kill_var");
}
if (!(dump_flags & ZEND_DUMP_HIDE_UNREACHABLE) && !(b->flags & ZEND_BB_REACHABLE)) {
fprintf(stderr, " unreachable");
}
if (b->flags & ZEND_BB_UNREACHABLE_FREE) {
fprintf(stderr, " unreachable_free");
}
if (b->flags & ZEND_BB_LOOP_HEADER) {
fprintf(stderr, " loop_header");
}
@ -1007,20 +1004,13 @@ void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, cons
}
}
}
if (op_array->last_live_range) {
if (op_array->last_live_range && (dump_flags & ZEND_DUMP_LIVE_RANGES)) {
fprintf(stderr, "LIVE RANGES:\n");
for (i = 0; i < op_array->last_live_range; i++) {
if ((cfg->flags & ZEND_CFG_SPLIT_AT_LIVE_RANGES)) {
fprintf(stderr, " %u: BB%u - BB%u ",
EX_VAR_TO_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK),
cfg->map[op_array->live_range[i].start],
cfg->map[op_array->live_range[i].end]);
} else {
fprintf(stderr, " %u: L%u - L%u ",
EX_VAR_TO_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK),
op_array->live_range[i].start,
op_array->live_range[i].end);
}
switch (op_array->live_range[i].var & ZEND_LIVE_MASK) {
case ZEND_LIVE_TMPVAR:
fprintf(stderr, "(tmp/var)\n");
@ -1070,7 +1060,7 @@ void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_flags, cons
zend_dump_op(op_array, NULL, opline, dump_flags, data);
opline++;
}
if (op_array->last_live_range) {
if (op_array->last_live_range && (dump_flags & ZEND_DUMP_LIVE_RANGES)) {
fprintf(stderr, "LIVE RANGES:\n");
for (i = 0; i < op_array->last_live_range; i++) {
fprintf(stderr, " %u: L%u - L%u ",

View file

@ -26,6 +26,7 @@
#define ZEND_DUMP_RC_INFERENCE (1<<1)
#define ZEND_DUMP_CFG (1<<2)
#define ZEND_DUMP_SSA (1<<3)
#define ZEND_DUMP_LIVE_RANGES (1<<4)
#define ZEND_DUMP_RT_CONSTANTS ZEND_RT_CONSTANTS
BEGIN_EXTERN_C()

View file

@ -611,104 +611,6 @@ handle_static_prop:
return 1;
}
void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var)
{
if (op_array->last_live_range) {
int i = 0;
int j = 0;
do {
if ((op_array->live_range[i].var & ~ZEND_LIVE_MASK) != var) {
if (i != j) {
op_array->live_range[j] = op_array->live_range[i];
}
j++;
}
i++;
} while (i < op_array->last_live_range);
if (i != j) {
op_array->last_live_range = j;
if (j == 0) {
efree(op_array->live_range);
op_array->live_range = NULL;
}
}
}
}
static uint32_t zend_determine_constructor_call(zend_op_array *op_array, uint32_t start) {
int call = 0;
while (++start < op_array->last) {
switch (op_array->opcodes[start].opcode) {
case ZEND_INIT_FCALL_BY_NAME:
case ZEND_INIT_NS_FCALL_BY_NAME:
case ZEND_INIT_STATIC_METHOD_CALL:
case ZEND_INIT_METHOD_CALL:
case ZEND_INIT_FCALL:
case ZEND_NEW:
case ZEND_INIT_DYNAMIC_CALL:
case ZEND_INIT_USER_CALL:
call++;
break;
case ZEND_DO_FCALL:
if (call == 0) {
return start;
}
/* break missing intentionally */
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
call--;
break;
default:
break;
}
}
ZEND_ASSERT(0);
return -1;
}
void zend_optimizer_remove_live_range_ex(zend_op_array *op_array, uint32_t var, uint32_t start)
{
uint32_t i = 0;
switch (op_array->opcodes[start].opcode) {
case ZEND_ROPE_ADD:
case ZEND_ADD_ARRAY_ELEMENT:
return;
case ZEND_ROPE_INIT:
var |= ZEND_LIVE_ROPE;
break;
case ZEND_BEGIN_SILENCE:
var |= ZEND_LIVE_SILENCE;
break;
case ZEND_FE_RESET_R:
case ZEND_FE_RESET_RW:
var |= ZEND_LIVE_LOOP;
start++;
break;
case ZEND_NEW:
start = zend_determine_constructor_call(op_array, start);
start++;
break;
default:
start++;
}
while (i < op_array->last_live_range) {
if (op_array->live_range[i].var == var
&& op_array->live_range[i].start == start) {
op_array->last_live_range--;
if (i < op_array->last_live_range) {
memmove(&op_array->live_range[i], &op_array->live_range[i+1], (op_array->last_live_range - i) * sizeof(zend_live_range));
}
break;
}
i++;
}
}
int zend_optimizer_replace_by_const(zend_op_array *op_array,
zend_op *opline,
zend_uchar type,
@ -774,67 +676,44 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
ZEND_ASSERT(m->opcode == ZEND_FREE && m->op1_type == type && m->op1.var == var);
MAKE_NOP(m);
zval_ptr_dtor_nogc(val);
zend_optimizer_remove_live_range(op_array, var);
return 1;
}
case ZEND_SWITCH_LONG:
case ZEND_SWITCH_STRING:
case ZEND_CASE:
case ZEND_FREE: {
zend_op *m, *n;
int brk = op_array->last_live_range;
zend_bool in_switch = 0;
while (brk--) {
if (op_array->live_range[brk].start <= (uint32_t)(opline - op_array->opcodes) &&
op_array->live_range[brk].end > (uint32_t)(opline - op_array->opcodes)) {
in_switch = 1;
break;
}
}
if (!in_switch) {
ZEND_ASSERT(opline->opcode == ZEND_FREE);
MAKE_NOP(opline);
zval_ptr_dtor_nogc(val);
return 1;
}
m = opline;
n = op_array->opcodes + op_array->live_range[brk].end;
if (n->opcode == ZEND_FREE &&
!(n->extended_value & ZEND_FREE_ON_RETURN)) {
n++;
} else {
n = op_array->opcodes + op_array->last;
}
while (m < n) {
if (m->op1_type == type &&
m->op1.var == var) {
if (m->opcode == ZEND_CASE
|| m->opcode == ZEND_SWITCH_LONG
|| m->opcode == ZEND_SWITCH_STRING) {
case ZEND_CASE: {
zend_op *end = op_array->opcodes + op_array->last;
while (opline < end) {
if (opline->op1_type == type && opline->op1.var == var) {
if (opline->opcode == ZEND_CASE
|| opline->opcode == ZEND_SWITCH_LONG
|| opline->opcode == ZEND_SWITCH_STRING) {
zval v;
if (m->opcode == ZEND_CASE) {
m->opcode = ZEND_IS_EQUAL;
if (opline->opcode == ZEND_CASE) {
opline->opcode = ZEND_IS_EQUAL;
}
ZVAL_COPY(&v, val);
if (Z_TYPE(v) == IS_STRING) {
zend_string_hash_val(Z_STR(v));
}
m->op1.constant = zend_optimizer_add_literal(op_array, &v);
m->op1_type = IS_CONST;
} else if (m->opcode == ZEND_FREE) {
MAKE_NOP(m);
opline->op1.constant = zend_optimizer_add_literal(op_array, &v);
opline->op1_type = IS_CONST;
} else if (opline->opcode == ZEND_FREE) {
if (opline->extended_value == ZEND_FREE_SWITCH) {
/* We found the end of the switch. */
MAKE_NOP(opline);
break;
}
ZEND_ASSERT(opline->extended_value == ZEND_FREE_ON_RETURN);
MAKE_NOP(opline);
} else {
ZEND_ASSERT(0);
}
}
m++;
opline++;
}
zval_ptr_dtor_nogc(val);
zend_optimizer_remove_live_range(op_array, var);
return 1;
}
case ZEND_VERIFY_RETURN_TYPE: {
@ -858,20 +737,12 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
default:
break;
}
if (zend_optimizer_update_op1_const(op_array, opline, val)) {
zend_optimizer_remove_live_range(op_array, var);
return 1;
}
return 0;
return zend_optimizer_update_op1_const(op_array, opline, val);
}
if (opline->op2_type == type &&
opline->op2.var == var) {
if (zend_optimizer_update_op2_const(op_array, opline, val)) {
zend_optimizer_remove_live_range(op_array, var);
return 1;
}
return 0;
return zend_optimizer_update_op2_const(op_array, opline, val);
}
opline++;
}
@ -1111,6 +982,19 @@ uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args)
}
}
zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op *free_opline) {
uint32_t var = free_opline->op1.var;
ZEND_ASSERT(free_opline->opcode == ZEND_FE_FREE ||
(free_opline->opcode == ZEND_FREE && free_opline->extended_value == ZEND_FREE_SWITCH));
while (--free_opline >= op_array->opcodes) {
if ((free_opline->result_type & (IS_TMP_VAR|IS_VAR)) && free_opline->result.var == var) {
return free_opline;
}
}
return NULL;
}
static void zend_optimize(zend_op_array *op_array,
zend_optimizer_ctx *ctx)
{
@ -1119,7 +1003,7 @@ static void zend_optimize(zend_op_array *op_array,
}
if (ctx->debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) {
zend_dump_op_array(op_array, 0, "before optimizer", NULL);
zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "before optimizer", NULL);
}
/* pass 1
@ -1442,6 +1326,8 @@ static void zend_optimize_op_array(zend_op_array *op_array,
/* Redo pass_two() */
zend_redo_pass_two(op_array);
zend_recalc_live_ranges(op_array, NULL);
}
static void zend_adjust_fcall_stack_size(zend_op_array *op_array, zend_optimizer_ctx *ctx)
@ -1482,6 +1368,16 @@ static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array)
}
}
}
static zend_bool needs_live_range(zend_op_array *op_array, zend_op *def_opline) {
zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
zend_ssa_op *ssa_op = &func_info->ssa.ops[def_opline - op_array->opcodes];
if (ssa_op->result_def >= 0) {
uint32_t type = func_info->ssa.var_info[ssa_op->result_def].type;
return (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) != 0;
}
return 1;
}
#endif
int zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level)
@ -1594,8 +1490,10 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]);
if (func_info && func_info->ssa.var_info) {
zend_redo_pass_two_ex(call_graph.op_arrays[i], &func_info->ssa);
zend_recalc_live_ranges(call_graph.op_arrays[i], needs_live_range);
} else {
zend_redo_pass_two(call_graph.op_arrays[i]);
zend_recalc_live_ranges(call_graph.op_arrays[i], NULL);
}
}
@ -1652,16 +1550,19 @@ int zend_optimize_script(zend_script *script, zend_long optimization_level, zend
if ((debug_level & ZEND_DUMP_AFTER_OPTIMIZER) &&
(ZEND_OPTIMIZER_PASS_7 & optimization_level)) {
zend_dump_op_array(&script->main_op_array, ZEND_DUMP_RT_CONSTANTS, "after optimizer", NULL);
zend_dump_op_array(&script->main_op_array,
ZEND_DUMP_RT_CONSTANTS | ZEND_DUMP_LIVE_RANGES, "after optimizer", NULL);
ZEND_HASH_FOREACH_PTR(&script->function_table, op_array) {
zend_dump_op_array(op_array, ZEND_DUMP_RT_CONSTANTS, "after optimizer", NULL);
zend_dump_op_array(op_array,
ZEND_DUMP_RT_CONSTANTS | ZEND_DUMP_LIVE_RANGES, "after optimizer", NULL);
} ZEND_HASH_FOREACH_END();
ZEND_HASH_FOREACH_PTR(&script->class_table, ce) {
ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->function_table, name, op_array) {
if (op_array->scope == ce && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
zend_dump_op_array(op_array, ZEND_DUMP_RT_CONSTANTS, "after optimizer", NULL);
zend_dump_op_array(op_array,
ZEND_DUMP_RT_CONSTANTS | ZEND_DUMP_LIVE_RANGES, "after optimizer", NULL);
}
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();

View file

@ -90,9 +90,8 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
zend_uchar type,
uint32_t var,
zval *val);
zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op *free_opline);
void zend_optimizer_remove_live_range(zend_op_array *op_array, uint32_t var);
void zend_optimizer_remove_live_range_ex(zend_op_array *op_array, uint32_t var, uint32_t start);
void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx);
void zend_optimizer_pass2(zend_op_array *op_array);
void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx);

View file

@ -1436,9 +1436,6 @@ void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int i) /* {{{
continue;
}
if (op_array->opcodes[j].result_type & (IS_TMP_VAR|IS_VAR)) {
zend_optimizer_remove_live_range_ex(op_array, op_array->opcodes[j].result.var, j);
}
zend_ssa_remove_defs_of_instr(ssa, &ssa->ops[j]);
zend_ssa_remove_instr(ssa, &op_array->opcodes[j], &ssa->ops[j]);
}