]> granicus.if.org Git - php/commitdiff
Implement jumptable optimization
authorNikita Popov <nikita.ppv@gmail.com>
Fri, 17 Mar 2017 22:45:05 +0000 (23:45 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Mon, 10 Apr 2017 20:23:14 +0000 (22:23 +0200)
17 files changed:
Zend/tests/switch_on_numeric_strings.phpt [new file with mode: 0644]
Zend/zend_compile.c
Zend/zend_opcode.c
Zend/zend_vm_def.h
Zend/zend_vm_execute.h
Zend/zend_vm_opcodes.c
Zend/zend_vm_opcodes.h
ext/opcache/Optimizer/block_pass.c
ext/opcache/Optimizer/dfa_pass.c
ext/opcache/Optimizer/nop_removal.c
ext/opcache/Optimizer/zend_cfg.c
ext/opcache/Optimizer/zend_dump.c
ext/opcache/Optimizer/zend_optimizer.c
ext/opcache/Optimizer/zend_optimizer_internal.h
ext/opcache/tests/switch_with_coinciding_targets.phpt [new file with mode: 0644]
ext/opcache/zend_file_cache.c
ext/opcache/zend_persist.c

diff --git a/Zend/tests/switch_on_numeric_strings.phpt b/Zend/tests/switch_on_numeric_strings.phpt
new file mode 100644 (file)
index 0000000..b436cef
--- /dev/null
@@ -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"
index ab6e23d7dfd442bbd777836c09e92c39bb2d77aa..e760676e6f6fee2728cb1efae687dc57fb340613 100644 (file)
@@ -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);
index 7c797033383705b81bb80afcade19ed56c6b8a1d..e8764e964a4cde9b461c06c1967d80a46bfd5224 100644 (file)
@@ -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);
index d80f1e6b56ec83ad7e60f6b4f4f83754116d847d..d486d1514ee5de75a139a27aab86f9d80a0fad45 100644 (file)
@@ -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
index bc369ae9b0778f58db2b0c9f5e66e3cda7752003..7136aa8790851e3c393b1b07056989072e6f743e 100644 (file)
@@ -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:
index 07b43f7f2b5c525e3a87c99af179d2d37480e68d..315f6e4a8ea66b0673256f37e6e3075e39bc1251 100644 (file)
@@ -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) {
index db0fdd10ec1b6c484d7cdb5dbbae2278cd0015cf..2a0eb93aeb7c0b5f22fb23f0b8d5696a6449873f 100644 (file)
@@ -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
index 7436c559c1cfbf3459c60b53be355e3f315d4fde..03e157e9c86aff32dbfbfb8d444f8d3348ac71ed 100644 (file)
@@ -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;
index 63f7fafa93df4312748478fd5f02e1bbc01dc462..20fe1d6d21dac760b8ed2604a2d65513a66e0bc7 100644 (file)
@@ -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);
                        }
                }
 
index c7ff73a61ba947af2bc33bcecaac86c737b2c876..da2d4610a3ee918df13c7324de3ae8a625277e48 100644 (file)
@@ -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 */
index c4394757b86f766be45ae49769f9ca52f0f28797..5524bff19156b804183604c941f98c45e5a116bc 100644 (file)
@@ -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;
index 2167fa6e6b316cfc2f5a0f4179827fe9af886bf4..49d579721cc7b84dcdecf306bf07c1c88a30c916 100644 (file)
@@ -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");
        }
 
index 1015ea498ca4258ad2af68e7e519d871f73d18c8..5fd6484a44545c8237d8b0bf4673ba63dcd21668 100644 (file)
@@ -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) {
index 90297ad816aba07ca2e5376792f6e0d67a88d7a0..710181317eaa636bd1f23a50030e78a4ff593eee 100644 (file)
@@ -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 (file)
index 0000000..1dd75e7
--- /dev/null
@@ -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
index 8747ac3787c53a91e967b9b400542f4a01897111..fb6827a9fd7074694aa3431c8e9e9147354c6e3f 100644 (file)
@@ -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;
                        }
index 59f3765fea5e9de659908f26c7d0079e095abcef..30bbfe08d427cb7d709bfa5ea80c8889414f2f44 100644 (file)
@@ -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;
                                }