summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2017-03-17 23:45:05 +0100
committerNikita Popov <nikita.ppv@gmail.com>2017-04-10 22:23:14 +0200
commitad8652818a54790e01fcf181791aa780ac6922c9 (patch)
tree7df0e640a877b87bde44c85410898ed8039b87fd
parentd1a012b60270b751449de79235028295696145b2 (diff)
downloadphp-git-ad8652818a54790e01fcf181791aa780ac6922c9.tar.gz
Implement jumptable optimization
-rw-r--r--Zend/tests/switch_on_numeric_strings.phpt29
-rw-r--r--Zend/zend_compile.c100
-rw-r--r--Zend/zend_opcode.c13
-rw-r--r--Zend/zend_vm_def.h58
-rw-r--r--Zend/zend_vm_execute.h300
-rw-r--r--Zend/zend_vm_opcodes.c8
-rw-r--r--Zend/zend_vm_opcodes.h4
-rw-r--r--ext/opcache/Optimizer/block_pass.c61
-rw-r--r--ext/opcache/Optimizer/dfa_pass.c61
-rw-r--r--ext/opcache/Optimizer/nop_removal.c60
-rw-r--r--ext/opcache/Optimizer/zend_cfg.c37
-rw-r--r--ext/opcache/Optimizer/zend_dump.c34
-rw-r--r--ext/opcache/Optimizer/zend_optimizer.c94
-rw-r--r--ext/opcache/Optimizer/zend_optimizer_internal.h2
-rw-r--r--ext/opcache/tests/switch_with_coinciding_targets.phpt21
-rw-r--r--ext/opcache/zend_file_cache.c4
-rw-r--r--ext/opcache/zend_persist.c2
17 files changed, 719 insertions, 169 deletions
diff --git a/Zend/tests/switch_on_numeric_strings.phpt b/Zend/tests/switch_on_numeric_strings.phpt
new file mode 100644
index 0000000000..b436cefe41
--- /dev/null
+++ b/Zend/tests/switch_on_numeric_strings.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Switch on numeric strings
+--FILE--
+<?php
+
+function test($value) {
+ switch ($value) {
+ case "01": return "01";
+ case "1": return "1";
+
+ case " 2": return " 2";
+ case "2": return "2";
+
+ case "10.0": return "10.0";
+ case "1e1": return "1e1";
+
+ default: return "default";
+ }
+}
+
+var_dump(test("1"));
+var_dump(test("2"));
+var_dump(test("1e1"));
+
+?>
+--EXPECT--
+string(2) "01"
+string(2) " 2"
+string(4) "10.0"
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index ab6e23d7df..e760676e6f 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -2081,6 +2081,8 @@ static void zend_check_live_ranges(zend_op *opline) /* {{{ */
} 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 ||
@@ -4603,6 +4605,58 @@ void zend_compile_if(zend_ast *ast) /* {{{ */
}
/* }}} */
+static zend_uchar determine_switch_jumptable_type(zend_ast_list *cases) {
+ uint32_t i;
+ zend_uchar common_type = IS_UNDEF;
+ for (i = 0; i < cases->children; i++) {
+ zend_ast *case_ast = cases->child[i];
+ zend_ast **cond_ast = &case_ast->child[0];
+ zval *cond_zv;
+ if (!case_ast->child[0]) {
+ /* Skip default clause */
+ continue;
+ }
+
+ zend_eval_const_expr(cond_ast);
+ if ((*cond_ast)->kind != ZEND_AST_ZVAL) {
+ /* Non-constant case */
+ return IS_UNDEF;
+ }
+
+ cond_zv = zend_ast_get_zval(case_ast->child[0]);
+ if (Z_TYPE_P(cond_zv) != IS_LONG && Z_TYPE_P(cond_zv) != IS_STRING) {
+ /* We only optimize switched on integers and strings */
+ return IS_UNDEF;
+ }
+
+ if (common_type == IS_UNDEF) {
+ common_type = Z_TYPE_P(cond_zv);
+ } else if (common_type != Z_TYPE_P(cond_zv)) {
+ /* Non-uniform case types */
+ return IS_UNDEF;
+ }
+
+ if (Z_TYPE_P(cond_zv) == IS_STRING
+ && is_numeric_string(Z_STRVAL_P(cond_zv), Z_STRLEN_P(cond_zv), NULL, NULL, 0)) {
+ /* Numeric strings cannot be compared with a simple hash lookup */
+ return IS_UNDEF;
+ }
+ }
+
+ return common_type;
+}
+
+static zend_bool should_use_jumptable(zend_ast_list *cases, zend_uchar jumptable_type) {
+ /* Thresholds are chosen based on when the average switch time for equidistributed
+ * input becomes smaller when using the jumptable optimization. */
+ if (jumptable_type == IS_LONG) {
+ return cases->children >= 5;
+ } else {
+ ZEND_ASSERT(jumptable_type == IS_STRING);
+ return cases->children >= 2;
+ }
+}
+
void zend_compile_switch(zend_ast *ast) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
@@ -4613,7 +4667,9 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
znode expr_node, case_node;
zend_op *opline;
- uint32_t *jmpnz_opnums, opnum_default_jmp;
+ uint32_t *jmpnz_opnums, opnum_default_jmp, opnum_switch;
+ zend_uchar jumptable_type;
+ HashTable *jumptable = NULL;
zend_compile_expr(&expr_node, expr_ast);
@@ -4622,6 +4678,24 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
case_node.op_type = IS_TMP_VAR;
case_node.u.op.var = get_temporary_variable(CG(active_op_array));
+ jumptable_type = determine_switch_jumptable_type(cases);
+ if (jumptable_type != IS_UNDEF && should_use_jumptable(cases, jumptable_type)) {
+ znode jumptable_op;
+
+ ALLOC_HASHTABLE(jumptable);
+ zend_hash_init(jumptable, cases->children, NULL, NULL, 0);
+ jumptable_op.op_type = IS_CONST;
+ ZVAL_ARR(&jumptable_op.u.constant, jumptable);
+
+ opline = zend_emit_op(NULL,
+ jumptable_type == IS_LONG ? ZEND_SWITCH_LONG : ZEND_SWITCH_STRING,
+ &expr_node, &jumptable_op);
+ if (opline->op1_type == IS_CONST) {
+ zval_copy_ctor(CT_CONSTANT(opline->op1));
+ }
+ opnum_switch = opline - CG(active_op_array)->opcodes;
+ }
+
jmpnz_opnums = safe_emalloc(sizeof(uint32_t), cases->children, 0);
for (i = 0; i < cases->children; ++i) {
zend_ast *case_ast = cases->child[i];
@@ -4666,8 +4740,27 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
if (cond_ast) {
zend_update_jump_target_to_next(jmpnz_opnums[i]);
+
+ if (jumptable) {
+ zval *cond_zv = zend_ast_get_zval(cond_ast);
+ zval jmp_target;
+ ZVAL_LONG(&jmp_target, get_next_op_number(CG(active_op_array)));
+
+ ZEND_ASSERT(Z_TYPE_P(cond_zv) == jumptable_type);
+ if (Z_TYPE_P(cond_zv) == IS_LONG) {
+ zend_hash_index_add(jumptable, Z_LVAL_P(cond_zv), &jmp_target);
+ } else {
+ ZEND_ASSERT(Z_TYPE_P(cond_zv) == IS_STRING);
+ zend_hash_add(jumptable, Z_STR_P(cond_zv), &jmp_target);
+ }
+ }
} else {
zend_update_jump_target_to_next(opnum_default_jmp);
+
+ if (jumptable) {
+ opline = &CG(active_op_array)->opcodes[opnum_switch];
+ opline->extended_value = get_next_op_number(CG(active_op_array));
+ }
}
zend_compile_stmt(stmt_ast);
@@ -4675,6 +4768,11 @@ void zend_compile_switch(zend_ast *ast) /* {{{ */
if (!has_default_case) {
zend_update_jump_target_to_next(opnum_default_jmp);
+
+ if (jumptable) {
+ opline = &CG(active_op_array)->opcodes[opnum_switch];
+ opline->extended_value = get_next_op_number(CG(active_op_array));
+ }
}
zend_end_loop(get_next_op_number(CG(active_op_array)), &expr_node);
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index 7c79703338..e8764e964a 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -670,6 +670,19 @@ ZEND_API int pass_two(zend_op_array *op_array)
opline->opcode = ZEND_GENERATOR_RETURN;
}
break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ {
+ /* absolute indexes to relative offsets */
+ HashTable *jumptable = Z_ARRVAL_P(CT_CONSTANT(opline->op2));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, Z_LVAL_P(zv));
+ } ZEND_HASH_FOREACH_END();
+
+ opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, opline->extended_value);
+ break;
+ }
}
if (opline->op1_type == IS_CONST) {
ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op1);
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index d80f1e6b56..d486d1514e 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -8043,6 +8043,64 @@ ZEND_VM_HANDLER(51, ZEND_MAKE_REF, VAR|CV, UNUSED)
ZEND_VM_NEXT_OPCODE();
}
+ZEND_VM_HANDLER(187, ZEND_SWITCH_LONG, CONST|TMPVAR|CV, CONST, JMP_ADDR)
+{
+ USE_OPLINE
+ zend_free_op free_op1, free_op2;
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
+ jumptable = Z_ARRVAL_P(GET_OP2_ZVAL_PTR(BP_VAR_R));
+
+ if (Z_TYPE_P(op) != IS_LONG) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_LONG) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
+ZEND_VM_HANDLER(188, ZEND_SWITCH_STRING, CONST|TMPVAR|CV, CONST, JMP_ADDR)
+{
+ USE_OPLINE
+ zend_free_op free_op1, free_op2;
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
+ jumptable = Z_ARRVAL_P(GET_OP2_ZVAL_PTR(BP_VAR_R));
+
+ if (Z_TYPE_P(op) != IS_STRING) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_STRING) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_find(jumptable, Z_STR_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
ZEND_VM_TYPE_SPEC_HANDLER(ZEND_ADD, (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG), ZEND_ADD_LONG_NO_OVERFLOW, CONST|TMPVARCV, CONST|TMPVARCV, SPEC(NO_CONST_CONST,COMMUTATIVE))
{
USE_OPLINE
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index bc369ae9b0..7136aa8790 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -6409,6 +6409,64 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_YIELD_SPEC_CONST_CONST_HANDLER
ZEND_VM_RETURN();
}
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SWITCH_LONG_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = EX_CONSTANT(opline->op1);
+ jumptable = Z_ARRVAL_P(EX_CONSTANT(opline->op2));
+
+ if (Z_TYPE_P(op) != IS_LONG) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_LONG) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SWITCH_STRING_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = EX_CONSTANT(opline->op1);
+ jumptable = Z_ARRVAL_P(EX_CONSTANT(opline->op2));
+
+ if (Z_TYPE_P(op) != IS_STRING) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_STRING) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_find(jumptable, Z_STR_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -38305,6 +38363,64 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BIND_STATIC_SPEC_CV_CONST_HAND
ZEND_VM_NEXT_OPCODE();
}
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SWITCH_LONG_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = _get_zval_ptr_cv_undef(execute_data, opline->op1.var);
+ jumptable = Z_ARRVAL_P(EX_CONSTANT(opline->op2));
+
+ if (Z_TYPE_P(op) != IS_LONG) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_LONG) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SWITCH_STRING_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = _get_zval_ptr_cv_undef(execute_data, opline->op1.var);
+ jumptable = Z_ARRVAL_P(EX_CONSTANT(opline->op2));
+
+ if (Z_TYPE_P(op) != IS_STRING) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_STRING) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_find(jumptable, Z_STR_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -49767,6 +49883,64 @@ try_instanceof:
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SWITCH_LONG_SPEC_TMPVAR_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+ zend_free_op free_op1;
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1);
+ jumptable = Z_ARRVAL_P(EX_CONSTANT(opline->op2));
+
+ if (Z_TYPE_P(op) != IS_LONG) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_LONG) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_index_find(jumptable, Z_LVAL_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
+static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SWITCH_STRING_SPEC_TMPVAR_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+ zend_free_op free_op1;
+ zval *op, *jump_zv;
+ HashTable *jumptable;
+
+ op = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1);
+ jumptable = Z_ARRVAL_P(EX_CONSTANT(opline->op2));
+
+ if (Z_TYPE_P(op) != IS_STRING) {
+ ZVAL_DEREF(op);
+ if (Z_TYPE_P(op) != IS_STRING) {
+ /* Wrong type, fall back to ZEND_CASE chain */
+ ZEND_VM_NEXT_OPCODE();
+ }
+ }
+
+ jump_zv = zend_hash_find(jumptable, Z_STR_P(op));
+ if (jump_zv != NULL) {
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, Z_LVAL_P(jump_zv));
+ ZEND_VM_CONTINUE();
+ } else {
+ /* default */
+ ZEND_VM_SET_RELATIVE_OPCODE(opline, opline->extended_value);
+ ZEND_VM_CONTINUE();
+ }
+}
+
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -57752,6 +57926,56 @@ void zend_init_opcodes_handlers(void)
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_SWITCH_LONG_SPEC_CONST_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_SWITCH_LONG_SPEC_TMPVAR_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_SWITCH_LONG_SPEC_TMPVAR_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_SWITCH_LONG_SPEC_CV_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_SWITCH_STRING_SPEC_CONST_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_SWITCH_STRING_SPEC_TMPVAR_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_SWITCH_STRING_SPEC_TMPVAR_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_SWITCH_STRING_SPEC_CV_CONST_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
+ ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_ADD_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV_HANDLER,
ZEND_ADD_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV_HANDLER,
@@ -58820,7 +59044,7 @@ void zend_init_opcodes_handlers(void)
2257 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
2282 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
2307 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
- 4596,
+ 4646,
2332,
2333,
2334,
@@ -58905,9 +59129,11 @@ void zend_init_opcodes_handlers(void)
3531 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
3556 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
3581 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
- 4596,
+ 4646,
3606 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
- 4596
+ 3631 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
+ 3656 | SPEC_RULE_OP1 | SPEC_RULE_OP2,
+ 4646
};
zend_opcode_handlers = labels;
zend_handlers_count = sizeof(labels) / sizeof(void*);
@@ -59014,7 +59240,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3631 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3681 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59022,7 +59248,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3656 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3706 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59030,7 +59256,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3681 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3731 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59041,17 +59267,17 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3706 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3756 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
} else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) {
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3731 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3781 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
} else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) {
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3756 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3806 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
}
break;
case ZEND_MUL:
@@ -59059,7 +59285,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3781 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3831 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59067,7 +59293,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3806 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3856 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59075,7 +59301,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3831 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 3881 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59086,7 +59312,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3856 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 3906 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59094,7 +59320,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 3931 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 3981 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59105,7 +59331,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 4006 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 4056 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59113,7 +59339,7 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 4081 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 4131 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
if (op->op1_type > op->op2_type) {
zend_swap_operands(op);
}
@@ -59124,12 +59350,12 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 4156 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 4206 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
} else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) {
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 4231 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 4281 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
}
break;
case ZEND_IS_SMALLER_OR_EQUAL:
@@ -59137,70 +59363,70 @@ ZEND_API void zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t op1_info, uint
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 4306 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 4356 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
} else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) {
if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) {
break;
}
- spec = 4381 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
+ spec = 4431 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH;
}
break;
case ZEND_QM_ASSIGN:
if (op1_info == MAY_BE_DOUBLE) {
- spec = 4546 | SPEC_RULE_OP1;
+ spec = 4596 | SPEC_RULE_OP1;
} else if (!(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE)))) {
- spec = 4551 | SPEC_RULE_OP1;
+ spec = 4601 | SPEC_RULE_OP1;
}
break;
case ZEND_PRE_INC:
if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) {
- spec = 4456 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
+ spec = 4506 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
} else if (op1_info == MAY_BE_LONG) {
- spec = 4466 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
+ spec = 4516 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
} else if (op1_info == (MAY_BE_LONG|MAY_BE_DOUBLE)) {
- spec = 4476 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
+ spec = 4526 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
}
break;
case ZEND_PRE_DEC:
if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) {
- spec = 4486 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
+ spec = 4536 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
} else if (op1_info == MAY_BE_LONG) {
- spec = 4496 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
+ spec = 4546 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
} else if (op1_info == (MAY_BE_LONG|MAY_BE_DOUBLE)) {
- spec = 4506 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
+ spec = 4556 | SPEC_RULE_OP1 | SPEC_RULE_RETVAL;
}
break;
case ZEND_POST_INC:
if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) {
- spec = 4516 | SPEC_RULE_OP1;
+ spec = 4566 | SPEC_RULE_OP1;
} else if (op1_info == MAY_BE_LONG) {
- spec = 4521 | SPEC_RULE_OP1;
+ spec = 4571 | SPEC_RULE_OP1;
} else if (op1_info == (MAY_BE_LONG|MAY_BE_DOUBLE)) {
- spec = 4526 | SPEC_RULE_OP1;
+ spec = 4576 | SPEC_RULE_OP1;
}
break;
case ZEND_POST_DEC:
if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) {
- spec = 4531 | SPEC_RULE_OP1;
+ spec = 4581 | SPEC_RULE_OP1;
} else if (op1_info == MAY_BE_LONG) {
- spec = 4536 | SPEC_RULE_OP1;
+ spec = 4586 | SPEC_RULE_OP1;
} else if (op1_info == (MAY_BE_LONG|MAY_BE_DOUBLE)) {
- spec = 4541 | SPEC_RULE_OP1;
+ spec = 4591 | SPEC_RULE_OP1;
}
break;
case ZEND_SEND_VAR_EX:
if ((op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) {
- spec = 4586 | SPEC_RULE_OP1 | SPEC_RULE_QUICK_ARG;
+ spec = 4636 | SPEC_RULE_OP1 | SPEC_RULE_QUICK_ARG;
}
break;
case ZEND_FETCH_DIM_R:
if (!(op2_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) {
- spec = 4556 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
+ spec = 4606 | SPEC_RULE_OP1 | SPEC_RULE_OP2;
}
break;
case ZEND_SEND_VAR:
if ((op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) {
- spec = 4581 | SPEC_RULE_OP1;
+ spec = 4631 | SPEC_RULE_OP1;
}
break;
default:
diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c
index 07b43f7f2b..315f6e4a8e 100644
--- a/Zend/zend_vm_opcodes.c
+++ b/Zend/zend_vm_opcodes.c
@@ -21,7 +21,7 @@
#include <stdio.h>
#include <zend.h>
-static const char *zend_vm_opcodes_names[187] = {
+static const char *zend_vm_opcodes_names[189] = {
"ZEND_NOP",
"ZEND_ADD",
"ZEND_SUB",
@@ -209,9 +209,11 @@ static const char *zend_vm_opcodes_names[187] = {
"ZEND_FETCH_THIS",
NULL,
"ZEND_ISSET_ISEMPTY_THIS",
+ "ZEND_SWITCH_LONG",
+ "ZEND_SWITCH_STRING",
};
-static uint32_t zend_vm_opcodes_flags[187] = {
+static uint32_t zend_vm_opcodes_flags[189] = {
0x00000000,
0x00000707,
0x00000707,
@@ -399,6 +401,8 @@ static uint32_t zend_vm_opcodes_flags[187] = {
0x00000101,
0x00000000,
0x00000101,
+ 0x03000307,
+ 0x03000307,
};
ZEND_API const char* zend_get_opcode_name(zend_uchar opcode) {
diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h
index db0fdd10ec..2a0eb93aeb 100644
--- a/Zend/zend_vm_opcodes.h
+++ b/Zend/zend_vm_opcodes.h
@@ -252,7 +252,9 @@ END_EXTERN_C()
#define ZEND_BIND_STATIC 183
#define ZEND_FETCH_THIS 184
#define ZEND_ISSET_ISEMPTY_THIS 186
+#define ZEND_SWITCH_LONG 187
+#define ZEND_SWITCH_STRING 188
-#define ZEND_VM_LAST_OPCODE 186
+#define ZEND_VM_LAST_OPCODE 188
#endif
diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c
index 7436c559c1..03e157e9c8 100644
--- a/ext/opcache/Optimizer/block_pass.c
+++ b/ext/opcache/Optimizer/block_pass.c
@@ -140,6 +140,27 @@ static void strip_nops(zend_op_array *op_array, zend_basic_block *b)
}
}
+static int get_const_switch_target(zend_cfg *cfg, zend_op_array *op_array, zend_basic_block *block, zend_op *opline, zval *val) {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ if ((opline->opcode == ZEND_SWITCH_LONG && Z_TYPE_P(val) != IS_LONG)
+ || (opline->opcode == ZEND_SWITCH_STRING && Z_TYPE_P(val) != IS_STRING)) {
+ /* fallback to next block */
+ return block->successors[block->successors_count - 1];
+ }
+ if (Z_TYPE_P(val) == IS_LONG) {
+ zv = zend_hash_index_find(jumptable, Z_LVAL_P(val));
+ } else {
+ ZEND_ASSERT(Z_TYPE_P(val) == IS_STRING);
+ zv = zend_hash_find(jumptable, Z_STR_P(val));
+ }
+ if (!zv) {
+ /* default */
+ return block->successors[block->successors_count - 2];
+ }
+ return cfg->map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))];
+}
+
static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array, zend_bitset used_ext, zend_cfg *cfg, zend_op **Tsource)
{
zend_op *opline, *src;
@@ -344,6 +365,25 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
}
break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ /* SWITCH variable will be deleted later by FREE, so we can't optimize it */
+ Tsource[VAR_NUM(opline->op1.var)] = NULL;
+ break;
+ }
+ if (opline->op1_type == IS_CONST) {
+ int target = get_const_switch_target(cfg, op_array, block, opline, &ZEND_OP1_LITERAL(opline));
+ literal_dtor(&ZEND_OP1_LITERAL(opline));
+ literal_dtor(&ZEND_OP2_LITERAL(opline));
+ opline->opcode = ZEND_JMP;
+ opline->op1_type = IS_UNUSED;
+ opline->op2_type = IS_UNUSED;
+ block->successors_count = 1;
+ block->successors[0] = target;
+ }
+ break;
+
case ZEND_CASE:
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
/* CASE variable will be deleted later by FREE, so we can't optimize it */
@@ -886,6 +926,20 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array)
case ZEND_FE_FETCH_RW:
opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[0]].start);
break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ uint32_t s = 0;
+ ZEND_ASSERT(b->successors_count == 2 + zend_hash_num_elements(jumptable));
+
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start);
+ } ZEND_HASH_FOREACH_END();
+ opline->extended_value = ZEND_OPLINE_TO_OFFSET(opline, new_opcodes + blocks[b->successors[s++]].start);
+ break;
+ }
}
}
@@ -1752,7 +1806,12 @@ static void zend_merge_blocks(zend_op_array *op_array, zend_cfg *cfg)
prev->flags |= (b->flags & ZEND_BB_EXIT);
prev->len = b->start + b->len - prev->start;
prev->successors_count = b->successors_count;
- memcpy(prev->successors, b->successors, b->successors_count * sizeof(int));
+ if (b->successors != b->successors_storage) {
+ prev->successors = b->successors;
+ b->successors = b->successors_storage;
+ } else {
+ memcpy(prev->successors, b->successors, b->successors_count * sizeof(int));
+ }
/* unlink & make block empty and unreachable */
b->flags = 0;
diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c
index 63f7fafa93..20fe1d6d21 100644
--- a/ext/opcache/Optimizer/dfa_pass.c
+++ b/ext/opcache/Optimizer/dfa_pass.c
@@ -165,37 +165,7 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa)
opline = op_array->opcodes + end - 1;
b->len = target - b->start;
new_opline = op_array->opcodes + target - 1;
- switch (new_opline->opcode) {
- case ZEND_JMP:
- case ZEND_FAST_CALL:
- ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline));
- break;
- case ZEND_JMPZNZ:
- new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
- /* break missing intentionally */
- case ZEND_JMPZ:
- case ZEND_JMPNZ:
- case ZEND_JMPZ_EX:
- case ZEND_JMPNZ_EX:
- case ZEND_FE_RESET_R:
- case ZEND_FE_RESET_RW:
- case ZEND_JMP_SET:
- case ZEND_COALESCE:
- case ZEND_ASSERT_CHECK:
- ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
- break;
- case ZEND_CATCH:
- if (!opline->result.num) {
- new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
- }
- break;
- case ZEND_DECLARE_ANON_CLASS:
- case ZEND_DECLARE_ANON_INHERITED_CLASS:
- case ZEND_FE_FETCH_R:
- case ZEND_FE_FETCH_RW:
- new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
- break;
- }
+ zend_optimizer_migrate_jump(op_array, new_opline, opline);
}
}
}
@@ -231,34 +201,7 @@ static void zend_ssa_remove_nops(zend_op_array *op_array, zend_ssa *ssa)
for (b = blocks; b < end; b++) {
if ((b->flags & ZEND_BB_REACHABLE) && b->len != 0) {
zend_op *opline = op_array->opcodes + b->start + b->len - 1;
-
- switch (opline->opcode) {
- case ZEND_JMP:
- case ZEND_FAST_CALL:
- ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]);
- break;
- case ZEND_JMPZNZ:
- 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)]);
- /* break missing intentionally */
- case ZEND_JMPZ:
- case ZEND_JMPNZ:
- case ZEND_JMPZ_EX:
- case ZEND_JMPNZ_EX:
- case ZEND_FE_RESET_R:
- case ZEND_FE_RESET_RW:
- case ZEND_JMP_SET:
- case ZEND_COALESCE:
- case ZEND_ASSERT_CHECK:
- ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
- break;
- case ZEND_DECLARE_ANON_CLASS:
- case ZEND_DECLARE_ANON_INHERITED_CLASS:
- case ZEND_FE_FETCH_R:
- case ZEND_FE_FETCH_RW:
- case ZEND_CATCH:
- 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)]);
- break;
- }
+ zend_optimizer_shift_jump(op_array, opline, shiftlist);
}
}
diff --git a/ext/opcache/Optimizer/nop_removal.c b/ext/opcache/Optimizer/nop_removal.c
index c7ff73a61b..da2d4610a3 100644
--- a/ext/opcache/Optimizer/nop_removal.c
+++ b/ext/opcache/Optimizer/nop_removal.c
@@ -66,37 +66,7 @@ void zend_optimizer_nop_removal(zend_op_array *op_array)
zend_op *new_opline = op_array->opcodes + new_count;
*new_opline = *opline;
- switch (new_opline->opcode) {
- case ZEND_JMP:
- case ZEND_FAST_CALL:
- ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline));
- break;
- case ZEND_JMPZNZ:
- new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
- /* break missing intentionally */
- case ZEND_JMPZ:
- case ZEND_JMPNZ:
- case ZEND_JMPZ_EX:
- case ZEND_JMPNZ_EX:
- case ZEND_FE_RESET_R:
- case ZEND_FE_RESET_RW:
- case ZEND_JMP_SET:
- case ZEND_COALESCE:
- case ZEND_ASSERT_CHECK:
- ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
- break;
- case ZEND_CATCH:
- if (!opline->result.num) {
- new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
- }
- break;
- case ZEND_DECLARE_ANON_CLASS:
- case ZEND_DECLARE_ANON_INHERITED_CLASS:
- case ZEND_FE_FETCH_R:
- case ZEND_FE_FETCH_RW:
- new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
- break;
- }
+ zend_optimizer_migrate_jump(op_array, new_opline, opline);
}
new_count++;
}
@@ -108,33 +78,7 @@ void zend_optimizer_nop_removal(zend_op_array *op_array)
/* update JMPs */
for (opline = op_array->opcodes; opline<end; opline++) {
- switch (opline->opcode) {
- case ZEND_JMP:
- case ZEND_FAST_CALL:
- ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]);
- break;
- case ZEND_JMPZNZ:
- 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)]);
- /* break missing intentionally */
- case ZEND_JMPZ:
- case ZEND_JMPNZ:
- case ZEND_JMPZ_EX:
- case ZEND_JMPNZ_EX:
- case ZEND_FE_RESET_R:
- case ZEND_FE_RESET_RW:
- case ZEND_JMP_SET:
- case ZEND_COALESCE:
- case ZEND_ASSERT_CHECK:
- ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
- break;
- case ZEND_DECLARE_ANON_CLASS:
- case ZEND_DECLARE_ANON_INHERITED_CLASS:
- case ZEND_FE_FETCH_R:
- case ZEND_FE_FETCH_RW:
- case ZEND_CATCH:
- 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)]);
- break;
- }
+ zend_optimizer_shift_jump(op_array, opline, shiftlist);
}
/* update brk/cont array */
diff --git a/ext/opcache/Optimizer/zend_cfg.c b/ext/opcache/Optimizer/zend_cfg.c
index c4394757b8..5524bff191 100644
--- a/ext/opcache/Optimizer/zend_cfg.c
+++ b/ext/opcache/Optimizer/zend_cfg.c
@@ -73,7 +73,12 @@ static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_bloc
succ->flags |= ZEND_BB_FOLLOW;
}
} else {
- ZEND_ASSERT(0);
+ ZEND_ASSERT(opcode == ZEND_SWITCH_LONG || opcode == ZEND_SWITCH_STRING);
+ if (i == b->successors_count) {
+ succ->flags |= ZEND_BB_FOLLOW;
+ } else {
+ succ->flags |= ZEND_BB_TARGET;
+ }
}
} else {
succ->flags |= ZEND_BB_FOLLOW;
@@ -399,6 +404,18 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
BB_START(i + 1);
break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ {
+ HashTable *jumptable = Z_ARRVAL_P(CRT_CONSTANT(opline->op2));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)));
+ } ZEND_HASH_FOREACH_END();
+ BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ BB_START(i + 1);
+ break;
+ }
case ZEND_UNSET_VAR:
case ZEND_ISSET_ISEMPTY_VAR:
if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) &&
@@ -553,6 +570,24 @@ int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t b
block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes];
block->successors[1] = j + 1;
break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ {
+ HashTable *jumptable = Z_ARRVAL_P(CRT_CONSTANT(opline->op2));
+ zval *zv;
+ uint32_t s = 0;
+
+ block->successors_count = 2 + zend_hash_num_elements(jumptable);
+ block->successors = zend_arena_calloc(arena, block->successors_count, sizeof(int));
+
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ block->successors[s++] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))];
+ } ZEND_HASH_FOREACH_END();
+
+ block->successors[s++] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
+ block->successors[s++] = j + 1;
+ break;
+ }
default:
block->successors_count = 1;
block->successors[0] = j + 1;
diff --git a/ext/opcache/Optimizer/zend_dump.c b/ext/opcache/Optimizer/zend_dump.c
index 2167fa6e6b..49d579721c 100644
--- a/ext/opcache/Optimizer/zend_dump.c
+++ b/ext/opcache/Optimizer/zend_dump.c
@@ -581,7 +581,28 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *
}
if (opline->op2_type == IS_CONST) {
- zend_dump_const(CRT_CONSTANT_EX(op_array, opline->op2, (dump_flags & ZEND_DUMP_RT_CONSTANTS)));
+ zval *op = CRT_CONSTANT_EX(op_array, opline->op2, (dump_flags & ZEND_DUMP_RT_CONSTANTS));
+ if (opline->opcode == ZEND_SWITCH_LONG || opline->opcode == ZEND_SWITCH_STRING) {
+ HashTable *jumptable = Z_ARRVAL_P(op);
+ zend_string *key;
+ zend_ulong num_key;
+ zval *zv;
+ ZEND_HASH_FOREACH_KEY_VAL(jumptable, num_key, key, zv) {
+ if (key) {
+ fprintf(stderr, " \"%s\":", ZSTR_VAL(key));
+ } else {
+ fprintf(stderr, " " ZEND_LONG_FMT ":", num_key);
+ }
+ if (b) {
+ fprintf(stderr, " BB%d,", b->successors[n++]);
+ } else {
+ fprintf(stderr, " L%u,", (uint32_t)ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)));
+ }
+ } ZEND_HASH_FOREACH_END();
+ fprintf(stderr, " default:");
+ } else {
+ zend_dump_const(op);
+ }
} else if (opline->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
if (ssa && ssa->ops) {
int ssa_var_num = ssa->ops[opline - op_array->opcodes].op2_use;
@@ -654,7 +675,6 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *
static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags)
{
zend_basic_block *b = cfg->blocks + n;
- int printed = 0;
fprintf(stderr, "BB%d:", n);
if (b->flags & ZEND_BB_START) {
@@ -717,14 +737,12 @@ static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags
fprintf(stderr, ")\n");
}
- if (b->successors[0] != -1) {
+ if (b->successors_count > 0) {
+ int s;
fprintf(stderr, " ; to=(BB%d", b->successors[0]);
- printed = 1;
- if (b->successors[1] != -1) {
- fprintf(stderr, ", BB%d", b->successors[1]);
+ for (s = 1; s < b->successors_count; s++) {
+ fprintf(stderr, ", BB%d", b->successors[s]);
}
- }
- if (printed) {
fprintf(stderr, ")\n");
}
diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c
index 1015ea498c..5fd6484a44 100644
--- a/ext/opcache/Optimizer/zend_optimizer.c
+++ b/ext/opcache/Optimizer/zend_optimizer.c
@@ -470,6 +470,8 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
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;
@@ -501,7 +503,9 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
while (m < n) {
if (ZEND_OP1_TYPE(m) == type &&
ZEND_OP1(m).var == var) {
- if (m->opcode == ZEND_CASE) {
+ if (m->opcode == ZEND_CASE
+ || m->opcode == ZEND_SWITCH_LONG
+ || m->opcode == ZEND_SWITCH_STRING) {
zval old_val;
ZVAL_COPY_VALUE(&old_val, val);
zval_copy_ctor(val);
@@ -562,6 +566,94 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array,
return 1;
}
+/* Update jump offsets after a jump was migrated to another opline */
+void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, zend_op *opline) {
+ switch (new_opline->opcode) {
+ case ZEND_JMP:
+ case ZEND_FAST_CALL:
+ ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op1, ZEND_OP1_JMP_ADDR(opline));
+ break;
+ case ZEND_JMPZNZ:
+ new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ /* break missing intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
+ break;
+ case ZEND_CATCH:
+ if (!opline->result.num) {
+ new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ }
+ break;
+ case ZEND_DECLARE_ANON_CLASS:
+ case ZEND_DECLARE_ANON_INHERITED_CLASS:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)));
+ } ZEND_HASH_FOREACH_END();
+ new_opline->extended_value = ZEND_OPLINE_NUM_TO_OFFSET(op_array, new_opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
+ break;
+ }
+ }
+}
+
+/* Shift jump offsets based on shiftlist */
+void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_t *shiftlist) {
+ switch (opline->opcode) {
+ case ZEND_JMP:
+ case ZEND_FAST_CALL:
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op1, ZEND_OP1_JMP_ADDR(opline) - shiftlist[ZEND_OP1_JMP_ADDR(opline) - op_array->opcodes]);
+ break;
+ case ZEND_JMPZNZ:
+ 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)]);
+ /* break missing intentionally */
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_ASSERT_CHECK:
+ ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
+ break;
+ case ZEND_DECLARE_ANON_CLASS:
+ case ZEND_DECLARE_ANON_INHERITED_CLASS:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ case ZEND_CATCH:
+ 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)]);
+ break;
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ {
+ HashTable *jumptable = Z_ARRVAL(ZEND_OP2_LITERAL(opline));
+ zval *zv;
+ ZEND_HASH_FOREACH_VAL(jumptable, zv) {
+ Z_LVAL_P(zv) = ZEND_OPLINE_NUM_TO_OFFSET(op_array, opline, ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)) - shiftlist[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))]);
+ } ZEND_HASH_FOREACH_END();
+ 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)]);
+ break;
+ }
+ }
+}
+
static zend_class_entry *get_class_entry_from_op1(
zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants) {
if (opline->op1_type == IS_CONST) {
diff --git a/ext/opcache/Optimizer/zend_optimizer_internal.h b/ext/opcache/Optimizer/zend_optimizer_internal.h
index 90297ad816..710181317e 100644
--- a/ext/opcache/Optimizer/zend_optimizer_internal.h
+++ b/ext/opcache/Optimizer/zend_optimizer_internal.h
@@ -109,5 +109,7 @@ int zend_optimizer_is_disabled_func(const char *name, size_t len);
zend_function *zend_optimizer_get_called_func(
zend_script *script, zend_op_array *op_array, zend_op *opline, zend_bool rt_constants);
uint32_t zend_optimizer_classify_function(zend_string *name, uint32_t num_args);
+void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, zend_op *opline);
+void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_t *shiftlist);
#endif
diff --git a/ext/opcache/tests/switch_with_coinciding_targets.phpt b/ext/opcache/tests/switch_with_coinciding_targets.phpt
new file mode 100644
index 0000000000..1dd75e76b0
--- /dev/null
+++ b/ext/opcache/tests/switch_with_coinciding_targets.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Switch where all targets, including default, coincide
+--FILE--
+<?php
+
+$foo = 42.0;
+$bar = true;
+
+switch ($foo) {
+default:
+case 0: case 1: case 2: case 3:
+ if ($bar) {
+ echo "true\n";
+ } else {
+ echo "false\n";
+ }
+}
+
+?>
+--EXPECT--
+true
diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c
index 8747ac3787..fb6827a9fd 100644
--- a/ext/opcache/zend_file_cache.c
+++ b/ext/opcache/zend_file_cache.c
@@ -431,6 +431,8 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra
case ZEND_DECLARE_ANON_INHERITED_CLASS:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
/* relative extended_value don't have to be changed */
break;
}
@@ -1030,6 +1032,8 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr
case ZEND_DECLARE_ANON_INHERITED_CLASS:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
/* relative extended_value don't have to be changed */
break;
}
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index 59f3765fea..30bbfe08d4 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -446,6 +446,8 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
case ZEND_DECLARE_ANON_INHERITED_CLASS:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
/* relative extended_value don't have to be changed */
break;
}