Skip to content

Commit b37f3bf

Browse files
committed
Fix live-range calculation for FREE_RANGE
1 parent c7aeb4e commit b37f3bf

File tree

6 files changed

+113
-31
lines changed

6 files changed

+113
-31
lines changed

Zend/Optimizer/zend_optimizer.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,11 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_
791791
opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]);
792792
break;
793793
}
794+
case ZEND_FREE_RANGE: {
795+
opline->op1.num -= shiftlist[opline->op1.num];
796+
opline->op2.num -= shiftlist[opline->op2.num];
797+
break;
798+
}
794799
}
795800
}
796801

Zend/tests/match/bugs/001.phpt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ WIP
66
function test() {
77
foreach ([1, 2, 3] as $value) {
88
$x = match (1) {
9-
1 => { return 42; },
9+
1 => {
10+
$y = match (1) {
11+
1 => {
12+
return 42;
13+
},
14+
};
15+
return 42;
16+
},
1017
};
1118
var_dump($value);
1219
}

Zend/tests/match/bugs/011.phpt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ WIP
33
--FILE--
44
<?php
55

6+
namespace Test;
7+
68
function test() {
7-
do {
8-
$foo = 'Hello';
9-
$bar = 'world';
10-
var_dump("$foo ${null ?? { 'bar' }}");
11-
var_dump("$foo ${null ?? { break; 'bar' }}");
12-
} while (0);
9+
$foo = 'Hello';
10+
$bar = 'world';
11+
var_dump("$foo ${null ?? { 'bar' }}");
12+
var_dump("$foo ${null ?? { return; 'bar' }}");
13+
str_repeat('a', 10) . (null ?? { return; 'bar' });
1314
}
1415

1516
test();
@@ -18,5 +19,7 @@ test();
1819
--EXPECTF--
1920
Deprecated: Using ${expr} (variable variables) in strings is deprecated, use {${expr}} instead in %s on line %d
2021

22+
Deprecated: Using ${expr} (variable variables) in strings is deprecated, use {${expr}} instead in %s on line %d
23+
2124
Deprecated: Using ${expr} (variable variables) in strings is deprecated, use {${expr}} instead in %s on line %d
2225
string(11) "Hello world"

Zend/zend_compile.c

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,16 @@
6262
typedef struct _zend_loop_var {
6363
uint8_t opcode;
6464
uint8_t var_type;
65-
uint32_t var_num;
66-
uint32_t try_catch_offset;
67-
uint32_t opcode_start;
65+
union {
66+
struct {
67+
uint32_t var_num;
68+
uint32_t try_catch_offset;
69+
};
70+
struct {
71+
uint32_t range_start;
72+
uint32_t range_end;
73+
};
74+
};
6875
} zend_loop_var;
6976

7077
static inline uint32_t zend_alloc_cache_slots(unsigned count) {
@@ -345,6 +352,8 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context, zend_op_arra
345352
CG(context).in_jmp_frameless_branch = false;
346353
CG(context).active_property_info_name = NULL;
347354
CG(context).active_property_hook_kind = (zend_property_hook_kind)-1;
355+
CG(context).in_block_expr = false;
356+
CG(context).stmt_start = (uint32_t)-1;
348357
}
349358
/* }}} */
350359

@@ -721,10 +730,8 @@ static inline void zend_begin_loop(
721730
brk_cont_element->parent = parent;
722731
brk_cont_element->kind = kind;
723732

724-
uint32_t start = get_next_op_number();
725-
info.opcode_start = start;
726-
727733
if (loop_var && (loop_var->op_type & (IS_VAR|IS_TMP_VAR))) {
734+
uint32_t start = get_next_op_number();
728735

729736
info.opcode = free_opcode;
730737
info.var_type = loop_var->op_type;
@@ -5612,27 +5619,22 @@ static bool zend_handle_loops_and_finally_ex(zend_long depth, znode *return_valu
56125619
SET_NODE(opline->op2, return_value);
56135620
}
56145621
opline->op1.num = loop_var->try_catch_offset;
5615-
if (free_range_opnum != (uint32_t)-1) {
5616-
zend_op *opline = &CG(active_op_array)->opcodes[free_range_opnum];
5617-
opline->op1.num = loop_var->opcode_start;
5618-
free_range_opnum = (uint32_t)-1;
5619-
}
56205622
} else if (loop_var->opcode == ZEND_DISCARD_EXCEPTION) {
56215623
zend_op *opline = get_next_op();
56225624
opline->opcode = ZEND_DISCARD_EXCEPTION;
56235625
opline->op1_type = IS_TMP_VAR;
56245626
opline->op1.var = loop_var->var_num;
56255627
} else if (loop_var->opcode == ZEND_RETURN) {
56265628
/* Stack separator */
5627-
if (free_range_opnum != (uint32_t)-1) {
5628-
zend_op *opline = &CG(active_op_array)->opcodes[free_range_opnum];
5629-
opline->op1.num = 0;
5630-
}
56315629
break;
56325630
} else {
56335631
if (free_range_opnum != (uint32_t)-1) {
56345632
zend_op *opline = &CG(active_op_array)->opcodes[free_range_opnum];
5635-
opline->op1.num = loop_var->opcode_start + 1;
5633+
uint32_t new_start = loop_var->range_start + (loop_var->opcode != ZEND_NOP ? 1 : 0);
5634+
/* We only want to shorten the life-time of ranges, but not extend them. */
5635+
if (opline->op1.num < new_start) {
5636+
opline->op1.num = new_start;
5637+
}
56365638
free_range_opnum = (uint32_t)-1;
56375639
}
56385640
/* ZEND_FREE_RANGE does not decrease depth. */
@@ -5648,7 +5650,8 @@ static bool zend_handle_loops_and_finally_ex(zend_long depth, znode *return_valu
56485650
opline->extended_value = ZEND_FREE_ON_RETURN;
56495651
if (loop_var->opcode == ZEND_FREE_RANGE) {
56505652
free_range_opnum = get_next_op_number() - 1;
5651-
opline->op2.var = loop_var->opcode_start;
5653+
opline->op1.var = loop_var->range_start;
5654+
opline->op2.var = loop_var->range_end;
56525655
}
56535656
}
56545657
}
@@ -6452,8 +6455,8 @@ static void zend_compile_block_expr(znode *result, zend_ast *ast, bool omit_free
64526455
zend_loop_var info = {0};
64536456
info.opcode = ZEND_FREE_RANGE;
64546457
info.var_type = IS_UNUSED;
6455-
info.var_num = (uint32_t)-1;
6456-
info.opcode_start = get_next_op_number();
6458+
info.range_start = CG(context).stmt_start;
6459+
info.range_end = get_next_op_number();
64576460
zend_stack_push(&CG(loop_var_stack), &info);
64586461
}
64596462
zend_compile_stmt_list(ast->child[0]);
@@ -6490,8 +6493,8 @@ static void zend_compile_match(znode *result, zend_ast *ast)
64906493
zend_loop_var info = {0};
64916494
info.opcode = ZEND_FREE_RANGE;
64926495
info.var_type = IS_UNUSED;
6493-
info.var_num = (uint32_t)-1;
6494-
info.opcode_start = start_opnum;
6496+
info.range_start = CG(context).stmt_start;
6497+
info.range_end = start_opnum;
64956498
zend_stack_push(&CG(loop_var_stack), &info);
64966499
}
64976500

@@ -6719,7 +6722,6 @@ static void zend_compile_try(zend_ast *ast) /* {{{ */
67196722
}
67206723

