Skip to content

Commit aa19742

Browse files
committed
Transform pattern bindings to opcodes
This solves issues with the optimizer where it would be unaware of the assignments that happen inside the pattern matcher. Instead, we now emit an array of bindings in op2, and extract & assign the values to the given variables through opcodes.
1 parent 0166290 commit aa19742

File tree

12 files changed

+187
-177
lines changed

12 files changed

+187
-177
lines changed

Zend/tests/pattern_matching/is/binding_destruct.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ var_dump($bar);
2525
--EXPECT--
2626
Here
2727
int(42)
28-
int(42)
28+
string(3) "bar"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Bug 001
3+
--FILE--
4+
<?php
5+
6+
function test() {
7+
$x = 42;
8+
43 is $x;
9+
var_dump($x);
10+
}
11+
12+
test();
13+
14+
?>
15+
--EXPECT--
16+
int(43)

Zend/zend_compile.c

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6418,6 +6418,12 @@ static bool can_match_use_jumptable(zend_ast_list *arms) {
64186418

64196419
static void zend_compile_pattern(zend_ast **ast_ptr, void *context);
64206420

6421+
typedef struct {
6422+
bool inside_or_pattern;
6423+
uint32_t bindings_var;
6424+
uint32_t binding_offset;
6425+
} zend_compile_pattern_context;
6426+
64216427
static void zend_compile_match(znode *result, zend_ast *ast)
64226428
{
64236429
zend_ast *expr_ast = ast->child[0];
@@ -6491,7 +6497,23 @@ static void zend_compile_match(znode *result, zend_ast *ast)
64916497
} else {
64926498
zend_ast **pattern_ast_ptr = &cond_ast->child[1];
64936499

6494-
zend_compile_pattern(pattern_ast_ptr, NULL);
6500+
znode bindings_node;
6501+
bindings_node.op_type = IS_TMP_VAR;
6502+
bindings_node.u.op.var = get_temporary_variable();
6503+
6504+
uint32_t is_opnum = get_next_op_number();
6505+
zend_op *is_opline = zend_emit_op_tmp(NULL, ZEND_IS, &expr_node, &bindings_node);
6506+
SET_NODE(is_opline->result, &case_node);
6507+
if (is_opline->op1_type == IS_CONST) {
6508+
Z_TRY_ADDREF_P(CT_CONSTANT(is_opline->op1));
6509+
}
6510+
6511+
uint32_t op_data_opnum = get_next_op_number();
6512+
zend_emit_op_data(NULL);
6513+
6514+
zend_compile_pattern_context pattern_context = { .bindings_var = bindings_node.u.op.var };
6515+
zend_compile_pattern(pattern_ast_ptr, &pattern_context);
6516+
64956517
zval pattern_zv;
64966518
ZVAL_AST(&pattern_zv, zend_ast_copy(*pattern_ast_ptr));
64976519
zend_ast_destroy(*pattern_ast_ptr);
@@ -6500,11 +6522,20 @@ static void zend_compile_match(znode *result, zend_ast *ast)
65006522
znode pattern_node;
65016523
pattern_node.op_type = IS_CONST;
65026524
ZVAL_COPY(&pattern_node.u.constant, &pattern_zv);
6525+
zend_op *op_data_op = &CG(active_op_array)->opcodes[op_data_opnum];
6526+
SET_NODE(op_data_op->op1, &pattern_node);
6527+
op_data_op->extended_value = pattern_context.binding_offset;
65036528

6504-
zend_op *opline = zend_emit_op_tmp(NULL, ZEND_IS, &expr_node, &pattern_node);
6505-
SET_NODE(opline->result, &case_node);
6506-
if (opline->op1_type == IS_CONST) {
6507-
Z_TRY_ADDREF_P(CT_CONSTANT(opline->op1));
6529+
is_opline = &CG(active_op_array)->opcodes[is_opnum];
6530+
is_opline->extended_value = get_next_op_number();
6531+
6532+
if (pattern_context.binding_offset) {
6533+
zend_emit_op(NULL, ZEND_FREE, &bindings_node, NULL);
6534+
}
6535+
6536+
if (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) {
6537+
// FIXME: Verify live ranges recognizes that OP1 needs to be freed if an exception occurs
6538+
zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL);
65086539
}
65096540
}
65106541

@@ -6658,16 +6689,37 @@ static void zend_compile_class_const_pattern(zend_ast **ast_ptr)
66586689
// zend_compile_pattern_class_name(ast);
66596690
}
66606691

