From: Nikita Popov Date: Fri, 17 Mar 2017 22:45:05 +0000 (+0100) Subject: Implement jumptable optimization X-Git-Tag: php-7.2.0alpha1~140 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ad8652818a54790e01fcf181791aa780ac6922c9;p=php Implement jumptable optimization --- 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-- + +--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 #include -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; oplineopcode) { - 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-- + +--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; }