Resolve GOTO at compile time and replace it with sequnce of FREE/FE_FREE and JMP.

This commit is contained in:
Dmitry Stogov 2015-07-10 04:44:21 +03:00
parent 44f7348caa
commit ef1b588f6a
14 changed files with 125 additions and 143 deletions

View file

@ -32,6 +32,7 @@
#include "zend_multibyte.h"
#include "zend_language_scanner.h"
#include "zend_inheritance.h"
#include "zend_vm.h"
#define SET_NODE(target, src) do { \
target ## _type = (src)->op_type; \
@ -874,61 +875,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 = op_array->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) /* {{{ */
@ -3652,16 +3598,125 @@ 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 (op_array->brk_cont_array[current].start >= 0) {
zend_emit_op(NULL, ZEND_NOP, NULL, NULL);
}
current = op_array->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 (op_array->brk_cont_array[current].start >= 0) {
if (pass2_opline) {
free_vars++;
} else {
generate_free_loop_var(loop_var);
loop_var--;
}
}
current = op_array->brk_cont_array[current].parent;
}
if (pass2_opline) {
if (free_vars) {
current = pass2_opline->extended_value;
while (current != dest->brk_cont) {
if (op_array->brk_cont_array[current].start >= 0) {
zend_op *brk_opline = &op_array->opcodes[op_array->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;
if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
(pass2_opline - free_vars)->op1.var = brk_opline->op1.var;
} else {
(pass2_opline - free_vars)->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + brk_opline->op1.var);
ZEND_VM_SET_OPCODE_HANDLER(pass2_opline - free_vars);
}
free_vars--;
} 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;
if (op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) {
(pass2_opline - free_vars)->op1.var = brk_opline->op1.var;
} else {
(pass2_opline - free_vars)->op1.var = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + brk_opline->op1.var);
ZEND_VM_SET_OPCODE_HANDLER(pass2_opline - free_vars);
}
free_vars--;
}
}
current = op_array->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);
}
/* }}} */

View file

@ -712,7 +712,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);
@ -953,7 +953,8 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf,
#define ZEND_ARRAY_SIZE_SHIFT 2
/* Pseudo-opcodes that are used only temporarily during compilation */
#define ZEND_BRK 254
#define ZEND_GOTO 253
#define ZEND_BRK 254
#define ZEND_CONT 255

View file

@ -700,11 +700,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:
@ -787,7 +784,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:

View file

@ -4792,31 +4792,6 @@ ZEND_VM_HANDLER(52, ZEND_BOOL, CONST|TMPVAR|CV, ANY)
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HANDLER(100, ZEND_GOTO, ANY, CONST)
{
USE_OPLINE
zend_brk_cont_element *el;
SAVE_OPLINE();
el = zend_brk_cont(Z_LVAL_P(EX_CONSTANT(opline->op2)), opline->extended_value,
&EX(func)->op_array, execute_data);
if (el->start >= 0) {
zend_op *brk_opline = EX(func)->op_array.opcodes + el->brk;
if (brk_opline->opcode == ZEND_FREE) {
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
} else if (brk_opline->opcode == ZEND_FE_FREE) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
}
}
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op1));
}
ZEND_VM_HANDLER(48, ZEND_CASE, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
USE_OPLINE

View file

@ -2231,31 +2231,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_INIT_SPEC_CONST_HANDLER(Z
ZEND_VM_NEXT_OPCODE();
}
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GOTO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_brk_cont_element *el;
SAVE_OPLINE();
el = zend_brk_cont(Z_LVAL_P(EX_CONSTANT(opline->op2)), opline->extended_value,
&EX(func)->op_array, execute_data);
if (el->start >= 0) {
zend_op *brk_opline = EX(func)->op_array.opcodes + el->brk;
if (brk_opline->opcode == ZEND_FREE) {
zval_ptr_dtor_nogc(EX_VAR(brk_opline->op1.var));
} else if (brk_opline->opcode == ZEND_FE_FREE) {
zval *var = EX_VAR(brk_opline->op1.var);
if (Z_TYPE_P(var) != IS_ARRAY && Z_FE_ITER_P(var) != (uint32_t)-1) {
zend_hash_iterator_del(Z_FE_ITER_P(var));
}
zval_ptr_dtor_nogc(var);
}
}
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op1));
}
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_INTERFACE_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@ -47704,27 +47679,27 @@ void zend_init_opcodes_handlers(void)
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_CONST_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_GOTO_SPEC_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,

View file

@ -122,7 +122,7 @@ const char *zend_vm_opcodes_map[173] = {
"ZEND_FETCH_OBJ_UNSET",
"ZEND_FETCH_LIST",
"ZEND_FETCH_CONSTANT",
"ZEND_GOTO",
NULL,
"ZEND_EXT_STMT",
"ZEND_EXT_FCALL_BEGIN",
"ZEND_EXT_FCALL_END",

View file

@ -130,7 +130,6 @@ END_EXTERN_C()
#define ZEND_FETCH_OBJ_UNSET 97
#define ZEND_FETCH_LIST 98
#define ZEND_FETCH_CONSTANT 99
#define ZEND_GOTO 100
#define ZEND_EXT_STMT 101
#define ZEND_EXT_FCALL_BEGIN 102
#define ZEND_EXT_FCALL_END 103

View file

@ -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) {

View file

@ -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; opline<end; opline++) {
switch (opline->opcode) {
case ZEND_JMP:
case ZEND_GOTO:
case ZEND_FAST_CALL:
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_ANON_INHERITED_CLASS:

View file

@ -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:

View file

@ -494,7 +494,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:
@ -539,7 +538,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:

View file

@ -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:

View file

@ -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:

View file

@ -56,7 +56,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;