6661-
static void zend_compile_binding_pattern(zend_ast **ast_ptr)
6692+
static void zend_compile_binding_pattern(zend_ast **ast_ptr, zend_compile_pattern_context *context)
66626693
{
66636694
zend_ast *binding_pattern_ast = *ast_ptr;
66646695
zend_ast *var_name_ast = binding_pattern_ast->child[0];
66656696
zend_string *var_name = zend_ast_get_str(var_name_ast);
66666697
uint32_t var = lookup_cv(var_name);
66676698

6699+
uint32_t binding_offset = context->binding_offset++;
6700+
66686701
zend_ast_destroy(var_name_ast);
66696702
binding_pattern_ast->child[0] = NULL;
6670-
binding_pattern_ast->attr = var;
6703+
binding_pattern_ast->attr = binding_offset;
6704+
6705+
znode bindings_node;
6706+
bindings_node.op_type = IS_TMP_VAR;
6707+
bindings_node.u.op.var = context->bindings_var;
6708+
6709+
znode offset_node;
6710+
offset_node.op_type = IS_CONST;
6711+
ZVAL_LONG(&offset_node.u.constant, binding_offset);
6712+
6713+
znode copy_tmp_node;
6714+
zend_emit_op_tmp(&copy_tmp_node, ZEND_COPY_TMP, &bindings_node, NULL);
6715+
6716+
znode dim_result;
6717+
zend_emit_op_tmp(&dim_result, ZEND_FETCH_DIM_R, &copy_tmp_node, &offset_node);
6718+
6719+
znode var_node;
6720+
var_node.op_type = IS_CV;
6721+
var_node.u.op.var = var;
6722+
zend_emit_op_tmp(NULL, ZEND_ASSIGN, &var_node, &dim_result);
66716723
}
66726724

66736725
static void zend_compile_array_pattern(zend_ast **ast_ptr)
@@ -6719,19 +6771,15 @@ static void zend_compile_and_pattern(zend_ast **ast_ptr)
67196771
verify_parenthesized_compound_pattern(ast_ptr, ZEND_AST_OR_PATTERN);
67206772
}
67216773

