Skip to content

[WIP] Auto-extend live-ranges #11880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
if (b->len == 0) {
continue;
}
if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
if (b->flags & ZEND_BB_REACHABLE) {
opline = op_array->opcodes + b->start + b->len - 1;
if (opline->opcode == ZEND_JMP) {
zend_basic_block *next = b + 1;
Expand Down Expand Up @@ -983,7 +983,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op

/* Copy code of reachable blocks into a single buffer */
for (b = blocks; b < end; b++) {
if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
if (b->flags & ZEND_BB_REACHABLE) {
memcpy(opline, op_array->opcodes + b->start, b->len * sizeof(zend_op));
b->start = opline - new_opcodes;
opline += b->len;
Expand Down Expand Up @@ -1100,7 +1100,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
/* rebuild map (just for printing) */
memset(cfg->map, -1, sizeof(int) * op_array->last);
for (int n = 0; n < cfg->blocks_count; n++) {
if (cfg->blocks[n].flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
if (cfg->blocks[n].flags & ZEND_BB_REACHABLE) {
cfg->map[cfg->blocks[n].start] = n;
}
}
Expand Down Expand Up @@ -1725,16 +1725,7 @@ void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx)

/* Eliminate NOPs */
for (b = blocks; b < end; b++) {
if (b->flags & ZEND_BB_UNREACHABLE_FREE) {
/* In unreachable_free blocks only preserve loop var frees. */
for (uint32_t i = b->start; i < b->start + b->len; i++) {
zend_op *opline = &op_array->opcodes[i];
if (!zend_optimizer_is_loop_var_free(opline)) {
MAKE_NOP(opline);
}
}
}
if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
if (b->flags & ZEND_BB_REACHABLE) {
strip_nops(op_array, b);
}
}
Expand Down
12 changes: 1 addition & 11 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,21 +138,14 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa, zend_op
}

for (b = blocks; b < blocks_end; b++) {
if (b->flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
if (b->flags & ZEND_BB_REACHABLE) {
if (b->len) {
uint32_t new_start, old_end;
while (i < b->start) {
shiftlist[i] = i - target;
i++;
}

if (b->flags & ZEND_BB_UNREACHABLE_FREE) {
/* Only keep the FREE for the loop var */
ZEND_ASSERT(op_array->opcodes[b->start].opcode == ZEND_FREE
|| op_array->opcodes[b->start].opcode == ZEND_FE_FREE);
b->len = 1;
}

new_start = target;
old_end = b->start + b->len;
while (i < old_end) {
Expand Down Expand Up @@ -757,9 +750,6 @@ static int zend_dfa_optimize_jmps(zend_op_array *op_array, zend_ssa *ssa)

while (next_block_num < ssa->cfg.blocks_count
&& !(ssa->cfg.blocks[next_block_num].flags & ZEND_BB_REACHABLE)) {
if (ssa->cfg.blocks[next_block_num].flags & ZEND_BB_UNREACHABLE_FREE) {
can_follow = 0;
}
next_block_num++;
}

Expand Down
34 changes: 2 additions & 32 deletions Zend/Optimizer/zend_cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,33 +196,6 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *
}
} while (changed);
}

if (cfg->flags & ZEND_FUNC_FREE_LOOP_VAR) {
zend_basic_block *b;
int j;
uint32_t *block_map = cfg->map;

/* 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 (zend_optimizer_is_loop_var_free(opline)) {
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;
}
}
}
}
}
}
}
/* }}} */

Expand Down Expand Up @@ -305,9 +278,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
case ZEND_MATCH_ERROR:
case ZEND_EXIT:
case ZEND_THROW:
/* Don't treat THROW as terminator if it's used in expression context,
* as we may lose live ranges when eliminating unreachable code. */
if (opline->extended_value != ZEND_THROW_IS_EXPR && i + 1 < op_array->last) {
if (i + 1 < op_array->last) {
BB_START(i + 1);
}
break;
Expand Down Expand Up @@ -430,8 +401,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
case ZEND_FREE:
case ZEND_FE_FREE:
if (zend_optimizer_is_loop_var_free(opline)
&& ((opline-1)->opcode != ZEND_MATCH_ERROR
|| (opline-1)->extended_value != ZEND_THROW_IS_EXPR)) {
&& ((opline-1)->opcode != ZEND_MATCH_ERROR)) {
BB_START(i);
flags |= ZEND_FUNC_FREE_LOOP_VAR;
}
Expand Down
3 changes: 1 addition & 2 deletions Zend/Optimizer/zend_cfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,14 @@
#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_UNREACHABLE_FREE (1<<11) /* unreachable loop free */
#define ZEND_BB_RECV_ENTRY (1<<12) /* RECV entry */

#define ZEND_BB_LOOP_HEADER (1<<16)
#define ZEND_BB_IRREDUCIBLE_LOOP (1<<17)

#define ZEND_BB_REACHABLE (1U<<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_UNREACHABLE_FREE)
#define ZEND_BB_PROTECTED (ZEND_BB_ENTRY|ZEND_BB_RECV_ENTRY|ZEND_BB_TRY|ZEND_BB_CATCH|ZEND_BB_FINALLY|ZEND_BB_FINALLY_END)

typedef struct _zend_basic_block {
int *successors; /* successor block indices */
Expand Down
3 changes: 0 additions & 3 deletions Zend/Optimizer/zend_dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -803,9 +803,6 @@ static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags
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");
}
Expand Down
4 changes: 2 additions & 2 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ static void zend_optimize(zend_op_array *op_array,
*/
if ((ZEND_OPTIMIZER_PASS_9 & ctx->optimization_level) &&
!(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level)) {
zend_optimize_temporary_variables(op_array, ctx);
// zend_optimize_temporary_variables(op_array, ctx);
if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_9) {
zend_dump_op_array(op_array, 0, "after pass 9", NULL);
}
Expand Down Expand Up @@ -1536,7 +1536,7 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l

if (ZEND_OPTIMIZER_PASS_9 & optimization_level) {
for (i = 0; i < call_graph.op_arrays_count; i++) {
zend_optimize_temporary_variables(call_graph.op_arrays[i], &ctx);
// zend_optimize_temporary_variables(call_graph.op_arrays[i], &ctx);
if (debug_level & ZEND_DUMP_AFTER_PASS_9) {
zend_dump_op_array(call_graph.op_arrays[i], 0, "after pass 9", NULL);
}
Expand Down
12 changes: 2 additions & 10 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -5181,10 +5181,8 @@ static void zend_compile_throw(znode *result, zend_ast *ast) /* {{{ */
znode expr_node;
zend_compile_expr(&expr_node, expr_ast);

zend_op *opline = zend_emit_op(NULL, ZEND_THROW, &expr_node, NULL);
zend_emit_op(NULL, ZEND_THROW, &expr_node, NULL);
if (result) {
/* Mark this as an "expression throw" for opcache. */
opline->extended_value = ZEND_THROW_IS_EXPR;
result->op_type = IS_CONST;
ZVAL_TRUE(&result->u.constant);
}
Expand Down Expand Up @@ -5947,10 +5945,6 @@ static void zend_compile_match(znode *result, zend_ast *ast)
if (opline->op1_type == IS_CONST) {
Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1));
}
if (arms->children == 0) {
/* Mark this as an "expression throw" for opcache. */
opline->extended_value = ZEND_THROW_IS_EXPR;
}
}

for (uint32_t i = 0; i < arms->children; ++i) {
Expand Down Expand Up @@ -9432,10 +9426,8 @@ static void zend_compile_exit(znode *result, zend_ast *ast) /* {{{ */
expr_node.op_type = IS_UNUSED;
}

zend_op *opline = zend_emit_op(NULL, ZEND_EXIT, &expr_node, NULL);
zend_emit_op(NULL, ZEND_EXIT, &expr_node, NULL);
if (result) {
/* Mark this as an "expression throw" for opcache. */
opline->extended_value = ZEND_THROW_IS_EXPR;
result->op_type = IS_CONST;
ZVAL_TRUE(&result->u.constant);
}
Expand Down
2 changes: 0 additions & 2 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -1021,8 +1021,6 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
#define ZEND_SEND_BY_REF 1u
#define ZEND_SEND_PREFER_REF 2u

#define ZEND_THROW_IS_EXPR 1u

#define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS 1

/* The send mode, the is_variadic, the is_promoted, and the is_tentative flags are stored as part of zend_type */
Expand Down
41 changes: 35 additions & 6 deletions Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,9 @@ static void emit_live_range(
case ZEND_DECLARE_ANON_CLASS:
/* FAST_CALLs don't have to be destroyed. */
case ZEND_FAST_CALL:
/* JMP_NULL only define null, false and true. */
case ZEND_JMP_NULL:
case ZEND_JMP_SET:
return;
case ZEND_BEGIN_SILENCE:
kind = ZEND_LIVE_SILENCE;
Expand Down Expand Up @@ -912,6 +915,9 @@ static void swap_live_range(zend_live_range *a, zend_live_range *b) {
b->end = tmp;
}

#define LIVE_RANGE_VAR_UNUSED ((uint32_t) -1)
#define LIVE_RANGE_VAR_DEFINED ((uint32_t) -2)

static void zend_calc_live_ranges(
zend_op_array *op_array, zend_needs_live_range_cb needs_live_range) {
uint32_t opnum = op_array->last;
Expand All @@ -928,12 +934,35 @@ static void zend_calc_live_ranges(

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;
// FIXME: Outdated comment
/* 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 (EXPECTED(last_use[var_num] != (uint32_t) -1)) {
if (UNEXPECTED(last_use[var_num] == LIVE_RANGE_VAR_UNUSED)) {
// FIXME: Make sure not to insert unnecessary live-ranges. But I think this is already handled.
/* Find the innermost try/catch that we are inside of. */
uint32_t live_range_end = (uint32_t) -1;
for (uint32_t i = 0; i < op_array->last_try_catch; i++) {
zend_try_catch_element *try_catch = &op_array->try_catch_array[i];
if (opnum < try_catch->try_op) {
break;
}
if (opnum < try_catch->catch_op) {
live_range_end = try_catch->catch_op;
} else if (opnum < try_catch->finally_end) {
live_range_end = try_catch->finally_end;
}
}
if (live_range_end == (uint32_t) -1) {
live_range_end = op_array->last;
}
if (opnum + 1 != live_range_end) {
emit_live_range(op_array, var_num, opnum, live_range_end, needs_live_range);
}
last_use[var_num] = LIVE_RANGE_VAR_DEFINED;
} else if (last_use[var_num] != LIVE_RANGE_VAR_DEFINED) {
/* Skip trivial live-range */
if (opnum + 1 != last_use[var_num]) {
uint32_t num;
Expand All @@ -948,13 +977,13 @@ static void zend_calc_live_ranges(
#endif
emit_live_range(op_array, var_num, num, last_use[var_num], needs_live_range);
}
last_use[var_num] = (uint32_t) -1;
last_use[var_num] = LIVE_RANGE_VAR_DEFINED;
}
}

if ((opline->op1_type & (IS_TMP_VAR|IS_VAR))) {
uint32_t var_num = EX_VAR_TO_NUM(opline->op1.var) - var_offset;
if (EXPECTED(last_use[var_num] == (uint32_t) -1)) {
if (EXPECTED(last_use[var_num] == LIVE_RANGE_VAR_UNUSED || last_use[var_num] == LIVE_RANGE_VAR_DEFINED)) {
if (EXPECTED(!keeps_op1_alive(opline))) {
/* OP_DATA is really part of the previous opcode. */
last_use[var_num] = opnum - (opline->opcode == ZEND_OP_DATA);
Expand All @@ -966,14 +995,14 @@ static void zend_calc_live_ranges(
if (UNEXPECTED(opline->opcode == ZEND_FE_FETCH_R
|| opline->opcode == ZEND_FE_FETCH_RW)) {
/* OP2 of FE_FETCH is actually a def, not a use. */
if (last_use[var_num] != (uint32_t) -1) {
if (last_use[var_num] != LIVE_RANGE_VAR_UNUSED && last_use[var_num] != LIVE_RANGE_VAR_DEFINED) {
if (opnum + 1 != last_use[var_num]) {
emit_live_range(
op_array, var_num, opnum, last_use[var_num], needs_live_range);
}
last_use[var_num] = (uint32_t) -1;
last_use[var_num] = LIVE_RANGE_VAR_DEFINED;
}
} else if (EXPECTED(last_use[var_num] == (uint32_t) -1)) {
} else if (EXPECTED(last_use[var_num] == LIVE_RANGE_VAR_UNUSED || last_use[var_num] == LIVE_RANGE_VAR_DEFINED)) {
#if 1
/* OP_DATA uses only op1 operand */
ZEND_ASSERT(opline->opcode != ZEND_OP_DATA);
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -7680,6 +7680,7 @@ ZEND_VM_COLD_CONST_HANDLER(169, ZEND_COALESCE, CONST|TMP|VAR|CV, JMP_ADDR)
efree_size(ref, sizeof(zend_reference));
}
}
ZVAL_UNDEF(EX_VAR(opline->result.var));
ZEND_VM_NEXT_OPCODE();
}

Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_vm_execute.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions ext/opcache/tests/match/001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ foreach (range('a', 'i') as $char) {
?>
--EXPECTF--
$_main:
; (lines=15, args=0, vars=1, tmps=2)
; (lines=15, args=0, vars=1, tmps=3)
; (after optimizer)
; %s
0000 INIT_FCALL 2 %d string("range")
0001 SEND_VAL string("a") 1
0002 SEND_VAL string("i") 2
0003 V2 = DO_ICALL
0004 V1 = FE_RESET_R V2 0013
0005 FE_FETCH_R V1 CV0($char) 0013
0003 V1 = DO_ICALL
0004 V2 = FE_RESET_R V1 0013
0005 FE_FETCH_R V2 CV0($char) 0013
0006 INIT_FCALL 1 %d string("var_dump")
0007 INIT_FCALL 1 %d string("test")
0008 SEND_VAR CV0($char) 1
0009 V2 = DO_UCALL
0010 SEND_VAR V2 1
0009 V3 = DO_UCALL
0010 SEND_VAR V3 1
0011 DO_ICALL
0012 JMP 0005
0013 FE_FREE V1
0013 FE_FREE V2
0014 RETURN int(1)
LIVE RANGES:
1: 0005 - 0013 (loop)
2: 0005 - 0013 (loop)

test:
; (lines=9, args=1, vars=1, tmps=0)
Expand Down
Loading