From 91b620d684c5a2296774432d5d0ff8f5d14397d6 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Tue, 7 Jul 2015 04:32:04 +0300 Subject: [PATCH] Replace GOTO by FREE/FE_FREE and JMP at compile time --- Zend/zend_compile.c | 161 ++++++++++++++++--------- Zend/zend_compile.h | 2 +- Zend/zend_opcode.c | 7 +- Zend/zend_vm_def.h | 9 +- Zend/zend_vm_execute.h | 9 +- ext/opcache/Optimizer/block_pass.c | 4 - ext/opcache/Optimizer/nop_removal.c | 9 -- ext/opcache/Optimizer/pass1_5.c | 1 - ext/opcache/Optimizer/zend_optimizer.c | 2 - ext/opcache/zend_file_cache.c | 2 - ext/opcache/zend_persist.c | 1 - sapi/phpdbg/phpdbg_opcode.c | 1 - 12 files changed, 109 insertions(+), 99 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5f423c22864..01abd7b202b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -879,61 +879,6 @@ static void str_dtor(zval *zv) /* {{{ */ { } /* }}} */ -void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2) /* {{{ */ -{ - zend_label *dest; - int current, distance; - zval *label; - - if (pass2) { - label = RT_CONSTANT(op_array, opline->op2); - } else { - label = CT_CONSTANT_EX(op_array, opline->op2.constant); - } - if (CG(context).labels == NULL || - (dest = zend_hash_find_ptr(CG(context).labels, Z_STR_P(label))) == NULL) { - - if (pass2) { - CG(in_compilation) = 1; - CG(active_op_array) = op_array; - CG(zend_lineno) = opline->lineno; - zend_error_noreturn(E_COMPILE_ERROR, "'goto' to undefined label '%s'", Z_STRVAL_P(label)); - } else { - /* Label is not defined. Delay to pass 2. */ - return; - } - } - - opline->op1.opline_num = dest->opline_num; - zval_dtor(label); - ZVAL_NULL(label); - - /* Check that we are not moving into loop or switch */ - current = opline->extended_value; - for (distance = 0; current != dest->brk_cont; distance++) { - if (current == -1) { - if (pass2) { - CG(in_compilation) = 1; - CG(active_op_array) = op_array; - CG(zend_lineno) = opline->lineno; - } - zend_error_noreturn(E_COMPILE_ERROR, "'goto' into loop or switch statement is disallowed"); - } - current = CG(context).brk_cont_array[current].parent; - } - - if (distance == 0) { - /* Nothing to break out of, optimize to ZEND_JMP */ - opline->opcode = ZEND_JMP; - opline->extended_value = 0; - SET_UNUSED(opline->op2); - } else { - /* Set real break distance */ - ZVAL_LONG(label, distance); - } -} -/* }}} */ - static zend_bool zend_is_call(zend_ast *ast); static int generate_free_loop_var(znode *var) /* {{{ */ @@ -3633,16 +3578,114 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ } /* }}} */ +void zend_resolve_goto_label(zend_op_array *op_array, znode *label_node, zend_op *pass2_opline) /* {{{ */ +{ + zend_label *dest; + int current, distance, free_vars; + zval *label; + znode *loop_var = NULL; + + if (pass2_opline) { + label = RT_CONSTANT(op_array, pass2_opline->op2); + } else { + label = &label_node->u.constant; + } + if (CG(context).labels == NULL || + (dest = zend_hash_find_ptr(CG(context).labels, Z_STR_P(label))) == NULL) { + + if (pass2_opline) { + CG(in_compilation) = 1; + CG(active_op_array) = op_array; + CG(zend_lineno) = pass2_opline->lineno; + zend_error_noreturn(E_COMPILE_ERROR, "'goto' to undefined label '%s'", Z_STRVAL_P(label)); + } else { + /* Label is not defined. Delay to pass 2. */ + zend_op *opline; + + current = CG(context).current_brk_cont; + while (current != -1) { + if (CG(context).brk_cont_array[current].start >= 0) { + zend_emit_op(NULL, ZEND_NOP, NULL, label_node); + } + current = CG(context).brk_cont_array[current].parent; + } + opline = zend_emit_op(NULL, ZEND_GOTO, NULL, label_node); + opline->extended_value = CG(context).current_brk_cont; + return; + } + } + + zval_dtor(label); + ZVAL_NULL(label); + + /* Check that we are not moving into loop or switch */ + if (pass2_opline) { + current = pass2_opline->extended_value; + } else { + current = CG(context).current_brk_cont; + } + if (!pass2_opline) { + loop_var = zend_stack_top(&CG(loop_var_stack)); + } + for (distance = 0, free_vars = 0; current != dest->brk_cont; distance++) { + if (current == -1) { + if (pass2_opline) { + CG(in_compilation) = 1; + CG(active_op_array) = op_array; + CG(zend_lineno) = pass2_opline->lineno; + } + zend_error_noreturn(E_COMPILE_ERROR, "'goto' into loop or switch statement is disallowed"); + } + if (CG(context).brk_cont_array[current].start >= 0) { + if (pass2_opline) { + free_vars++; + } else { + generate_free_loop_var(loop_var); + loop_var--; + } + } + current = CG(context).brk_cont_array[current].parent; + } + + if (pass2_opline) { + if (free_vars) { + current = pass2_opline->extended_value; + while (current != dest->brk_cont) { + if (CG(context).brk_cont_array[current].start >= 0) { + zend_op *brk_opline = &op_array->opcodes[CG(context).brk_cont_array[current].brk]; + + if (brk_opline->opcode == ZEND_FREE) { + (pass2_opline - free_vars)->opcode = ZEND_FREE; + (pass2_opline - free_vars)->op1_type = brk_opline->op1_type; + (pass2_opline - free_vars)->op1.var = brk_opline->op1.var; + } else if (brk_opline->opcode == ZEND_FE_FREE) { + (pass2_opline - free_vars)->opcode = ZEND_FE_FREE; + (pass2_opline - free_vars)->op1_type = brk_opline->op1_type; + (pass2_opline - free_vars)->op1.var = brk_opline->op1.var; + } + free_vars--; + } + current = CG(context).brk_cont_array[current].parent; + } + } + pass2_opline->opcode = ZEND_JMP; + pass2_opline->op1.opline_num = dest->opline_num; + SET_UNUSED(pass2_opline->op2); + pass2_opline->extended_value = 0; + } else { + zend_op *opline = zend_emit_op(NULL, ZEND_JMP, NULL, NULL); + opline->op1.opline_num = dest->opline_num; + } +} +/* }}} */ + void zend_compile_goto(zend_ast *ast) /* {{{ */ { zend_ast *label_ast = ast->child[0]; znode label_node; - zend_op *opline; zend_compile_expr(&label_node, label_ast); - opline = zend_emit_op(NULL, ZEND_GOTO, NULL, &label_node); - opline->extended_value = CG(context).current_brk_cont; - zend_resolve_goto_label(CG(active_op_array), opline, 0); + zend_resolve_goto_label(CG(active_op_array), &label_node, NULL); } /* }}} */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index bff37d4c67e..3ab3299a015 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -717,7 +717,7 @@ void zend_do_extended_fcall_end(void); void zend_verify_namespace(void); -void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2); +void zend_resolve_goto_label(zend_op_array *op_array, znode *label_node, zend_op *pass2_opline); ZEND_API void function_add_ref(zend_function *function); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 56de9ccc0f5..f512995dcea 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -699,11 +699,8 @@ static void zend_resolve_finally_calls(zend_op_array *op_array) break; case ZEND_GOTO: if (Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) != IS_LONG) { - uint32_t num = opline->op2.constant; - ZEND_PASS_TWO_UPDATE_CONSTANT(op_array, opline->op2); - zend_resolve_goto_label(op_array, opline, 1); - opline->op2.constant = num; + zend_resolve_goto_label(op_array, NULL, opline); } /* break omitted intentionally */ case ZEND_JMP: @@ -789,7 +786,7 @@ ZEND_API int pass_two(zend_op_array *op_array) break; case ZEND_GOTO: if (Z_TYPE_P(RT_CONSTANT(op_array, opline->op2)) != IS_LONG) { - zend_resolve_goto_label(op_array, opline, 1); + zend_resolve_goto_label(op_array, NULL, opline); } /* break omitted intentionally */ case ZEND_JMP: diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0dafc6bd4d7..64dcb7a7515 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4863,13 +4863,8 @@ ZEND_VM_HANDLER(52, ZEND_BOOL, CONST|TMPVAR|CV, ANY) ZEND_VM_HANDLER(100, ZEND_GOTO, ANY, CONST) { - USE_OPLINE - zend_op *target = OP_JMP_ADDR(opline, opline->op1); - - SAVE_OPLINE(); - - i_cleanup_unfinished_execution(execute_data, opline - EX(func)->op_array.opcodes, target - EX(func)->op_array.opcodes); - ZEND_VM_JMP(target); + zend_error_noreturn(E_ERROR, "GOTO must be resolved at compile-time."); + ZEND_VM_NEXT_OPCODE(); /* Never reached */ } ZEND_VM_HANDLER(48, ZEND_CASE, CONST|TMPVAR|CV, CONST|TMPVAR|CV) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d53dc4beac4..a726881c853 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2227,13 +2227,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_INIT_SPEC_CONST_HANDLER(Z static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GOTO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { - USE_OPLINE - zend_op *target = OP_JMP_ADDR(opline, opline->op1); - - SAVE_OPLINE(); - - i_cleanup_unfinished_execution(execute_data, opline - EX(func)->op_array.opcodes, target - EX(func)->op_array.opcodes); - ZEND_VM_JMP(target); + zend_error_noreturn(E_ERROR, "GOTO must be resolved at compile-time."); + ZEND_VM_NEXT_OPCODE(); /* Never reached */ } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_INTERFACE_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index 95320508905..dfe0e4baef9 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -123,10 +123,6 @@ static int find_code_blocks(zend_op_array *op_array, zend_cfg *cfg, zend_optimiz blocks[0].start_opline_no = 0; while (opline < end) { switch((unsigned)opline->opcode) { - case ZEND_GOTO: - /* would not optimize GOTOs - we cannot really know where it jumps, - * so these optimizations are too dangerous */ - return 0; case ZEND_FAST_CALL: START_BLOCK_OP(ZEND_OP1(opline).opline_num); if (opline->extended_value) { diff --git a/ext/opcache/Optimizer/nop_removal.c b/ext/opcache/Optimizer/nop_removal.c index 0af6c687d8a..9983ff4f601 100644 --- a/ext/opcache/Optimizer/nop_removal.c +++ b/ext/opcache/Optimizer/nop_removal.c @@ -44,14 +44,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) end = op_array->opcodes + op_array->last; for (opline = op_array->opcodes; opline < end; opline++) { - /* GOTO target is unresolved yet. We can't optimize. */ - if (opline->opcode == ZEND_GOTO && - Z_TYPE(ZEND_OP2_LITERAL(opline)) != IS_LONG) { - /* TODO: in general we can avoid this restriction */ - FREE_ALLOCA(shiftlist); - return; - } - /* Kill JMP-over-NOP-s */ if (opline->opcode == ZEND_JMP && ZEND_OP1(opline).opline_num > i) { /* check if there are only NOPs under the branch */ @@ -85,7 +77,6 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) for (opline = op_array->opcodes; oplineopcode) { case ZEND_JMP: - case ZEND_GOTO: case ZEND_FAST_CALL: case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: diff --git a/ext/opcache/Optimizer/pass1_5.c b/ext/opcache/Optimizer/pass1_5.c index 6fcdc3e47a6..766eb2c2d4b 100644 --- a/ext/opcache/Optimizer/pass1_5.c +++ b/ext/opcache/Optimizer/pass1_5.c @@ -624,7 +624,6 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx) case ZEND_EXIT: case ZEND_THROW: case ZEND_CATCH: - case ZEND_GOTO: case ZEND_FAST_CALL: case ZEND_FAST_RET: case ZEND_JMP: diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index 5a2325191fe..86b08371371 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -488,7 +488,6 @@ static void zend_accel_optimize(zend_op_array *op_array, } switch (opline->opcode) { case ZEND_JMP: - case ZEND_GOTO: case ZEND_FAST_CALL: case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: @@ -533,7 +532,6 @@ static void zend_accel_optimize(zend_op_array *op_array, } switch (opline->opcode) { case ZEND_JMP: - case ZEND_GOTO: case ZEND_FAST_CALL: case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 67dfd32f4f1..54e2ab639ad 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -386,7 +386,6 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra # if ZEND_USE_ABS_JMP_ADDR switch (opline->opcode) { case ZEND_JMP: - case ZEND_GOTO: case ZEND_FAST_CALL: case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: @@ -913,7 +912,6 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr # if ZEND_USE_ABS_JMP_ADDR switch (opline->opcode) { case ZEND_JMP: - case ZEND_GOTO: case ZEND_FAST_CALL: case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 4a9a9a773ca..0f789090cdc 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -501,7 +501,6 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc /* fix jumps to point to new array */ switch (opline->opcode) { case ZEND_JMP: - case ZEND_GOTO: case ZEND_FAST_CALL: case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: diff --git a/sapi/phpdbg/phpdbg_opcode.c b/sapi/phpdbg/phpdbg_opcode.c index a172248cc8a..b95b9dd974b 100644 --- a/sapi/phpdbg/phpdbg_opcode.c +++ b/sapi/phpdbg/phpdbg_opcode.c @@ -57,7 +57,6 @@ char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op) /*{{{ */ /* OP1 */ switch (op->opcode) { case ZEND_JMP: - case ZEND_GOTO: case ZEND_FAST_CALL: asprintf(&decode[1], "J%ld", OP_JMP_ADDR(op, op->op1) - ops->opcodes); break;