67216724
try_catch_offset = zend_add_try_element(get_next_op_number());
6722-
uint32_t try_opnum = get_next_op_number();
67236725

67246726
if (finally_ast) {
67256727
zend_loop_var fast_call;
@@ -6733,7 +6735,6 @@ static void zend_compile_try(zend_ast *ast) /* {{{ */
67336735
fast_call.var_type = IS_TMP_VAR;
67346736
fast_call.var_num = CG(context).fast_call_var;
67356737
fast_call.try_catch_offset = try_catch_offset;
6736-
fast_call.opcode_start = try_opnum;
67376738
zend_stack_push(&CG(loop_var_stack), &fast_call);
67386739
}
67396740

@@ -11460,6 +11461,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */
1146011461
zend_do_extended_stmt();
1146111462
}
1146211463

11464+
uint32_t prev_stmt_start = CG(context).stmt_start;
11465+
CG(context).stmt_start = get_next_op_number();
11466+
1146311467
switch (ast->kind) {
1146411468
case ZEND_AST_STMT_LIST:
1146511469
zend_compile_stmt_list(ast);
@@ -11555,7 +11559,7 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */
1155511559
break;
1155611560
case ZEND_AST_BLOCK_EXPR:
1155711561
zend_compile_block_expr(NULL, ast, /* omit_free_range */ false);
11558-
return;
11562+
break;
1155911563
default:
1156011564
{
1156111565
znode result;
@@ -11564,6 +11568,8 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */
1156411568
}
1156511569
}
1156611570

11571+
CG(context).stmt_start = prev_stmt_start;
11572+
1156711573
if (FC(declarables).ticks && !zend_is_unticked_stmt(ast)) {
1156811574
zend_emit_tick();
1156911575
}

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ typedef struct _zend_oparray_context {
213213
zend_property_hook_kind active_property_hook_kind;
214214
bool in_jmp_frameless_branch;
215215
bool in_block_expr;
216+
uint32_t stmt_start;
216217
} zend_oparray_context;
217218

