Skip to content

Add destructuring coalesce #9747

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
if (opline->opcode != ZEND_CASE
&& opline->opcode != ZEND_CASE_STRICT
&& opline->opcode != ZEND_FETCH_LIST_R
&& opline->opcode != ZEND_FETCH_LIST_IS
&& opline->opcode != ZEND_SWITCH_LONG
&& opline->opcode != ZEND_SWITCH_STRING
&& opline->opcode != ZEND_MATCH
Expand Down Expand Up @@ -371,6 +372,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array

case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_W:
case ZEND_FETCH_LIST_IS:
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
/* LIST variable will be deleted later by FREE */
Tsource[VAR_NUM(opline->op1.var)] = NULL;
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_FETCH_DIM_UNSET:
case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_W:
case ZEND_FETCH_LIST_IS:
case ZEND_ASSIGN_DIM_OP:
if (opline->op1_type == IS_CONST) {
LITERAL_INFO(opline->op1.constant, 1);
Expand Down
3 changes: 2 additions & 1 deletion Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1508,10 +1508,11 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
case ZEND_FETCH_DIM_R:
case ZEND_FETCH_DIM_IS:
case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_IS:
SKIP_IF_TOP(op1);
SKIP_IF_TOP(op2);

if (ct_eval_fetch_dim(&zv, op1, op2, (opline->opcode != ZEND_FETCH_LIST_R)) == SUCCESS) {
if (ct_eval_fetch_dim(&zv, op1, op2, (opline->opcode != ZEND_FETCH_LIST_R && opline->opcode != ZEND_FETCH_LIST_IS)) == SUCCESS) {
SET_RESULT(result, &zv);
zval_ptr_dtor_nogc(&zv);
break;
Expand Down
9 changes: 6 additions & 3 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -3245,6 +3245,7 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_FETCH_DIM_FUNC_ARG:
case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_W:
case ZEND_FETCH_LIST_IS:
if (ssa_op->op1_def >= 0) {
uint32_t key_type = 0;
tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN);
Expand Down Expand Up @@ -3397,7 +3398,8 @@ static zend_always_inline zend_result _zend_update_type_info(
|| opline->opcode == ZEND_FETCH_DIM_R
|| opline->opcode == ZEND_FETCH_DIM_IS
|| opline->opcode == ZEND_FETCH_DIM_UNSET
|| opline->opcode == ZEND_FETCH_LIST_R) {
|| opline->opcode == ZEND_FETCH_LIST_R
|| opline->opcode == ZEND_FETCH_LIST_IS) {
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
} else {
/* invalid key type */
Expand All @@ -3409,10 +3411,10 @@ static zend_always_inline zend_result _zend_update_type_info(
}
/* FETCH_LIST on a string behaves like FETCH_R on null */
tmp = zend_array_element_type(
opline->opcode != ZEND_FETCH_LIST_R ? t1 : ((t1 & ~MAY_BE_STRING) | MAY_BE_NULL),
opline->opcode != ZEND_FETCH_LIST_R && opline->opcode != ZEND_FETCH_LIST_IS ? t1 : ((t1 & ~MAY_BE_STRING) | MAY_BE_NULL),
opline->op1_type,
opline->opcode != ZEND_FETCH_DIM_R && opline->opcode != ZEND_FETCH_DIM_IS
&& opline->opcode != ZEND_FETCH_LIST_R,
&& opline->opcode != ZEND_FETCH_LIST_R && opline->opcode != ZEND_FETCH_LIST_IS,
opline->op2_type == IS_UNUSED);
if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG && (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE))) {
tmp |= MAY_BE_NULL;
Expand Down Expand Up @@ -4524,6 +4526,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_IS:
case ZEND_QM_ASSIGN:
case ZEND_SEND_VAL:
case ZEND_SEND_VAL_EX:
Expand Down
3 changes: 3 additions & 0 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ bool zend_optimizer_update_op2_const(zend_op_array *op_array,
case ZEND_FETCH_DIM_UNSET:
case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_W:
case ZEND_FETCH_LIST_IS:
if (Z_TYPE_P(val) == IS_STRING) {
zend_ulong index;

Expand Down Expand Up @@ -636,6 +637,7 @@ bool zend_optimizer_replace_by_const(zend_op_array *op_array,
* we want to try to replace all uses of the temporary.
*/
case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_IS:
case ZEND_CASE:
case ZEND_CASE_STRICT:
case ZEND_SWITCH_LONG:
Expand All @@ -648,6 +650,7 @@ bool zend_optimizer_replace_by_const(zend_op_array *op_array,
/* If this opcode doesn't keep the operand alive, we're done. Check
* this early, because op replacement may modify the opline. */
bool is_last = opline->opcode != ZEND_FETCH_LIST_R
&& opline->opcode != ZEND_FETCH_LIST_IS
&& opline->opcode != ZEND_CASE
&& opline->opcode != ZEND_CASE_STRICT
&& opline->opcode != ZEND_SWITCH_LONG
Expand Down
55 changes: 55 additions & 0 deletions Zend/tests/destructuring_coalesce_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--TEST--
Destructuring operations with coalesce
--FILE--
<?php

[$a ?? 1] = [];
var_dump($a);

["strkey" => $a ?? 2] = [];
var_dump($a);

[, $a ?? 0] = [0, 3];
var_dump($a);

["strkey" => $a ?? 0] = ["strkey" => 4];
var_dump($a);

[$a ?? 5] = "";
var_dump($a);

[$a ?? 6] = [null];
var_dump($a);

[[$a ?? 7]] = [[]];
var_dump($a);

[[$a ?? 8]] = [];
var_dump($a);

[$a ?? 9] = 0;
var_dump($a);

[$a ?? 10] = $undefinedVar;
var_dump($a);

[[$a] ?? [11]] = [];
var_dump($a);

?>
--EXPECTF--
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)

Warning: Undefined array key 0 in %s on line %d
int(8)
int(9)

Warning: Undefined variable $undefinedVar in %s on line %d
int(10)
int(11)
30 changes: 26 additions & 4 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -3062,7 +3062,7 @@ static void zend_compile_list_assign(

for (i = 0; i < list->children; ++i) {
zend_ast *elem_ast = list->child[i];
zend_ast *var_ast, *key_ast;
zend_ast *var_ast, *key_ast, *coaleasce_default_ast = NULL;
znode fetch_result, dim_node;
zend_op *opline;

Expand Down Expand Up @@ -3105,16 +3105,38 @@ static void zend_compile_list_assign(
Z_TRY_ADDREF(expr_node->u.constant);
}

zend_uchar opcode = ZEND_FETCH_LIST_R;
if (var_ast->kind == ZEND_AST_COALESCE) {
coaleasce_default_ast = var_ast->child[1];
var_ast = var_ast->child[0];

opcode = ZEND_FETCH_LIST_IS;
} else if (elem_ast->attr) {
opcode = expr_node->op_type == IS_CV ? ZEND_FETCH_DIM_W : ZEND_FETCH_LIST_W;
}

zend_verify_list_assign_target(var_ast, array_style);

opline = zend_emit_op(&fetch_result,
elem_ast->attr ? (expr_node->op_type == IS_CV ? ZEND_FETCH_DIM_W : ZEND_FETCH_LIST_W) : ZEND_FETCH_LIST_R, expr_node, &dim_node);
opline = zend_emit_op(&fetch_result, opcode, expr_node, &dim_node);

if (dim_node.op_type == IS_CONST) {
zend_handle_numeric_dim(opline, &dim_node);
}

if (elem_ast->attr) {
if (coaleasce_default_ast) {
znode default_node;

uint32_t opnum = get_next_op_number();
zend_emit_op_tmp(&fetch_result, ZEND_COALESCE, &fetch_result, NULL);

zend_compile_expr(&default_node, coaleasce_default_ast);

opline = zend_emit_op_tmp(NULL, ZEND_QM_ASSIGN, &default_node, NULL);
SET_NODE(opline->result, &fetch_result);

opline = &CG(active_op_array)->opcodes[opnum];
opline->op2.opline_num = get_next_op_number();
} else if (elem_ast->attr) {
zend_emit_op(&fetch_result, ZEND_MAKE_REF, &fetch_result, NULL);
}
if (var_ast->kind == ZEND_AST_ARRAY) {
Expand Down
6 changes: 6 additions & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -2808,6 +2808,12 @@ static zend_never_inline void ZEND_FASTCALL zend_fetch_dimension_address_LIST_r(
zend_fetch_dimension_address_read(result, container, dim, dim_type, BP_VAR_R, 1, 0 EXECUTE_DATA_CC);
}

static zend_never_inline void ZEND_FASTCALL zend_fetch_dimension_address_LIST_is(zval *container, zval *dim, int dim_type OPLINE_DC EXECUTE_DATA_DC)
{
zval *result = EX_VAR(opline->result.var);
zend_fetch_dimension_address_read(result, container, dim, dim_type, BP_VAR_IS, 1, 0 EXECUTE_DATA_CC);
}

ZEND_API void zend_fetch_dimension_const(zval *result, zval *container, zval *dim, int type)
{
zend_fetch_dimension_address_read(result, container, dim, IS_TMP_VAR, type, 0, 0 NO_EXECUTE_DATA_CC);
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,7 @@ static bool keeps_op1_alive(zend_op *opline) {
|| opline->opcode == ZEND_SWITCH_STRING
|| opline->opcode == ZEND_MATCH
|| opline->opcode == ZEND_FETCH_LIST_R
|| opline->opcode == ZEND_FETCH_LIST_IS
|| opline->opcode == ZEND_COPY_TMP) {
return 1;
}
Expand Down
12 changes: 12 additions & 0 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -2348,6 +2348,18 @@ ZEND_VM_HANDLER(98, ZEND_FETCH_LIST_R, CONST|TMPVARCV, CONST|TMPVAR|CV)
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

ZEND_VM_HANDLER(45, ZEND_FETCH_LIST_IS, CONST|TMPVARCV, CONST|TMPVAR|CV)
{
USE_OPLINE
zval *container;

SAVE_OPLINE();
container = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_IS);
zend_fetch_dimension_address_LIST_is(container, GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R), OP2_TYPE OPLINE_CC EXECUTE_DATA_CC);
FREE_OP2();
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

ZEND_VM_HANDLER(155, ZEND_FETCH_LIST_W, VAR, CONST|TMPVAR|CV)
{
USE_OPLINE
Expand Down
Loading