From 549e8c495947d92083bab8101cef777dc4d1cb6f Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Fri, 10 Jul 2015 03:31:52 +0300 Subject: [PATCH] Squashed commit of the following: commit 03cf871f1576f08b2348c141b209894a7bf17a86 Author: Dmitry Stogov Date: Fri Jul 10 02:45:31 2015 +0300 Revert "Fixed bug #62210 (Exceptions can leak temporary variables. As a part of the fix serious refactoring was done. op_array->brk_cont_array was removed, and replaced with more general and speed efficient op_array->T_liveliness. ZEND_GOTO opcode is always replaced by ZEND_JMP at compile time). (Bob, Dmitry, Laruence)" This reverts commit 5ee841325901a4b040cfea56292a24702fe224d9. commit 285a68227ce3d380e821a24fa389aa5239bd3fe1 Author: Dmitry Stogov Date: Fri Jul 10 02:45:26 2015 +0300 Revert "Tuned off dubugging of live ranges" This reverts commit 404dc93d35f7061fc4b1b41ad6cb0721b9b52bcc. commit 93d9d11157301ee2ec99afb6f5744b126d17f637 Author: Dmitry Stogov Date: Fri Jul 10 02:45:17 2015 +0300 Revert "Remove loop_var_stack" This reverts commit b3a4c05071c3786e27e1326fa1b4d5acad62fccd. commit ede68ebbc284aec79e3f719f2c8dbf9da6907752 Author: Dmitry Stogov Date: Fri Jul 10 02:45:12 2015 +0300 Revert "ZEND_SEPARATE reuses temporaries" This reverts commit 1852f538b9f8d5e7d67fe5a4f6080396d8b10034. commit 96d8995dc1f517fb01b481736273767509f76c47 Author: Dmitry Stogov Date: Fri Jul 10 02:45:10 2015 +0300 Revert "Add assertion in liveliness computation" This reverts commit ed14019e8c0c852480eebc6fc552d8c3d939dce1. commit 0649d7bfef152e6cc8e67b922534e9946c634d9c Author: Dmitry Stogov Date: Fri Jul 10 02:45:07 2015 +0300 Revert "Fixed invalid live-range detection" This reverts commit 54f367ee2a2e4cb7c952b17915c226fdc56038ab. commit dfe8f3851f6b04595eb089323e3492115a59363e Author: Dmitry Stogov Date: Fri Jul 10 02:45:04 2015 +0300 Revert "Add test guaranteeing that loop vars are only freed after potential return type exceptions" This reverts commit f5db5a558d550bf441373febebbb02f3884209d1. commit 52a94aad6f48a199358cc07f7e4f56bb73050504 Author: Dmitry Stogov Date: Fri Jul 10 02:45:01 2015 +0300 Revert "Fixed exception habdling on "return" statement." This reverts commit 17c5315bdf8f8087979aeb55f6d3a512ba197cf5. commit 6e90ad7331901711e89c2ceb2bcab5023e5cee60 Author: Dmitry Stogov Date: Fri Jul 10 02:44:58 2015 +0300 Revert "Fix too early terminated temporary range with break/cont/goto" This reverts commit cc876c04b420589cb1f62b650d0c0e24975dd4af. commit 7b766e44b1970e4031f75109c302c07ead2c05cb Author: Dmitry Stogov Date: Fri Jul 10 02:44:55 2015 +0300 Revert "Fixed exception catching on break/continue" This reverts commit 8c3f701eebfa92d761bb368cfa8c2d1ccf821b9d. --- NEWS | 5 - Zend/tests/jump15.phpt | 29 -- Zend/tests/return_types/029.phpt | 22 -- Zend/tests/return_types/030.phpt | 23 -- Zend/tests/return_types/031.phpt | 43 --- Zend/tests/temporary_cleaning_001.phpt | 23 -- Zend/tests/temporary_cleaning_002.phpt | 32 --- Zend/tests/temporary_cleaning_003.phpt | 19 -- Zend/tests/temporary_cleaning_004.phpt | 44 --- Zend/tests/temporary_cleaning_005.phpt | 48 ---- Zend/tests/temporary_cleaning_006.phpt | 18 -- Zend/tests/temporary_cleaning_007.phpt | 22 -- Zend/tests/temporary_cleaning_008.phpt | 42 --- Zend/tests/temporary_cleaning_009.phpt | 29 -- Zend/zend.c | 3 + Zend/zend_compile.c | 260 +++++++++--------- Zend/zend_compile.h | 67 ++--- Zend/zend_execute.c | 131 +++++---- Zend/zend_globals.h | 2 + Zend/zend_opcode.c | 250 +---------------- Zend/zend_vm_def.h | 44 +-- Zend/zend_vm_execute.h | 66 +++-- Zend/zend_vm_opcodes.c | 2 +- Zend/zend_vm_opcodes.h | 1 + ext/opcache/Optimizer/block_pass.c | 83 ++++++ ext/opcache/Optimizer/nop_removal.c | 16 ++ ext/opcache/Optimizer/pass1_5.c | 1 + ext/opcache/Optimizer/zend_optimizer.c | 40 +-- .../Optimizer/zend_optimizer_internal.h | 3 + ext/opcache/zend_file_cache.c | 6 +- ext/opcache/zend_persist.c | 9 +- ext/opcache/zend_persist_calc.c | 8 +- sapi/phpdbg/phpdbg_opcode.c | 40 +-- 33 files changed, 445 insertions(+), 986 deletions(-) delete mode 100644 Zend/tests/jump15.phpt delete mode 100644 Zend/tests/return_types/029.phpt delete mode 100644 Zend/tests/return_types/030.phpt delete mode 100644 Zend/tests/return_types/031.phpt delete mode 100644 Zend/tests/temporary_cleaning_001.phpt delete mode 100644 Zend/tests/temporary_cleaning_002.phpt delete mode 100644 Zend/tests/temporary_cleaning_003.phpt delete mode 100644 Zend/tests/temporary_cleaning_004.phpt delete mode 100644 Zend/tests/temporary_cleaning_005.phpt delete mode 100644 Zend/tests/temporary_cleaning_006.phpt delete mode 100644 Zend/tests/temporary_cleaning_007.phpt delete mode 100644 Zend/tests/temporary_cleaning_008.phpt delete mode 100644 Zend/tests/temporary_cleaning_009.phpt diff --git a/NEWS b/NEWS index 829fd3ca20..547ed4667e 100644 --- a/NEWS +++ b/NEWS @@ -27,11 +27,6 @@ ArrayAccess object). (Laruence) . Fixed bug #69957 (Different ways of handling div/mod/intdiv). (Bob) . Fixed bug #69900 (Too long timeout on pipes). (Anatol) - . Fixed bug #62210 (Exceptions can leak temporary variables. As a part of - the fix serious refactoring was done. op_array->brk_cont_array was removed, - and replaced with more general and speed efficient op_array->T_liveliness. - ZEND_GOTO opcode is always replaced by ZEND_JMP at compile time). - (Bob, Dmitry, Laruence) - CLI server: . Fixed bug #69655 (php -S changes MKCALENDAR request method to MKCOL). (cmb) diff --git a/Zend/tests/jump15.phpt b/Zend/tests/jump15.phpt deleted file mode 100644 index 456d27785d..0000000000 --- a/Zend/tests/jump15.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -jump 15: goto from loop (forward) ---FILE-- - ---EXPECT-- -1: ok -2: ok -3: ok diff --git a/Zend/tests/return_types/029.phpt b/Zend/tests/return_types/029.phpt deleted file mode 100644 index c854456a6b..0000000000 --- a/Zend/tests/return_types/029.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -Return types must not double free loop variables ---FILE-- - ---EXPECT-- -no leak or segfault diff --git a/Zend/tests/return_types/030.phpt b/Zend/tests/return_types/030.phpt deleted file mode 100644 index 73b512ed14..0000000000 --- a/Zend/tests/return_types/030.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -Return types must not double free loop variables ---FILE-- - ---EXPECT-- -OK diff --git a/Zend/tests/return_types/031.phpt b/Zend/tests/return_types/031.phpt deleted file mode 100644 index 7240274804..0000000000 --- a/Zend/tests/return_types/031.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -Excepton on return types mismutch shouldn't execute "catch" code but should execute "finally" code. ---FILE-- - ---EXPECT-- -finally1 -OK -finally2 -OK diff --git a/Zend/tests/temporary_cleaning_001.phpt b/Zend/tests/temporary_cleaning_001.phpt deleted file mode 100644 index f2ccbb35b8..0000000000 --- a/Zend/tests/temporary_cleaning_001.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -Temporary leak on exception ---FILE-- - -==DONE== ---EXPECT-- -==DONE== diff --git a/Zend/tests/temporary_cleaning_002.phpt b/Zend/tests/temporary_cleaning_002.phpt deleted file mode 100644 index bea54e7f77..0000000000 --- a/Zend/tests/temporary_cleaning_002.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Temporary leak on rope (encapsed string) ---FILE-- -x|"; -} catch (Exception $e) { -} - -try { - $r = "$x->x|$y|"; -} catch (Exception $e) { -} - -try { - $r = "$y|$y|$x->x"; -} catch (Exception $e) { -} - -?> -==DONE== ---EXPECT-- -==DONE== diff --git a/Zend/tests/temporary_cleaning_003.phpt b/Zend/tests/temporary_cleaning_003.phpt deleted file mode 100644 index acff8c85f0..0000000000 --- a/Zend/tests/temporary_cleaning_003.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -Fundamental memory leak test on temporaries ---FILE-- - -==DONE== ---EXPECT-- -==DONE== diff --git a/Zend/tests/temporary_cleaning_004.phpt b/Zend/tests/temporary_cleaning_004.phpt deleted file mode 100644 index b8a02516b0..0000000000 --- a/Zend/tests/temporary_cleaning_004.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -Temporary leak with switch ---FILE-- - -==DONE== ---EXPECT-- -==DONE== diff --git a/Zend/tests/temporary_cleaning_005.phpt b/Zend/tests/temporary_cleaning_005.phpt deleted file mode 100644 index e8c7febe0b..0000000000 --- a/Zend/tests/temporary_cleaning_005.phpt +++ /dev/null @@ -1,48 +0,0 @@ ---TEST-- -Temporary leak with foreach ---FILE-- - -==DONE== ---EXPECT-- -==DONE== diff --git a/Zend/tests/temporary_cleaning_006.phpt b/Zend/tests/temporary_cleaning_006.phpt deleted file mode 100644 index 758260d55a..0000000000 --- a/Zend/tests/temporary_cleaning_006.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -Exception after separation during indirect write to fcall result ---FILE-- - ---EXPECT-- -Exception diff --git a/Zend/tests/temporary_cleaning_007.phpt b/Zend/tests/temporary_cleaning_007.phpt deleted file mode 100644 index 0e29ed6c33..0000000000 --- a/Zend/tests/temporary_cleaning_007.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -Exception inside a foreach loop with return ---FILE-- -isConsistent()) { - return $controller; - } - } catch (\Exception $e) { - echo "Exception\n"; - } -} -?> ---EXPECT-- -Exception -Exception diff --git a/Zend/tests/temporary_cleaning_008.phpt b/Zend/tests/temporary_cleaning_008.phpt deleted file mode 100644 index cbbbc6cd6c..0000000000 --- a/Zend/tests/temporary_cleaning_008.phpt +++ /dev/null @@ -1,42 +0,0 @@ ---TEST-- -Exception inside a foreach loop with on an object with destructor ---FILE-- -getMessage() . "\n"; - } catch (Error $e) { - echo "Error1: " . $e->getMessage() . "\n"; - } - } - echo "bag!\n"; -} -try { - foo(); -} catch (Throwable $e) { - echo (($e instanceof Exception) ? "Exception2: " : "Error2: ") . - $e->getMessage() . "\n"; - $e = $e->getPrevious(); - while ($e instanceof Throwable) { - echo "\tPrev " . (($e instanceof Exception) ? "Exception2: " : "Error2: ") . - $e->getMessage() . "\n"; - $e = $e->getPrevious(); - } -} -echo "ok\n"; -?> ---EXPECTF-- -Exception2: test - Prev Error2: Return value of foo() must be of the type string, none returned in %stemporary_cleaning_008.php on line %d -ok diff --git a/Zend/tests/temporary_cleaning_009.phpt b/Zend/tests/temporary_cleaning_009.phpt deleted file mode 100644 index c6f747edb5..0000000000 --- a/Zend/tests/temporary_cleaning_009.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Exception inside a foreach loop with on an object with destructor ---FILE-- - ---EXPECT-- -Exception2 diff --git a/Zend/zend.c b/Zend/zend.c index 200c6a9aa2..168658655c 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1045,6 +1045,7 @@ static void zend_error_va_list(int type, const char *format, va_list args) zval orig_user_error_handler; zend_bool in_compilation; zend_class_entry *saved_class_entry; + zend_stack loop_var_stack; zend_stack delayed_oplines_stack; zend_array *symbol_table; @@ -1211,6 +1212,7 @@ static void zend_error_va_list(int type, const char *format, va_list args) if (in_compilation) { saved_class_entry = CG(active_class_entry); CG(active_class_entry) = NULL; + SAVE_STACK(loop_var_stack); SAVE_STACK(delayed_oplines_stack); CG(in_compilation) = 0; } @@ -1229,6 +1231,7 @@ static void zend_error_va_list(int type, const char *format, va_list args) if (in_compilation) { CG(active_class_entry) = saved_class_entry; + RESTORE_STACK(loop_var_stack); RESTORE_STACK(delayed_oplines_stack); CG(in_compilation) = 1; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d4b2767940..0927f0cd23 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -32,7 +32,6 @@ #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; \ @@ -211,21 +210,16 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context) /* {{{ */ CG(context).opcodes_size = INITIAL_OP_ARRAY_SIZE; CG(context).vars_size = 0; CG(context).literals_size = 0; + CG(context).current_brk_cont = -1; CG(context).backpatch_count = 0; CG(context).in_finally = 0; CG(context).fast_call_var = -1; - CG(context).current_brk_cont = -1; - CG(context).last_brk_cont = 0; - CG(context).brk_cont_array = NULL; CG(context).labels = NULL; } /* }}} */ void zend_oparray_context_end(zend_oparray_context *prev_context) /* {{{ */ { - if (CG(context).brk_cont_array) { - efree(CG(context).brk_cont_array); - } if (CG(context).labels) { zend_hash_destroy(CG(context).labels); FREE_HASHTABLE(CG(context).labels); @@ -289,6 +283,7 @@ void zend_file_context_end(zend_file_context *prev_context) /* {{{ */ void zend_init_compiler_data_structures(void) /* {{{ */ { + zend_stack_init(&CG(loop_var_stack), sizeof(znode)); zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op)); CG(active_class_entry) = NULL; CG(in_compilation) = 0; @@ -321,6 +316,7 @@ void init_compiler(void) /* {{{ */ void shutdown_compiler(void) /* {{{ */ { + zend_stack_destroy(&CG(loop_var_stack)); zend_stack_destroy(&CG(delayed_oplines_stack)); zend_hash_destroy(&CG(filenames_table)); zend_hash_destroy(&CG(const_filenames)); @@ -566,13 +562,17 @@ static inline void zend_begin_loop(const znode *loop_var) /* {{{ */ zend_brk_cont_element *brk_cont_element; int parent = CG(context).current_brk_cont; - CG(context).current_brk_cont = CG(context).last_brk_cont; + CG(context).current_brk_cont = CG(active_op_array)->last_brk_cont; brk_cont_element = get_next_brk_cont_element(CG(active_op_array)); brk_cont_element->parent = parent; - if (loop_var && (loop_var->op_type & (IS_TMP_VAR|IS_VAR))) { - brk_cont_element->loop_var = *loop_var; + + if (loop_var) { + zend_stack_push(&CG(loop_var_stack), loop_var); + brk_cont_element->start = get_next_op_number(CG(active_op_array)); } else { - brk_cont_element->loop_var.op_type = IS_UNUSED; + /* The start field is used to free temporary variables in case of exceptions. + * We won't try to free something of we don't have loop variable. */ + brk_cont_element->start = -1; } } /* }}} */ @@ -580,10 +580,14 @@ static inline void zend_begin_loop(const znode *loop_var) /* {{{ */ static inline void zend_end_loop(int cont_addr) /* {{{ */ { zend_brk_cont_element *brk_cont_element - = &CG(context).brk_cont_array[CG(context).current_brk_cont]; + = &CG(active_op_array)->brk_cont_array[CG(context).current_brk_cont]; brk_cont_element->cont = cont_addr; brk_cont_element->brk = get_next_op_number(CG(active_op_array)); CG(context).current_brk_cont = brk_cont_element->parent; + + if (brk_cont_element->start >= 0) { + zend_stack_del_top(&CG(loop_var_stack)); + } } /* }}} */ @@ -870,25 +874,83 @@ static void str_dtor(zval *zv) /* {{{ */ { } /* }}} */ -static zend_bool zend_is_call(zend_ast *ast); - -static void generate_free_loop_var_ex(znode *var, uint32_t flags) /* {{{ */ +void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2) /* {{{ */ { - if (var->op_type != IS_UNUSED) { - zend_op *opline = get_next_op(CG(active_op_array)); + 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) { - opline->opcode = var->flag ? ZEND_FE_FREE : ZEND_FREE; - SET_NODE(opline->op1, var); + 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); - opline->extended_value = flags; + } else { + /* Set real break distance */ + ZVAL_LONG(label, distance); } } /* }}} */ -static void generate_free_loop_var(znode *var) /* {{{ */ +static zend_bool zend_is_call(zend_ast *ast); + +static int generate_free_loop_var(znode *var) /* {{{ */ { - generate_free_loop_var_ex(var, 0); + switch (var->op_type) { + case IS_UNUSED: + /* Stack separator on function boundary, stop applying */ + return 1; + case IS_VAR: + case IS_TMP_VAR: + { + zend_op *opline = get_next_op(CG(active_op_array)); + + opline->opcode = var->flag ? ZEND_FE_FREE : ZEND_FREE; + SET_NODE(opline->op1, var); + SET_UNUSED(opline->op2); + } + } + + return 0; } +/* }}} */ static uint32_t zend_add_try_element(uint32_t try_op) /* {{{ */ { @@ -3452,14 +3514,9 @@ void zend_compile_unset(zend_ast *ast) /* {{{ */ } /* }}} */ -static void zend_free_foreach_and_switch_variables(uint32_t flags) /* {{{ */ +static void zend_free_foreach_and_switch_variables(void) /* {{{ */ { - int array_offset = CG(context).current_brk_cont; - while (array_offset != -1) { - zend_brk_cont_element *brk_cont = &CG(context).brk_cont_array[array_offset]; - generate_free_loop_var_ex(&brk_cont->loop_var, flags); - array_offset = brk_cont->parent; - } + zend_stack_apply(&CG(loop_var_stack), ZEND_STACK_APPLY_TOPDOWN, (int (*)(void *element)) generate_free_loop_var); } /* }}} */ @@ -3481,12 +3538,7 @@ void zend_compile_return(zend_ast *ast) /* {{{ */ zend_compile_expr(&expr_node, expr_ast); } - /* Generator return types are handled separately */ - if (!(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) && CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { - zend_emit_return_type_check(expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1); - } - - zend_free_foreach_and_switch_variables(ZEND_FREE_ON_RETURN); + zend_free_foreach_and_switch_variables(); if (CG(context).in_finally) { opline = zend_emit_op(NULL, ZEND_DISCARD_EXCEPTION, NULL, NULL); @@ -3494,6 +3546,10 @@ void zend_compile_return(zend_ast *ast) /* {{{ */ opline->op1.var = CG(context).fast_call_var; } + /* Generator return types are handled separately */ + if (!(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) && CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_emit_return_type_check(expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1); + } opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, &expr_node, NULL); @@ -3562,6 +3618,7 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ } else { int array_offset = CG(context).current_brk_cont; zend_long nest_level = depth; + znode *loop_var = zend_stack_top(&CG(loop_var_stack)); do { if (array_offset == -1) { @@ -3570,11 +3627,12 @@ void zend_compile_break_continue(zend_ast *ast) /* {{{ */ depth, depth == 1 ? "" : "s"); } - if (nest_level > 1) { - generate_free_loop_var_ex(&CG(context).brk_cont_array[array_offset].loop_var, ZEND_FREE_ON_BREAK); + if (nest_level > 1 && CG(active_op_array)->brk_cont_array[array_offset].start >= 0) { + generate_free_loop_var(loop_var); + loop_var--; } - array_offset = CG(context).brk_cont_array[array_offset].parent; + array_offset = CG(active_op_array)->brk_cont_array[array_offset].parent; } while (--nest_level > 0); } opline = zend_emit_op(NULL, ast->kind == ZEND_AST_BREAK ? ZEND_BRK : ZEND_CONT, NULL, NULL); @@ -3583,108 +3641,16 @@ 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; - - 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].loop_var.op_type != IS_UNUSED) { - zend_emit_op(NULL, ZEND_NOP, NULL, NULL); - } - 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; - } - 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].loop_var.op_type != IS_UNUSED) { - if (pass2_opline) { - free_vars++; - } else { - generate_free_loop_var_ex(&CG(context).brk_cont_array[current].loop_var, ZEND_FREE_ON_BREAK); - } - } - 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].loop_var.op_type != IS_UNUSED) { - zend_op *brk_opline = &op_array->opcodes[CG(context).brk_cont_array[current].brk]; - - (pass2_opline - free_vars)->opcode = brk_opline->opcode; - (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 = 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); - zend_resolve_goto_label(CG(active_op_array), &label_node, NULL); + 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); } /* }}} */ @@ -4780,6 +4746,14 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */ opline_ext->lineno = decl->start_lineno; } + { + /* Push a separator to the loop variable stack */ + znode dummy_var; + dummy_var.op_type = IS_UNUSED; + + zend_stack_push(&CG(loop_var_stack), (void *) &dummy_var); + } + zend_compile_params(params_ast, return_type_ast); if (uses_ast) { zend_compile_closure_uses(uses_ast); @@ -4797,6 +4771,9 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */ pass_two(CG(active_op_array)); zend_oparray_context_end(&orig_oparray_context); + /* Pop the loop variable stack separator */ + zend_stack_del_top(&CG(loop_var_stack)); + CG(active_op_array) = orig_op_array; } /* }}} */ @@ -6313,7 +6290,10 @@ void zend_compile_silence(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; znode silence_node; + uint32_t begin_opline_num, end_opline_num; + zend_brk_cont_element *brk_cont_element; + begin_opline_num = get_next_op_number(CG(active_op_array)); zend_emit_op_tmp(&silence_node, ZEND_BEGIN_SILENCE, NULL, NULL); if (expr_ast->kind == ZEND_AST_VAR) { @@ -6324,7 +6304,15 @@ void zend_compile_silence(znode *result, zend_ast *ast) /* {{{ */ zend_compile_expr(result, expr_ast); } + end_opline_num = get_next_op_number(CG(active_op_array)); zend_emit_op(NULL, ZEND_END_SILENCE, &silence_node, NULL); + + /* Store BEGIN_SILENCE/END_SILENCE pair to restore previous + * EG(error_reporting) value on exception */ + brk_cont_element = get_next_brk_cont_element(CG(active_op_array)); + brk_cont_element->start = begin_opline_num; + brk_cont_element->cont = brk_cont_element->brk = end_opline_num; + brk_cont_element->parent = -1; } /* }}} */ @@ -6652,6 +6640,10 @@ static void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */ GET_NODE(result, opline->result); } else { uint32_t var; + zend_brk_cont_element *info = get_next_brk_cont_element(CG(active_op_array)); + info->start = rope_init_lineno; + info->parent = CG(context).current_brk_cont; + info->cont = info->brk = opline - CG(active_op_array)->opcodes; init_opline->extended_value = j; opline->opcode = ZEND_ROPE_END; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 52456a79e9..a3bc17f425 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -109,36 +109,15 @@ typedef struct _zend_declarables { zend_long ticks; } zend_declarables; -typedef struct _zend_brk_cont_element { - int cont; - int brk; - int parent; - znode loop_var; -} zend_brk_cont_element; - -typedef struct _zend_label { - int brk_cont; - uint32_t opline_num; -} zend_label; - -typedef struct _zend_try_catch_element { - uint32_t try_op; - uint32_t catch_op; /* ketchup! */ - uint32_t finally_op; - uint32_t finally_end; -} zend_try_catch_element; - /* Compilation context that is different for each op array. */ typedef struct _zend_oparray_context { uint32_t opcodes_size; int vars_size; int literals_size; + int current_brk_cont; int backpatch_count; int in_finally; uint32_t fast_call_var; - int current_brk_cont; - int last_brk_cont; - zend_brk_cont_element *brk_cont_array; HashTable *labels; } zend_oparray_context; @@ -184,6 +163,26 @@ struct _zend_op { zend_uchar result_type; }; + +typedef struct _zend_brk_cont_element { + int start; + int cont; + int brk; + int parent; +} zend_brk_cont_element; + +typedef struct _zend_label { + int brk_cont; + uint32_t opline_num; +} zend_label; + +typedef struct _zend_try_catch_element { + uint32_t try_op; + uint32_t catch_op; /* ketchup! */ + uint32_t finally_op; + uint32_t finally_end; +} zend_try_catch_element; + /* method flags (types) */ #define ZEND_ACC_STATIC 0x01 #define ZEND_ACC_ABSTRACT 0x02 @@ -329,11 +328,6 @@ typedef struct _zend_internal_function_info { zend_bool _is_variadic; } zend_internal_function_info; -#define ZEND_LIVE_ROPE 1 -#define ZEND_LIVE_SILENCE 2 -#define ZEND_LIVE_LOOP 3 -#define ZEND_LIVE_MASK 3 - struct _zend_op_array { /* Common elements */ zend_uchar type; @@ -357,12 +351,10 @@ struct _zend_op_array { int last_var; uint32_t T; zend_string **vars; - uint32_t *T_liveliness; - - void **run_time_cache; - int cache_size; + int last_brk_cont; int last_try_catch; + zend_brk_cont_element *brk_cont_array; zend_try_catch_element *try_catch_array; /* static variables support */ @@ -377,6 +369,9 @@ struct _zend_op_array { int last_literal; zval *literals; + int cache_size; + void **run_time_cache; + void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; @@ -717,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, znode *label_node, zend_op *pass2_opline); +void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2); ZEND_API void function_add_ref(zend_function *function); @@ -732,8 +727,6 @@ ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...); ZEND_API int open_file_for_scanning(zend_file_handle *file_handle); ZEND_API void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_size); ZEND_API void destroy_op_array(zend_op_array *op_array); -ZEND_API void zend_generate_var_liveliness_info(zend_op_array *op_array); -ZEND_API void zend_regenerate_var_liveliness_info(zend_op_array *op_array); ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle); ZEND_API void zend_cleanup_user_class_data(zend_class_entry *ce); ZEND_API void zend_cleanup_internal_class_data(zend_class_entry *ce); @@ -882,10 +875,7 @@ ZEND_API void zend_assert_valid_class_name(const zend_string *const_name); #define ZEND_FETCH_ARG_MASK 0x000fffff -#define ZEND_FREE_ON_RETURN (1<<0) -#define ZEND_FREE_ON_BREAK (1<<1) - -#define ZEND_MEMBER_FUNC_CALL (1<<0) +#define ZEND_MEMBER_FUNC_CALL 1<<0 #define ZEND_ARG_SEND_BY_REF (1<<0) #define ZEND_ARG_COMPILE_TIME_BOUND (1<<1) @@ -961,7 +951,6 @@ 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_GOTO 253 #define ZEND_BRK 254 #define ZEND_CONT 255 diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 7aa34bb2df..257f627d22 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1939,6 +1939,31 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c } } +static inline zend_brk_cont_element* zend_brk_cont(int nest_levels, int array_offset, const zend_op_array *op_array, const zend_execute_data *execute_data) +{ + zend_brk_cont_element *jmp_to; + + do { + ZEND_ASSERT(array_offset != -1); + jmp_to = &op_array->brk_cont_array[array_offset]; + if (nest_levels > 1 && jmp_to->start >= 0) { + zend_op *brk_opline = &op_array->opcodes[jmp_to->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); + } + } + array_offset = jmp_to->parent; + } while (--nest_levels > 0); + return jmp_to; +} + #if ZEND_INTENSIVE_DEBUGGING #define CHECK_SYMBOL_TABLES() \ @@ -2359,66 +2384,7 @@ static zend_always_inline zend_generator *zend_get_running_generator(zend_execut static zend_always_inline void i_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num) /* {{{ */ { - if (EX(func)->op_array.T_liveliness - && op_num < EX(func)->op_array.last - && EX(func)->op_array.T_liveliness[op_num] != (uint32_t)-1) { - uint32_t *off = EX(func)->op_array.T_liveliness + EX(func)->op_array.T_liveliness[op_num]; - uint32_t *catch_off = NULL; - uint32_t var = *off; - - if (catch_op_num && EX(func)->op_array.T_liveliness[catch_op_num] != (uint32_t)-1) { - catch_off = EX(func)->op_array.T_liveliness + EX(func)->op_array.T_liveliness[catch_op_num]; - } - - do { - /* we should be safe to assume that all temporaries at catch_op_num will be present at op_num too, in same order */ - if (catch_off && *catch_off == var) { - catch_off++; - var = *(++off); - continue; - } - - if ((var & ZEND_LIVE_MASK) == ZEND_LIVE_ROPE) { - /* free incomplete rope */ - zend_string **rope; - zend_op *last; - - var = var & ~ZEND_LIVE_ROPE; - rope = (zend_string **) EX_VAR(var); - last = EX(func)->op_array.opcodes + op_num; - while ((last->opcode != ZEND_ROPE_ADD && last->opcode != ZEND_ROPE_INIT) - || last->result.var != var) { - ZEND_ASSERT(last >= EX(func)->op_array.opcodes); - last--; - } - if (last->opcode == ZEND_ROPE_INIT) { - zend_string_release(*rope); - } else { - int j = last->extended_value; - do { - zend_string_release(rope[j]); - } while (j--); - } - } else if ((var & ZEND_LIVE_MASK) == ZEND_LIVE_SILENCE) { - /* restore previous error_reporting value */ - var = var & ~ZEND_LIVE_SILENCE; - if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(var)) != 0) { - EG(error_reporting) = Z_LVAL_P(EX_VAR(var)); - } - } else if ((var & ZEND_LIVE_MASK) == ZEND_LIVE_LOOP) { - /* free loop variables */ - var = var & ~ZEND_LIVE_LOOP; - if (Z_TYPE_P(EX_VAR(var)) != IS_ARRAY && Z_FE_ITER_P(EX_VAR(var)) != (uint32_t) -1) { - zend_hash_iterator_del(Z_FE_ITER_P(EX_VAR(var))); - } - zval_ptr_dtor_nogc(EX_VAR(var)); - } else { - zval_ptr_dtor_nogc(EX_VAR(var)); - } - var = *(++off); - } while (var != (uint32_t)-1); - } - + int i; if (UNEXPECTED(EX(call))) { zend_execute_data *call = EX(call); zend_op *opline = EX(func)->op_array.opcodes + op_num; @@ -2532,6 +2498,51 @@ static zend_always_inline void i_cleanup_unfinished_execution(zend_execute_data call = EX(call); } while (call); } + + for (i = 0; i < EX(func)->op_array.last_brk_cont; i++) { + const zend_brk_cont_element *brk_cont = &EX(func)->op_array.brk_cont_array[i]; + if (brk_cont->start < 0) { + continue; + } else if (brk_cont->start > op_num) { + /* further blocks will not be relevant... */ + break; + } else if (op_num < brk_cont->brk) { + if (!catch_op_num || catch_op_num >= brk_cont->brk) { + zend_op *brk_opline = &EX(func)->op_array.opcodes[brk_cont->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); + } else if (brk_opline->opcode == ZEND_ROPE_END) { + zend_string **rope = (zend_string **) EX_VAR(brk_opline->op1.var); + zend_op *last = EX(func)->op_array.opcodes + op_num; + while ((last->opcode != ZEND_ROPE_ADD && last->opcode != ZEND_ROPE_INIT) + || last->result.var != brk_opline->op1.var) { + ZEND_ASSERT(last >= EX(func)->op_array.opcodes); + last--; + } + if (last->opcode == ZEND_ROPE_INIT) { + zend_string_release(*rope); + } else { + int j = last->extended_value; + do { + zend_string_release(rope[j]); + } while (j--); + } + } else if (brk_opline->opcode == ZEND_END_SILENCE) { + /* restore previous error_reporting value */ + if (!EG(error_reporting) && Z_LVAL_P(EX_VAR(brk_opline->op1.var)) != 0) { + EG(error_reporting) = Z_LVAL_P(EX_VAR(brk_opline->op1.var)); + } + } + } + } + } } /* }}} */ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 780007174e..28487a2a4a 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -68,6 +68,8 @@ typedef struct _zend_ini_entry zend_ini_entry; struct _zend_compiler_globals { + zend_stack loop_var_stack; + zend_class_entry *active_class_entry; zend_string *compiled_filename; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 5fe5ae7f33..a971a5e900 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -65,7 +65,6 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz op_array->vars = NULL; op_array->T = 0; - op_array->T_liveliness = NULL; op_array->function_name = NULL; op_array->filename = zend_get_compiled_filename(); @@ -78,7 +77,9 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz op_array->scope = NULL; op_array->prototype = NULL; + op_array->brk_cont_array = NULL; op_array->try_catch_array = NULL; + op_array->last_brk_cont = 0; op_array->static_variables = NULL; op_array->last_try_catch = 0; @@ -382,12 +383,12 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) if (op_array->doc_comment) { zend_string_release(op_array->doc_comment); } + if (op_array->brk_cont_array) { + efree(op_array->brk_cont_array); + } if (op_array->try_catch_array) { efree(op_array->try_catch_array); } - if (op_array->T_liveliness) { - efree(op_array->T_liveliness); - } if (op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) { zend_llist_apply_with_argument(&zend_extensions, (llist_apply_with_arg_func_t) zend_extension_op_array_dtor_handler, op_array); } @@ -446,9 +447,9 @@ int get_next_op_number(zend_op_array *op_array) zend_brk_cont_element *get_next_brk_cont_element(zend_op_array *op_array) { - CG(context).last_brk_cont++; - CG(context).brk_cont_array = erealloc(CG(context).brk_cont_array, sizeof(zend_brk_cont_element)*CG(context).last_brk_cont); - return &CG(context).brk_cont_array[CG(context).last_brk_cont-1]; + op_array->last_brk_cont++; + op_array->brk_cont_array = erealloc(op_array->brk_cont_array, sizeof(zend_brk_cont_element)*op_array->last_brk_cont); + return &op_array->brk_cont_array[op_array->last_brk_cont-1]; } static void zend_update_extended_info(zend_op_array *op_array) @@ -575,7 +576,7 @@ static void zend_resolve_finally_call(zend_op_array *op_array, uint32_t op_num, fast_call_var = op_array->opcodes[op_array->try_catch_array[i].finally_end].op1.var; /* generate a FAST_CALL to finally block */ - start_op = get_next_op_number(op_array); + start_op = get_next_op_number(op_array); opline = get_next_op(op_array); opline->opcode = ZEND_FAST_CALL; @@ -671,7 +672,7 @@ static uint32_t zend_get_brk_cont_target(const zend_op_array *op_array, const ze int array_offset = opline->op1.num; zend_brk_cont_element *jmp_to; do { - jmp_to = &CG(context).brk_cont_array[array_offset]; + jmp_to = &op_array->brk_cont_array[array_offset]; if (nest_levels > 1) { array_offset = jmp_to->parent; } @@ -699,8 +700,11 @@ 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, NULL, opline); + zend_resolve_goto_label(op_array, opline, 1); + opline->op2.constant = num; } /* break omitted intentionally */ case ZEND_JMP: @@ -747,9 +751,6 @@ ZEND_API int pass_two(zend_op_array *op_array) op_array->literals = (zval*)erealloc(op_array->literals, sizeof(zval) * op_array->last_literal); CG(context).literals_size = op_array->last_literal; } - - zend_generate_var_liveliness_info(op_array); - opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { @@ -786,7 +787,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, NULL, opline); + zend_resolve_goto_label(op_array, opline, 1); } /* break omitted intentionally */ case ZEND_JMP: @@ -839,227 +840,6 @@ int pass_two_wrapper(zval *el) return pass_two((zend_op_array *) Z_PTR_P(el)); } -/* The following liveliness analyzing algorithm assumes that - * 1) temporary variables are defined before use - * 2) they have linear live-ranges without "holes" - * 3) Opcodes never use and define the same temorary variables - */ -typedef struct _op_var_info { - struct _op_var_info *next; - uint32_t var; -} op_var_info; - -static zend_always_inline uint32_t liveliness_kill_var(zend_op_array *op_array, zend_op *cur_op, uint32_t var, uint32_t *Tstart, op_var_info **opTs) -{ - uint32_t start = Tstart[var]; - uint32_t end = cur_op - op_array->opcodes; - uint32_t count = 0; - uint32_t var_offset, j; - - Tstart[var] = -1; - if (cur_op->opcode == ZEND_OP_DATA) { - end--; - } - start++; - if (op_array->opcodes[start].opcode == ZEND_OP_DATA - || op_array->opcodes[start].opcode == ZEND_FE_FETCH_R - || op_array->opcodes[start].opcode == ZEND_FE_FETCH_RW) { - start++; - } - if (start < end) { - op_var_info *new_opTs; - - var_offset = (uint32_t)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL, op_array->last_var + var); - if (op_array->opcodes[end].opcode == ZEND_ROPE_END) { - var_offset |= ZEND_LIVE_ROPE; - } else if (op_array->opcodes[end].opcode == ZEND_END_SILENCE) { - var_offset |= ZEND_LIVE_SILENCE; - } else if (op_array->opcodes[end].opcode == ZEND_FE_FREE) { - var_offset |= ZEND_LIVE_LOOP; - } - - if (opTs[start]) { - if (start > 0 && opTs[start-1] == opTs[start]) { - op_var_info *opT = opTs[start]; - do { - count++; - opT = opT->next; - } while (opT); - count += 2; - } else { - count++; - } - } else { - count += 2; - } - - new_opTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info)); - new_opTs->next = opTs[start]; - new_opTs->var = var_offset; - opTs[start] = new_opTs; - - for (j = start + 1; j < end; j++) { - if (opTs[j-1]->next == opTs[j]) { - opTs[j] = opTs[j-1]; - } else { - if (opTs[j]) { - count++; - } else { - count += 2; - } - new_opTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info)); - new_opTs->next = opTs[j]; - new_opTs->var = var_offset; - opTs[j] = new_opTs; - } - } - } - - return count; -} - -static zend_always_inline uint32_t *generate_var_liveliness_info_ex(zend_op_array *op_array, zend_bool done_pass_two) -{ - zend_op *opline, *end; - uint32_t var, i, op_live_total = 0; - uint32_t *info, info_off = op_array->last + 1; - void *checkpoint = zend_arena_checkpoint(CG(arena)); - uint32_t *Tstart = zend_arena_alloc(&CG(arena), sizeof(uint32_t) * op_array->T); - op_var_info **opTs = zend_arena_alloc(&CG(arena), sizeof(op_var_info *) * op_array->last); - - memset(Tstart, -1, sizeof(uint32_t) * op_array->T); - memset(opTs, 0, sizeof(op_var_info *) * op_array->last); - - opline = op_array->opcodes; - end = opline + op_array->last; - do { - if ((opline->result_type & (IS_VAR|IS_TMP_VAR)) - && !((opline)->result_type & EXT_TYPE_UNUSED) - /* the following opcodes are used in inline branching - * (and anyway always bool, so no need to free) and may - * not be defined depending on the taken branch */ - && opline->opcode != ZEND_BOOL - && opline->opcode != ZEND_JMPZ_EX - && opline->opcode != ZEND_JMPNZ_EX - /* These opcodes write the result of the true branch of a ternary, short - * ternary or coalesce and are immediately followed by the instructions - * for the false branch (where this result is not live) */ - && (opline->opcode != ZEND_QM_ASSIGN || (opline + 1)->opcode != ZEND_JMP) - && opline->opcode != ZEND_JMP_SET - && opline->opcode != ZEND_COALESCE - /* exception for opcache, it might nowhere use the temporary - * (anyway bool, so no need to free) */ - && opline->opcode != ZEND_CASE - /* the following opcodes reuse TMP created before */ - && opline->opcode != ZEND_ROPE_ADD - && opline->opcode != ZEND_ADD_ARRAY_ELEMENT - && opline->opcode != ZEND_SEPARATE - /* passes fast_call */ - && opline->opcode != ZEND_FAST_CALL - /* the following opcodes pass class_entry */ - && opline->opcode != ZEND_FETCH_CLASS - && opline->opcode != ZEND_DECLARE_CLASS - && opline->opcode != ZEND_DECLARE_INHERITED_CLASS - && opline->opcode != ZEND_DECLARE_INHERITED_CLASS_DELAYED - && opline->opcode != ZEND_DECLARE_ANON_CLASS - && opline->opcode != ZEND_DECLARE_ANON_INHERITED_CLASS - ) { - if (done_pass_two) { - var = EX_VAR_TO_NUM(opline->result.var) - op_array->last_var; - } else { - var = opline->result.var; - } - ZEND_ASSERT(Tstart[var] == (unsigned) -1); - if (opline->opcode == ZEND_NEW) { - /* Objects created via ZEND_NEW are only fully initialized - * after the DO_FCALL (constructor call) */ - Tstart[var] = opline->op2.opline_num - 1; - } else { - Tstart[var] = opline - op_array->opcodes; - } - } - if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) { - if (done_pass_two) { - var = EX_VAR_TO_NUM(opline->op1.var) - op_array->last_var; - } else { - var = opline->op1.var; - } - if (Tstart[var] != (uint32_t)-1 - /* the following opcodes don't free TMP */ - && opline->opcode != ZEND_ROPE_ADD - && opline->opcode != ZEND_SEPARATE - && opline->opcode != ZEND_FETCH_LIST - && opline->opcode != ZEND_CASE - && opline->opcode != ZEND_FE_FETCH_R - && opline->opcode != ZEND_FE_FETCH_RW - /* the following opcodes are parts of "return" statement */ - && opline->opcode != ZEND_VERIFY_RETURN_TYPE - && (opline->opcode != ZEND_FREE || !(opline->extended_value & (ZEND_FREE_ON_RETURN|ZEND_FREE_ON_BREAK))) - && (opline->opcode != ZEND_FE_FREE || !(opline->extended_value & (ZEND_FREE_ON_RETURN|ZEND_FREE_ON_BREAK))) - ) { - op_live_total += liveliness_kill_var(op_array, opline, var, Tstart, opTs); - } - } - if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) { - if (done_pass_two) { - var = EX_VAR_TO_NUM(opline->op2.var) - op_array->last_var; - } else { - var = opline->op2.var; - } - if (Tstart[var] != (uint32_t)-1) { - op_live_total += liveliness_kill_var(op_array, opline, var, Tstart, opTs); - } - } - } while (++opline != end); - -#if 0 - /* Check that all TMP variable live-ranges are closed */ - for (i = 0; i < op_array->T; i++) { - ZEND_ASSERT(Tstart[i] == (uint32_t)-1); - } -#endif - - if (!op_live_total) { - info = NULL; - } else { - info = emalloc((op_array->last + 1 + op_live_total) * sizeof(uint32_t)); - - for (i = 0; i < op_array->last; i++) { - if (!opTs[i]) { - info[i] = (uint32_t)-1; - } else if (i > 0 && opTs[i-1] == opTs[i]) { - info[i] = info[i-1]; - } else { - op_var_info *opT = opTs[i]; - info[i] = info_off; - while (opT) { - info[info_off++] = opT->var; - opT = opT->next; - } - info[info_off++] = (uint32_t)-1; - } - } - info[op_array->last] = info_off; - ZEND_ASSERT(info_off == op_array->last + 1 + op_live_total); - } - - zend_arena_release(&CG(arena), checkpoint); - return info; -} - -ZEND_API void zend_generate_var_liveliness_info(zend_op_array *op_array) -{ - op_array->T_liveliness = generate_var_liveliness_info_ex(op_array, 0); -} - -ZEND_API void zend_regenerate_var_liveliness_info(zend_op_array *op_array) -{ - if (op_array->T_liveliness) { - efree(op_array->T_liveliness); - } - op_array->T_liveliness = generate_var_liveliness_info_ex(op_array, 1); -} - int print_class(zend_class_entry *class_entry) { printf("Class %s:\n", ZSTR_VAL(class_entry->name)); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 98f6be671a..b94614e659 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2610,13 +2610,10 @@ ZEND_VM_HANDLER(47, ZEND_JMPNZ_EX, CONST|TMPVAR|CV, ANY) ZEND_VM_HANDLER(70, ZEND_FREE, TMPVAR, ANY) { - zval *var; USE_OPLINE SAVE_OPLINE(); - var = EX_VAR(opline->op1.var); - zval_ptr_dtor_nogc(var); - ZVAL_NULL(var); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -2629,10 +2626,8 @@ ZEND_VM_HANDLER(127, ZEND_FE_FREE, TMPVAR, ANY) var = EX_VAR(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)); - Z_FE_ITER_P(var) = (uint32_t)-1; } zval_ptr_dtor_nogc(var); - ZVAL_NULL(var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -3888,9 +3883,6 @@ ZEND_VM_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED) if (UNEXPECTED(EG(exception) != NULL)) { FREE_OP1(); - if (OP1_TYPE == IS_TMP_VAR || OP1_TYPE == IS_VAR) { - ZVAL_NULL(retval_ref); - } } #endif } @@ -4795,6 +4787,31 @@ 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 @@ -7082,15 +7099,6 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) } } - if (catch_op_num) { - if (EX(func)->op_array.opcodes[op_num].opcode == ZEND_VERIFY_RETURN_TYPE - || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FE_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - ) { - catch_op_num = 0; - } - } - i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 95c1b5dfaa..828babb98d 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1500,15 +1500,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER( } } - if (catch_op_num) { - if (EX(func)->op_array.opcodes[op_num].opcode == ZEND_VERIFY_RETURN_TYPE - || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - || (EX(func)->op_array.opcodes[op_num].opcode == ZEND_FE_FREE && (EX(func)->op_array.opcodes[op_num].extended_value & ZEND_FREE_ON_RETURN)) - ) { - catch_op_num = 0; - } - } - i_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) { @@ -2228,6 +2219,31 @@ 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 @@ -7749,9 +7765,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CONST_ if (UNEXPECTED(EG(exception) != NULL)) { - if (IS_CONST == IS_TMP_VAR || IS_CONST == IS_VAR) { - ZVAL_NULL(retval_ref); - } } #endif } @@ -13548,9 +13561,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN if (UNEXPECTED(EG(exception) != NULL)) { zval_ptr_dtor_nogc(free_op1); - if (IS_TMP_VAR == IS_TMP_VAR || IS_TMP_VAR == IS_VAR) { - ZVAL_NULL(retval_ref); - } } #endif } @@ -19240,9 +19250,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN if (UNEXPECTED(EG(exception) != NULL)) { zval_ptr_dtor_nogc(free_op1); - if (IS_VAR == IS_TMP_VAR || IS_VAR == IS_VAR) { - ZVAL_NULL(retval_ref); - } } #endif } @@ -24928,9 +24935,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED if (UNEXPECTED(EG(exception) != NULL)) { - if (IS_UNUSED == IS_TMP_VAR || IS_UNUSED == IS_VAR) { - ZVAL_NULL(retval_ref); - } } #endif } @@ -34367,9 +34371,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU if (UNEXPECTED(EG(exception) != NULL)) { - if (IS_CV == IS_TMP_VAR || IS_CV == IS_VAR) { - ZVAL_NULL(retval_ref); - } } #endif } @@ -40188,13 +40189,10 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_JMPNZ_EX_SPEC_TMPVAR_HANDLER(Z static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FREE_SPEC_TMPVAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { - zval *var; USE_OPLINE SAVE_OPLINE(); - var = EX_VAR(opline->op1.var); - zval_ptr_dtor_nogc(var); - ZVAL_NULL(var); + zval_ptr_dtor_nogc(EX_VAR(opline->op1.var)); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -40207,10 +40205,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_FREE_SPEC_TMPVAR_HANDLER(ZE var = EX_VAR(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)); - Z_FE_ITER_P(var) = (uint32_t)-1; } zval_ptr_dtor_nogc(var); - ZVAL_NULL(var); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } @@ -47691,27 +47687,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_NULL_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, diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 1f26f0439e..705ab9cd29 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -122,7 +122,7 @@ const char *zend_vm_opcodes_map[173] = { "ZEND_FETCH_OBJ_UNSET", "ZEND_FETCH_LIST", "ZEND_FETCH_CONSTANT", - NULL, + "ZEND_GOTO", "ZEND_EXT_STMT", "ZEND_EXT_FCALL_BEGIN", "ZEND_EXT_FCALL_END", diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index bb2d7717bd..f6de5b1b57 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -130,6 +130,7 @@ 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 diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index dfe0e4baef..e1e07ea01b 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -123,6 +123,10 @@ 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) { @@ -193,6 +197,65 @@ static int find_code_blocks(zend_op_array *op_array, zend_cfg *cfg, zend_optimiz blocks[op_array->try_catch_array[i].try_op].protected = 1; } } + /* Currently, we don't optimize op_arrays with BRK/CONT/GOTO opcodes, + * but, we have to keep brk_cont_array to avoid memory leaks during + * exception handling */ + if (op_array->last_brk_cont) { + int i, j; + + j = 0; + for (i = 0; i< op_array->last_brk_cont; i++) { + if (op_array->brk_cont_array[i].start >= 0 && + (op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FREE || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FE_FREE || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_ROPE_END || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_END_SILENCE)) { + int parent = op_array->brk_cont_array[i].parent; + + while (parent >= 0 && + op_array->brk_cont_array[parent].start < 0 && + (op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_FREE || + op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_FE_FREE || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode != ZEND_ROPE_END || + op_array->opcodes[op_array->brk_cont_array[parent].brk].opcode != ZEND_END_SILENCE)) { + parent = op_array->brk_cont_array[parent].parent; + } + op_array->brk_cont_array[i].parent = parent; + j++; + } + } + if (j) { + cfg->loop_start = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *)); + cfg->loop_cont = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *)); + cfg->loop_brk = zend_arena_calloc(&ctx->arena, op_array->last_brk_cont, sizeof(zend_code_block *)); + j = 0; + for (i = 0; i< op_array->last_brk_cont; i++) { + if (op_array->brk_cont_array[i].start >= 0 && + (op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FREE || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_FE_FREE || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_ROPE_END || + op_array->opcodes[op_array->brk_cont_array[i].brk].opcode == ZEND_END_SILENCE)) { + if (i != j) { + op_array->brk_cont_array[j] = op_array->brk_cont_array[i]; + } + cfg->loop_start[j] = &blocks[op_array->brk_cont_array[j].start]; + cfg->loop_cont[j] = &blocks[op_array->brk_cont_array[j].cont]; + cfg->loop_brk[j] = &blocks[op_array->brk_cont_array[j].brk]; + START_BLOCK_OP(op_array->brk_cont_array[j].start); + START_BLOCK_OP(op_array->brk_cont_array[j].cont); + START_BLOCK_OP(op_array->brk_cont_array[j].brk); + blocks[op_array->brk_cont_array[j].start].protected = 1; + blocks[op_array->brk_cont_array[j].brk].protected = 1; + j++; + } + } + op_array->last_brk_cont = j; + } else { + efree(op_array->brk_cont_array); + op_array->brk_cont_array = NULL; + op_array->last_brk_cont = 0; + } + } /* Build CFG (Control Flow Graph) */ cur_block = blocks; @@ -460,6 +523,16 @@ static void zend_rebuild_access_path(zend_cfg *cfg, zend_op_array *op_array, int /* Walk thorough all paths */ zend_access_path(start, ctx); + /* Add brk/cont paths */ + if (op_array->last_brk_cont) { + int i; + for (i=0; i< op_array->last_brk_cont; i++) { + zend_access_path(cfg->loop_start[i], ctx); + zend_access_path(cfg->loop_cont[i], ctx); + zend_access_path(cfg->loop_brk[i], ctx); + } + } + /* Add exception paths */ if (op_array->last_try_catch) { int i; @@ -1123,6 +1196,16 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array) op_array->last_try_catch = j; } + /* adjust loop jump targets */ + if (op_array->last_brk_cont) { + int i; + for (i = 0; i< op_array->last_brk_cont; i++) { + op_array->brk_cont_array[i].start = cfg->loop_start[i]->start_opline - new_opcodes; + op_array->brk_cont_array[i].cont = cfg->loop_cont[i]->start_opline - new_opcodes; + op_array->brk_cont_array[i].brk = cfg->loop_brk[i]->start_opline - new_opcodes; + } + } + /* adjust jump targets */ for (cur_block = blocks; cur_block; cur_block = cur_block->next) { if (!cur_block->access) { diff --git a/ext/opcache/Optimizer/nop_removal.c b/ext/opcache/Optimizer/nop_removal.c index 9983ff4f60..20510b4163 100644 --- a/ext/opcache/Optimizer/nop_removal.c +++ b/ext/opcache/Optimizer/nop_removal.c @@ -44,6 +44,14 @@ 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 */ @@ -77,6 +85,7 @@ 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: @@ -108,6 +117,13 @@ void zend_optimizer_nop_removal(zend_op_array *op_array) } } + /* update brk/cont array */ + for (j = 0; j < op_array->last_brk_cont; j++) { + op_array->brk_cont_array[j].brk -= shiftlist[op_array->brk_cont_array[j].brk]; + op_array->brk_cont_array[j].cont -= shiftlist[op_array->brk_cont_array[j].cont]; + op_array->brk_cont_array[j].start -= shiftlist[op_array->brk_cont_array[j].start]; + } + /* update try/catch array */ for (j = 0; j < op_array->last_try_catch; j++) { op_array->try_catch_array[j].try_op -= shiftlist[op_array->try_catch_array[j].try_op]; diff --git a/ext/opcache/Optimizer/pass1_5.c b/ext/opcache/Optimizer/pass1_5.c index 766eb2c2d4..6fcdc3e47a 100644 --- a/ext/opcache/Optimizer/pass1_5.c +++ b/ext/opcache/Optimizer/pass1_5.c @@ -624,6 +624,7 @@ 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 86b0837137..dc69d2511e 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -340,11 +340,29 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, */ case ZEND_FREE: case ZEND_CASE: { - zend_op *m = opline; - zend_op *end = op_array->opcodes + op_array->last; + zend_op *m, *n; + int brk = op_array->last_brk_cont; + zend_bool in_switch = 0; + while (brk--) { + if (op_array->brk_cont_array[brk].start <= (opline - op_array->opcodes) && + op_array->brk_cont_array[brk].brk > (opline - op_array->opcodes)) { + in_switch = 1; + break; + } + } - while (m < end) { - if (ZEND_OP1_TYPE(m) == type && ZEND_OP1(m).var == var) { + if (!in_switch) { + ZEND_ASSERT(opline->opcode == ZEND_FREE); + MAKE_NOP(opline); + zval_dtor(val); + return 1; + } + + m = opline; + n = op_array->opcodes + op_array->brk_cont_array[brk].brk + 1; + while (m < n) { + if (ZEND_OP1_TYPE(m) == type && + ZEND_OP1(m).var == var) { if (m->opcode == ZEND_CASE) { zval old_val; ZVAL_COPY_VALUE(&old_val, val); @@ -353,7 +371,6 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, ZVAL_COPY_VALUE(val, &old_val); } else if (m->opcode == ZEND_FREE) { MAKE_NOP(m); - break; } else { ZEND_ASSERT(0); } @@ -458,17 +475,6 @@ static void zend_optimize(zend_op_array *op_array, if (ZEND_OPTIMIZER_PASS_11 & OPTIMIZATION_LEVEL) { zend_optimizer_compact_literals(op_array, ctx); } - - if ((ZEND_OPTIMIZER_PASS_1 - |ZEND_OPTIMIZER_PASS_2 - |ZEND_OPTIMIZER_PASS_3 - |ZEND_OPTIMIZER_PASS_4 - |ZEND_OPTIMIZER_PASS_5 - |ZEND_OPTIMIZER_PASS_9 - |ZEND_OPTIMIZER_PASS_10 - |ZEND_OPTIMIZER_PASS_11) & OPTIMIZATION_LEVEL) { - zend_regenerate_var_liveliness_info(op_array); - } } static void zend_accel_optimize(zend_op_array *op_array, @@ -488,6 +494,7 @@ 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: @@ -532,6 +539,7 @@ 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/Optimizer/zend_optimizer_internal.h b/ext/opcache/Optimizer/zend_optimizer_internal.h index 69288c7228..c2f97ff715 100644 --- a/ext/opcache/Optimizer/zend_optimizer_internal.h +++ b/ext/opcache/Optimizer/zend_optimizer_internal.h @@ -73,6 +73,9 @@ typedef struct _zend_cfg { zend_code_block *blocks; zend_code_block **try; zend_code_block **catch; + zend_code_block **loop_start; + zend_code_block **loop_cont; + zend_code_block **loop_brk; zend_op **Tsource; char *same_t; } zend_cfg; diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 54e2ab639a..0781b91c9d 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -386,6 +386,7 @@ 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: @@ -458,11 +459,11 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_STR(op_array->function_name); SERIALIZE_STR(op_array->filename); + SERIALIZE_PTR(op_array->brk_cont_array); SERIALIZE_PTR(op_array->scope); SERIALIZE_STR(op_array->doc_comment); SERIALIZE_PTR(op_array->try_catch_array); SERIALIZE_PTR(op_array->prototype); - SERIALIZE_PTR(op_array->T_liveliness); } } @@ -912,6 +913,7 @@ 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: @@ -980,11 +982,11 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_STR(op_array->function_name); UNSERIALIZE_STR(op_array->filename); + UNSERIALIZE_PTR(op_array->brk_cont_array); UNSERIALIZE_PTR(op_array->scope); UNSERIALIZE_STR(op_array->doc_comment); UNSERIALIZE_PTR(op_array->try_catch_array); UNSERIALIZE_PTR(op_array->prototype); - UNSERIALIZE_PTR(op_array->T_liveliness); } } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 0f789090cd..bbcb20713b 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -501,6 +501,7 @@ 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: @@ -589,6 +590,10 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc op_array->arg_info = arg_info; } + if (op_array->brk_cont_array) { + zend_accel_store(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont); + } + if (op_array->scope) { op_array->scope = zend_shared_alloc_get_xlat_entry(op_array->scope); } @@ -613,10 +618,6 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc zend_accel_store(op_array->try_catch_array, sizeof(zend_try_catch_element) * op_array->last_try_catch); } - if (op_array->T_liveliness) { - zend_accel_store(op_array->T_liveliness, sizeof(uint32_t) * op_array->T_liveliness[op_array->last]); - } - if (op_array->vars) { if (already_stored) { persist_ptr = zend_shared_alloc_get_xlat_entry(op_array->vars); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 9265ba5ce9..d78cc59259 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -229,6 +229,10 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) } } + if (op_array->brk_cont_array) { + ADD_DUP_SIZE(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont); + } + if (ZCG(accel_directives).save_comments && op_array->doc_comment) { ADD_STRING(op_array->doc_comment); } @@ -237,10 +241,6 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) ADD_DUP_SIZE(op_array->try_catch_array, sizeof(zend_try_catch_element) * op_array->last_try_catch); } - if (op_array->T_liveliness) { - ADD_DUP_SIZE(op_array->T_liveliness, sizeof(uint32_t) * op_array->T_liveliness[op_array->last]); - } - if (op_array->vars) { int i; diff --git a/sapi/phpdbg/phpdbg_opcode.c b/sapi/phpdbg/phpdbg_opcode.c index 9b19d37de3..3af52f17cd 100644 --- a/sapi/phpdbg/phpdbg_opcode.c +++ b/sapi/phpdbg/phpdbg_opcode.c @@ -24,7 +24,6 @@ #include "phpdbg_opcode.h" #include "phpdbg_utils.h" #include "ext/standard/php_string.h" -#include "zend_smart_str.h" ZEND_EXTERN_MODULE_GLOBALS(phpdbg); @@ -57,6 +56,7 @@ 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; @@ -114,44 +114,6 @@ char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op) /*{{{ */ break; } -#if 0 - if (ops->T_liveliness) { - uint32_t *var = ops->T_liveliness + (op - ops->opcodes); - - if (*var != (uint32_t)-1) { - smart_str str = {0}; - - var = ops->T_liveliness + (*var); - smart_str_appends(&str, "; [@"); - smart_str_append_long(&str, EX_VAR_TO_NUM(((*var) & ~0x3)) - ops->last_var); - while (*(++var) != (uint32_t)-1) { - smart_str_appends(&str, ", @"); - smart_str_append_long(&str, EX_VAR_TO_NUM(((*var) & ~0x3)) - ops->last_var); - } - smart_str_appendc(&str, ']'); - smart_str_0(&str); - - asprintf(&decode[0], - "%-20s %-20s %-20s%-20s", - decode[1] ? decode[1] : "", - decode[2] ? decode[2] : "", - decode[3] ? decode[3] : "", - ZSTR_VAL(str.s)); - - smart_str_free(&str); - - if (decode[1]) - free(decode[1]); - if (decode[2]) - free(decode[2]); - if (decode[3]) - free(decode[3]); - - return decode[0]; - } - } -#endif - asprintf(&decode[0], "%-20s %-20s %-20s", decode[1] ? decode[1] : "", -- 2.40.0