218219
/* Class, property and method flags class|meth.|prop.|const*/

Zend/zend_opcode.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,51 @@ static void swap_live_range(zend_live_range *a, zend_live_range *b) {
926926
b->end = tmp;
927927
}
928928

929+
static void remove_dead_free_ranges(zend_op_array *op_array, zend_stack *stack, uint32_t opnum)
930+
{
931+
while (!zend_stack_is_empty(stack)) {
932+
uint32_t top_num = *(uint32_t*)zend_stack_top(stack);
933+
zend_op *top = &op_array->opcodes[top_num];
934+
/* We're past the range's start. */
935+
if (opnum < top->op1.num) {
936+
zend_stack_del_top(stack);
937+
} else {
938+
break;
939+
}
940+
}
941+
}
942+
943+
static void append_free_range(zend_op_array *op_array, zend_stack *stack, zend_op *opline, uint32_t opnum)
944+
{
945+
uint32_t i = zend_stack_count(stack);
946+
uint32_t *existing_num = zend_stack_top(stack);
947+
while (i-- != 0) {
948+
zend_op *existing = &op_array->opcodes[*existing_num];
949+
if (existing->op1.num <= opline->op1.num && existing->op2.num >= opline->op2.num) {
950+
return;
951+
} else if (existing->op1.num >= opline->op1.num && existing->op2.num <= opline->op2.num) {
952+
*existing_num = opnum;
953+
return;
954+
}
955+
existing_num--;
956+
}
957+
zend_stack_push(stack, &opnum);
958+
}
959+
960+
static uint32_t find_free_range(zend_op_array *op_array, zend_stack *stack, uint32_t opnum)
961+
{
962+
uint32_t i = zend_stack_count(stack);
963+
uint32_t *num = zend_stack_top(stack);
964+
while (i-- != 0) {
965+
zend_op *op = &op_array->opcodes[*num];
966+
if (op->op1.num <= opnum && op->op2.num > opnum) {
967+
return *num;
968+
}
969+
num--;
970+
}
971+
return (uint32_t)-1;
972+
}
973+
929974
static void zend_calc_live_ranges(
930975
zend_op_array *op_array, zend_needs_live_range_cb needs_live_range) {
931976
uint32_t opnum = op_array->last;
@@ -935,11 +980,19 @@ static void zend_calc_live_ranges(
935980
uint32_t *last_use = do_alloca(sizeof(uint32_t) * op_array->T, use_heap);
936981
memset(last_use, -1, sizeof(uint32_t) * op_array->T);
937982

983+
zend_stack free_range_stack;
984+
zend_stack_init(&free_range_stack, sizeof(uint32_t));
985+
938986
ZEND_ASSERT(!op_array->live_range);
939987
while (opnum > 0) {
940988
opnum--;
941989
opline--;
942990

991+
if (UNEXPECTED(opline->opcode == ZEND_FREE_RANGE)) {
992+
remove_dead_free_ranges(op_array, &free_range_stack, opnum);
993+
append_free_range(op_array, &free_range_stack, opline, opnum);
994+
}
995+
943996
if ((opline->result_type & (IS_TMP_VAR|IS_VAR)) && !is_fake_def(opline)) {
944997
uint32_t var_num = EX_VAR_TO_NUM(opline->result.var) - var_offset;
945998
/* Defs without uses can occur for two reasons: Either because the result is
@@ -948,6 +1001,7 @@ static void zend_calc_live_ranges(
9481001
* which case the last one starts the live range. As such, we can simply ignore
9491002
* missing uses here. */
9501003
if (EXPECTED(last_use[var_num] != (uint32_t) -1)) {
1004+
found:
9511005
/* Skip trivial live-range */
9521006
if (opnum + 1 != last_use[var_num]) {
9531007
uint32_t num;
@@ -963,6 +1017,11 @@ static void zend_calc_live_ranges(
9631017
emit_live_range(op_array, var_num, num, last_use[var_num], needs_live_range);
9641018
}
9651019
last_use[var_num] = (uint32_t) -1;
1020+
} else {
1021+
last_use[var_num] = find_free_range(op_array, &free_range_stack, opnum);
1022+
if (last_use[var_num] != (uint32_t) -1) {
1023+
goto found;
1024+
}
9661025
}
9671026
}
9681027

@@ -1024,6 +1083,7 @@ static void zend_calc_live_ranges(
10241083
}
10251084

10261085
free_alloca(last_use, use_heap);
1086+
zend_stack_destroy(&free_range_stack);
10271087
}
10281088

10291089
ZEND_API void zend_recalc_live_ranges(

0 commit comments

Comments
 (0)