6722-
struct zend_compile_pattern_context {
6723-
bool inside_or_pattern;
6724-
};
6725-
67266774
static void zend_compile_pattern(zend_ast **ast_ptr, void *context)
67276775
{
67286776
zend_ast *ast = *ast_ptr;
67296777
if (ast == NULL || ast->kind == ZEND_AST_ZVAL) {
67306778
return;
67316779
}
67326780

6733-
struct zend_compile_pattern_context *pattern_context = context;
6734-
struct zend_compile_pattern_context tmp_context = {0};
6781+
zend_compile_pattern_context *pattern_context = context;
6782+
zend_compile_pattern_context tmp_context = {0};
67356783
if (!pattern_context) {
67366784
pattern_context = &tmp_context;
67376785
}
@@ -6745,7 +6793,7 @@ static void zend_compile_pattern(zend_ast **ast_ptr, void *context)
67456793
if (pattern_context->inside_or_pattern) {
67466794
zend_throw_exception(zend_ce_compile_error, "Must not bind to variables inside | pattern", 0);
67476795
}
6748-
zend_compile_binding_pattern(ast_ptr);
6796+
zend_compile_binding_pattern(ast_ptr, context);
67496797
break;
67506798
case ZEND_AST_ARRAY_PATTERN:
67516799
zend_compile_array_pattern(ast_ptr);
@@ -6774,7 +6822,19 @@ static void zend_compile_is(znode *result, zend_ast *ast)
67746822
znode expr_node;
67756823
zend_compile_expr(&expr_node, expr_ast);
67766824

6777-
zend_compile_pattern(pattern_ast_ptr, NULL);
6825+
znode bindings_node;
6826+
bindings_node.op_type = IS_TMP_VAR;
6827+
bindings_node.u.op.var = get_temporary_variable();
6828+
6829+
uint32_t is_opnum = get_next_op_number();
6830+
zend_emit_op_tmp(result, ZEND_IS, &expr_node, &bindings_node);
6831+
6832+
uint32_t op_data_opnum = get_next_op_number();
6833+
zend_emit_op_data(NULL);
6834+
6835+
zend_compile_pattern_context pattern_context = { .bindings_var = bindings_node.u.op.var };
6836+
zend_compile_pattern(pattern_ast_ptr, &pattern_context);
6837+
67786838
zval pattern_zv;
67796839
ZVAL_AST(&pattern_zv, zend_ast_copy(*pattern_ast_ptr));
67806840
zend_ast_destroy(*pattern_ast_ptr);
@@ -6783,8 +6843,17 @@ static void zend_compile_is(znode *result, zend_ast *ast)
67836843
znode pattern_node;
67846844
pattern_node.op_type = IS_CONST;
67856845
ZVAL_COPY(&pattern_node.u.constant, &pattern_zv);
6846+
zend_op *op_data_op = &CG(active_op_array)->opcodes[op_data_opnum];
6847+
SET_NODE(op_data_op->op1, &pattern_node);
6848+
op_data_op->extended_value = pattern_context.binding_offset;
6849+
6850+
zend_op *is_opline = &CG(active_op_array)->opcodes[is_opnum];
6851+
is_opline->extended_value = get_next_op_number();
6852+
6853+
if (pattern_context.binding_offset) {
6854+
zend_emit_op(NULL, ZEND_FREE, &bindings_node, NULL);
6855+
}
67866856

6787-
zend_emit_op_tmp(result, ZEND_IS, &expr_node, &pattern_node);
67886857
if (expr_node.op_type & (IS_VAR|IS_TMP_VAR)) {
67896858
// FIXME: Verify live ranges recognizes that OP1 needs to be freed if an exception occurs
67906859
zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL);

Zend/zend_execute_API.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,6 @@ void init_executor(void) /* {{{ */
206206
zend_fiber_init();
207207
zend_weakrefs_init();
208208

209-
zend_pm_contexts_free();
210-
211209
EG(active) = 1;
212210
}
213211
/* }}} */

Zend/zend_globals.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,6 @@ struct _zend_executor_globals {
314314
#endif
315315

316316
zend_strtod_state strtod_state;
317-
zend_pm_context *pm_context;
318-
zend_pm_context pm_context_spare;
319317

320318
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
321319
};

Zend/zend_opcode.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,8 @@ static void zend_calc_live_ranges(
979979
if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
980980
uint32_t var_num = EX_VAR_TO_NUM(opline->op2.var) - var_offset;
981981
if (UNEXPECTED(opline->opcode == ZEND_FE_FETCH_R
982-
|| opline->opcode == ZEND_FE_FETCH_RW)) {
982+
|| opline->opcode == ZEND_FE_FETCH_RW
983+
|| opline->opcode == ZEND_IS)) {
983984
/* OP2 of FE_FETCH is actually a def, not a use. */
984985
if (last_use[var_num] != (uint32_t) -1) {
985986
if (opnum + 1 != last_use[var_num]) {
@@ -1184,6 +1185,9 @@ ZEND_API void pass_two(zend_op_array *op_array)
11841185
opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, opline->extended_value);
11851186
break;
11861187
}
1188+
case ZEND_IS:
1189+
opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, opline->extended_value);
1190+
break;
11871191
}
11881192
if (opline->op1_type == IS_CONST) {
11891193
ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline, opline->op1);

0 commit comments

Comments
 (0)