From 93e2cd93aaca0c614446776d849c7241b269766f Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Wed, 6 May 2026 17:25:28 +0200 Subject: [PATCH] Implement Scope Functions fn() { /* body */ } Signed-off-by: Bob Weinand --- Zend/Optimizer/block_pass.c | 2 + Zend/Optimizer/compact_literals.c | 23 +- Zend/Optimizer/compact_vars.c | 61 + Zend/Optimizer/optimize_temp_vars_5.c | 5 + Zend/Optimizer/zend_call_graph.c | 6 + Zend/Optimizer/zend_inference.c | 3 + Zend/Optimizer/zend_optimizer.c | 85 +- Zend/tests/function_arguments/gh20435_2.phpt | 2 +- Zend/tests/scope_fn/backtrace.phpt | 21 + Zend/tests/scope_fn/backtrace_named_args.phpt | 52 + .../tests/scope_fn/basic_read_parent_var.phpt | 13 + Zend/tests/scope_fn/bind_disallowed.phpt | 19 + Zend/tests/scope_fn/bind_scope.phpt | 37 + Zend/tests/scope_fn/clone_disallowed.phpt | 16 + Zend/tests/scope_fn/compact_extract.phpt | 28 + Zend/tests/scope_fn/default_params.phpt | 16 + Zend/tests/scope_fn/error_path_cleanup.phpt | 27 + Zend/tests/scope_fn/escape_error.phpt | 15 + .../tests/scope_fn/escape_in_conditional.phpt | 14 + Zend/tests/scope_fn/escape_via_global.phpt | 16 + .../scope_fn/escape_with_extra_args.phpt | 15 + .../tests/scope_fn/exception_in_scope_fn.phpt | 19 + .../fiber_exit_in_unwind_finally.phpt | 26 + .../scope_fn/fiber_holds_unused_scope_fn.phpt | 28 + Zend/tests/scope_fn/fiber_inner_scope_fn.phpt | 23 + Zend/tests/scope_fn/fiber_multi_suspend.phpt | 41 + .../tests/scope_fn/fiber_outlives_parent.phpt | 61 + .../fiber_panic_resuspend_in_unwind.phpt | 34 + .../scope_fn/fiber_resuspend_in_unwind.phpt | 40 + .../fiber_suspend_in_inner_generator.phpt | 54 + .../fiber_suspend_inside_scope_fn.phpt | 47 + Zend/tests/scope_fn/fiber_within_parent.phpt | 32 + Zend/tests/scope_fn/func_get_arg.phpt | 25 + Zend/tests/scope_fn/func_get_args.phpt | 32 + .../scope_fn/include_escape_via_return.inc | 3 + .../scope_fn/include_escape_via_static.inc | 4 + Zend/tests/scope_fn/include_no_escape.inc | 4 + Zend/tests/scope_fn/include_scope_fn.phpt | 34 + Zend/tests/scope_fn/lifetime_error.phpt | 15 + Zend/tests/scope_fn/loop_reinvalidation.phpt | 24 + Zend/tests/scope_fn/multiple_calls.phpt | 16 + Zend/tests/scope_fn/multiple_scope_fns.phpt | 16 + .../scope_fn/multiple_scope_fns_escape.phpt | 21 + Zend/tests/scope_fn/mutation.phpt | 14 + Zend/tests/scope_fn/nested_mutation.phpt | 17 + Zend/tests/scope_fn/nested_scope_fn.phpt | 19 + .../scope_fn/new_var_visible_in_parent.phpt | 13 + Zend/tests/scope_fn/no_args.phpt | 13 + Zend/tests/scope_fn/no_escape_ok.phpt | 14 + .../parent_cv_written_during_destructor.phpt | 31 + Zend/tests/scope_fn/pass_as_callback.phpt | 20 + Zend/tests/scope_fn/recursion_error.phpt | 16 + Zend/tests/scope_fn/reflection.phpt | 34 + Zend/tests/scope_fn/return_parent_cv.phpt | 18 + Zend/tests/scope_fn/return_type.phpt | 16 + Zend/tests/scope_fn/return_value.phpt | 14 + Zend/tests/scope_fn/static_disallowed.phpt | 10 + .../throwing_destructor_on_arg_assign.phpt | 34 + Zend/tests/scope_fn/too_few_args.phpt | 18 + Zend/tests/scope_fn/type_error.phpt | 18 + Zend/tests/scope_fn/typed_params.phpt | 12 + Zend/tests/scope_fn/undefined_parent_var.phpt | 31 + Zend/tests/scope_fn/use_clause_error.phpt | 11 + Zend/tests/scope_fn/var_dump_closure.phpt | 24 + Zend/tests/scope_fn/variable_variables.phpt | 13 + Zend/tests/scope_fn/variadic_args.phpt | 12 + Zend/tests/scope_fn/varvar.phpt | 38 + Zend/tests/scope_fn/vm_stack_leak.phpt | 14 + Zend/tests/scope_fn/yield_basic.phpt | 28 + .../yield_fiber_suspend_inside_body.phpt | 79 + .../yield_finishes_before_parent.phpt | 35 + Zend/tests/scope_fn/yield_from_array.phpt | 25 + Zend/tests/scope_fn/yield_in_fiber.phpt | 71 + .../tests/scope_fn/yield_outlives_parent.phpt | 58 + .../scope_fn/yield_throw_into_generator.phpt | 34 + .../scope_fn/yield_uncaught_exception.phpt | 27 + Zend/tests/scope_fn/yield_with_send.phpt | 35 + Zend/zend_API.c | 2 +- Zend/zend_API.h | 4 +- Zend/zend_builtin_functions.c | 219 +- Zend/zend_closures.c | 62 +- Zend/zend_closures.h | 2 + Zend/zend_compile.c | 272 ++- Zend/zend_compile.h | 27 +- Zend/zend_exceptions.c | 30 + Zend/zend_exceptions.h | 3 + Zend/zend_execute.c | 199 +- Zend/zend_execute.h | 102 + Zend/zend_execute_API.c | 17 +- Zend/zend_fibers.c | 59 +- Zend/zend_fibers.h | 9 + Zend/zend_generators.c | 53 +- Zend/zend_generators.h | 9 + Zend/zend_language_parser.y | 6 + Zend/zend_opcode.c | 2 + Zend/zend_vm_def.h | 397 +++- Zend/zend_vm_execute.h | 1992 ++++++++++++++--- Zend/zend_vm_gen.php | 23 + Zend/zend_vm_handlers.h | 1335 +++++------ Zend/zend_vm_opcodes.c | 8 +- Zend/zend_vm_opcodes.h | 4 +- ext/opcache/jit/zend_jit.c | 17 + ext/opcache/jit/zend_jit_ir.c | 6 +- ext/opcache/jit/zend_jit_vm_helpers.c | 17 +- ext/reflection/php_reflection.c | 13 + ext/reflection/php_reflection.stub.php | 2 + ext/reflection/php_reflection_arginfo.h | 58 +- ext/reflection/php_reflection_decl.h | 8 +- 108 files changed, 5719 insertions(+), 1179 deletions(-) create mode 100644 Zend/tests/scope_fn/backtrace.phpt create mode 100644 Zend/tests/scope_fn/backtrace_named_args.phpt create mode 100644 Zend/tests/scope_fn/basic_read_parent_var.phpt create mode 100644 Zend/tests/scope_fn/bind_disallowed.phpt create mode 100644 Zend/tests/scope_fn/bind_scope.phpt create mode 100644 Zend/tests/scope_fn/clone_disallowed.phpt create mode 100644 Zend/tests/scope_fn/compact_extract.phpt create mode 100644 Zend/tests/scope_fn/default_params.phpt create mode 100644 Zend/tests/scope_fn/error_path_cleanup.phpt create mode 100644 Zend/tests/scope_fn/escape_error.phpt create mode 100644 Zend/tests/scope_fn/escape_in_conditional.phpt create mode 100644 Zend/tests/scope_fn/escape_via_global.phpt create mode 100644 Zend/tests/scope_fn/escape_with_extra_args.phpt create mode 100644 Zend/tests/scope_fn/exception_in_scope_fn.phpt create mode 100644 Zend/tests/scope_fn/fiber_exit_in_unwind_finally.phpt create mode 100644 Zend/tests/scope_fn/fiber_holds_unused_scope_fn.phpt create mode 100644 Zend/tests/scope_fn/fiber_inner_scope_fn.phpt create mode 100644 Zend/tests/scope_fn/fiber_multi_suspend.phpt create mode 100644 Zend/tests/scope_fn/fiber_outlives_parent.phpt create mode 100644 Zend/tests/scope_fn/fiber_panic_resuspend_in_unwind.phpt create mode 100644 Zend/tests/scope_fn/fiber_resuspend_in_unwind.phpt create mode 100644 Zend/tests/scope_fn/fiber_suspend_in_inner_generator.phpt create mode 100644 Zend/tests/scope_fn/fiber_suspend_inside_scope_fn.phpt create mode 100644 Zend/tests/scope_fn/fiber_within_parent.phpt create mode 100644 Zend/tests/scope_fn/func_get_arg.phpt create mode 100644 Zend/tests/scope_fn/func_get_args.phpt create mode 100644 Zend/tests/scope_fn/include_escape_via_return.inc create mode 100644 Zend/tests/scope_fn/include_escape_via_static.inc create mode 100644 Zend/tests/scope_fn/include_no_escape.inc create mode 100644 Zend/tests/scope_fn/include_scope_fn.phpt create mode 100644 Zend/tests/scope_fn/lifetime_error.phpt create mode 100644 Zend/tests/scope_fn/loop_reinvalidation.phpt create mode 100644 Zend/tests/scope_fn/multiple_calls.phpt create mode 100644 Zend/tests/scope_fn/multiple_scope_fns.phpt create mode 100644 Zend/tests/scope_fn/multiple_scope_fns_escape.phpt create mode 100644 Zend/tests/scope_fn/mutation.phpt create mode 100644 Zend/tests/scope_fn/nested_mutation.phpt create mode 100644 Zend/tests/scope_fn/nested_scope_fn.phpt create mode 100644 Zend/tests/scope_fn/new_var_visible_in_parent.phpt create mode 100644 Zend/tests/scope_fn/no_args.phpt create mode 100644 Zend/tests/scope_fn/no_escape_ok.phpt create mode 100644 Zend/tests/scope_fn/parent_cv_written_during_destructor.phpt create mode 100644 Zend/tests/scope_fn/pass_as_callback.phpt create mode 100644 Zend/tests/scope_fn/recursion_error.phpt create mode 100644 Zend/tests/scope_fn/reflection.phpt create mode 100644 Zend/tests/scope_fn/return_parent_cv.phpt create mode 100644 Zend/tests/scope_fn/return_type.phpt create mode 100644 Zend/tests/scope_fn/return_value.phpt create mode 100644 Zend/tests/scope_fn/static_disallowed.phpt create mode 100644 Zend/tests/scope_fn/throwing_destructor_on_arg_assign.phpt create mode 100644 Zend/tests/scope_fn/too_few_args.phpt create mode 100644 Zend/tests/scope_fn/type_error.phpt create mode 100644 Zend/tests/scope_fn/typed_params.phpt create mode 100644 Zend/tests/scope_fn/undefined_parent_var.phpt create mode 100644 Zend/tests/scope_fn/use_clause_error.phpt create mode 100644 Zend/tests/scope_fn/var_dump_closure.phpt create mode 100644 Zend/tests/scope_fn/variable_variables.phpt create mode 100644 Zend/tests/scope_fn/variadic_args.phpt create mode 100644 Zend/tests/scope_fn/varvar.phpt create mode 100644 Zend/tests/scope_fn/vm_stack_leak.phpt create mode 100644 Zend/tests/scope_fn/yield_basic.phpt create mode 100644 Zend/tests/scope_fn/yield_fiber_suspend_inside_body.phpt create mode 100644 Zend/tests/scope_fn/yield_finishes_before_parent.phpt create mode 100644 Zend/tests/scope_fn/yield_from_array.phpt create mode 100644 Zend/tests/scope_fn/yield_in_fiber.phpt create mode 100644 Zend/tests/scope_fn/yield_outlives_parent.phpt create mode 100644 Zend/tests/scope_fn/yield_throw_into_generator.phpt create mode 100644 Zend/tests/scope_fn/yield_uncaught_exception.phpt create mode 100644 Zend/tests/scope_fn/yield_with_send.phpt diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 02c28ead33e1..60f20424632f 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -942,6 +942,8 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array src->opcode != ZEND_ADD_ARRAY_ELEMENT && src->opcode != ZEND_ADD_ARRAY_UNPACK && (src->opcode != ZEND_DECLARE_LAMBDA_FUNCTION || + src == opline -1) && + (src->opcode != ZEND_DECLARE_SCOPE_FUNC || src == opline -1)) { src->result.var = opline->result.var; VAR_SOURCE(opline->op1) = NULL; diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index cf74dd8fc147..187f97fecca7 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -35,12 +35,18 @@ typedef struct _literal_info { uint8_t num_related; + bool pinned; } literal_info; #define LITERAL_INFO(n, related) do { \ info[n].num_related = (related); \ } while (0) +#define LITERAL_INFO_PIN(n) do { \ + info[n].num_related = 1; \ + info[n].pinned = true; \ + } while (0) + static uint32_t add_static_slot(HashTable *hash, zend_op_array *op_array, uint32_t op1, @@ -239,6 +245,14 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx } } break; + case ZEND_ENTER_SCOPE_FUNC: { + /* We must preserve the precise ordering of the scope fn literals mapping to CV offsets. */ + uint32_t num_params = opline->op1.num; + for (uint32_t k = 0; k < num_params; k++) { + LITERAL_INFO_PIN(k); + } + break; + } default: if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, 1); @@ -315,7 +329,14 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx map[i] = l_true; break; case IS_LONG: - if (info[i].num_related == 1) { + if (info[i].pinned) { + map[i] = j; + if (i != j) { + op_array->literals[j] = op_array->literals[i]; + info[j] = info[i]; + } + j++; + } else if (info[i].num_related == 1) { if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) { map[i] = Z_LVAL_P(pos); } else { diff --git a/Zend/Optimizer/compact_vars.c b/Zend/Optimizer/compact_vars.c index b4a861d3595c..9f5f515eb2e5 100644 --- a/Zend/Optimizer/compact_vars.c +++ b/Zend/Optimizer/compact_vars.c @@ -18,11 +18,58 @@ #include "zend_bitset.h" #include "zend_observer.h" +/* Recursively include scope fns for shared CVs; apply changes with vars_map, otherwise fill used_vars */ +static void scope_fn_visit_parent_cvs(zend_op_array *scope_op, zend_bitset used_vars, const uint32_t *vars_map) +{ +#define VISIT_CV(slot) if (slot##_type == IS_CV) { \ + uint32_t old = VAR_NUM(slot.var); \ + if (vars_map) slot.var = NUM_VAR(vars_map[old]); \ + else zend_bitset_incl(used_vars, old); \ +} + bool in_body = false; + for (uint32_t i = 0; i < scope_op->last; i++) { + zend_op *opline = &scope_op->opcodes[i]; + if (opline->opcode == ZEND_ENTER_SCOPE_FUNC) { + in_body = true; + /* Mapping is pinned at literals[0..num_params). */ + uint32_t num_params = opline->op1.num; + for (uint32_t j = 0; j < num_params; j++) { + zval *zv = &scope_op->literals[j]; + uint32_t off = (uint32_t)Z_LVAL_P(zv); + if (vars_map) { + ZVAL_LONG(zv, NUM_VAR(vars_map[VAR_NUM(off)])); + } else { + zend_bitset_incl(used_vars, VAR_NUM(off)); + } + } + continue; + } + if (!in_body) { + continue; + } + VISIT_CV(opline->op1); + VISIT_CV(opline->op2); + VISIT_CV(opline->result); + } + for (uint32_t i = 0; i < scope_op->num_dynamic_func_defs; i++) { + zend_op_array *nested = scope_op->dynamic_func_defs[i]; + if (nested->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + scope_fn_visit_parent_cvs(nested, used_vars, vars_map); + } + } +#undef VISIT_CV +} + /* This pass removes all CVs and temporaries that are completely unused. It does *not* merge any CVs or TMPs. * This pass does not operate on SSA form anymore. */ void zend_optimizer_compact_vars(zend_op_array *op_array) { int i; + /* Scope fn CVs are handled by the ancestor. Just skip this here. */ + if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + return; + } + ALLOCA_FLAG(use_heap1); ALLOCA_FLAG(use_heap2); uint32_t used_vars_len = zend_bitset_len(op_array->last_var + op_array->T); @@ -52,6 +99,13 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) { } } + for (i = 0; i < (int)op_array->num_dynamic_func_defs; i++) { + zend_op_array *child = op_array->dynamic_func_defs[i]; + if (child->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + scope_fn_visit_parent_cvs(child, used_vars, NULL); + } + } + num_cvs = 0; for (i = 0; i < op_array->last_var; i++) { if (zend_bitset_in(used_vars, i)) { @@ -93,6 +147,13 @@ void zend_optimizer_compact_vars(zend_op_array *op_array) { } } + for (i = 0; i < (int)op_array->num_dynamic_func_defs; i++) { + zend_op_array *child = op_array->dynamic_func_defs[i]; + if (child->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + scope_fn_visit_parent_cvs(child, NULL, vars_map); + } + } + /* Update CV name table */ if (num_cvs != op_array->last_var) { if (num_cvs) { diff --git a/Zend/Optimizer/optimize_temp_vars_5.c b/Zend/Optimizer/optimize_temp_vars_5.c index 6a5c99f4755b..6493ac19d9d0 100644 --- a/Zend/Optimizer/optimize_temp_vars_5.c +++ b/Zend/Optimizer/optimize_temp_vars_5.c @@ -122,6 +122,11 @@ void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_c } } } + /* DECLARE_SCOPE_FUNC op1 is never consumed, just used for itself. + * It has to simply exist for the whole function, so it gets its own TMP. */ + if (opline->opcode == ZEND_DECLARE_SCOPE_FUNC) { + use_new_var = 1; + } if (use_new_var) { i = ++max; zend_bitset_incl(taken_T, i); diff --git a/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c index bb80e21a2465..efcbe221e11d 100644 --- a/Zend/Optimizer/zend_call_graph.c +++ b/Zend/Optimizer/zend_call_graph.c @@ -25,12 +25,18 @@ static void zend_op_array_calc(zend_op_array *op_array, void *context) { zend_call_graph *call_graph = context; + if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + return; + } call_graph->op_arrays_count++; } static void zend_op_array_collect(zend_op_array *op_array, void *context) { zend_call_graph *call_graph = context; + if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + return; + } zend_func_info *func_info = call_graph->func_infos + call_graph->op_arrays_count; ZEND_SET_FUNC_INFO(op_array, func_info); diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 05d33d3d75fb..fb43a9eb6883 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -4010,9 +4010,12 @@ static zend_always_inline zend_result _zend_update_type_info( UPDATE_SSA_TYPE(MAY_BE_BOOL, ssa_op->result_def); break; case ZEND_DECLARE_LAMBDA_FUNCTION: + case ZEND_DECLARE_SCOPE_FUNC: UPDATE_SSA_TYPE(MAY_BE_OBJECT|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def); UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def); break; + case ZEND_ENTER_SCOPE_FUNC: + break; case ZEND_PRE_DEC_STATIC_PROP: case ZEND_PRE_INC_STATIC_PROP: case ZEND_POST_DEC_STATIC_PROP: diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 245c67c45446..d4c408b0bc33 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -1048,6 +1048,10 @@ zend_op *zend_optimizer_get_loop_var_def(const zend_op_array *op_array, zend_op return NULL; } +static zend_always_inline bool zend_op_array_is_scope_fn(const zend_op_array *op_array) { + return (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) != 0; +} + static void zend_optimize(zend_op_array *op_array, zend_optimizer_ctx *ctx) { @@ -1055,6 +1059,8 @@ static void zend_optimize(zend_op_array *op_array, return; } + bool is_scope_fn = zend_op_array_is_scope_fn(op_array); + if (ctx->debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) { zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "before optimizer", NULL); } @@ -1106,9 +1112,13 @@ static void zend_optimize(zend_op_array *op_array, /* pass 6: * - DFA optimization - */ + * + * Skipped for scope-fn op_arrays: DCE on a body's CV write would + * eliminate a store the parent later reads, but single-op_array SSA + * cannot see that. Re-enabling needs cross-op-array DCE protection + * (treat every body CV write as live-out). */ if ((ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) && - !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level)) { + !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level) && !is_scope_fn) { zend_optimize_dfa(op_array, ctx); if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_6) { zend_dump_op_array(op_array, 0, "after pass 6", NULL); @@ -1141,7 +1151,8 @@ static void zend_optimize(zend_op_array *op_array, */ if ((ZEND_OPTIMIZER_PASS_11 & ctx->optimization_level) && (!(ZEND_OPTIMIZER_PASS_6 & ctx->optimization_level) || - !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level))) { + !(ZEND_OPTIMIZER_PASS_7 & ctx->optimization_level) || + is_scope_fn /* normally passes 6/7 would handle it, but scope fns are exempt */)) { zend_optimizer_compact_literals(op_array, ctx); if (ctx->debug_level & ZEND_DUMP_AFTER_PASS_11) { zend_dump_op_array(op_array, 0, "after pass 11", NULL); @@ -1172,6 +1183,8 @@ static void zend_revert_pass_two(zend_op_array *op_array) ZEND_ASSERT((op_array->fn_flags & ZEND_ACC_DONE_PASS_TWO) != 0); + zend_pass_two_revert_scope_fn_reservations(op_array); + opline = op_array->opcodes; const zend_op *end = opline + op_array->last; while (opline < end) { @@ -1304,6 +1317,7 @@ static void zend_redo_pass_two(zend_op_array *op_array) } op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO; + zend_pass_two_install_scope_fn_reservations(op_array); } static void zend_redo_pass_two_ex(zend_op_array *op_array, const zend_ssa *ssa) @@ -1446,23 +1460,35 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, const zend_ssa *ssa) } op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO; + zend_pass_two_install_scope_fn_reservations(op_array); } static void zend_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx) { + bool is_scope_fn = zend_op_array_is_scope_fn(op_array); + + /* Make scope fns individually optimizeable first, before running optimizer. */ + uint32_t saved_scope_T = is_scope_fn ? op_array->T : 0; + uint32_t saved_scope_ex_offset = is_scope_fn ? zend_unfixup_scope_func_self(op_array, saved_scope_T) : 0; + /* Revert pass_two() */ zend_revert_pass_two(op_array); /* Do actual optimizations */ zend_optimize(op_array, ctx); - /* Redo pass_two() */ - zend_redo_pass_two(op_array); - + /* Live ranges must be recalculated before scope fn fixup. */ if (op_array->live_range) { zend_recalc_live_ranges(op_array, NULL); } + + if (is_scope_fn) { + zend_refixup_scope_func_self(op_array, saved_scope_ex_offset, saved_scope_T); + } + + /* Redo pass_two() */ + zend_redo_pass_two(op_array); } static void zend_adjust_fcall_stack_size(const zend_op_array *op_array, const zend_optimizer_ctx *ctx) @@ -1576,6 +1602,12 @@ static void step_optimize_op_array(zend_op_array *op_array, void *context) { zend_optimize_op_array(op_array, (zend_optimizer_ctx *) context); } +static void step_optimize_scope_fn_op_array(zend_op_array *op_array, void *context) { + if (zend_op_array_is_scope_fn(op_array)) { + zend_optimize_op_array(op_array, (zend_optimizer_ctx *) context); + } +} + static void step_adjust_fcall_stack_size(zend_op_array *op_array, void *context) { zend_adjust_fcall_stack_size(op_array, (zend_optimizer_ctx *) context); } @@ -1634,6 +1666,12 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l } for (i = 0; i < call_graph.op_arrays_count; i++) { + /* Single-op_array SSA can't see scope-fn children's reads of + * parent CVs. Subsequent DFA-based passes (DCE, SCCP) would + * treat parent CVs only used by a child as dead. */ + if (call_graph.op_arrays[i]->fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES) { + continue; + } func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (func_info) { if (zend_dfa_analyze_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa) == SUCCESS) { @@ -1646,6 +1684,9 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l //TODO: perform inner-script inference??? for (i = 0; i < call_graph.op_arrays_count; i++) { + if (call_graph.op_arrays[i]->fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES) { + continue; + } func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (func_info) { zend_dfa_optimize_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa, func_info->call_map); @@ -1685,14 +1726,9 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l } } - if (ZEND_OPTIMIZER_PASS_12 & optimization_level) { - for (i = 0; i < call_graph.op_arrays_count; i++) { - zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]); - } - } - for (i = 0; i < call_graph.op_arrays_count; i++) { op_array = call_graph.op_arrays[i]; + zend_op *old_opcodes = op_array->opcodes; func_info = ZEND_FUNC_INFO(op_array); if (func_info && func_info->ssa.var_info) { zend_redo_pass_two_ex(op_array, &func_info->ssa); @@ -1705,6 +1741,31 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l zend_recalc_live_ranges(op_array, NULL); } } + /* Update offsets for pass 12, as zend_redo_pass_two may reallocate */ + if (func_info && op_array->opcodes != old_opcodes) { + ptrdiff_t delta = op_array->opcodes - old_opcodes; + zend_call_info *call = func_info->callee_info; + while (call) { + if (call->caller_init_opline) { + call->caller_init_opline += delta; + } + if (call->caller_call_opline) { + call->caller_call_opline += delta; + } + call = call->next_callee; + } + } + } + + /* zend_build_call_graph ignores scope fns, so explicitely optimize them here. */ + zend_foreach_op_array(script, step_optimize_scope_fn_op_array, &ctx); + + /* PASS_12 requires knowledge of the final callee->T to compute INIT_FCALL stack sizes; + * for scope fns these change during zend_redo_pass_two, hence we do this late. */ + if (ZEND_OPTIMIZER_PASS_12 & optimization_level) { + for (i = 0; i < call_graph.op_arrays_count; i++) { + zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]); + } } for (i = 0; i < call_graph.op_arrays_count; i++) { diff --git a/Zend/tests/function_arguments/gh20435_2.phpt b/Zend/tests/function_arguments/gh20435_2.phpt index a58c4d6be1ca..202d02ff59b6 100644 --- a/Zend/tests/function_arguments/gh20435_2.phpt +++ b/Zend/tests/function_arguments/gh20435_2.phpt @@ -1,5 +1,5 @@ --TEST-- -GH-20435: ZEND_CALL_HAS_EXTRA_NAMED_PARAMS & magic methods in debug_backtrace_get_args() +GH-20435: ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS & magic methods in debug_backtrace_get_args() --FILE-- +--EXPECTF-- +{closure:%s} +2 +test diff --git a/Zend/tests/scope_fn/backtrace_named_args.phpt b/Zend/tests/scope_fn/backtrace_named_args.phpt new file mode 100644 index 000000000000..48be6399b807 --- /dev/null +++ b/Zend/tests/scope_fn/backtrace_named_args.phpt @@ -0,0 +1,52 @@ +--TEST-- +debug_backtrace() includes extra named args for scope functions +--FILE-- + +--EXPECT-- +array(4) { + [0]=> + int(1) + [1]=> + int(2) + ["name1"]=> + string(5) "hello" + ["name2"]=> + string(5) "world" +} +array(1) { + ["only"]=> + string(5) "named" +} +array(2) { + [0]=> + int(1) + [1]=> + int(2) +} diff --git a/Zend/tests/scope_fn/basic_read_parent_var.phpt b/Zend/tests/scope_fn/basic_read_parent_var.phpt new file mode 100644 index 000000000000..24f1f2771f09 --- /dev/null +++ b/Zend/tests/scope_fn/basic_read_parent_var.phpt @@ -0,0 +1,13 @@ +--TEST-- +Scope function reads parent variable +--FILE-- + +--EXPECT-- +int(52) diff --git a/Zend/tests/scope_fn/bind_disallowed.phpt b/Zend/tests/scope_fn/bind_disallowed.phpt new file mode 100644 index 000000000000..6951baf181cc --- /dev/null +++ b/Zend/tests/scope_fn/bind_disallowed.phpt @@ -0,0 +1,19 @@ +--TEST-- +Scope function closure: $this rebind to a different object is rejected +--FILE-- +getMessage() . "\n"; + } +} +test(); +?> +--EXPECT-- +Cannot rebind $this of a scope function diff --git a/Zend/tests/scope_fn/bind_scope.phpt b/Zend/tests/scope_fn/bind_scope.phpt new file mode 100644 index 000000000000..00c506fef481 --- /dev/null +++ b/Zend/tests/scope_fn/bind_scope.phpt @@ -0,0 +1,37 @@ +--TEST-- +Scope function closure: scope rebind to a compatible scope mutates in place +--FILE-- +getClosureScopeClass()?->name); + + /* Rebind to incompatible internal class fails (warning today, error in PHP 9). */ + try { + $r = Closure::bind($fn, null, ArrayObject::class); + var_dump($r); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} +test(); +?> +--EXPECTF-- +bool(true) +string(1) "A" + +Warning: Cannot bind closure to scope of internal class ArrayObject, this will be an error in PHP 9 in %s on line %d +NULL diff --git a/Zend/tests/scope_fn/clone_disallowed.phpt b/Zend/tests/scope_fn/clone_disallowed.phpt new file mode 100644 index 000000000000..6665d97a24bb --- /dev/null +++ b/Zend/tests/scope_fn/clone_disallowed.phpt @@ -0,0 +1,16 @@ +--TEST-- +Scope function closure cannot be cloned +--FILE-- +getMessage() . "\n"; + } +} +test(); +?> +--EXPECT-- +Cannot clone a scope function closure diff --git a/Zend/tests/scope_fn/compact_extract.phpt b/Zend/tests/scope_fn/compact_extract.phpt new file mode 100644 index 000000000000..ad3597a7dfd8 --- /dev/null +++ b/Zend/tests/scope_fn/compact_extract.phpt @@ -0,0 +1,28 @@ +--TEST-- +compact() and extract() inside scope function +--FILE-- + 10, 'y' => 20]); }; + $fn(); + var_dump($x, $y); +} +test_extract(); +?> +--EXPECT-- +array(2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +int(10) +int(20) diff --git a/Zend/tests/scope_fn/default_params.phpt b/Zend/tests/scope_fn/default_params.phpt new file mode 100644 index 000000000000..76a2bd0af604 --- /dev/null +++ b/Zend/tests/scope_fn/default_params.phpt @@ -0,0 +1,16 @@ +--TEST-- +Scope function with default parameter values +--FILE-- + +--EXPECT-- +int(15) +int(11) +int(3) diff --git a/Zend/tests/scope_fn/error_path_cleanup.phpt b/Zend/tests/scope_fn/error_path_cleanup.phpt new file mode 100644 index 000000000000..d231ef9dd7db --- /dev/null +++ b/Zend/tests/scope_fn/error_path_cleanup.phpt @@ -0,0 +1,27 @@ +--TEST-- +Error paths in scope function don't leak VM stack frames +--FILE-- + +--EXPECT-- +lifetime: ok +recursion: ok diff --git a/Zend/tests/scope_fn/escape_error.phpt b/Zend/tests/scope_fn/escape_error.phpt new file mode 100644 index 000000000000..f8f3d000990b --- /dev/null +++ b/Zend/tests/scope_fn/escape_error.phpt @@ -0,0 +1,15 @@ +--TEST-- +Scope function throws Error when escaping the declaring scope +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +Scope function closure must not outlive the declaring scope diff --git a/Zend/tests/scope_fn/escape_in_conditional.phpt b/Zend/tests/scope_fn/escape_in_conditional.phpt new file mode 100644 index 000000000000..218c00a34698 --- /dev/null +++ b/Zend/tests/scope_fn/escape_in_conditional.phpt @@ -0,0 +1,14 @@ +--TEST-- +Scope function in conditional branch that is not taken does not crash +--FILE-- + +--EXPECT-- +string(2) "ok" diff --git a/Zend/tests/scope_fn/escape_via_global.phpt b/Zend/tests/scope_fn/escape_via_global.phpt new file mode 100644 index 000000000000..89a7ec17da8b --- /dev/null +++ b/Zend/tests/scope_fn/escape_via_global.phpt @@ -0,0 +1,16 @@ +--TEST-- +Scope function throws Error when stored in global +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +Scope function closure must not outlive the declaring scope diff --git a/Zend/tests/scope_fn/escape_with_extra_args.phpt b/Zend/tests/scope_fn/escape_with_extra_args.phpt new file mode 100644 index 000000000000..41af55a4253e --- /dev/null +++ b/Zend/tests/scope_fn/escape_with_extra_args.phpt @@ -0,0 +1,15 @@ +--TEST-- +Escape detection works with extra args +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +Scope function closure must not outlive the declaring scope diff --git a/Zend/tests/scope_fn/exception_in_scope_fn.phpt b/Zend/tests/scope_fn/exception_in_scope_fn.phpt new file mode 100644 index 000000000000..1cc3244f7146 --- /dev/null +++ b/Zend/tests/scope_fn/exception_in_scope_fn.phpt @@ -0,0 +1,19 @@ +--TEST-- +Exception thrown inside scope function propagates to caller +--FILE-- +getMessage() . "\n"; + } + var_dump($x); +} +test(); +?> +--EXPECT-- +Caught: boom +int(42) diff --git a/Zend/tests/scope_fn/fiber_exit_in_unwind_finally.phpt b/Zend/tests/scope_fn/fiber_exit_in_unwind_finally.phpt new file mode 100644 index 000000000000..1be4312d0a23 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_exit_in_unwind_finally.phpt @@ -0,0 +1,26 @@ +--TEST-- +exit() in a finally block of a fiber being force-unwound by scope-fn must not be swallowed +--FILE-- +start()); +} +try { outer(); } catch (Throwable $e) { echo "MUST NOT REACH (caught): ", $e->getMessage(), "\n"; } +$fiber = null; +echo "MUST NOT REACH (after)\n"; +?> +--EXPECT-- +string(6) "paused" +finally entered +EXIT FROM FINALLY diff --git a/Zend/tests/scope_fn/fiber_holds_unused_scope_fn.phpt b/Zend/tests/scope_fn/fiber_holds_unused_scope_fn.phpt new file mode 100644 index 000000000000..a3c7aa850b18 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_holds_unused_scope_fn.phpt @@ -0,0 +1,28 @@ +--TEST-- +Fiber captures a scope-fn closure but never starts: outer exit fires escape Error +--FILE-- +start() */ +} + +try { + outer(); + echo "no error?\n"; +} catch (Error $e) { + echo "caught: ", $e->getMessage(), "\n"; +} + +/* Fiber is in INIT state; safe to drop. */ +var_dump($fiber === null ? "null" : get_class($fiber)); +$fiber = null; +echo "done\n"; +?> +--EXPECT-- +caught: Scope function closure must not outlive the declaring scope +string(5) "Fiber" +done diff --git a/Zend/tests/scope_fn/fiber_inner_scope_fn.phpt b/Zend/tests/scope_fn/fiber_inner_scope_fn.phpt new file mode 100644 index 000000000000..dbd120b17e86 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_inner_scope_fn.phpt @@ -0,0 +1,23 @@ +--TEST-- +Scope fn declared inside a Fiber's wrapper closure: parent on fiber's vm_stack +--FILE-- +start()); // string(1) "a" +var_dump($fiber->resume()); // NULL (fiber returned 2, not via suspend) +var_dump($fiber->getReturn()); // int(2) +?> +--EXPECT-- +string(1) "a" +NULL +int(2) diff --git a/Zend/tests/scope_fn/fiber_multi_suspend.phpt b/Zend/tests/scope_fn/fiber_multi_suspend.phpt new file mode 100644 index 000000000000..447de259feb2 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_multi_suspend.phpt @@ -0,0 +1,41 @@ +--TEST-- +Cross-stack scope fn: multiple suspend / resume cycles before parent exits +--FILE-- +start()); // s1 + var_dump($fiber->resume()); // s2 + var_dump($fiber->resume()); // NULL (fiber returned) + var_dump($log); +} +try { + outer(); + echo "no error\n"; +} catch (Error $e) { + echo "unexpected: ", $e->getMessage(), "\n"; +} +echo "done\n"; +?> +--EXPECT-- +string(2) "s1" +string(2) "s2" +NULL +array(3) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" + [2]=> + string(1) "c" +} +no error +done diff --git a/Zend/tests/scope_fn/fiber_outlives_parent.phpt b/Zend/tests/scope_fn/fiber_outlives_parent.phpt new file mode 100644 index 000000000000..0b9485c05101 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_outlives_parent.phpt @@ -0,0 +1,61 @@ +--TEST-- +Cross-stack scope fn used as fiber body: parent exit drives forced unwind +--FILE-- +start()); // string(6) "paused" +} + +try { + outer(); + echo "no error?\n"; +} catch (Error $e) { + echo "caught at outer return: ", $e->getMessage(), "\n"; +} + +/* Pollute the vm_stack region outer used (where scope_ex lived) so any + * stale pointer the fiber retained would land in overwritten memory. */ +function churn(int $depth): int { + $a=1; $b=2; $c=3; $d=4; $e=5; $f=6; $g=7; $h=8; + $i=9; $j=10; $k=11; $l=12; $m=13; $n=14; $o=15; $p=16; + $q=17; $r=18; $s=19; $t=20; $u=21; $v=22; $w=23; $x=24; + if ($depth > 0) return churn($depth - 1) + $a + $x; + return $a + $b + $c + $d + $e + $f + $g + $h + + $i + $j + $k + $l + $m + $n + $o + $p + + $q + $r + $s + $t + $u + $v + $w + $x; +} +for ($iter = 0; $iter < 100; $iter++) churn(50); + +/* Fiber was force-unwound through scope_ex at outer's exit; the visible + * escape Error is stashed on the fiber and surfaces here. */ +try { + $fiber->resume(); + echo "no resume error?\n"; +} catch (Throwable $e) { + echo "caught at resume: ", $e->getMessage(), "\n"; +} + +/* A subsequent resume gets the regular "not suspended" error (the deferred + * throw is one-shot). */ +try { + $fiber->resume(); +} catch (Throwable $e) { + echo "caught at second resume: ", $e->getMessage(), "\n"; +} + +$fiber = null; +echo "done\n"; +?> +--EXPECT-- +string(6) "paused" +caught at outer return: Scope function closure must not outlive the declaring scope +caught at resume: Scope function closure must not outlive the declaring scope +caught at second resume: Cannot resume a fiber that is not suspended +done diff --git a/Zend/tests/scope_fn/fiber_panic_resuspend_in_unwind.phpt b/Zend/tests/scope_fn/fiber_panic_resuspend_in_unwind.phpt new file mode 100644 index 000000000000..4c90a74bc288 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_panic_resuspend_in_unwind.phpt @@ -0,0 +1,34 @@ +--TEST-- +Forced unwind through a fiber's scope-fn body runs only finally blocks; sentinel is invisible to catch +--FILE-- +getMessage(), "\n"; + } finally { + echo "finally fired\n"; + } + echo "after try (unreachable)\n"; + } catch (Throwable $e) { + echo "outer catch fired (unreachable): ", $e->getMessage(), "\n"; + } + }; + $fiber = new Fiber($fn); + var_dump($fiber->start()); // string(6) "paused" +} +try { outer(); } catch (Error $e) { echo "outer escape: ", $e->getMessage(), "\n"; } +$fiber = null; +echo "done\n"; +?> +--EXPECT-- +string(6) "paused" +finally fired +outer escape: Scope function closure must not outlive the declaring scope +done diff --git a/Zend/tests/scope_fn/fiber_resuspend_in_unwind.phpt b/Zend/tests/scope_fn/fiber_resuspend_in_unwind.phpt new file mode 100644 index 000000000000..621d617b0fe0 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_resuspend_in_unwind.phpt @@ -0,0 +1,40 @@ +--TEST-- +Fiber::suspend called from a finally block during scope-fn force-unwind throws FiberError +--FILE-- +getMessage(), "\n"; + } + echo "finally exiting\n"; + } + }; + $fiber = new Fiber($fn); + var_dump($fiber->start()); // "paused" +} + +try { + outer(); +} catch (Error $e) { + echo "outer: ", $e->getMessage(), "\n"; +} +$fiber = null; +echo "done\n"; +?> +--EXPECT-- +string(6) "paused" +finally entered +caught: Cannot suspend in a force-closed fiber +finally exiting +outer: Scope function closure must not outlive the declaring scope +done diff --git a/Zend/tests/scope_fn/fiber_suspend_in_inner_generator.phpt b/Zend/tests/scope_fn/fiber_suspend_in_inner_generator.phpt new file mode 100644 index 000000000000..9536f01c2dd3 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_suspend_in_inner_generator.phpt @@ -0,0 +1,54 @@ +--TEST-- +Forced unwind transits through a non-scope-fn generator's resume to reach the surrounding scope_fn boundary +--FILE-- +current(); + } finally { + echo "scope_fn finally\n"; + } + echo "unreachable\n"; + }; + $fiber = new Fiber($sfn); + var_dump($fiber->start()); +} + +try { + outer(); +} catch (Error $e) { + echo "outer caught: ", $e->getMessage(), "\n"; +} + +try { + var_dump($fiber->resume()); +} catch (Throwable $e) { + echo "resume caught: ", $e::class, ": ", $e->getMessage(), "\n"; +} +echo "done\n"; +?> +--EXPECT-- +string(16) "from-regular-gen" +regular_gen finally +scope_fn finally +outer caught: Scope function closure must not outlive the declaring scope +resume caught: Error: Scope function closure must not outlive the declaring scope +done diff --git a/Zend/tests/scope_fn/fiber_suspend_inside_scope_fn.phpt b/Zend/tests/scope_fn/fiber_suspend_inside_scope_fn.phpt new file mode 100644 index 000000000000..98b73be85224 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_suspend_inside_scope_fn.phpt @@ -0,0 +1,47 @@ +--TEST-- +Fiber::suspend called directly from within a scope-fn body (non-generator), then parent exits +--FILE-- +start()); // string(9) "inside-fn" +} + +try { + outer(); +} catch (Error $e) { + echo "outer: ", $e->getMessage(), "\n"; +} + +var_dump($fiber->resume()); // null — fiber body finished after catch + +$fiber = null; +echo "done\n"; +?> +--EXPECTF-- +string(9) "inside-fn" +outer: Scope function closure must not outlive the declaring scope +Error: Scope function closure must not outlive the declaring scope in %sfiber_suspend_inside_scope_fn.php:%d +Stack trace: +#0 %sfiber_suspend_inside_scope_fn.php(%d): {closure:%s}() +#1 [internal function]: {closure:%s}() +#2 %sfiber_suspend_inside_scope_fn.php(%d): unknown() +#3 {main} +NULL +done diff --git a/Zend/tests/scope_fn/fiber_within_parent.phpt b/Zend/tests/scope_fn/fiber_within_parent.phpt new file mode 100644 index 000000000000..2e3dacfba779 --- /dev/null +++ b/Zend/tests/scope_fn/fiber_within_parent.phpt @@ -0,0 +1,32 @@ +--TEST-- +Scope function runs inside a Fiber and is fully resumed before parent exits +--FILE-- +start()); + var_dump($x); + var_dump($fiber->resume()); + var_dump($x); + $fiber->resume(); + var_dump($x); + /* Fiber finished -> only this scope holds $fn, no escape. */ +} +outer(); +echo "ok\n"; +?> +--EXPECT-- +string(1) "a" +int(1) +string(1) "b" +int(2) +int(3) +ok diff --git a/Zend/tests/scope_fn/func_get_arg.phpt b/Zend/tests/scope_fn/func_get_arg.phpt new file mode 100644 index 000000000000..428b3432432a --- /dev/null +++ b/Zend/tests/scope_fn/func_get_arg.phpt @@ -0,0 +1,25 @@ +--TEST-- +func_get_arg() inside scope function including extra args +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + int(10) + [1]=> + int(20) + [2]=> + int(30) +} diff --git a/Zend/tests/scope_fn/func_get_args.phpt b/Zend/tests/scope_fn/func_get_args.phpt new file mode 100644 index 000000000000..e82eb9bacf6c --- /dev/null +++ b/Zend/tests/scope_fn/func_get_args.phpt @@ -0,0 +1,32 @@ +--TEST-- +func_get_args() inside scope function including extra args +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + int(10) + [1]=> + int(20) +} +array(4) { + [0]=> + int(10) + [1]=> + int(20) + [2]=> + int(30) + [3]=> + int(40) +} diff --git a/Zend/tests/scope_fn/include_escape_via_return.inc b/Zend/tests/scope_fn/include_escape_via_return.inc new file mode 100644 index 000000000000..2a3ef800154e --- /dev/null +++ b/Zend/tests/scope_fn/include_escape_via_return.inc @@ -0,0 +1,3 @@ +getMessage(), "\n"; +} + +// 3. Escape via storage outside the include's symbol table (a class +// static property survives the include's frame teardown). +try { + include __DIR__ . '/include_escape_via_static.inc'; + echo "MUST NOT REACH (static)\n"; +} catch (Error $e) { + echo "static: ", $e->getMessage(), "\n"; +} +?> +--EXPECT-- +in include, fn() = 7 +include 1 returned +bool(false) +return: Scope function closure must not outlive the declaring scope +static: Scope function closure must not outlive the declaring scope diff --git a/Zend/tests/scope_fn/lifetime_error.phpt b/Zend/tests/scope_fn/lifetime_error.phpt new file mode 100644 index 000000000000..314d85ba27b1 --- /dev/null +++ b/Zend/tests/scope_fn/lifetime_error.phpt @@ -0,0 +1,15 @@ +--TEST-- +Scope function throws when escaping the declaring scope +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +Scope function closure must not outlive the declaring scope diff --git a/Zend/tests/scope_fn/loop_reinvalidation.phpt b/Zend/tests/scope_fn/loop_reinvalidation.phpt new file mode 100644 index 000000000000..cb7ae81d7e8d --- /dev/null +++ b/Zend/tests/scope_fn/loop_reinvalidation.phpt @@ -0,0 +1,24 @@ +--TEST-- +Re-evaluating scope function in loop invalidates previous instance +--FILE-- +getMessage() . "\n"; + } +} +test(); +?> +--EXPECT-- +int(3) +Cannot call scope function: defining scope has exited diff --git a/Zend/tests/scope_fn/multiple_calls.phpt b/Zend/tests/scope_fn/multiple_calls.phpt new file mode 100644 index 000000000000..dbdf5bd20394 --- /dev/null +++ b/Zend/tests/scope_fn/multiple_calls.phpt @@ -0,0 +1,16 @@ +--TEST-- +Scope function can be called multiple times +--FILE-- + +--EXPECT-- +int(3) diff --git a/Zend/tests/scope_fn/multiple_scope_fns.phpt b/Zend/tests/scope_fn/multiple_scope_fns.phpt new file mode 100644 index 000000000000..e274707d9bea --- /dev/null +++ b/Zend/tests/scope_fn/multiple_scope_fns.phpt @@ -0,0 +1,16 @@ +--TEST-- +Multiple scope functions in same parent +--FILE-- + +--EXPECT-- +int(15) +int(50) diff --git a/Zend/tests/scope_fn/multiple_scope_fns_escape.phpt b/Zend/tests/scope_fn/multiple_scope_fns_escape.phpt new file mode 100644 index 000000000000..5bbede60f07f --- /dev/null +++ b/Zend/tests/scope_fn/multiple_scope_fns_escape.phpt @@ -0,0 +1,21 @@ +--TEST-- +Multiple scope functions - one escapes +--FILE-- +getMessage() . "\n"; +} +?> +--EXPECT-- +int(6) +int(10) +Scope function closure must not outlive the declaring scope diff --git a/Zend/tests/scope_fn/mutation.phpt b/Zend/tests/scope_fn/mutation.phpt new file mode 100644 index 000000000000..49d6866b2052 --- /dev/null +++ b/Zend/tests/scope_fn/mutation.phpt @@ -0,0 +1,14 @@ +--TEST-- +Scope function modifies parent variable +--FILE-- + +--EXPECT-- +int(99) diff --git a/Zend/tests/scope_fn/nested_mutation.phpt b/Zend/tests/scope_fn/nested_mutation.phpt new file mode 100644 index 000000000000..f7ea4f6f715d --- /dev/null +++ b/Zend/tests/scope_fn/nested_mutation.phpt @@ -0,0 +1,17 @@ +--TEST-- +Nested scope function mutates grandparent variable +--FILE-- + +--EXPECT-- +int(42) diff --git a/Zend/tests/scope_fn/nested_scope_fn.phpt b/Zend/tests/scope_fn/nested_scope_fn.phpt new file mode 100644 index 000000000000..6602a43e532d --- /dev/null +++ b/Zend/tests/scope_fn/nested_scope_fn.phpt @@ -0,0 +1,19 @@ +--TEST-- +Nested scope functions share CVs transitively with top-level parent +--FILE-- + +--EXPECT-- +int(125) diff --git a/Zend/tests/scope_fn/new_var_visible_in_parent.phpt b/Zend/tests/scope_fn/new_var_visible_in_parent.phpt new file mode 100644 index 000000000000..d365706ccd2d --- /dev/null +++ b/Zend/tests/scope_fn/new_var_visible_in_parent.phpt @@ -0,0 +1,13 @@ +--TEST-- +New variable created in scope function is visible in parent +--FILE-- + +--EXPECT-- +string(5) "hello" diff --git a/Zend/tests/scope_fn/no_args.phpt b/Zend/tests/scope_fn/no_args.phpt new file mode 100644 index 000000000000..d54fc568abc4 --- /dev/null +++ b/Zend/tests/scope_fn/no_args.phpt @@ -0,0 +1,13 @@ +--TEST-- +Scope function with no arguments +--FILE-- + +--EXPECT-- +int(42) diff --git a/Zend/tests/scope_fn/no_escape_ok.phpt b/Zend/tests/scope_fn/no_escape_ok.phpt new file mode 100644 index 000000000000..56ed07c1c019 --- /dev/null +++ b/Zend/tests/scope_fn/no_escape_ok.phpt @@ -0,0 +1,14 @@ +--TEST-- +Scope function that doesn't escape is fine +--FILE-- + +--EXPECT-- +int(42) +ok diff --git a/Zend/tests/scope_fn/parent_cv_written_during_destructor.phpt b/Zend/tests/scope_fn/parent_cv_written_during_destructor.phpt new file mode 100644 index 000000000000..cbf3fa13c6ca --- /dev/null +++ b/Zend/tests/scope_fn/parent_cv_written_during_destructor.phpt @@ -0,0 +1,31 @@ +--TEST-- +A CV destructor that re-enters the parent's scope-fn body must not leak CVs auto-registered there +--FILE-- +start(); + /* outer() exits: $fiber CV release → fiber dtor → body finally → catch + * writes $e. No leak should result. */ +} +outer(); +echo "ok\n"; +?> +--EXPECT-- +ok diff --git a/Zend/tests/scope_fn/pass_as_callback.phpt b/Zend/tests/scope_fn/pass_as_callback.phpt new file mode 100644 index 000000000000..f61948cc582a --- /dev/null +++ b/Zend/tests/scope_fn/pass_as_callback.phpt @@ -0,0 +1,20 @@ +--TEST-- +Scope function passed as callback to array_map +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + int(3) + [1]=> + int(6) + [2]=> + int(9) +} diff --git a/Zend/tests/scope_fn/recursion_error.phpt b/Zend/tests/scope_fn/recursion_error.phpt new file mode 100644 index 000000000000..7ce3507dd952 --- /dev/null +++ b/Zend/tests/scope_fn/recursion_error.phpt @@ -0,0 +1,16 @@ +--TEST-- +Scope function throws on recursive call +--FILE-- +getMessage() . "\n"; + } +} +test(); +?> +--EXPECT-- +Cannot recursively call scope function diff --git a/Zend/tests/scope_fn/reflection.phpt b/Zend/tests/scope_fn/reflection.phpt new file mode 100644 index 000000000000..cc6f81de4ad6 --- /dev/null +++ b/Zend/tests/scope_fn/reflection.phpt @@ -0,0 +1,34 @@ +--TEST-- +Reflection on scope function closures +--FILE-- +isClosure()); + var_dump($r->isScopeFunction()); + var_dump($r->getNumberOfParameters()); + var_dump($r->getNumberOfRequiredParameters()); + var_dump($r->getReturnType()->getName()); + + // Regular closure should return false for isScopeFunction + $regular = function() {}; + $r2 = new ReflectionFunction($regular); + var_dump($r2->isScopeFunction()); + + // Arrow function should return false too + $arrow = fn() => 1; + $r3 = new ReflectionFunction($arrow); + var_dump($r3->isScopeFunction()); +} +test(); +?> +--EXPECT-- +bool(true) +bool(true) +int(2) +int(1) +string(4) "bool" +bool(false) +bool(false) diff --git a/Zend/tests/scope_fn/return_parent_cv.phpt b/Zend/tests/scope_fn/return_parent_cv.phpt new file mode 100644 index 000000000000..c3e78d6c35f7 --- /dev/null +++ b/Zend/tests/scope_fn/return_parent_cv.phpt @@ -0,0 +1,18 @@ +--TEST-- +Returning a refcounted parent CV from a scope-fn body must not null the parent CV +--FILE-- +v = 'hello'; + $f = fn() { + return $x; /* parent CV; ZEND_RETURN's cv-to-result move would null $x without the OBSERVED guard */ + }; + $r = $f(); + var_dump($x->v); // must still be 'hello' — parent CV not nulled + var_dump($r === $x); // result is same object +} +test(); +--EXPECT-- +string(5) "hello" +bool(true) diff --git a/Zend/tests/scope_fn/return_type.phpt b/Zend/tests/scope_fn/return_type.phpt new file mode 100644 index 000000000000..323b074e5bb2 --- /dev/null +++ b/Zend/tests/scope_fn/return_type.phpt @@ -0,0 +1,16 @@ +--TEST-- +Scope function with return type declaration +--FILE-- + +--EXPECT-- +int(42) +string(5) "hello" diff --git a/Zend/tests/scope_fn/return_value.phpt b/Zend/tests/scope_fn/return_value.phpt new file mode 100644 index 000000000000..165438b1dfa4 --- /dev/null +++ b/Zend/tests/scope_fn/return_value.phpt @@ -0,0 +1,14 @@ +--TEST-- +Scope function returns a value +--FILE-- + +--EXPECT-- +int(35) diff --git a/Zend/tests/scope_fn/static_disallowed.phpt b/Zend/tests/scope_fn/static_disallowed.phpt new file mode 100644 index 000000000000..4368c9ff3532 --- /dev/null +++ b/Zend/tests/scope_fn/static_disallowed.phpt @@ -0,0 +1,10 @@ +--TEST-- +static fn() {} is disallowed for scope functions +--FILE-- + +--EXPECTF-- +Fatal error: Scope functions cannot be static in %s on line %d diff --git a/Zend/tests/scope_fn/throwing_destructor_on_arg_assign.phpt b/Zend/tests/scope_fn/throwing_destructor_on_arg_assign.phpt new file mode 100644 index 000000000000..5ff45063d9c1 --- /dev/null +++ b/Zend/tests/scope_fn/throwing_destructor_on_arg_assign.phpt @@ -0,0 +1,34 @@ +--TEST-- +ENTER_SCOPE_FUNC arg→CV remap is exception-safe when a parent CV's destructor throws +--FILE-- +getMessage(), "\n"; + } + /* dst was set to 42 BEFORE old.dtor ran. So $x is now 42, even though + * the dtor threw. */ + var_dump($x); +} +go(); +--EXPECTF-- +caught: dtor boom +int(42) diff --git a/Zend/tests/scope_fn/too_few_args.phpt b/Zend/tests/scope_fn/too_few_args.phpt new file mode 100644 index 000000000000..ccb5382f27f6 --- /dev/null +++ b/Zend/tests/scope_fn/too_few_args.phpt @@ -0,0 +1,18 @@ +--TEST-- +Too few arguments to scope function +--FILE-- +getMessage() . "\n"; + } + echo "ok\n"; +} +test(); +?> +--EXPECTF-- +Caught: Too few arguments to function %s, 1 passed %s and exactly 2 expected +ok diff --git a/Zend/tests/scope_fn/type_error.phpt b/Zend/tests/scope_fn/type_error.phpt new file mode 100644 index 000000000000..708aa2e73002 --- /dev/null +++ b/Zend/tests/scope_fn/type_error.phpt @@ -0,0 +1,18 @@ +--TEST-- +Type error in scope function parameter is catchable +--FILE-- + +--EXPECT-- +Caught TypeError +ok diff --git a/Zend/tests/scope_fn/typed_params.phpt b/Zend/tests/scope_fn/typed_params.phpt new file mode 100644 index 000000000000..9cf19ea9436a --- /dev/null +++ b/Zend/tests/scope_fn/typed_params.phpt @@ -0,0 +1,12 @@ +--TEST-- +Scope function with typed parameters +--FILE-- + +--EXPECT-- +string(6) "val=42" diff --git a/Zend/tests/scope_fn/undefined_parent_var.phpt b/Zend/tests/scope_fn/undefined_parent_var.phpt new file mode 100644 index 000000000000..0d37cb7e36ea --- /dev/null +++ b/Zend/tests/scope_fn/undefined_parent_var.phpt @@ -0,0 +1,31 @@ +--TEST-- +Reading an undefined parent CV from a scope-fn body emits the warning with the correct variable name +--FILE-- + +--EXPECTF-- +Warning: Undefined variable $i in %sundefined_parent_var.php on line %d +NULL + +Warning: Undefined variable $missing_param in %sundefined_parent_var.php on line %d +NULL +done diff --git a/Zend/tests/scope_fn/use_clause_error.phpt b/Zend/tests/scope_fn/use_clause_error.phpt new file mode 100644 index 000000000000..e90c0c7096ad --- /dev/null +++ b/Zend/tests/scope_fn/use_clause_error.phpt @@ -0,0 +1,11 @@ +--TEST-- +fn() use() {} is disallowed for scope functions (parse error) +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "use", expecting %s in %s on line %d diff --git a/Zend/tests/scope_fn/var_dump_closure.phpt b/Zend/tests/scope_fn/var_dump_closure.phpt new file mode 100644 index 000000000000..7564a1dd04bf --- /dev/null +++ b/Zend/tests/scope_fn/var_dump_closure.phpt @@ -0,0 +1,24 @@ +--TEST-- +var_dump of scope function closure +--FILE-- + +--EXPECTF-- +object(Closure)#%d (%d) { + ["name"]=> + string(%d) "{closure:%s:%d}" + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["parameter"]=> + array(1) { + ["$a"]=> + string(10) "" + } +} diff --git a/Zend/tests/scope_fn/variable_variables.phpt b/Zend/tests/scope_fn/variable_variables.phpt new file mode 100644 index 000000000000..347b32661098 --- /dev/null +++ b/Zend/tests/scope_fn/variable_variables.phpt @@ -0,0 +1,13 @@ +--TEST-- +Variable variables inside scope function access parent scope +--FILE-- + +--EXPECT-- +int(42) diff --git a/Zend/tests/scope_fn/variadic_args.phpt b/Zend/tests/scope_fn/variadic_args.phpt new file mode 100644 index 000000000000..6a4464261774 --- /dev/null +++ b/Zend/tests/scope_fn/variadic_args.phpt @@ -0,0 +1,12 @@ +--TEST-- +Scope function with variadic parameters +--FILE-- + +--EXPECT-- +int(6) diff --git a/Zend/tests/scope_fn/varvar.phpt b/Zend/tests/scope_fn/varvar.phpt new file mode 100644 index 000000000000..ad60fa6a85be --- /dev/null +++ b/Zend/tests/scope_fn/varvar.phpt @@ -0,0 +1,38 @@ +--TEST-- +Variable variables ($$name) inside a scope-fn body resolve against the parent's CVs +--FILE-- + +--EXPECT-- +int(1) +int(99) +int(2) +string(7) "created" +done diff --git a/Zend/tests/scope_fn/vm_stack_leak.phpt b/Zend/tests/scope_fn/vm_stack_leak.phpt new file mode 100644 index 000000000000..a84880ceabec --- /dev/null +++ b/Zend/tests/scope_fn/vm_stack_leak.phpt @@ -0,0 +1,14 @@ +--TEST-- +Repeated scope function calls don't leak VM stack frames +--FILE-- + +--EXPECT-- +int(49995000) diff --git a/Zend/tests/scope_fn/yield_basic.phpt b/Zend/tests/scope_fn/yield_basic.phpt new file mode 100644 index 000000000000..1e9413f8d7d1 --- /dev/null +++ b/Zend/tests/scope_fn/yield_basic.phpt @@ -0,0 +1,28 @@ +--TEST-- +Scope function with yield: basic generator behavior, accessing parent CVs +--FILE-- + +--EXPECT-- +got: 10 +got: 11 +got: 12 +x after: 12 +done diff --git a/Zend/tests/scope_fn/yield_fiber_suspend_inside_body.phpt b/Zend/tests/scope_fn/yield_fiber_suspend_inside_body.phpt new file mode 100644 index 000000000000..78c749fbc264 --- /dev/null +++ b/Zend/tests/scope_fn/yield_fiber_suspend_inside_body.phpt @@ -0,0 +1,79 @@ +--TEST-- +Fiber::suspend called from within a scope-fn generator's body, then parent exits +--FILE-- +current()); // 1 + + $fiber = new Fiber(function () use ($g) { + try { + $g->next(); + echo "after next (unreachable)\n"; + } catch (Throwable $e) { + /* The throw materializes here, inside the fiber, at the + * suspension resumption point ($g->next() returns into the + * fiber with the Error in flight). The stacktrace shows the + * Error was created inside the generator's body. */ + echo $e, "\n"; + } + }); + /* Wrapping start() in try/catch demonstrates that start() does NOT + * throw — the deferred Error is injected into the fiber on its next + * resume, not surfaced through start(). */ + try { + var_dump($fiber->start()); // string(10) "inside-gen" + } catch (Throwable $e) { + echo "start caught (unreachable): ", $e->getMessage(), "\n"; + } +} + +try { + outer(); +} catch (Error $e) { + echo "outer: ", $e->getMessage(), "\n"; +} + +/* Pollute the vm_stack region that held outer's frame (and scope_ex): if + * the generator's force-destruct or the fiber's saved state retained a + * stale pointer into outer's frame, the next access lands in overwritten + * memory. */ +function churn(int $depth): int { + $a=1; $b=2; $c=3; $d=4; $e=5; $f=6; $g=7; $h=8; + $i=9; $j=10; $k=11; $l=12; $m=13; $n=14; $o=15; $p=16; + $q=17; $r=18; $s=19; $t=20; $u=21; $v=22; $w=23; $x=24; + if ($depth > 0) return churn($depth - 1) + $a + $x; + return $a + $b + $c + $d + $e + $f + $g + $h + + $i + $j + $k + $l + $m + $n + $o + $p + + $q + $r + $s + $t + $u + $v + $w + $x; +} +for ($iter = 0; $iter < 100; $iter++) churn(50); + +/* Resume the fiber: the deferred Error is injected at the fiber's + * suspension resumption point, the fiber body's catch above sees it and + * prints the trace, then the fiber finishes naturally (NULL). */ +var_dump($fiber->resume()); + +$fiber = null; +echo "done\n"; +?> +--EXPECTF-- +int(1) +string(10) "inside-gen" +outer: Scope function closure must not outlive the declaring scope +Error: Scope function closure must not outlive the declaring scope in %syield_fiber_suspend_inside_body.php:%d +Stack trace: +#0 [internal function]: {closure:%s}() +#1 %syield_fiber_suspend_inside_body.php(%d): Generator->next() +#2 [internal function]: {closure:%s}() +#3 %syield_fiber_suspend_inside_body.php(%d): unknown() +#4 {main} +NULL +done diff --git a/Zend/tests/scope_fn/yield_finishes_before_parent.phpt b/Zend/tests/scope_fn/yield_finishes_before_parent.phpt new file mode 100644 index 000000000000..c7955aa14a76 --- /dev/null +++ b/Zend/tests/scope_fn/yield_finishes_before_parent.phpt @@ -0,0 +1,35 @@ +--TEST-- +Scope-fn generator finishes before parent exits: no escape error +--FILE-- + +--EXPECT-- +got: 1 +got: 2 +array(3) { + [0]=> + string(1) "a" + [1]=> + string(1) "b" + [2]=> + string(1) "c" +} +done diff --git a/Zend/tests/scope_fn/yield_from_array.phpt b/Zend/tests/scope_fn/yield_from_array.phpt new file mode 100644 index 000000000000..30131b592afe --- /dev/null +++ b/Zend/tests/scope_fn/yield_from_array.phpt @@ -0,0 +1,25 @@ +--TEST-- +Scope-fn generator: yield from another iterable +--FILE-- + +--EXPECT-- +got: start +got: a +got: b +got: c +got: end +done diff --git a/Zend/tests/scope_fn/yield_in_fiber.phpt b/Zend/tests/scope_fn/yield_in_fiber.phpt new file mode 100644 index 000000000000..659474357cc5 --- /dev/null +++ b/Zend/tests/scope_fn/yield_in_fiber.phpt @@ -0,0 +1,71 @@ +--TEST-- +Scope-fn generator created in parent, captured by a Fiber: parent-exit cleanup destructs the generator +--FILE-- +current()); // int(100) + + $fiber = new Fiber(function () { + global $gen; + Fiber::suspend("paused"); + /* When user resumes us, the generator has been force-destructed + * (parent exited). It reports as already finished. */ + echo "valid: ", var_export($gen->valid(), true), "\n"; + try { + $gen->next(); + } catch (Throwable $e) { + echo "next: ", $e->getMessage(), "\n"; + } + }); + var_dump($fiber->start()); // string(6) "paused" + /* outer returns: $gen and $fiber both still alive. The closure is + * referenced by the generator; the generator's parent-exit cleanup + * force-destructs it. The fiber doesn't reference the closure + * directly (only via $gen) so no fiber unwind happens. */ +} + +try { + outer(); +} catch (Error $e) { + echo "caught: ", $e->getMessage(), "\n"; +} + +/* Pollute the vm_stack region that held outer's frame (and scope_ex): if + * the generator or fiber kept a stale pointer into that region, the next + * access would land in overwritten memory. */ +function churn(int $depth): int { + $a=1; $b=2; $c=3; $d=4; $e=5; $f=6; $g=7; $h=8; + $i=9; $j=10; $k=11; $l=12; $m=13; $n=14; $o=15; $p=16; + $q=17; $r=18; $s=19; $t=20; $u=21; $v=22; $w=23; $x=24; + if ($depth > 0) return churn($depth - 1) + $a + $x; + return $a + $b + $c + $d + $e + $f + $g + $h + + $i + $j + $k + $l + $m + $n + $o + $p + + $q + $r + $s + $t + $u + $v + $w + $x; +} +for ($iter = 0; $iter < 100; $iter++) churn(50); + +/* Generator is dead. Fiber is alive, suspended. Resume it. */ +echo "post-outer: gen valid=", var_export($gen->valid(), true), "\n"; +$fiber->resume(); + +$fiber = null; +$gen = null; +echo "done\n"; +?> +--EXPECT-- +int(100) +string(6) "paused" +caught: Scope function closure must not outlive the declaring scope +post-outer: gen valid=false +valid: false +done diff --git a/Zend/tests/scope_fn/yield_outlives_parent.phpt b/Zend/tests/scope_fn/yield_outlives_parent.phpt new file mode 100644 index 000000000000..5e4726119c99 --- /dev/null +++ b/Zend/tests/scope_fn/yield_outlives_parent.phpt @@ -0,0 +1,58 @@ +--TEST-- +Scope-fn generator that outlives its parent: force-destructed at parent exit +--FILE-- +current()); // int(1) + /* outer returns with $gen still suspended after first yield. Parent-exit + * cleanup must force-destruct the generator before freeing parent's + * frame, then throw the escape Error. */ +} +try { + outer(); + echo "no error?\n"; +} catch (Error $e) { + echo "caught: ", $e->getMessage(), "\n"; +} + +/* Force the parent's vm_stack region (where scope_ex lived) to be + * reused by subsequent function calls. If the generator's saved state + * wasn't properly torn down, this would clobber the scope_ex memory + * the generator still references. */ +function churn(int $depth): int { + $a=1; $b=2; $c=3; $d=4; $e=5; $f=6; $g=7; $h=8; + $i=9; $j=10; $k=11; $l=12; $m=13; $n=14; $o=15; $p=16; + $q=17; $r=18; $s=19; $t=20; $u=21; $v=22; $w=23; $x=24; + if ($depth > 0) return churn($depth - 1) + $a + $x; + return $a + $b + $c + $d + $e + $f + $g + $h + + $i + $j + $k + $l + $m + $n + $o + $p + + $q + $r + $s + $t + $u + $v + $w + $x; +} +for ($iter = 0; $iter < 100; $iter++) churn(50); + +/* Generator is dead. Subsequent ops report "already finished". */ +var_dump($gen->valid()); +try { + $gen->next(); + var_dump($gen->current()); +} catch (Throwable $e) { + echo "next: ", $e->getMessage(), "\n"; +} +$gen = null; +echo "done\n"; +?> +--EXPECT-- +int(1) +caught: Scope function closure must not outlive the declaring scope +bool(false) +NULL +done diff --git a/Zend/tests/scope_fn/yield_throw_into_generator.phpt b/Zend/tests/scope_fn/yield_throw_into_generator.phpt new file mode 100644 index 000000000000..785e877ce890 --- /dev/null +++ b/Zend/tests/scope_fn/yield_throw_into_generator.phpt @@ -0,0 +1,34 @@ +--TEST-- +Generator::throw into a scope-fn generator: exception propagates through scope_ex body +--FILE-- +getMessage(); + yield 99; + } + }; + $g = $fn(); + var_dump($g->current()); // int(1) + var_dump($g->throw(new RuntimeException("boom"))); // int(99) + var_dump($log); +} +outer(); +echo "done\n"; +?> +--EXPECT-- +int(1) +int(99) +array(2) { + [0]=> + string(12) "before yield" + [1]=> + string(12) "caught: boom" +} +done diff --git a/Zend/tests/scope_fn/yield_uncaught_exception.phpt b/Zend/tests/scope_fn/yield_uncaught_exception.phpt new file mode 100644 index 000000000000..3f5903066e8f --- /dev/null +++ b/Zend/tests/scope_fn/yield_uncaught_exception.phpt @@ -0,0 +1,27 @@ +--TEST-- +Uncaught exception inside scope-fn generator body propagates to caller of next() +--FILE-- +current()); // 1 + try { + $g->next(); + } catch (RuntimeException $e) { + echo "caught: ", $e->getMessage(), "\n"; + } + /* Generator is finished after the throw. */ + var_dump($g->valid()); +} +outer(); +echo "done\n"; +?> +--EXPECT-- +int(1) +caught: oops +bool(false) +done diff --git a/Zend/tests/scope_fn/yield_with_send.phpt b/Zend/tests/scope_fn/yield_with_send.phpt new file mode 100644 index 000000000000..e987947bf4a3 --- /dev/null +++ b/Zend/tests/scope_fn/yield_with_send.phpt @@ -0,0 +1,35 @@ +--TEST-- +Scope-fn generator: send() values into the generator +--FILE-- +current()); + var_dump($g->send("X")); + var_dump($g->send("Y")); + var_dump($g->getReturn()); + var_dump($log); +} +outer(); +echo "ok\n"; +?> +--EXPECT-- +string(5) "first" +string(6) "second" +NULL +string(4) "done" +array(2) { + [0]=> + string(7) "got a=X" + [1]=> + string(7) "got b=Y" +} +ok diff --git a/Zend/zend_API.c b/Zend/zend_API.c index ccb770a9963b..a4cba4dd8fc5 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1176,7 +1176,7 @@ static zend_result zend_parse_va_args(uint32_t num_args, const char *type_spec, /* mark the beginning of varargs */ post_varargs = max_num_args; - if (ZEND_CALL_INFO(EG(current_execute_data)) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (ZEND_CALL_INFO(EG(current_execute_data)) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_unexpected_extra_named_error(); return FAILURE; } diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 01a9202be1c3..5a71f5614803 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -2122,7 +2122,7 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string dest = NULL; \ dest_num = 0; \ } \ - if (UNEXPECTED(ZEND_CALL_INFO(execute_data) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { \ + if (UNEXPECTED(ZEND_CALL_INFO(execute_data) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { \ _error_code = ZPP_ERROR_UNEXPECTED_EXTRA_NAMED; \ break; \ } \ @@ -2140,7 +2140,7 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string dest = NULL; \ dest_num = 0; \ } \ - if (ZEND_CALL_INFO(execute_data) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { \ + if (ZEND_CALL_INFO(execute_data) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { \ dest_named = execute_data->extra_named_params; \ } else { \ dest_named = NULL; \ diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index c19bf2779fbf..528801f0874e 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -290,6 +290,14 @@ ZEND_FUNCTION(func_get_arg) RETURN_THROWS(); } + if (UNEXPECTED(zend_is_scope_ex(ex))) { + arg = zend_scope_fn_get_arg_zval(ex, requested_offset); + if (arg && EXPECTED(!Z_ISUNDEF_P(arg))) { + RETURN_COPY_DEREF(arg); + } + return; + } + first_extra_arg = ex->func->op_array.num_args; if ((zend_ulong)requested_offset >= first_extra_arg && (ZEND_CALL_NUM_ARGS(ex) > first_extra_arg)) { arg = ZEND_CALL_VAR_NUM(ex, ex->func->op_array.last_var + ex->func->op_array.T) + (requested_offset - first_extra_arg); @@ -323,7 +331,28 @@ ZEND_FUNCTION(func_get_args) arg_count = ZEND_CALL_NUM_ARGS(ex); - if (arg_count) { + if (!arg_count) { + RETURN_EMPTY_ARRAY(); + } + + if (UNEXPECTED(zend_is_scope_ex(ex))) { + /* scope fns live in parent ex; zend_scope_fn_get_arg_zval takes care of mapping extra args as well. */ + array_init_size(return_value, arg_count); + zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + for (i = 0; i < arg_count; i++) { + q = zend_scope_fn_get_arg_zval(ex, i); + if (q && EXPECTED(!Z_ISUNDEF_P(q))) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + } else { array_init_size(return_value, arg_count); first_extra_arg = ex->func->op_array.num_args; zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); @@ -365,8 +394,6 @@ ZEND_FUNCTION(func_get_args) } } ZEND_HASH_FILL_END(); Z_ARRVAL_P(return_value)->nNumOfElements = arg_count; - } else { - RETURN_EMPTY_ARRAY(); } } /* }}} */ @@ -1718,107 +1745,125 @@ static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array) / array_init_size(arg_array, num_args); zend_hash_real_init_packed(Z_ARRVAL_P(arg_array)); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(arg_array)) { - if (call->func->type == ZEND_USER_FUNCTION) { - uint32_t first_extra_arg = MIN(num_args, call->func->op_array.num_args); - - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_SYMBOL_TABLE)) { - /* In case of attached symbol_table, values on stack may be invalid - * and we have to access them through symbol_table - * See: https://bugs.php.net/bug.php?id=73156 - */ - while (i < first_extra_arg) { - zend_string *arg_name = call->func->op_array.vars[i]; - zval original_arg; - zval *arg = zend_hash_find_ex_ind(call->symbol_table, arg_name, 1); - bool is_sensitive = backtrace_is_arg_sensitive(call, i); - - if (arg) { - ZVAL_DEREF(arg); - ZVAL_COPY_VALUE(&original_arg, arg); - } else { - ZVAL_NULL(&original_arg); - } - - if (is_sensitive) { - zval redacted_arg; - object_init_with_constructor(&redacted_arg, zend_ce_sensitive_parameter_value, 1, &original_arg, NULL); - ZEND_HASH_FILL_SET(&redacted_arg); - } else { - Z_TRY_ADDREF_P(&original_arg); - ZEND_HASH_FILL_SET(&original_arg); - } - - ZEND_HASH_FILL_NEXT(); - i++; + /* scope fns live in parent ex; zend_scope_fn_get_arg_zval takes care of mapping extra args as well. */ + if (UNEXPECTED(call->func->type == ZEND_USER_FUNCTION && zend_is_scope_ex(call))) { + for (i = 0; i < num_args; i++) { + zval *q = zend_scope_fn_get_arg_zval(call, i); + zval original_arg; + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + ZVAL_COPY_VALUE(&original_arg, q); + } else { + ZVAL_NULL(&original_arg); + /* -Wmaybe-uninitialized doesn't see that ZEND_HASH_FILL_SET only reads .value.counted when the type is refcounted... */ + Z_PTR(original_arg) = NULL; } - } else { - while (i < first_extra_arg) { - zval original_arg; - bool is_sensitive = backtrace_is_arg_sensitive(call, i); - - if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { - zval *arg = p; - ZVAL_DEREF(arg); - ZVAL_COPY_VALUE(&original_arg, arg); - } else { - ZVAL_NULL(&original_arg); + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + ZEND_HASH_FILL_NEXT(); + } + } else { + if (call->func->type == ZEND_USER_FUNCTION) { + uint32_t first_extra_arg = MIN(num_args, call->func->op_array.num_args); + + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_SYMBOL_TABLE)) { + /* In case of attached symbol_table, values on stack may be invalid + * and we have to access them through symbol_table + * See: https://bugs.php.net/bug.php?id=73156 + */ + while (i < first_extra_arg) { + zend_string *arg_name = call->func->op_array.vars[i]; + zval original_arg; + zval *arg = zend_hash_find_ex_ind(call->symbol_table, arg_name, 1); + bool is_sensitive = backtrace_is_arg_sensitive(call, i); + + if (arg) { + ZVAL_DEREF(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } + + if (is_sensitive) { + zval redacted_arg; + object_init_with_constructor(&redacted_arg, zend_ce_sensitive_parameter_value, 1, &original_arg, NULL); + ZEND_HASH_FILL_SET(&redacted_arg); + } else { + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + } + + ZEND_HASH_FILL_NEXT(); + i++; } - - if (is_sensitive) { - zval redacted_arg; - object_init_with_constructor(&redacted_arg, zend_ce_sensitive_parameter_value, 1, &original_arg, NULL); - ZEND_HASH_FILL_SET(&redacted_arg); - } else { - Z_TRY_ADDREF_P(&original_arg); - ZEND_HASH_FILL_SET(&original_arg); + } else { + while (i < first_extra_arg) { + zval original_arg; + bool is_sensitive = backtrace_is_arg_sensitive(call, i); + + if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { + zval *arg = p; + ZVAL_DEREF(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } + + if (is_sensitive) { + zval redacted_arg; + object_init_with_constructor(&redacted_arg, zend_ce_sensitive_parameter_value, 1, &original_arg, NULL); + ZEND_HASH_FILL_SET(&redacted_arg); + } else { + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + } + + ZEND_HASH_FILL_NEXT(); + p++; + i++; } - - ZEND_HASH_FILL_NEXT(); - p++; - i++; } + p = ZEND_CALL_VAR_NUM(call, call->func->op_array.last_var + call->func->op_array.T); } - p = ZEND_CALL_VAR_NUM(call, call->func->op_array.last_var + call->func->op_array.T); - } - while (i < num_args) { - zval original_arg; - bool is_sensitive = 0; + while (i < num_args) { + zval original_arg; + bool is_sensitive = 0; - if (i < call->func->common.num_args || call->func->common.fn_flags & ZEND_ACC_VARIADIC) { - is_sensitive = backtrace_is_arg_sensitive(call, MIN(i, call->func->common.num_args)); - } + if (i < call->func->common.num_args || call->func->common.fn_flags & ZEND_ACC_VARIADIC) { + is_sensitive = backtrace_is_arg_sensitive(call, MIN(i, call->func->common.num_args)); + } - if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { - zval *arg = p; - ZVAL_DEREF(arg); - ZVAL_COPY_VALUE(&original_arg, arg); - } else { - ZVAL_NULL(&original_arg); - } + if (EXPECTED(Z_TYPE_INFO_P(p) != IS_UNDEF)) { + zval *arg = p; + ZVAL_DEREF(arg); + ZVAL_COPY_VALUE(&original_arg, arg); + } else { + ZVAL_NULL(&original_arg); + } - if (is_sensitive) { - zval redacted_arg; - object_init_with_constructor(&redacted_arg, zend_ce_sensitive_parameter_value, 1, &original_arg, NULL); - ZEND_HASH_FILL_SET(&redacted_arg); - } else { - Z_TRY_ADDREF_P(&original_arg); - ZEND_HASH_FILL_SET(&original_arg); - } + if (is_sensitive) { + zval redacted_arg; + object_init_with_constructor(&redacted_arg, zend_ce_sensitive_parameter_value, 1, &original_arg, NULL); + ZEND_HASH_FILL_SET(&redacted_arg); + } else { + Z_TRY_ADDREF_P(&original_arg); + ZEND_HASH_FILL_SET(&original_arg); + } - ZEND_HASH_FILL_NEXT(); - p++; - i++; + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } } } ZEND_HASH_FILL_END(); - Z_ARRVAL_P(arg_array)->nNumOfElements = num_args; } else { ZVAL_EMPTY_ARRAY(arg_array); } - if ((ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) + if ((ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) && call->extra_named_params != NULL /* __call and __callStatic are non-variadic, potentially with - * HAS_EXTRA_NAMED_PARAMS set. Don't add extra args, as they're already + * MAYBE_HAS_EXTRA_NAMED_PARAMS set. Don't add extra args, as they're already * contained in the 2nd param. */ && (call->func->common.fn_flags & ZEND_ACC_VARIADIC)) { zend_string *name; diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index b46085c80afa..1dd84cbd59a2 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -30,9 +30,13 @@ typedef struct _zend_closure { zend_object std; zend_function func; + /* IS_PTR = parent execute_data for scope fns; IS_OBJ: normal $this */ zval this_ptr; zend_class_entry *called_scope; zif_handler orig_internal_handler; + /* Scope fn closures can be bound to fibers or generators. + * When a scope closure goes out of bounds, it needs to be cleaned up. */ + zend_object *attached_object; } zend_closure; /* non-static since it needs to be referenced */ @@ -84,6 +88,11 @@ static bool zend_valid_closure_binding( return false; } + if ((func->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) && (Z_ISUNDEF(closure->this_ptr) || Z_OBJ_P(newthis) != Z_OBJ(closure->this_ptr))) { + zend_throw_error(NULL, "Cannot rebind $this of a scope function"); + return false; + } + if (is_fake_closure && func->common.scope && !instanceof_function(Z_OBJCE_P(newthis), func->common.scope)) { /* Binding incompatible $this to an internal method is not supported. */ @@ -250,6 +259,14 @@ static void do_closure_bind(zval *return_value, zval *zclosure, zval *newthis, z called_scope = ce; } + if (closure->func.common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + /* Scope Fns are unique and must be bound in place */ + closure->func.common.scope = ce; + closure->called_scope = called_scope; + ZVAL_OBJ_COPY(return_value, &closure->std); + return; + } + zend_create_closure(return_value, &closure->func, ce, called_scope, newthis); } @@ -316,7 +333,7 @@ static ZEND_NAMED_FUNCTION(zend_closure_call_magic) /* {{{ */ { fci.params = params; fci.param_count = 2; ZVAL_STR(&fci.params[0], EX(func)->common.function_name); - if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (EX_CALL_INFO() & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_string *name; zval *named_param_zval; array_init_size(&fci.params[1], ZEND_NUM_ARGS() + zend_hash_num_elements(EX(extra_named_params))); @@ -537,6 +554,19 @@ ZEND_API zval* zend_get_closure_this_ptr(zval *obj) /* {{{ */ } /* }}} */ +ZEND_API zval* zend_closure_get_this_ptr_ptr(zend_object *obj) /* {{{ */ +{ + zend_closure *closure = (zend_closure *)obj; + return &closure->this_ptr; +} +/* }}} */ + +ZEND_API zend_object **zend_closure_get_attached_object_ptr(zend_object *obj) +{ + zend_closure *closure = (zend_closure *)obj; + return &closure->attached_object; +} + static zend_function *zend_closure_get_method(zend_object **object, zend_string *method, const zval *key) /* {{{ */ { if (zend_string_equals_literal_ci(method, ZEND_INVOKE_FUNC_NAME)) { @@ -563,7 +593,7 @@ static void zend_closure_free_storage(zend_object *object) /* {{{ */ zend_string_release(closure->func.common.function_name); } - if (Z_TYPE(closure->this_ptr) != IS_UNDEF) { + if (Z_TYPE(closure->this_ptr) == IS_OBJECT) { zval_ptr_dtor(&closure->this_ptr); } } @@ -585,6 +615,14 @@ static zend_object *zend_closure_new(zend_class_entry *class_type) /* {{{ */ static zend_object *zend_closure_clone(zend_object *zobject) /* {{{ */ { zend_closure *closure = (zend_closure *)zobject; + + if (closure->func.common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + zend_throw_error(NULL, "Cannot clone a scope function closure"); + /* Return the original with an extra ref for the exception handler to release. */ + GC_ADDREF(zobject); + return zobject; + } + zval result; zend_create_closure(&result, &closure->func, @@ -600,7 +638,7 @@ static zend_result zend_closure_get_closure(zend_object *obj, zend_class_entry * *fptr_ptr = &closure->func; *ce_ptr = closure->called_scope; - if (Z_TYPE(closure->this_ptr) != IS_UNDEF) { + if (Z_TYPE(closure->this_ptr) == IS_OBJECT) { *obj_ptr = Z_OBJ(closure->this_ptr); } else { *obj_ptr = NULL; @@ -672,9 +710,15 @@ static HashTable *zend_closure_get_debug_info(zend_object *object, int *is_temp) } } - if (Z_TYPE(closure->this_ptr) != IS_UNDEF) { + if (Z_TYPE(closure->this_ptr) == IS_OBJECT) { Z_ADDREF(closure->this_ptr); zend_hash_update(debug_info, ZSTR_KNOWN(ZEND_STR_THIS), &closure->this_ptr); + } else if (Z_TYPE(closure->this_ptr) == IS_PTR) { + zend_execute_data *execute_data = Z_PTR(closure->this_ptr); + if (Z_TYPE(EX(This)) == IS_OBJECT) { + Z_ADDREF(EX(This)); + zend_hash_update(debug_info, ZSTR_KNOWN(ZEND_STR_THIS), &EX(This)); + } } if (arg_info && @@ -711,8 +755,14 @@ static HashTable *zend_closure_get_gc(zend_object *obj, zval **table, int *n) /* { zend_closure *closure = (zend_closure *)obj; - *table = Z_TYPE(closure->this_ptr) != IS_NULL ? &closure->this_ptr : NULL; - *n = Z_TYPE(closure->this_ptr) != IS_NULL ? 1 : 0; + /* For scope functions, this_ptr is IS_PTR (not a GC-traceable zval) */ + if (Z_TYPE(closure->this_ptr) == IS_OBJECT) { + *table = &closure->this_ptr; + *n = 1; + } else { + *table = NULL; + *n = 0; + } /* Fake closures don't own the static variables they reference. */ return (closure->func.type == ZEND_USER_FUNCTION && !(closure->func.op_array.fn_flags & ZEND_ACC_FAKE_CLOSURE)) ? diff --git a/Zend/zend_closures.h b/Zend/zend_closures.h index 2ff4934f2a3a..9bff053c17e2 100644 --- a/Zend/zend_closures.h +++ b/Zend/zend_closures.h @@ -39,6 +39,8 @@ ZEND_API void zend_create_fake_closure(zval *res, zend_function *op_array, zend_ ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *obj); ZEND_API const zend_function *zend_get_closure_method_def(zend_object *obj); ZEND_API zval* zend_get_closure_this_ptr(zval *obj); +ZEND_API zval* zend_closure_get_this_ptr_ptr(zend_object *obj); +ZEND_API zend_object **zend_closure_get_attached_object_ptr(zend_object *obj); END_EXTERN_C() diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ee57e1dafb87..d42e9aa97a92 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -343,6 +343,7 @@ void zend_oparray_context_begin(zend_oparray_context *prev_context, zend_op_arra CG(context).brk_cont_array = NULL; CG(context).labels = NULL; CG(context).in_jmp_frameless_branch = false; + CG(context).scope_func_parent_op_array = NULL; CG(context).active_property_info_name = NULL; CG(context).active_property_hook_kind = (zend_property_hook_kind)-1; } @@ -536,8 +537,25 @@ static zend_always_inline uint32_t get_temporary_variable(void) /* {{{ */ } /* }}} */ +static void grow_op_array_vars(zend_op_array *op_array) +{ + zend_oparray_context *ctx = &CG(context); + /* Handle scope fn shared variables */ + while (ctx && ctx->op_array != op_array) { + ctx = ctx->prev; + } + ZEND_ASSERT(ctx); + if (op_array->last_var > ctx->vars_size) { + ctx->vars_size += 16; /* FIXME */ + op_array->vars = erealloc(op_array->vars, ctx->vars_size * sizeof(zend_string*)); + } +} + static uint32_t lookup_cv(zend_string *name) /* {{{ */{ - zend_op_array *op_array = CG(active_op_array); + /* For scope-fn body compilation, all variable lookups go to the + * top-level parent's CV table. */ + zend_op_array *op_array = CG(context).scope_func_parent_op_array ? CG(context).scope_func_parent_op_array : CG(active_op_array); + int i = 0; zend_ulong hash_value = zend_string_hash_val(name); @@ -548,13 +566,8 @@ static uint32_t lookup_cv(zend_string *name) /* {{{ */{ } i++; } - i = op_array->last_var; - op_array->last_var++; - if (op_array->last_var > CG(context).vars_size) { - CG(context).vars_size += 16; /* FIXME */ - op_array->vars = erealloc(op_array->vars, CG(context).vars_size * sizeof(zend_string*)); - } - + i = op_array->last_var++; + grow_op_array_vars(op_array); op_array->vars[i] = zend_string_copy(name); return EX_NUM_TO_VAR(i); } @@ -4662,7 +4675,11 @@ static zend_result zend_compile_func_num_args(znode *result, const zend_ast_list static zend_result zend_compile_func_get_args(znode *result, const zend_ast_list *args) /* {{{ */ { if (CG(active_op_array)->function_name && args->children == 0) { - zend_emit_op_tmp(result, ZEND_FUNC_GET_ARGS, NULL, NULL); + zend_op *opline = zend_emit_op_tmp(result, ZEND_FUNC_GET_ARGS, NULL, NULL); + /* enable SPEC(SCOPE_FN) */ + if (CG(active_op_array)->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + opline->extended_value = 1; + } return SUCCESS; } else { return FAILURE; @@ -4709,7 +4726,10 @@ static zend_result zend_compile_func_array_slice(znode *result, const zend_ast_l && Z_LVAL_P(zv) >= 0) { first.op_type = IS_CONST; ZVAL_LONG(&first.u.constant, Z_LVAL_P(zv)); - zend_emit_op_tmp(result, ZEND_FUNC_GET_ARGS, &first, NULL); + zend_op *opline = zend_emit_op_tmp(result, ZEND_FUNC_GET_ARGS, &first, NULL); + if (CG(active_op_array)->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + opline->extended_value = 1; + } zend_string_release_ex(name, 0); return SUCCESS; } @@ -8707,7 +8727,14 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, switch (level) { case FUNC_DECL_LEVEL_NESTED: { uint32_t func_ref = zend_add_dynamic_func_def(op_array); - if (op_array->fn_flags & ZEND_ACC_CLOSURE) { + if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + opline = zend_emit_op_tmp(result, ZEND_DECLARE_SCOPE_FUNC, NULL, NULL); + opline->op2.num = func_ref; + /* op1 caches our entry's index in the parent's tracked-temps array across re-executions. */ + opline->op1_type = IS_TMP_VAR; + opline->op1.var = get_temporary_variable(); + /* extended_value (T_base) will be filled after scope func compilation */ + } else if (op_array->fn_flags & ZEND_ACC_CLOSURE) { opline = zend_emit_op_tmp(result, ZEND_DECLARE_LAMBDA_FUNCTION, NULL, NULL); opline->op2.num = func_ref; } else { @@ -8728,6 +8755,163 @@ static zend_string *zend_begin_func_decl(znode *result, zend_op_array *op_array, } /* }}} */ +/* Apply/restore CV and TMP offsets for scope functions: + * these are relative to the scope_ex that sits inside the top-level parent's frame */ +static void zend_apply_scope_func_offsets(zend_op_array *scope_op, uint32_t scope_ex_offset, uint32_t scope_T, bool encode) +{ + uint32_t scope_frame_size = (uint32_t)((ZEND_CALL_FRAME_SLOT + scope_op->last_var + scope_T) * sizeof(zval)); + uint32_t ex_off = encode ? (uint32_t)-(int32_t)scope_ex_offset : scope_ex_offset; + uint32_t frame_off = encode ? (uint32_t)-(int32_t)scope_frame_size : scope_frame_size; + + bool in_body = false; + for (uint32_t i = 0; i < scope_op->last; i++) { + zend_op *opline = &scope_op->opcodes[i]; + if (opline->opcode == ZEND_ENTER_SCOPE_FUNC) { + in_body = true; + opline->extended_value = encode ? scope_ex_offset : 0; + continue; + } + if (!in_body) continue; + if (opline->op1_type == IS_CV) opline->op1.var += ex_off; + else if (opline->op1_type & (IS_VAR|IS_TMP_VAR)) opline->op1.var += frame_off; + if (opline->op2_type == IS_CV) opline->op2.var += ex_off; + else if (opline->op2_type & (IS_VAR|IS_TMP_VAR)) opline->op2.var += frame_off; + if (opline->result_type == IS_CV) opline->result.var += ex_off; + else if (opline->result_type & (IS_VAR|IS_TMP_VAR)) opline->result.var += frame_off; + } + for (uint32_t i = 0; i < scope_op->last_live_range; i++) { + scope_op->live_range[i].var += frame_off; + } +} + +/* Recursively apply zend_apply_scope_func_offsets for each declared scope func. */ +static void zend_walk_scope_func_offsets(zend_op_array *scope_op, uint32_t parent_last_var, uint32_t T_base, uint32_t scope_T, bool encode) +{ + uint32_t scope_ex_offset = (uint32_t)((ZEND_CALL_FRAME_SLOT + parent_last_var + T_base + scope_T) * sizeof(zval)); + zend_apply_scope_func_offsets(scope_op, scope_ex_offset, scope_T, encode); + + for (uint32_t i = 0; i < scope_op->num_dynamic_func_defs; i++) { + zend_op_array *nested = scope_op->dynamic_func_defs[i]; + if (!(nested->fn_flags2 & ZEND_ACC2_SCOPE_FUNC)) continue; + for (uint32_t j = 0; j < scope_op->last; j++) { + zend_op *op = &scope_op->opcodes[j]; + if (op->opcode == ZEND_DECLARE_SCOPE_FUNC && op->op2.num == i) { + zend_walk_scope_func_offsets(nested, parent_last_var, T_base + op->extended_value, nested->T, encode); + break; + } + } + } +} + +/* Collect number of declared scope functions for temporary allocation. */ +static uint32_t zend_count_declare_scope_func_recursive(const zend_op_array *op_array) +{ + uint32_t count = 0; + for (uint32_t i = 0; i < op_array->last; i++) { + const zend_op *op = &op_array->opcodes[i]; + if (op->opcode == ZEND_DECLARE_SCOPE_FUNC) { + count++; + count += zend_count_declare_scope_func_recursive(op_array->dynamic_func_defs[op->op2.num]); + } + } + return count; +} + +ZEND_API void zend_pass_two_install_scope_fn_reservations(zend_op_array *op_array) +{ + uint32_t scope_func_count = 0; + for (uint32_t i = 0; i < op_array->last; i++) { + zend_op *op = &op_array->opcodes[i]; + if (op->opcode == ZEND_DECLARE_SCOPE_FUNC) { + zend_op_array *child = op_array->dynamic_func_defs[op->op2.num]; + op->extended_value = op_array->T; + /* Allocate space for a scope fn execute_data, including a first temporary pointing to the original call frame. */ + op_array->T += child->T + ZEND_CALL_FRAME_SLOT + 1; + scope_func_count++; + } + } + + if (scope_func_count || (op_array->fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + op_array->T++; /* add base slot for tracked temporaries */ + } + + if (!scope_func_count) { + return; + } + + /* One tracked temporary for each scope fn, so that they're cleaned up on frame exit. */ + uint32_t tracked_temp_entries = (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) ? scope_func_count : zend_count_declare_scope_func_recursive(op_array); + op_array->T += tracked_temp_entries; + op_array->fn_flags2 |= ZEND_ACC2_HAS_TRACKED_TEMPORARIES; + + if (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) { + /* In nested scope fn; scope func offsets are set/unset recursively by the top-level parent */ + return; + } + + for (uint32_t i = 0; i < op_array->last; i++) { + zend_op *op = &op_array->opcodes[i]; + if (op->opcode == ZEND_DECLARE_SCOPE_FUNC) { + zend_op_array *child = op_array->dynamic_func_defs[op->op2.num]; + zend_walk_scope_func_offsets(child, op_array->last_var, op->extended_value, child->T, /*encode=*/true); + } + } +} + +/* Reverts zend_pass_two_install_scope_fn_reservations, but leaves ZEND_ACC2_HAS_TRACKED_TEMPORARIES intact. + * (Optimizer passes use ZEND_ACC2_HAS_TRACKED_TEMPORARIES to recognize present scope fns.) */ +ZEND_API void zend_pass_two_revert_scope_fn_reservations(zend_op_array *op_array) +{ + if (!(op_array->fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + return; + } + + if (!(op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC)) { + for (uint32_t i = 0; i < op_array->last; i++) { + zend_op *op = &op_array->opcodes[i]; + if (op->opcode == ZEND_DECLARE_SCOPE_FUNC) { + zend_op_array *child = op_array->dynamic_func_defs[op->op2.num]; + zend_walk_scope_func_offsets(child, op_array->last_var, op->extended_value, child->T, /*encode=*/false); + } + } + } + + uint32_t scope_func_count = 0; + for (uint32_t i = 0; i < op_array->last; i++) { + zend_op *op = &op_array->opcodes[i]; + if (op->opcode == ZEND_DECLARE_SCOPE_FUNC) { + zend_op_array *child = op_array->dynamic_func_defs[op->op2.num]; + op_array->T -= child->T + ZEND_CALL_FRAME_SLOT + 1; + op->extended_value = 0; + scope_func_count++; + } + } + uint32_t tracked_temp_entries = (op_array->fn_flags2 & ZEND_ACC2_SCOPE_FUNC) + ? scope_func_count + : zend_count_declare_scope_func_recursive(op_array); + op_array->T -= tracked_temp_entries + 1; +} + +ZEND_API uint32_t zend_unfixup_scope_func_self(zend_op_array *scope_op, uint32_t scope_T) +{ + ZEND_ASSERT(scope_op->fn_flags2 & ZEND_ACC2_SCOPE_FUNC); + uint32_t scope_ex_offset = 0; + for (uint32_t i = 0; i < scope_op->last; i++) { + if (scope_op->opcodes[i].opcode == ZEND_ENTER_SCOPE_FUNC) { + scope_ex_offset = scope_op->opcodes[i].extended_value; + break; + } + } + zend_apply_scope_func_offsets(scope_op, scope_ex_offset, scope_T, /*encode=*/false); + return scope_ex_offset; +} + +ZEND_API void zend_refixup_scope_func_self(zend_op_array *scope_op, uint32_t scope_ex_offset, uint32_t scope_T) +{ + ZEND_ASSERT(scope_op->fn_flags2 & ZEND_ACC2_SCOPE_FUNC); + zend_apply_scope_func_offsets(scope_op, scope_ex_offset, scope_T, /*encode=*/true); +} + static zend_op_array *zend_compile_func_decl_ex( znode *result, zend_ast *ast, enum func_decl_level level, zend_string *property_info_name, @@ -8741,6 +8925,7 @@ static zend_op_array *zend_compile_func_decl_ex( bool is_method = decl->kind == ZEND_AST_METHOD; zend_string *lcname = NULL; bool is_hook = decl->kind == ZEND_AST_PROPERTY_HOOK; + uint32_t num_params = zend_ast_get_list(params_ast)->children; zend_class_entry *orig_class_entry = CG(active_class_entry); zend_op_array *orig_op_array = CG(active_op_array); @@ -8762,9 +8947,20 @@ static zend_op_array *zend_compile_func_decl_ex( op_array->doc_comment = zend_string_copy(decl->doc_comment); } + bool is_scope_fn = decl->kind == ZEND_AST_CLOSURE && (decl->attr & ZEND_ATTR_SCOPE_FUNC); + if (decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) { op_array->fn_flags |= ZEND_ACC_CLOSURE; } + if (is_scope_fn) { + op_array->fn_flags2 |= ZEND_ACC2_SCOPE_FUNC; + if (decl->flags & ZEND_ACC_STATIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Scope functions cannot be static"); + } + if (uses_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Scope functions cannot have a use() clause"); + } + } if (is_hook) { zend_class_entry *ce = CG(active_class_entry); @@ -8789,6 +8985,15 @@ static zend_op_array *zend_compile_func_decl_ex( CG(context).active_property_info_name = property_info_name; CG(context).active_property_hook_kind = hook_kind; + /* Initialize literals (starting at index 0!) for arg -> CV remapping in scope fn enter. */ + if (is_scope_fn) { + for (uint32_t i = 0; i < num_params; i++) { + zval zv; + ZVAL_LONG(&zv, 0); + zend_add_literal(&zv); + } + } + if (decl->child[4]) { int target = ZEND_ATTRIBUTE_TARGET_FUNCTION; @@ -8844,7 +9049,6 @@ static zend_op_array *zend_compile_func_decl_ex( is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME) ? IS_STRING : 0); if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) { zend_mark_function_as_generator(); - zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL); } if (decl->kind == ZEND_AST_ARROW_FUNC) { zend_compile_implicit_closure_uses(&info); @@ -8853,6 +9057,50 @@ static zend_op_array *zend_compile_func_decl_ex( zend_compile_closure_uses(uses_ast); } + if (is_scope_fn) { + /* Scope fn CVs are registered in the top-level parent's CV table. */ + zend_op_array *toplevel_parent = orig_op_array; + if (CG(context).prev && CG(context).prev->scope_func_parent_op_array) { + toplevel_parent = CG(context).prev->scope_func_parent_op_array; + } + + zend_op *enter_opline = zend_emit_op(NULL, ZEND_ENTER_SCOPE_FUNC, NULL, NULL); + enter_opline->op1.num = num_params; + /* Now mapping args to CVs is pinned in literals[0..num_params) */ + for (uint32_t i = 0; i < num_params; i++) { + zend_op_array *parent = toplevel_parent; + zend_ulong hash_value = zend_string_hash_val(op_array->vars[i]); + uint32_t parent_cv = (uint32_t)-1; + for (int j = 0; j < parent->last_var; j++) { + if (ZSTR_H(parent->vars[j]) == hash_value + && zend_string_equals(parent->vars[j], op_array->vars[i])) { + parent_cv = EX_NUM_TO_VAR(j); + break; + } + } + if (parent_cv == (uint32_t)-1) { + int idx = parent->last_var++; + grow_op_array_vars(parent); + parent->vars[idx] = zend_string_copy(op_array->vars[i]); + parent_cv = EX_NUM_TO_VAR(idx); + } + ZVAL_LONG(&op_array->literals[i], parent_cv); + } + + /* Inherit for nested scope fns. */ + if (CG(context).prev && CG(context).prev->scope_func_parent_op_array) { + CG(context).scope_func_parent_op_array = CG(context).prev->scope_func_parent_op_array; + } else { + CG(context).scope_func_parent_op_array = orig_op_array; + } + } + + /* GENERATOR_CREATE runs after ZEND_ENTER_SCOPE_FUNC to have a properly prepared scope fn for initial generator suspension. */ + if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) { + zend_op *gen_create_opline = zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL); + gen_create_opline->extended_value = is_scope_fn; + } + if (ast->kind == ZEND_AST_ARROW_FUNC && decl->child[2]->kind != ZEND_AST_RETURN) { bool needs_return = true; if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 0e31332c97f0..a8c866678a32 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -208,6 +208,7 @@ typedef struct _zend_oparray_context { zend_property_hook_kind active_property_hook_kind; bool in_jmp_frameless_branch; bool has_assigned_to_http_response_header; + zend_op_array *scope_func_parent_op_array; } zend_oparray_context; /* Class, property and method flags class|meth.|prop.|const*/ @@ -412,11 +413,15 @@ typedef struct _zend_oparray_context { /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ /* | | | */ -/* Function Flags 2 (fn_flags2) (unused: 1-31) | | | */ +/* Function Flags 2 (fn_flags2) (unused: 3-31) | | | */ /* ============================ | | | */ /* | | | */ /* Function forbids dynamic calls | | | */ -#define ZEND_ACC2_FORBID_DYN_CALLS (1 << 0) /* | X | | */ +#define ZEND_ACC2_FORBID_DYN_CALLS (1 << 0) /* | X | | */ +/* Scope functions | | | */ +#define ZEND_ACC2_SCOPE_FUNC (1 << 1) /* | X | | */ +/* Functions with tracked temporaries | | | */ +#define ZEND_ACC2_HAS_TRACKED_TEMPORARIES (1 << 2) /* | X | | */ #define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE) #define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET) @@ -670,10 +675,13 @@ struct _zend_execute_data { #define ZEND_CALL_GENERATOR (1 << 24) #define ZEND_CALL_DYNAMIC (1 << 25) #define ZEND_CALL_MAY_HAVE_UNDEF (1 << 26) -#define ZEND_CALL_HAS_EXTRA_NAMED_PARAMS (1 << 27) +#define ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS (1 << 27) +#define ZEND_CALL_TRACKED_TEMPORARIES ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS /* shared with extra named params; field is NULL when only tracked TMPs */ #define ZEND_CALL_OBSERVED (1 << 28) /* "fcall_begin" observer handler may set this flag */ /* to prevent optimization in RETURN handler and */ /* keep all local variables for "fcall_end" handler */ +#define ZEND_CALL_SCOPE_FN ZEND_CALL_OBSERVED /* aliased to OBSERVED so ZEND_RETURN's cv-to-result */ + /* move is skipped (otherwise it would null parent CVs) */ #define ZEND_CALL_JIT_RESERVED (1 << 29) /* reserved for tracing JIT */ #define ZEND_CALL_NEEDS_REATTACH (1 << 30) #define ZEND_CALL_SEND_ARG_BY_REF (1u << 31) @@ -1009,6 +1017,16 @@ ZEND_API void zend_recalc_live_ranges( zend_op_array *op_array, zend_needs_live_range_cb needs_live_range); ZEND_API void pass_two(zend_op_array *op_array); + +/* Reserve and revert TMP slots for each DECLARE_SCOPE_FUNC's scope_ex frame plus tracked-temp entries */ +ZEND_API void zend_pass_two_install_scope_fn_reservations(zend_op_array *op_array); +ZEND_API void zend_pass_two_revert_scope_fn_reservations(zend_op_array *op_array); + +/* Remove or add the temporary offsets for a scope fn op_array. */ +/* When temporarily removing the offsets, pass the return value of zend_unfixup_scope_func_self as scope_T to revert. */ +ZEND_API uint32_t zend_unfixup_scope_func_self(zend_op_array *scope_op, uint32_t scope_T); +ZEND_API void zend_refixup_scope_func_self(zend_op_array *scope_op, uint32_t scope_ex_offset, uint32_t scope_T); + ZEND_API bool zend_is_compiling(void); ZEND_API char *zend_make_compiled_string_description(const char *name); ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_handlers); @@ -1219,6 +1237,9 @@ static zend_always_inline bool zend_check_arg_send_type(const zend_function *zf, /* Used to disallow pipes with arrow functions that lead to confusing parse trees. */ #define ZEND_PARENTHESIZED_ARROW_FUNC 1 +/* Distinguishing normal from scope closures. */ +#define ZEND_ATTR_SCOPE_FUNC 1 + /* For "use" AST nodes and the seen symbol table */ #define ZEND_SYMBOL_CLASS (1<<0) #define ZEND_SYMBOL_FUNCTION (1<<1) diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index d23fb647af9d..3b4886bab1cc 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -56,6 +56,9 @@ static zend_class_entry zend_ce_unwind_exit; /* Internal pseudo-exception that is not exposed to userland. Throwing this exception *does* execute finally blocks. */ static zend_class_entry zend_ce_graceful_exit; +/* Internal pseudo-exception thrown into a scope-fn body when its declaring scope is exiting. Throwing this exception *does* execute finally blocks and is swallowed at the scope-fn boundary. */ +static zend_class_entry zend_ce_scope_fn_unwind; + ZEND_API void (*zend_throw_exception_hook)(zend_object *ex); static zend_object_handlers default_exception_handlers; @@ -830,6 +833,8 @@ void zend_register_default_exception(void) /* {{{ */ INIT_CLASS_ENTRY(zend_ce_unwind_exit, "UnwindExit", NULL); INIT_CLASS_ENTRY(zend_ce_graceful_exit, "GracefulExit", NULL); + + INIT_CLASS_ENTRY(zend_ce_scope_fn_unwind, "ScopeFnUnwind", NULL); } /* }}} */ @@ -1063,3 +1068,28 @@ ZEND_API bool zend_is_graceful_exit(const zend_object *ex) { return ex->ce == &zend_ce_graceful_exit; } + +ZEND_API ZEND_COLD zend_object *zend_create_scope_fn_unwind(void) +{ + return zend_objects_new(&zend_ce_scope_fn_unwind); +} + +ZEND_API bool zend_is_scope_fn_unwind(const zend_object *ex) +{ + return ex->ce == &zend_ce_scope_fn_unwind; +} + +/* Builds the error exception, but does *not* throw it. */ +ZEND_API ZEND_COLD zend_object *zend_build_error(zend_class_entry *exception_ce, const char *message) +{ + zval ex, msg_zv; + if (!exception_ce) { + exception_ce = zend_ce_error; + } + object_init_ex(&ex, exception_ce); + ZVAL_STRING(&msg_zv, message); + zend_update_property_ex(exception_ce, Z_OBJ(ex), + ZSTR_KNOWN(ZEND_STR_MESSAGE), &msg_zv); + zval_ptr_dtor(&msg_zv); + return Z_OBJ(ex); +} diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index f9b472598012..f11b9fa6edaf 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -73,6 +73,9 @@ ZEND_API ZEND_COLD void zend_throw_unwind_exit(void); ZEND_API ZEND_COLD void zend_throw_graceful_exit(void); ZEND_API bool zend_is_unwind_exit(const zend_object *ex); ZEND_API bool zend_is_graceful_exit(const zend_object *ex); +ZEND_API ZEND_COLD zend_object *zend_create_scope_fn_unwind(void); +ZEND_API bool zend_is_scope_fn_unwind(const zend_object *ex); +ZEND_API ZEND_COLD zend_object *zend_build_error(zend_class_entry *exception_ce, const char *message); #include "zend_globals.h" diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 4253037fda52..15b0de173e0d 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -34,6 +34,7 @@ #include "zend_interfaces.h" #include "zend_closures.h" #include "zend_generators.h" +#include "zend_fibers.h" #include "zend_vm.h" #include "zend_dtrace.h" #include "zend_inheritance.h" @@ -275,7 +276,15 @@ static zend_always_inline zval *_get_zval_ptr_var_deref(uint32_t var EXECUTE_DAT static zend_never_inline ZEND_COLD zval* zval_undefined_cv(uint32_t var EXECUTE_DATA_DC) { if (EXPECTED(EG(exception) == NULL)) { - zend_string *cv = CV_DEF_OF(EX_VAR_TO_NUM(var)); + zend_string *cv; + if (UNEXPECTED(zend_is_scope_ex(execute_data))) { + /* var is a negative offset; restore by adding the difference to parent execute data. */ + zend_execute_data *parent_ex = zend_scope_fn_parent_ex(execute_data); + uint32_t scope_ex_offset = (uint32_t)((char *)execute_data - (char *)parent_ex); + cv = parent_ex->func->op_array.vars[EX_VAR_TO_NUM(var + scope_ex_offset)]; + } else { + cv = CV_DEF_OF(EX_VAR_TO_NUM(var)); + } zend_error_unchecked(E_WARNING, "Undefined variable $%S", cv); } return &EG(uninitialized_zval); @@ -4755,7 +4764,7 @@ ZEND_API void zend_unfinished_calls_gc(zend_execute_data *execute_data, zend_exe if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { zend_get_gc_buffer_add_obj(buf, Z_OBJ(call->This)); } - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zval *val; ZEND_HASH_FOREACH_VAL(call->extra_named_params, val) { zend_get_gc_buffer_add_zval(buf, val); @@ -4888,7 +4897,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { OBJ_RELEASE(Z_OBJ(call->This)); } - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } if (call->func->common.fn_flags & ZEND_ACC_CLOSURE) { @@ -4976,6 +4985,176 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, cleanup_live_vars(execute_data, op_num, catch_op_num); } +/* We need to force-unwind any attached Fiber/Generator before the parent's CVs are freed. */ +ZEND_API void zend_force_unwind_scope_fn_closures(zend_execute_data *call) +{ + uint32_t count; + zval *base = zend_first_tracked_tmp(call, &count); + + for (uint32_t i = 0; i < count; i++) { + zval *entry = base - i; + if ((Z_EXTRA_P(entry) & 0xFF) == ZEND_TRACKED_TMP_SCOPE_FUNC) { + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(Z_OBJ_P(entry)); + if (EXPECTED(*attached_object_ptr == NULL)) { + continue; + } + + zend_fiber *unwind_fiber = NULL; + if ((*attached_object_ptr)->ce == zend_ce_fiber) { + unwind_fiber = (zend_fiber *) *attached_object_ptr; + } else if ((*attached_object_ptr)->ce == zend_ce_generator) { + zend_generator *gen = (zend_generator *) *attached_object_ptr; + if (gen->fiber_running_me != NULL) { + unwind_fiber = gen->fiber_running_me; + ZEND_ASSERT(unwind_fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && unwind_fiber->caller == NULL); + } + } + + if (unwind_fiber != NULL) { + /* Locate the original call frame in the fiber's callstack to stop unwinding there. */ + zend_execute_data *target = unwind_fiber->execute_data; + while (target != NULL) { + if (zend_is_scope_ex(target) && ZEND_CLOSURE_OBJECT(target->func) == Z_OBJ_P(entry)) { + break; + } + target = target->prev_execute_data; + } + ZEND_ASSERT(target != NULL); + + /* In case there's already a pending throw, just release it. */ + if (unwind_fiber->pending_resume_throw) { + OBJ_RELEASE(unwind_fiber->pending_resume_throw); + unwind_fiber->pending_resume_throw = NULL; + } + + zend_object *prev_exception = EG(exception); + + zval thrown; + ZVAL_OBJ(&thrown, zend_create_scope_fn_unwind()); + unwind_fiber->forced_unwind_target = target; + /* Set DESTROYED for the duration of the unwind: causes Fiber::suspend() to throw. Cleared in zend_leave_scope_ex. */ + unwind_fiber->flags |= ZEND_FIBER_FLAG_DESTROYED; + + zend_fiber_transfer transfer = zend_fiber_force_unwind_resume(unwind_fiber, &thrown); + zval_ptr_dtor(&thrown); + + ZEND_ASSERT(unwind_fiber->forced_unwind_target == NULL); + ZEND_ASSERT(!(unwind_fiber->flags & ZEND_FIBER_FLAG_DESTROYED)); + + if (transfer.flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) { + /* The sentinel is internal — it's absorbed at scope_ex's leave_helper + * and never surfaces as the fiber's terminal exception. */ + ZEND_ASSERT(!zend_is_scope_fn_unwind(Z_OBJ(transfer.value))); + EG(exception) = Z_OBJ(transfer.value); + if (prev_exception) { + zend_exception_set_previous(EG(exception), prev_exception); + } + } else { + zval_ptr_dtor(&transfer.value); + /* Preserve the exception, e.g. an exit unwind MUST never be swallowed. */ + if (EG(exception) == NULL) { + EG(exception) = prev_exception; + } else if (prev_exception) { + zend_exception_set_previous(EG(exception), prev_exception); + } + } + } + + if (*attached_object_ptr != NULL && (*attached_object_ptr)->ce == zend_ce_generator) { + zend_generator *gen = (zend_generator *) *attached_object_ptr; + zend_generator_close(gen, false); + } + + ZEND_ASSERT(*attached_object_ptr == NULL); + } + } +} + +ZEND_API zend_tracked_temporary_handler zend_tracked_temporary_handlers[0xFF]; + +/* To be done as final step before actually removing the call frame, after CVs etc. are freed. */ +ZEND_API void zend_clear_tracked_temporaries(zend_execute_data *execute_data) +{ + uint32_t count; + zval *base = zend_first_tracked_tmp(execute_data, &count); + + for (uint32_t i = 0; i < count; i++) { + zval *entry = base - i; + switch (Z_EXTRA_P(entry) & 0xFF) { + case 0: + // Unused + break; + + case ZEND_TRACKED_TMP_ZVAL: + zval_ptr_dtor(entry); + break; + + case ZEND_TRACKED_TMP_SCOPE_FUNC: { + zend_object *closure = Z_OBJ_P(entry); + zval *tmp = zend_closure_get_this_ptr_ptr(closure); + bool escaped = GC_REFCOUNT(closure) > 1; + if (escaped) { + /* Include/eval share the symbol table with the caller. + * Straight up requiring manual unset() would be quite un-ergonomic. + * Hence we remove them from the local CVs (before detaching!). */ + if (ZEND_CALL_INFO(execute_data) & ZEND_CALL_CODE) { + /* Do a single pass for all scope fns possibly declared in this frame */ + for (int v = EX(func)->op_array.last_var - 1; v >= 0; v--) { + zval *cv = ZEND_CALL_VAR_NUM(execute_data, v), *zv = cv; + ZVAL_DEREF(zv); + if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ce_closure) { + zval *closure_ptr = zend_closure_get_this_ptr_ptr(Z_OBJ_P(zv)); + if (Z_TYPE_P(closure_ptr) == IS_PTR && Z_PTR_P(closure_ptr) == execute_data) { + zval garbage; + ZVAL_COPY_VALUE(&garbage, cv); + ZVAL_UNDEF(cv); + zval_ptr_dtor(&garbage); + } + } + } + } + escaped = GC_REFCOUNT(closure) > 1; + } + + Z_PTR_P(tmp) = NULL; /* invalidate parent_execute_data */ + OBJ_RELEASE(closure); + if (UNEXPECTED(escaped) && EG(exception) == NULL) { + zend_throw_error(NULL, + "Scope function closure must not outlive the declaring scope"); + } + break; + } + + default: + zend_tracked_temporary_handlers[Z_EXTRA_P(entry) & 0xFF](execute_data, entry); + break; + } + } +} + +/* Handle an in-progress force unwinding when an invalidated scope fn called on a foreign fiber leaves the frame. */ +ZEND_API ZEND_COLD zend_fiber *zend_scope_fn_consume_forced_unwind(void) +{ + zend_fiber *unwind_fiber = EG(active_fiber); + ZEND_ASSERT(unwind_fiber->flags & ZEND_FIBER_FLAG_DESTROYED); + unwind_fiber->forced_unwind_target = NULL; + /* Clear the DESTROYED flag so the fiber can resume normally; this was temporary for unwinding. */ + unwind_fiber->flags &= ~ZEND_FIBER_FLAG_DESTROYED; + + ZEND_ASSERT(EG(exception) != NULL); + if (zend_is_scope_fn_unwind(EG(exception))) { + /* Hide the internal unwind marker; the pending_resume_throw is what we need */ + OBJ_RELEASE(EG(exception)); + EG(exception) = NULL; + } + + ZEND_ASSERT(unwind_fiber->pending_resume_throw == NULL); + unwind_fiber->pending_resume_throw = zend_build_error(NULL, + "Scope function closure must not outlive the declaring scope"); + + return unwind_fiber; +} + ZEND_API ZEND_ATTRIBUTE_DEPRECATED HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer) { return zend_unfinished_execution_gc_ex(execute_data, call, gc_buffer, false); @@ -4996,7 +5175,7 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d } if (!ZEND_USER_CODE(EX(func)->common.type)) { - ZEND_ASSERT(!(EX_CALL_INFO() & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))); + ZEND_ASSERT(!(EX_CALL_INFO() & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS))); return NULL; } @@ -5017,7 +5196,7 @@ ZEND_API HashTable *zend_unfinished_execution_gc_ex(zend_execute_data *execute_d } } - if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if ((EX_CALL_INFO() & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) && EX(extra_named_params) != NULL) { zval extra_named_params; ZVAL_ARR(&extra_named_params, EX(extra_named_params)); zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params); @@ -5534,8 +5713,8 @@ zval * ZEND_FASTCALL zend_handle_named_arg( zval *arg; if (UNEXPECTED(arg_offset == fbc->common.num_args)) { /* Unknown named parameter that will be collected into a variadic. */ - if (!(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { - ZEND_ADD_CALL_FLAG(call, ZEND_CALL_HAS_EXTRA_NAMED_PARAMS); + if (!(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + ZEND_ADD_CALL_FLAG(call, ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS); call->extra_named_params = zend_new_array(0); } @@ -5706,8 +5885,10 @@ ZEND_API zend_result ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *cal ZEND_API void ZEND_FASTCALL zend_free_extra_named_params(zend_array *extra_named_params) { - /* Extra named params may be shared. */ - zend_array_release(extra_named_params); + /* extra_named_params may be NULL on frames where ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS was set as a tracked-temporaries marker */ + if (extra_named_params != NULL) { + zend_array_release(extra_named_params); + } } #if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index ba48b19bcfe1..f7b948998f75 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -25,6 +25,7 @@ #include "zend_operators.h" #include "zend_variables.h" #include "zend_constants.h" +#include "zend_closures.h" #include @@ -331,6 +332,34 @@ static zend_always_inline zend_vm_stack zend_vm_stack_new_page(size_t size, zend return page; } +static zend_always_inline bool zend_pointer_in_vm_stack(zend_vm_stack stack, void *ptr) { + while (stack) { + if (ptr >= (void *)stack && ptr < (void *)stack->end) { + return true; + } + stack = stack->prev; + } + return false; +} + +static zend_always_inline void zend_scope_fn_detach(zend_execute_data *scope_ex) +{ + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(scope_ex->func); + *zend_closure_get_attached_object_ptr(closure_obj) = NULL; + Z_EXTRA_P(zend_closure_get_this_ptr_ptr(closure_obj)) = 0; +} + +/* Taking into account that ZEND_CALL_OBSERVED is aliased of ZEND_CALL_SCOPE_FN, checking that ZEND_ACC2_SCOPE_FUNC is actually set to distinguish */ +static zend_always_inline bool zend_is_scope_ex(const zend_execute_data *ex) +{ + ZEND_ASSERT(ex->func != NULL); + return (ZEND_CALL_INFO(ex) & ZEND_CALL_SCOPE_FN) + && (ex->func->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC); +} + +struct _zend_fiber; +ZEND_API ZEND_COLD struct _zend_fiber *zend_scope_fn_consume_forced_unwind(void); + static zend_always_inline void zend_vm_init_call_frame(zend_execute_data *call, uint32_t call_info, zend_function *func, uint32_t num_args, void *object_or_called_scope) { ZEND_ASSERT(!func->common.scope || object_or_called_scope); @@ -393,6 +422,46 @@ static zend_always_inline void zend_vm_stack_free_extra_args(zend_execute_data * zend_vm_stack_free_extra_args_ex(ZEND_CALL_INFO(call), call); } +/* Tracked temporaries: a backwards-growing array at the end of the TMP space. + * + * Layout: TMP[T-1] = base entry (Z_EXTRA high 24 bits = count), + * TMP[T-2..] = entries (Z_EXTRA low 8 bits = mode). + * + * Cleanup happens at the very end of a frame. + * Any function wishing to hold tracked temporaries must have set ZEND_ACC2_HAS_TRACKED_TEMPORARIES in its fn_flags2. + * ZEND_CALL_TRACKED_TEMPORARIES must be set when the first tracked temporary gets used. */ +typedef void (*zend_tracked_temporary_handler)(zend_execute_data *execute_data, zval *temporary); +/* Custom tracked temporaries for extensions to define */ +extern ZEND_API zend_tracked_temporary_handler zend_tracked_temporary_handlers[0xFF]; + +#define ZEND_TRACKED_TMP_ZVAL 1 +#define ZEND_TRACKED_TMP_SCOPE_FUNC 2 + +static zend_always_inline zval *zend_first_tracked_tmp(const zend_execute_data *call, uint32_t *count) +{ + const zend_op_array *op_array = &call->func->op_array; + zval *base = ZEND_CALL_VAR_NUM(call, op_array->last_var + op_array->T - 1); + *count = Z_EXTRA_P(base) >> 8; + return base - 1; +} + +ZEND_API void zend_force_unwind_scope_fn_closures(zend_execute_data *execute_data); +ZEND_API void zend_clear_tracked_temporaries(zend_execute_data *execute_data); + +static zend_always_inline void zend_vm_force_unwind_scope_fn_closures(uint32_t call_info, zend_execute_data *execute_data) +{ + if (UNEXPECTED((call_info & ZEND_CALL_TRACKED_TEMPORARIES) != 0) && (EX(func)->common.fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + zend_force_unwind_scope_fn_closures(execute_data); + } +} + +static zend_always_inline void zend_vm_stack_free_tracked_temporaries(uint32_t call_info, zend_execute_data *execute_data) +{ + if (UNEXPECTED((call_info & ZEND_CALL_TRACKED_TEMPORARIES) != 0) && (EX(func)->common.fn_flags2 & ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + zend_clear_tracked_temporaries(execute_data); + } +} + static zend_always_inline void zend_vm_stack_free_args(zend_execute_data *call) { uint32_t num_args = ZEND_CALL_NUM_ARGS(call); @@ -447,6 +516,39 @@ static zend_always_inline void zend_vm_stack_extend_call_frame( ZEND_API void ZEND_FASTCALL zend_free_extra_named_params(zend_array *extra_named_params); +/* Pop the call frame that originally invoked a scope fn from the vm_stack: + * it's left there when a scope fn is entered to preserve extra args. */ +static zend_always_inline void zend_scope_ex_pop_original_call_frame(zend_execute_data *execute_data) +{ + uint32_t orig_info = EX_CALL_INFO(); + if (UNEXPECTED(orig_info & ZEND_CALL_FREE_EXTRA_ARGS)) { + zend_vm_stack_free_extra_args_ex(orig_info, execute_data); + } + zend_vm_stack_free_call_frame_ex(orig_info, execute_data); +} + +/* Top-level parent execute_data; NULL if the parent has already exited. */ +static zend_always_inline zend_execute_data *zend_scope_fn_parent_ex(const zend_execute_data *execute_data) +{ + return (zend_execute_data *)Z_PTR_P(zend_closure_get_this_ptr_ptr(ZEND_CLOSURE_OBJECT(EX(func)))); +} + +/* Get the i-th argument of a scope fn: parent CVs for i < num_args, otherwise original frame extra args */ +static zend_always_inline zval *zend_scope_fn_get_arg_zval(const zend_execute_data *execute_data, uint32_t i) +{ + const zend_op_array *op_array = &EX(func)->op_array; + if (i < op_array->num_args) { + zend_execute_data *parent_ex = zend_scope_fn_parent_ex(execute_data); + ZEND_ASSERT(parent_ex); + uint32_t parent_cv_offset = (uint32_t)Z_LVAL(op_array->literals[i]); + return ZEND_CALL_VAR(parent_ex, parent_cv_offset); + } + zend_execute_data *call_frame = (zend_execute_data *)Z_PTR_P(ZEND_CALL_VAR_NUM(execute_data, 0)); + ZEND_ASSERT(call_frame); + zval *base = ZEND_CALL_VAR_NUM(call_frame, call_frame->func->op_array.last_var + call_frame->func->op_array.T); + return base + (i - op_array->num_args); +} + /* services */ ZEND_API const char *get_active_class_name(const char **space); ZEND_API const char *get_active_function_name(void); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 0022eb4a1df8..94d165dc0451 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -885,7 +885,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_ ZEND_CALL_NUM_ARGS(call) = i; cleanup_args: zend_vm_stack_free_args(call); - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame(call); @@ -1036,7 +1036,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_ ZEND_OBSERVER_FCALL_END(call, fci->retval); EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_array_release(call->extra_named_params); } @@ -1811,6 +1811,19 @@ ZEND_API zend_array *zend_rebuild_symbol_table(void) /* {{{ */ return ex->symbol_table; } + /* Scope functions share CVs with their parent: rebuild on the parent frame and adopt the result. */ + if (zend_is_scope_ex(ex)) { + zend_execute_data *parent_ex = zend_scope_fn_parent_ex(ex); + ZEND_ASSERT(parent_ex); + zend_execute_data *saved = EG(current_execute_data); + EG(current_execute_data) = parent_ex; + symbol_table = zend_rebuild_symbol_table(); + EG(current_execute_data) = saved; + ex->symbol_table = symbol_table; + ZEND_ADD_CALL_FLAG(ex, ZEND_CALL_HAS_SYMBOL_TABLE); + return symbol_table; + } + ZEND_ADD_CALL_FLAG(ex, ZEND_CALL_HAS_SYMBOL_TABLE); if (EG(symtable_cache_ptr) > EG(symtable_cache)) { symbol_table = ex->symbol_table = *(--EG(symtable_cache_ptr)); diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index c91436050856..f82cc41d9e26 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -488,6 +488,7 @@ ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer) (Z_TYPE(transfer->value) == IS_OBJECT && ( zend_is_unwind_exit(Z_OBJ(transfer->value)) || zend_is_graceful_exit(Z_OBJ(transfer->value)) || + zend_is_scope_fn_unwind(Z_OBJ(transfer->value)) || instanceof_function(Z_OBJCE(transfer->value), zend_ce_throwable) )) ) && "Error transfer requires a throwable value"); @@ -736,19 +737,23 @@ ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_val zend_fiber_delegate_transfer_result(&transfer, EG(current_execute_data), return_value); } -ZEND_API void zend_fiber_resume_exception(zend_fiber *fiber, zval *exception, zval *return_value) +ZEND_API zend_fiber_transfer zend_fiber_force_unwind_resume(zend_fiber *fiber, zval *exception) { ZEND_ASSERT(fiber->context.status == ZEND_FIBER_STATUS_SUSPENDED && fiber->caller == NULL); - fiber->stack_bottom->prev_execute_data = EG(current_execute_data); + return zend_fiber_resume_internal(fiber, exception, /* exception */ true); +} - zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, exception, /* exception */ true); - +ZEND_API void zend_fiber_resume_exception(zend_fiber *fiber, zval *exception, zval *return_value) +{ + zend_fiber_transfer transfer = zend_fiber_force_unwind_resume(fiber, exception); zend_fiber_delegate_transfer_result(&transfer, EG(current_execute_data), return_value); } ZEND_API void zend_fiber_suspend(zend_fiber *fiber, zval *value, zval *return_value) { + ZEND_ASSERT((fiber->flags & ZEND_FIBER_FLAG_DESTROYED) == 0); + fiber->stack_bottom->prev_execute_data = NULL; zend_fiber_transfer transfer = zend_fiber_suspend_internal(fiber, value); @@ -811,6 +816,10 @@ static void zend_fiber_object_free(zend_object *object) zval_ptr_dtor(&fiber->fci.function_name); zval_ptr_dtor(&fiber->result); + if (UNEXPECTED(fiber->pending_resume_throw != NULL)) { + OBJ_RELEASE(fiber->pending_resume_throw); + } + zend_object_std_dtor(&fiber->std); } @@ -973,13 +982,32 @@ ZEND_METHOD(Fiber, resume) fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) { + if (UNEXPECTED(fiber->pending_resume_throw != NULL)) { + /* fiber finished, because top frame was scope fn, we still should surface the error */ + zend_throw_exception_internal(fiber->pending_resume_throw); + fiber->pending_resume_throw = NULL; + RETURN_THROWS(); + } zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended"); RETURN_THROWS(); } + zval injected; + bool inject_exception = fiber->pending_resume_throw != NULL; + zval *resume_value = value; + if (UNEXPECTED(inject_exception)) { + ZVAL_OBJ(&injected, fiber->pending_resume_throw); + fiber->pending_resume_throw = NULL; + resume_value = &injected; + } + fiber->stack_bottom->prev_execute_data = EG(current_execute_data); - zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, value, false); + zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, resume_value, inject_exception); + + if (inject_exception) { + zval_ptr_dtor(&injected); + } zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU); } @@ -1001,13 +1029,32 @@ ZEND_METHOD(Fiber, throw) fiber = (zend_fiber *) Z_OBJ_P(ZEND_THIS); if (UNEXPECTED(fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED || fiber->caller != NULL)) { + if (UNEXPECTED(fiber->pending_resume_throw != NULL)) { + /* fiber finished, because top frame was scope fn, we still should surface the error */ + zend_throw_exception_internal(fiber->pending_resume_throw); + fiber->pending_resume_throw = NULL; + RETURN_THROWS(); + } zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended"); RETURN_THROWS(); } + zval injected; + bool injected_pending = fiber->pending_resume_throw != NULL; + zval *exception_value = exception; + if (UNEXPECTED(injected_pending)) { + ZVAL_OBJ(&injected, fiber->pending_resume_throw); + fiber->pending_resume_throw = NULL; + exception_value = &injected; + } + fiber->stack_bottom->prev_execute_data = EG(current_execute_data); - zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, exception, true); + zend_fiber_transfer transfer = zend_fiber_resume_internal(fiber, exception_value, true); + + if (injected_pending) { + zval_ptr_dtor(&injected); + } zend_fiber_delegate_transfer_result(&transfer, INTERNAL_FUNCTION_PARAM_PASSTHRU); } diff --git a/Zend/zend_fibers.h b/Zend/zend_fibers.h index 7c0d30acab37..906e3ca6363b 100644 --- a/Zend/zend_fibers.h +++ b/Zend/zend_fibers.h @@ -129,12 +129,21 @@ struct _zend_fiber { /* Storage for fiber return value. */ zval result; + + /* End function when unwinding exception. */ + zend_execute_data *forced_unwind_target; + + /* Exception to throw to the fiber on its next resume (e.g. scope fn unwound). */ + zend_object *pending_resume_throw; }; ZEND_API zend_result zend_fiber_start(zend_fiber *fiber, zval *return_value); ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value); ZEND_API void zend_fiber_suspend(zend_fiber *fiber, zval *value, zval *return_value); +/* Resume, but requiring manual zend_fiber_transfer handling */ +ZEND_API zend_fiber_transfer zend_fiber_force_unwind_resume(zend_fiber *fiber, zval *exception); + /* These functions may be used to create custom fiber objects using the bundled fiber switching context. */ ZEND_API zend_result zend_fiber_init_context(zend_fiber_context *context, void *kind, zend_fiber_coroutine coroutine, size_t stack_size); ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context); diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 52d409f2e69b..5ac07f42ccee 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -22,6 +22,7 @@ #include "zend_interfaces.h" #include "zend_exceptions.h" #include "zend_generators.h" +#include "zend_fibers.h" #include "zend_closures.h" #include "zend_generators_arginfo.h" #include "zend_observer.h" @@ -138,12 +139,44 @@ ZEND_API void zend_generator_close(zend_generator *generator, bool finished_exec * already cleaning up execute_data. */ generator->execute_data = NULL; + if (UNEXPECTED(zend_is_scope_ex(execute_data))) { + if (UNEXPECTED(!finished_execution) && !CG(unclean_shutdown)) { + zend_generator_cleanup_unfinished_execution(generator, execute_data, 0); + } + + /* Handle scope fn generator being suspended in fiber. */ + zend_fiber *unwind_fiber = NULL; + if (EG(active_fiber) && EG(active_fiber)->forced_unwind_target == execute_data) { + zend_execute_data *current_execute_data = EG(current_execute_data); + EG(current_execute_data) = execute_data; + unwind_fiber = zend_scope_fn_consume_forced_unwind(); + EG(current_execute_data) = current_execute_data; + } + + + zend_scope_fn_detach(execute_data); + + zend_vm_stack_free_extra_args(execute_data); + zend_vm_stack_free_tracked_temporaries(EX_CALL_INFO(), execute_data); + if (EX_CALL_INFO() & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { + zend_free_extra_named_params(execute_data->extra_named_params); + } + + if (UNEXPECTED(unwind_fiber != NULL)) { + /* the error will be thrown here on the user's next resume. */ + zend_fiber_suspend(unwind_fiber, NULL, NULL); + } + return; + } + + zend_vm_force_unwind_scope_fn_closures(EX_CALL_INFO(), execute_data); + if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) { zend_clean_and_cache_symbol_table(execute_data->symbol_table); } /* always free the CV's, in the symtable are only not-free'd IS_INDIRECT's */ zend_free_compiled_variables(execute_data); - if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (EX_CALL_INFO() & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(execute_data->extra_named_params); } @@ -166,6 +199,8 @@ ZEND_API void zend_generator_close(zend_generator *generator, bool finished_exec zend_generator_cleanup_unfinished_execution(generator, execute_data, 0); } + zend_vm_stack_free_tracked_temporaries(EX_CALL_INFO(), execute_data); + efree(execute_data); } } @@ -783,6 +818,8 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ if (EG(active_fiber)) { orig_generator->flags |= ZEND_GENERATOR_IN_FIBER; generator->flags |= ZEND_GENERATOR_IN_FIBER; + orig_generator->fiber_running_me = EG(active_fiber); + generator->fiber_running_me = EG(active_fiber); } /* Drop the AT_FIRST_YIELD flag */ @@ -819,6 +856,8 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER); + orig_generator->fiber_running_me = NULL; + generator->fiber_running_me = NULL; return; } /* If there are no more delegated values, resume the generator @@ -852,6 +891,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ } } generator->flags &= ~(ZEND_GENERATOR_CURRENTLY_RUNNING | ZEND_GENERATOR_IN_FIBER); + generator->fiber_running_me = NULL; generator->frozen_call_stack = NULL; if (EXPECTED(generator->execute_data) && @@ -869,7 +909,15 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ * In case we did yield from, the Exception must be rethrown into * its calling frame (see above in if (check_yield_from). */ if (UNEXPECTED(EG(exception) != NULL)) { - if (generator == orig_generator) { + /* Absorb scope unwind only when targeting this generator directly: + * - This is not being part of a fiber unwind. + * - The unwind ends at exactly this targeted frame. */ + if (zend_is_scope_fn_unwind(EG(exception)) + && (((generator->func->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) && (EG(active_fiber) == NULL || EG(active_fiber)->forced_unwind_target == NULL)) + || (EG(active_fiber) != NULL&& EG(active_fiber)->forced_unwind_target == generator->execute_data))) { + OBJ_RELEASE(EG(exception)); + EG(exception) = NULL; + } else if (generator == orig_generator) { zend_generator_close(generator, false); if (!EG(current_execute_data)) { zend_throw_exception_internal(NULL); @@ -892,6 +940,7 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */ } orig_generator->flags &= ~(ZEND_GENERATOR_DO_INIT | ZEND_GENERATOR_IN_FIBER); + orig_generator->fiber_running_me = NULL; } /* }}} */ diff --git a/Zend/zend_generators.h b/Zend/zend_generators.h index 73dd9ddc1f91..06bb631ba44a 100644 --- a/Zend/zend_generators.h +++ b/Zend/zend_generators.h @@ -31,6 +31,8 @@ extern ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException; typedef struct _zend_generator_node zend_generator_node; typedef struct _zend_generator zend_generator; +struct _zend_fiber; /* forward declaration for generator struct */ + /* The concept of `yield from` exposes problems when accessed at different levels of the chain of delegated generators. We need to be able to reference the currently executed Generator in all cases and still being able to access the return values of finished Generators. * The solution to this problem is a doubly-linked tree, which all Generators referenced in maintain a reference to. It should be impossible to avoid walking the tree in all cases. This way, we only need tree walks from leaf to root in case where some part of the `yield from` chain is passed to another `yield from`. (Update of leaf node pointer and list of multi-children nodes needed when leaf gets a child in direct path from leaf to root node.) But only in that case, which should be a fairly rare case (which is then possible, but not totally cheap). * The root of the tree is then the currently executed Generator. The subnodes of the tree (all except the root node) are all Generators which do `yield from`. Each node of the tree knows a pointer to one leaf descendant node. Each node with multiple children needs a list of all leaf descendant nodes paired with pointers to their respective child node. (The stack is determined by leaf node pointers) Nodes with only one child just don't need a list, there it is enough to just have a pointer to the child node. Further, leaf nodes store a pointer to the root node. @@ -91,6 +93,13 @@ struct _zend_generator { * the generator is alive. */ zend_function *func; + /* While ZEND_GENERATOR_IN_FIBER is set: the fiber currently host'ing + * this generator's body. NULL otherwise. Used by the scope-fn + * parent-exit cleanup to find the fiber it must force-unwind when a + * fiber is suspended inside the generator's body and the generator + * is being force-destructed. */ + struct _zend_fiber *fiber_running_me; + /* ZEND_GENERATOR_* flags */ uint8_t flags; }; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index b4dda00404ea..a3e90df3b5dd 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1404,6 +1404,12 @@ inline_function: { $$ = zend_ast_create_decl(ZEND_AST_ARROW_FUNC, $2 | $12, $1, $3, NULL, $5, NULL, $11, $7, NULL); CG(extra_fn_flags) = $9; } + | fn returns_ref backup_doc_comment '(' parameter_list ')' return_type + backup_fn_flags '{' inner_statement_list '}' backup_fn_flags + { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2 | $12, $1, $3, + NULL, $5, NULL, $10, $7, NULL); + ((zend_ast_decl *) $$)->attr = ZEND_ATTR_SCOPE_FUNC; + CG(extra_fn_flags) = $8; } ; fn: diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0c6b22473514..0efe0a2d7dd7 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -1134,6 +1134,8 @@ ZEND_API void pass_two(zend_op_array *op_array) op_array->T += ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled + zend_pass_two_install_scope_fn_reservations(op_array); + /* Needs to be set directly after the opcode/literal reallocation, to ensure destruction * happens correctly if any of the following fixups generate a fatal error. */ op_array->fn_flags |= ZEND_ACC_DONE_PASS_TWO; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 05923bbc2b61..64bce6c8e34a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -22,6 +22,8 @@ * php zend_vm_gen.php */ +#include "zend_compile.h" + ZEND_VM_HELPER(zend_add_helper, ANY, ANY, zval *op_1, zval *op_2) { USE_OPLINE @@ -2992,7 +2994,8 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) SAVE_OPLINE(); #endif - if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) { +ZEND_VM_C_LABEL(observed_fn_try_again): + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); i_free_compiled_variables(execute_data); @@ -3014,8 +3017,11 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP)) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD @@ -3025,14 +3031,16 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) zend_clean_and_cache_symbol_table(EX(symbol_table)); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { - zend_free_extra_named_params(EX(extra_named_params)); - } - /* Free extra args before releasing the closure, * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -3050,12 +3058,16 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & ZEND_CALL_TOP) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; } zend_destroy_static_vars(&EX(func)->op_array); + destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; @@ -3077,28 +3089,83 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); } else { - if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) { + if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS))) { if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) { zend_clean_and_cache_symbol_table(EX(symbol_table)); } zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); } if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } ZEND_VM_RETURN(); + } else if (UNEXPECTED(call_info & ZEND_CALL_SCOPE_FN)) { + /* ZEND_CALL_SCOPE_FN is aliased to ZEND_CALL_OBSERVED. Slow path. */ + if (UNEXPECTED((EX(func)->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) == 0)) { + call_info &= ~ZEND_CALL_SCOPE_FN; + ZEND_VM_C_GOTO(observed_fn_try_again); + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + + if (UNEXPECTED(Z_EXTRA_P(scope_ex_t0))) { + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr != NULL); + *attached_object_ptr = NULL; + } + + zend_fiber *unwind_fiber = NULL; + if (EG(active_fiber) && EG(active_fiber)->forced_unwind_target == execute_data) { + unwind_fiber = zend_scope_fn_consume_forced_unwind(); + } + + EG(current_execute_data) = EX(prev_execute_data); + zend_scope_fn_detach(execute_data); + + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + OBJ_RELEASE(closure_obj); + zend_scope_ex_pop_original_call_frame(original_call_frame); + + if (UNEXPECTED(unwind_fiber != NULL)) { + /* the error will be thrown here on the user's next resume. */ + zend_fiber_suspend(unwind_fiber, NULL, NULL); + } + + if (UNEXPECTED(call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + + execute_data = EG(current_execute_data); + if (UNEXPECTED(EG(exception) != NULL)) { + zend_rethrow_exception(execute_data); + HANDLE_EXCEPTION_LEAVE(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); } else /* if (call_kind == ZEND_CALL_TOP_CODE) */ { zend_array *symbol_table = EX(symbol_table); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; @@ -4171,8 +4238,8 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -4312,8 +4379,8 @@ ZEND_VM_C_LABEL(fcall_by_name_end): zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -4443,7 +4510,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) ZEND_VM_C_LABEL(fcall_end): zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } @@ -4686,7 +4753,7 @@ ZEND_VM_COLD_CONST_HANDLER(111, ZEND_RETURN_BY_REF, CONST|TMP|VAR|CV, ANY, SRC, ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper); } -ZEND_VM_HANDLER(139, ZEND_GENERATOR_CREATE, ANY, ANY) +ZEND_VM_HANDLER(139, ZEND_GENERATOR_CREATE, ANY, ANY, SPEC(SCOPE_FN)) { zval *return_value = EX(return_value); @@ -4697,29 +4764,49 @@ ZEND_VM_HANDLER(139, ZEND_GENERATOR_CREATE, ANY, ANY) uint32_t num_args, used_stack, call_info; SAVE_OPLINE(); + + /* SPEC(SCOPE_FN) selects the variant via opline->extended_value bit 0. + * Scope-fn variant: EX is the scope_ex, which lives inside the parent + * function's frame. We must NOT memcpy/emalloc — the body's CV + * accesses use negative offsets that only work when the frame stays + * in place. The scope_ex itself IS the generator's execute_data. The + * generator is attached to the closure so parent-exit cleanup can + * force-destruct it. */ + bool is_scope_fn = ZEND_VM_IS_SCOPE_FN; + ZEND_ASSERT(!is_scope_fn || zend_is_scope_ex(execute_data)); + + /* Capture EX(This)'s original call_info before we OR in TOP_FUNCTION | + * GENERATOR. The scope-fn leave path checks ZEND_CALL_TOP against the + * pre-modification value (TOP_FUNCTION includes TOP). */ + uint32_t orig_call_info = EX_CALL_INFO(); + object_init_ex(return_value, zend_ce_generator); - /* - * Normally the execute_data is allocated on the VM stack (because it does - * not actually do any allocation and thus is faster). For generators - * though this behavior would be suboptimal, because the (rather large) - * structure would have to be copied back and forth every time execution is - * suspended or resumed. That's why for generators the execution context - * is allocated on heap. - */ - num_args = EX_NUM_ARGS(); - if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { - used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); - gen_execute_data = (zend_execute_data*)emalloc(used_stack); - used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + if (is_scope_fn) { + gen_execute_data = execute_data; } else { - used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); - gen_execute_data = (zend_execute_data*)emalloc(used_stack); + /* + * Normally the execute_data is allocated on the VM stack (because it does + * not actually do any allocation and thus is faster). For generators + * though this behavior would be suboptimal, because the (rather large) + * structure would have to be copied back and forth every time execution is + * suspended or resumed. That's why for generators the execution context + * is allocated on heap. + */ + num_args = EX_NUM_ARGS(); + if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + } else { + used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + } + memcpy(gen_execute_data, execute_data, used_stack); } - memcpy(gen_execute_data, execute_data, used_stack); /* Save execution context in generator object. */ - generator = (zend_generator *) Z_OBJ_P(EX(return_value)); + generator = (zend_generator *) Z_OBJ_P(return_value); generator->func = gen_execute_data->func; generator->execute_data = gen_execute_data; generator->frozen_call_stack = NULL; @@ -4729,19 +4816,44 @@ ZEND_VM_HANDLER(139, ZEND_GENERATOR_CREATE, ANY, ANY) ZVAL_OBJ(&generator->execute_fake.This, (zend_object *) generator); gen_execute_data->opline = opline; - /* EX(return_value) keeps pointer to zend_object (not a real zval) */ gen_execute_data->return_value = (zval*)generator; call_info = Z_TYPE_INFO(EX(This)); - if ((call_info & Z_TYPE_MASK) == IS_OBJECT - && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) - /* Bug #72523 */ - || UNEXPECTED(zend_execute_ex != execute_ex))) { - ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); - Z_ADDREF(gen_execute_data->This); - } - ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_ALLOCATED | ZEND_CALL_GENERATOR)); + if (!is_scope_fn) { + if ((call_info & Z_TYPE_MASK) == IS_OBJECT + && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) + /* Bug #72523 */ + || UNEXPECTED(zend_execute_ex != execute_ex))) { + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); + Z_ADDREF(gen_execute_data->This); + } + /* scope_ex lives in parent's TMP, not heap-allocated. */ + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_ALLOCATED); + /* scope-fn keeps prev_execute_data — body CV access traverses it. */ + gen_execute_data->prev_execute_data = NULL; + } + ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_GENERATOR)); Z_TYPE_INFO(gen_execute_data->This) = call_info; - gen_execute_data->prev_execute_data = NULL; + + if (is_scope_fn) { + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zend_object **attached_object_ptr = + zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &generator->std; + + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to generator */ + + EG(current_execute_data) = EX(prev_execute_data); + execute_data = EX(prev_execute_data); + zend_scope_ex_pop_original_call_frame(original_call_frame); + if (UNEXPECTED(orig_call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } call_info = EX_CALL_INFO(); EG(current_execute_data) = EX(prev_execute_data); @@ -5848,7 +5960,7 @@ ZEND_VM_HANDLER(164, ZEND_RECV_VARIADIC, NUM, UNUSED) ZVAL_EMPTY_ARRAY(params); } - if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (EX_CALL_INFO() & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_string *name; zval *param; zend_arg_info *arg_info = &EX(func)->common.arg_info[EX(func)->common.num_args]; @@ -9033,7 +9145,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER)) zend_array *args = NULL; zend_function *fbc = EX(func); zval *ret = EX(return_value); - uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_EXTRA_NAMED_PARAMS); + uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS); uint32_t num_args = EX_NUM_ARGS(); zend_execute_data *call; @@ -9068,7 +9180,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER)) } else { ZVAL_EMPTY_ARRAY(call_args); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { if (zend_hash_num_elements(Z_ARRVAL_P(call_args)) == 0) { GC_ADDREF(call->extra_named_params); ZVAL_ARR(call_args, call->extra_named_params); @@ -9146,7 +9258,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER)) EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } if (ret == &retval) { @@ -9714,7 +9826,7 @@ ZEND_VM_HANDLER(171, ZEND_FUNC_NUM_ARGS, UNUSED, UNUSED) ZEND_VM_NEXT_OPCODE(); } -ZEND_VM_HANDLER(172, ZEND_FUNC_GET_ARGS, UNUSED|CONST, UNUSED) +ZEND_VM_HANDLER(172, ZEND_FUNC_GET_ARGS, UNUSED|CONST, UNUSED, SPEC(SCOPE_FN)) { USE_OPLINE zend_array *ht; @@ -9733,7 +9845,35 @@ ZEND_VM_HANDLER(172, ZEND_FUNC_GET_ARGS, UNUSED|CONST, UNUSED) result_size = arg_count; } - if (result_size) { + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (ZEND_VM_IS_SCOPE_FN && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!ZEND_VM_IS_SCOPE_FN && result_size) { SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; @@ -10647,3 +10787,162 @@ ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) } ZEND_VM_CONTINUE(); } + +ZEND_VM_HANDLER(212, ZEND_DECLARE_SCOPE_FUNC, TMP, NUM) +{ + USE_OPLINE + zend_function *func; + zval *object; + zend_class_entry *called_scope; + + func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num]; + if (Z_TYPE(EX(This)) == IS_OBJECT) { + called_scope = Z_OBJCE(EX(This)); + object = &EX(This); + } else { + called_scope = Z_CE(EX(This)); + object = NULL; + } + SAVE_OPLINE(); + zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); + + zend_execute_data *parent_ex = execute_data; + if (UNEXPECTED(zend_is_scope_ex(execute_data))) { + /* CVs live on the top-level parent, hence we need to reference that one */ + parent_ex = zend_scope_fn_parent_ex(execute_data); + } + + /* The closure this_ptr references parent execute_data and a recursion guard in Z_EXTRA. */ + zval *this_ptr = zend_closure_get_this_ptr_ptr(Z_OBJ_P(EX_VAR(opline->result.var))); + Z_PTR_P(this_ptr) = parent_ex; + Z_TYPE_INFO_P(this_ptr) = IS_PTR; + Z_EXTRA_P(this_ptr) = 0; + + /* Register in the parent's tracked-temporaries array (loop re-evaluation + * replaces the existing entry). Z_EXTRA per entry: bits 0-7 = mode, + * bits 8-23 = func_def index. */ + const zend_op_array *parent_op_array = &parent_ex->func->op_array; + zval *base = ZEND_CALL_VAR_NUM(parent_ex, parent_op_array->last_var + parent_op_array->T - 1); + + if (!(ZEND_CALL_INFO(parent_ex) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + /* Bit was not yet set: no real named params on this frame, so it's + * safe to NULL extra_named_params (the bit is now set purely as a + * tracked-temps marker; deref sites NULL-check before using). */ + ZEND_ADD_CALL_FLAG(parent_ex, ZEND_CALL_TRACKED_TEMPORARIES); + parent_ex->extra_named_params = NULL; + Z_EXTRA_P(base) = 0; + } + + uint32_t func_ref = opline->op2.num; + uint32_t count = Z_EXTRA_P(base) >> 8; + zval *existing = NULL; + /* op1 is a TMP that caches our entry's index across re-executions. + * Pass 9 reserves a fresh slot via ++max + permanent bitset so other + * TMPs can't be allocated on top of it. The slot is uninitialized on + * first reach; bounds + entry validation reject garbage. */ + zval *tracked_tmp_cache = EX_VAR(opline->op1.var); + uint32_t cached_i = (uint32_t)Z_LVAL_P(tracked_tmp_cache); + uint32_t tracked_temporary_data = ZEND_TRACKED_TMP_SCOPE_FUNC | (func_ref << 8); + if (cached_i < count) { + zval *e = base - cached_i - 1; + if (Z_EXTRA_P(e) == tracked_temporary_data) { + existing = e; + } + } + + if (existing) { + /* Invalidate the previous closure so any escaped reference + * throws on call instead of reaching a freed parent frame. */ + zval *old_tp = zend_closure_get_this_ptr_ptr(Z_OBJ_P(existing)); + Z_PTR_P(old_tp) = NULL; + OBJ_RELEASE(Z_OBJ_P(existing)); + ZVAL_OBJ(existing, Z_OBJ_P(EX_VAR(opline->result.var))); + Z_ADDREF_P(existing); + } else { + zval *entry = base - count - 1; + ZVAL_OBJ(entry, Z_OBJ_P(EX_VAR(opline->result.var))); + Z_ADDREF_P(entry); + Z_EXTRA_P(entry) = tracked_temporary_data; + Z_EXTRA_P(base) = ((count + 1) << 8) | (Z_EXTRA_P(base) & 0xFF); + Z_LVAL_P(tracked_tmp_cache) = count; + } + + ZEND_VM_NEXT_OPCODE(); +} + +ZEND_VM_HANDLER(213, ZEND_ENTER_SCOPE_FUNC, ANY, ANY) +{ + USE_OPLINE + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zval *this_ptr = zend_closure_get_this_ptr_ptr(closure_obj); + + SAVE_OPLINE(); + + zend_execute_data *parent_ex = Z_PTR_P(this_ptr); + if (UNEXPECTED(!parent_ex)) { + zend_throw_error(NULL, "Cannot call scope function: defining scope has exited"); + HANDLE_EXCEPTION(); + } + + if (UNEXPECTED(Z_EXTRA_P(this_ptr) != 0)) { + zend_throw_error(NULL, "Cannot recursively call scope function"); + HANDLE_EXCEPTION(); + } + + uint32_t num_params = opline->op1.num; + uint32_t scope_ex_offset = opline->extended_value; + zend_execute_data *restrict scope_ex = (zend_execute_data *)((char *)parent_ex + scope_ex_offset); + + /* Move args into parent CVs via the literal mapping. */ + if (num_params > 0) { + zval *literals = EX(func)->op_array.literals; + for (uint32_t i = 0; i < num_params; i++) { + zval *src = ZEND_CALL_ARG(execute_data, i + 1); + uint32_t parent_cv_offset = (uint32_t)Z_LVAL(literals[i]); + zval *dst = ZEND_CALL_VAR(parent_ex, parent_cv_offset); + zval garbage; + ZVAL_COPY_VALUE(&garbage, dst); + ZVAL_COPY_VALUE(dst, src); + ZVAL_UNDEF(src); + i_zval_ptr_dtor(&garbage); + } + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } + } + + scope_ex->opline = opline + 1; + scope_ex->call = NULL; + scope_ex->return_value = EX(return_value); + scope_ex->This = EX(This); + scope_ex->func = EX(func); + scope_ex->prev_execute_data = EX(prev_execute_data); + scope_ex->symbol_table = parent_ex->symbol_table; + scope_ex->run_time_cache = EX(run_time_cache); + scope_ex->extra_named_params = EX(extra_named_params); + /* Store the original call frame pointer in scope_ex's first temporary for later retrieval / freeing */ + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(scope_ex, 0); + Z_PTR_P(scope_ex_t0) = execute_data; + Z_EXTRA_P(scope_ex_t0) = 0; + if (UNEXPECTED(EG(active_fiber) != NULL && !zend_pointer_in_vm_stack(EG(vm_stack), scope_ex)) && EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) == 0)) { + /* When scope fns run in a fiber different to where they were declared, + * we must cleanup this fiber when they go out of scope on their original VM stack. + * Generator scope fns have their own handling. */ + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &EG(active_fiber)->std; + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to fiber */ + } + + /* Call flags are copied when moving This, now we update them */ + ZEND_DEL_CALL_FLAG(scope_ex, ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_ALLOCATED); + ZEND_ADD_CALL_FLAG(scope_ex, ZEND_CALL_SCOPE_FN); + + Z_EXTRA_P(this_ptr) = 1; /* recursion guard */ + + execute_data = scope_ex; + EG(current_execute_data) = scope_ex; + + LOAD_OPLINE(); + ZEND_VM_CONTINUE(); +} diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 2b5b9e5fcd47..a72a0ad5e9a0 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -318,6 +318,7 @@ static uint8_t zend_user_opcodes[256] = {0, #define SPEC_RULE_COMMUTATIVE 0x00800000 #define SPEC_RULE_ISSET 0x01000000 #define SPEC_RULE_OBSERVER 0x02000000 +#define SPEC_RULE_SCOPE_FN 0x04000000 static const uint32_t *zend_spec_handlers; static zend_vm_opcode_handler_t const *zend_opcode_handlers; @@ -1152,7 +1153,8 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV SAVE_OPLINE(); #endif - if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) { +observed_fn_try_again: + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); i_free_compiled_variables(execute_data); @@ -1174,8 +1176,11 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP)) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD @@ -1185,14 +1190,16 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_clean_and_cache_symbol_table(EX(symbol_table)); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { - zend_free_extra_named_params(EX(extra_named_params)); - } - /* Free extra args before releasing the closure, * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -1210,12 +1217,16 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & ZEND_CALL_TOP) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; } zend_destroy_static_vars(&EX(func)->op_array); + destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; @@ -1237,28 +1248,83 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); } else { - if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) { + if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS))) { if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) { zend_clean_and_cache_symbol_table(EX(symbol_table)); } zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); } if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } ZEND_VM_RETURN(); + } else if (UNEXPECTED(call_info & ZEND_CALL_SCOPE_FN)) { + /* ZEND_CALL_SCOPE_FN is aliased to ZEND_CALL_OBSERVED. Slow path. */ + if (UNEXPECTED((EX(func)->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) == 0)) { + call_info &= ~ZEND_CALL_SCOPE_FN; + goto observed_fn_try_again; + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + + if (UNEXPECTED(Z_EXTRA_P(scope_ex_t0))) { + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr != NULL); + *attached_object_ptr = NULL; + } + + zend_fiber *unwind_fiber = NULL; + if (EG(active_fiber) && EG(active_fiber)->forced_unwind_target == execute_data) { + unwind_fiber = zend_scope_fn_consume_forced_unwind(); + } + + EG(current_execute_data) = EX(prev_execute_data); + zend_scope_fn_detach(execute_data); + + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + OBJ_RELEASE(closure_obj); + zend_scope_ex_pop_original_call_frame(original_call_frame); + + if (UNEXPECTED(unwind_fiber != NULL)) { + /* the error will be thrown here on the user's next resume. */ + zend_fiber_suspend(unwind_fiber, NULL, NULL); + } + + if (UNEXPECTED(call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + + execute_data = EG(current_execute_data); + if (UNEXPECTED(EG(exception) != NULL)) { + zend_rethrow_exception(execute_data); + HANDLE_EXCEPTION_LEAVE(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); } else /* if (call_kind == ZEND_CALL_TOP_CODE) */ { zend_array *symbol_table = EX(symbol_table); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; @@ -1340,8 +1406,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -1410,8 +1476,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -1479,8 +1545,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -1674,8 +1740,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -1792,8 +1858,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -1908,8 +1974,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -2043,7 +2109,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D fcall_end: zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } @@ -2178,7 +2244,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D fcall_end: zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } @@ -2309,7 +2375,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ fcall_end: zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } @@ -2342,29 +2408,172 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_GENERATOR_CRE uint32_t num_args, used_stack, call_info; SAVE_OPLINE(); + + /* SPEC(SCOPE_FN) selects the variant via opline->extended_value bit 0. + * Scope-fn variant: EX is the scope_ex, which lives inside the parent + * function's frame. We must NOT memcpy/emalloc — the body's CV + * accesses use negative offsets that only work when the frame stays + * in place. The scope_ex itself IS the generator's execute_data. The + * generator is attached to the closure so parent-exit cleanup can + * force-destruct it. */ + bool is_scope_fn = 0; + ZEND_ASSERT(!is_scope_fn || zend_is_scope_ex(execute_data)); + + /* Capture EX(This)'s original call_info before we OR in TOP_FUNCTION | + * GENERATOR. The scope-fn leave path checks ZEND_CALL_TOP against the + * pre-modification value (TOP_FUNCTION includes TOP). */ + uint32_t orig_call_info = EX_CALL_INFO(); + object_init_ex(return_value, zend_ce_generator); - /* - * Normally the execute_data is allocated on the VM stack (because it does - * not actually do any allocation and thus is faster). For generators - * though this behavior would be suboptimal, because the (rather large) - * structure would have to be copied back and forth every time execution is - * suspended or resumed. That's why for generators the execution context - * is allocated on heap. - */ - num_args = EX_NUM_ARGS(); - if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { - used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); - gen_execute_data = (zend_execute_data*)emalloc(used_stack); - used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + if (is_scope_fn) { + gen_execute_data = execute_data; + } else { + /* + * Normally the execute_data is allocated on the VM stack (because it does + * not actually do any allocation and thus is faster). For generators + * though this behavior would be suboptimal, because the (rather large) + * structure would have to be copied back and forth every time execution is + * suspended or resumed. That's why for generators the execution context + * is allocated on heap. + */ + num_args = EX_NUM_ARGS(); + if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + } else { + used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + } + memcpy(gen_execute_data, execute_data, used_stack); + } + + /* Save execution context in generator object. */ + generator = (zend_generator *) Z_OBJ_P(return_value); + generator->func = gen_execute_data->func; + generator->execute_data = gen_execute_data; + generator->frozen_call_stack = NULL; + generator->execute_fake.opline = NULL; + generator->execute_fake.func = NULL; + generator->execute_fake.prev_execute_data = NULL; + ZVAL_OBJ(&generator->execute_fake.This, (zend_object *) generator); + + gen_execute_data->opline = opline; + gen_execute_data->return_value = (zval*)generator; + call_info = Z_TYPE_INFO(EX(This)); + if (!is_scope_fn) { + if ((call_info & Z_TYPE_MASK) == IS_OBJECT + && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) + /* Bug #72523 */ + || UNEXPECTED(zend_execute_ex != execute_ex))) { + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); + Z_ADDREF(gen_execute_data->This); + } + /* scope_ex lives in parent's TMP, not heap-allocated. */ + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_ALLOCATED); + /* scope-fn keeps prev_execute_data — body CV access traverses it. */ + gen_execute_data->prev_execute_data = NULL; + } + ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_GENERATOR)); + Z_TYPE_INFO(gen_execute_data->This) = call_info; + + if (is_scope_fn) { + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zend_object **attached_object_ptr = + zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &generator->std; + + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to generator */ + + EG(current_execute_data) = EX(prev_execute_data); + execute_data = EX(prev_execute_data); + zend_scope_ex_pop_original_call_frame(original_call_frame); + if (UNEXPECTED(orig_call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } + + call_info = EX_CALL_INFO(); + EG(current_execute_data) = EX(prev_execute_data); + if (EXPECTED(!(call_info & (ZEND_CALL_TOP|ZEND_CALL_ALLOCATED)))) { + EG(vm_stack_top) = (zval*)execute_data; + execute_data = EX(prev_execute_data); + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } else if (EXPECTED(!(call_info & ZEND_CALL_TOP))) { + zend_execute_data *old_execute_data = execute_data; + execute_data = EX(prev_execute_data); + zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); } else { - used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); - gen_execute_data = (zend_execute_data*)emalloc(used_stack); + ZEND_VM_RETURN(); + } + } else { + ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); + } +} + +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + zval *return_value = EX(return_value); + + if (EXPECTED(return_value)) { + USE_OPLINE + zend_generator *generator; + zend_execute_data *gen_execute_data; + uint32_t num_args, used_stack, call_info; + + SAVE_OPLINE(); + + /* SPEC(SCOPE_FN) selects the variant via opline->extended_value bit 0. + * Scope-fn variant: EX is the scope_ex, which lives inside the parent + * function's frame. We must NOT memcpy/emalloc — the body's CV + * accesses use negative offsets that only work when the frame stays + * in place. The scope_ex itself IS the generator's execute_data. The + * generator is attached to the closure so parent-exit cleanup can + * force-destruct it. */ + bool is_scope_fn = 1; + ZEND_ASSERT(!is_scope_fn || zend_is_scope_ex(execute_data)); + + /* Capture EX(This)'s original call_info before we OR in TOP_FUNCTION | + * GENERATOR. The scope-fn leave path checks ZEND_CALL_TOP against the + * pre-modification value (TOP_FUNCTION includes TOP). */ + uint32_t orig_call_info = EX_CALL_INFO(); + + object_init_ex(return_value, zend_ce_generator); + + if (is_scope_fn) { + gen_execute_data = execute_data; + } else { + /* + * Normally the execute_data is allocated on the VM stack (because it does + * not actually do any allocation and thus is faster). For generators + * though this behavior would be suboptimal, because the (rather large) + * structure would have to be copied back and forth every time execution is + * suspended or resumed. That's why for generators the execution context + * is allocated on heap. + */ + num_args = EX_NUM_ARGS(); + if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + } else { + used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + } + memcpy(gen_execute_data, execute_data, used_stack); } - memcpy(gen_execute_data, execute_data, used_stack); /* Save execution context in generator object. */ - generator = (zend_generator *) Z_OBJ_P(EX(return_value)); + generator = (zend_generator *) Z_OBJ_P(return_value); generator->func = gen_execute_data->func; generator->execute_data = gen_execute_data; generator->frozen_call_stack = NULL; @@ -2374,19 +2583,44 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_GENERATOR_CRE ZVAL_OBJ(&generator->execute_fake.This, (zend_object *) generator); gen_execute_data->opline = opline; - /* EX(return_value) keeps pointer to zend_object (not a real zval) */ gen_execute_data->return_value = (zval*)generator; call_info = Z_TYPE_INFO(EX(This)); - if ((call_info & Z_TYPE_MASK) == IS_OBJECT - && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) - /* Bug #72523 */ - || UNEXPECTED(zend_execute_ex != execute_ex))) { - ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); - Z_ADDREF(gen_execute_data->This); - } - ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_ALLOCATED | ZEND_CALL_GENERATOR)); + if (!is_scope_fn) { + if ((call_info & Z_TYPE_MASK) == IS_OBJECT + && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) + /* Bug #72523 */ + || UNEXPECTED(zend_execute_ex != execute_ex))) { + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); + Z_ADDREF(gen_execute_data->This); + } + /* scope_ex lives in parent's TMP, not heap-allocated. */ + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_ALLOCATED); + /* scope-fn keeps prev_execute_data — body CV access traverses it. */ + gen_execute_data->prev_execute_data = NULL; + } + ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_GENERATOR)); Z_TYPE_INFO(gen_execute_data->This) = call_info; - gen_execute_data->prev_execute_data = NULL; + + if (is_scope_fn) { + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zend_object **attached_object_ptr = + zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &generator->std; + + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to generator */ + + EG(current_execute_data) = EX(prev_execute_data); + execute_data = EX(prev_execute_data); + zend_scope_ex_pop_original_call_frame(original_call_frame); + if (UNEXPECTED(orig_call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } call_info = EX_CALL_INFO(); EG(current_execute_data) = EX(prev_execute_data); @@ -3573,7 +3807,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI zend_array *args = NULL; zend_function *fbc = EX(func); zval *ret = EX(return_value); - uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_EXTRA_NAMED_PARAMS); + uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS); uint32_t num_args = EX_NUM_ARGS(); zend_execute_data *call; @@ -3608,7 +3842,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI } else { ZVAL_EMPTY_ARRAY(call_args); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { if (zend_hash_num_elements(Z_ARRVAL_P(call_args)) == 0) { GC_ADDREF(call->extra_named_params); ZVAL_ARR(call_args, call->extra_named_params); @@ -3690,7 +3924,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } if (ret == &retval) { @@ -3725,7 +3959,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI zend_array *args = NULL; zend_function *fbc = EX(func); zval *ret = EX(return_value); - uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_EXTRA_NAMED_PARAMS); + uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS); uint32_t num_args = EX_NUM_ARGS(); zend_execute_data *call; @@ -3760,7 +3994,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI } else { ZVAL_EMPTY_ARRAY(call_args); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { if (zend_hash_num_elements(Z_ARRVAL_P(call_args)) == 0) { GC_ADDREF(call->extra_named_params); ZVAL_ARR(call_args, call->extra_named_params); @@ -3838,7 +4072,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } if (ret == &retval) { @@ -4050,6 +4284,83 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV } ZEND_VM_CONTINUE(); } + +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ENTER_SCOPE_FUNC_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zval *this_ptr = zend_closure_get_this_ptr_ptr(closure_obj); + + SAVE_OPLINE(); + + zend_execute_data *parent_ex = Z_PTR_P(this_ptr); + if (UNEXPECTED(!parent_ex)) { + zend_throw_error(NULL, "Cannot call scope function: defining scope has exited"); + HANDLE_EXCEPTION(); + } + + if (UNEXPECTED(Z_EXTRA_P(this_ptr) != 0)) { + zend_throw_error(NULL, "Cannot recursively call scope function"); + HANDLE_EXCEPTION(); + } + + uint32_t num_params = opline->op1.num; + uint32_t scope_ex_offset = opline->extended_value; + zend_execute_data *restrict scope_ex = (zend_execute_data *)((char *)parent_ex + scope_ex_offset); + + /* Move args into parent CVs via the literal mapping. */ + if (num_params > 0) { + zval *literals = EX(func)->op_array.literals; + for (uint32_t i = 0; i < num_params; i++) { + zval *src = ZEND_CALL_ARG(execute_data, i + 1); + uint32_t parent_cv_offset = (uint32_t)Z_LVAL(literals[i]); + zval *dst = ZEND_CALL_VAR(parent_ex, parent_cv_offset); + zval garbage; + ZVAL_COPY_VALUE(&garbage, dst); + ZVAL_COPY_VALUE(dst, src); + ZVAL_UNDEF(src); + i_zval_ptr_dtor(&garbage); + } + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } + } + + scope_ex->opline = opline + 1; + scope_ex->call = NULL; + scope_ex->return_value = EX(return_value); + scope_ex->This = EX(This); + scope_ex->func = EX(func); + scope_ex->prev_execute_data = EX(prev_execute_data); + scope_ex->symbol_table = parent_ex->symbol_table; + scope_ex->run_time_cache = EX(run_time_cache); + scope_ex->extra_named_params = EX(extra_named_params); + /* Store the original call frame pointer in scope_ex's first temporary for later retrieval / freeing */ + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(scope_ex, 0); + Z_PTR_P(scope_ex_t0) = execute_data; + Z_EXTRA_P(scope_ex_t0) = 0; + if (UNEXPECTED(EG(active_fiber) != NULL && !zend_pointer_in_vm_stack(EG(vm_stack), scope_ex)) && EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) == 0)) { + /* When scope fns run in a fiber different to where they were declared, + * we must cleanup this fiber when they go out of scope on their original VM stack. + * Generator scope fns have their own handling. */ + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &EG(active_fiber)->std; + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to fiber */ + } + + /* Call flags are copied when moving This, now we update them */ + ZEND_DEL_CALL_FLAG(scope_ex, ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_ALLOCATED); + ZEND_ADD_CALL_FLAG(scope_ex, ZEND_CALL_SCOPE_FN); + + Z_EXTRA_P(this_ptr) = 1; /* recursion guard */ + + execute_data = scope_ex; + EG(current_execute_data) = scope_ex; + + LOAD_OPLINE(); + ZEND_VM_CONTINUE(); +} static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -4378,7 +4689,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_RECV_VARIADIC ZVAL_EMPTY_ARRAY(params); } - if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (EX_CALL_INFO() & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_string *name; zval *param; zend_arg_info *arg_info = &EX(func)->common.arg_info[EX(func)->common.num_args]; @@ -11916,7 +12227,140 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FUNC_GET_ARGS result_size = arg_count; } - if (result_size) { + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (0 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!0 && result_size) { + SAVE_OPLINE(); + uint32_t first_extra_arg = EX(func)->op_array.num_args; + + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + zval *p, *q; + uint32_t i = skip; + p = EX_VAR_NUM(i); + if (arg_count > first_extra_arg) { + while (i < first_extra_arg) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + if (skip < first_extra_arg) { + skip = 0; + } else { + skip -= first_extra_arg; + } + p = EX_VAR_NUM(EX(func)->op_array.last_var + EX(func)->op_array.T + skip); + } + while (i < arg_count) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + } ZEND_HASH_FILL_END(); + ht->nNumOfElements = result_size; + } else { + ZVAL_EMPTY_ARRAY(EX_VAR(opline->result.var)); + } + ZEND_VM_NEXT_OPCODE(); +} + +/* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_array *ht; + uint32_t arg_count, result_size, skip; + + arg_count = EX_NUM_ARGS(); + if (IS_CONST == IS_CONST) { + skip = Z_LVAL_P(RT_CONSTANT(opline, opline->op1)); + if (arg_count < skip) { + result_size = 0; + } else { + result_size = arg_count - skip; + } + } else { + skip = 0; + result_size = arg_count; + } + + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (1 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!1 && result_size) { SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; @@ -18282,6 +18726,88 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FETCH_CLASS_N ZEND_VM_NEXT_OPCODE(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_function *func; + zval *object; + zend_class_entry *called_scope; + + func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num]; + if (Z_TYPE(EX(This)) == IS_OBJECT) { + called_scope = Z_OBJCE(EX(This)); + object = &EX(This); + } else { + called_scope = Z_CE(EX(This)); + object = NULL; + } + SAVE_OPLINE(); + zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); + + zend_execute_data *parent_ex = execute_data; + if (UNEXPECTED(zend_is_scope_ex(execute_data))) { + /* CVs live on the top-level parent, hence we need to reference that one */ + parent_ex = zend_scope_fn_parent_ex(execute_data); + } + + /* The closure this_ptr references parent execute_data and a recursion guard in Z_EXTRA. */ + zval *this_ptr = zend_closure_get_this_ptr_ptr(Z_OBJ_P(EX_VAR(opline->result.var))); + Z_PTR_P(this_ptr) = parent_ex; + Z_TYPE_INFO_P(this_ptr) = IS_PTR; + Z_EXTRA_P(this_ptr) = 0; + + /* Register in the parent's tracked-temporaries array (loop re-evaluation + * replaces the existing entry). Z_EXTRA per entry: bits 0-7 = mode, + * bits 8-23 = func_def index. */ + const zend_op_array *parent_op_array = &parent_ex->func->op_array; + zval *base = ZEND_CALL_VAR_NUM(parent_ex, parent_op_array->last_var + parent_op_array->T - 1); + + if (!(ZEND_CALL_INFO(parent_ex) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + /* Bit was not yet set: no real named params on this frame, so it's + * safe to NULL extra_named_params (the bit is now set purely as a + * tracked-temps marker; deref sites NULL-check before using). */ + ZEND_ADD_CALL_FLAG(parent_ex, ZEND_CALL_TRACKED_TEMPORARIES); + parent_ex->extra_named_params = NULL; + Z_EXTRA_P(base) = 0; + } + + uint32_t func_ref = opline->op2.num; + uint32_t count = Z_EXTRA_P(base) >> 8; + zval *existing = NULL; + /* op1 is a TMP that caches our entry's index across re-executions. + * Pass 9 reserves a fresh slot via ++max + permanent bitset so other + * TMPs can't be allocated on top of it. The slot is uninitialized on + * first reach; bounds + entry validation reject garbage. */ + zval *tracked_tmp_cache = EX_VAR(opline->op1.var); + uint32_t cached_i = (uint32_t)Z_LVAL_P(tracked_tmp_cache); + uint32_t tracked_temporary_data = ZEND_TRACKED_TMP_SCOPE_FUNC | (func_ref << 8); + if (cached_i < count) { + zval *e = base - cached_i - 1; + if (Z_EXTRA_P(e) == tracked_temporary_data) { + existing = e; + } + } + + if (existing) { + /* Invalidate the previous closure so any escaped reference + * throws on call instead of reaching a freed parent frame. */ + zval *old_tp = zend_closure_get_this_ptr_ptr(Z_OBJ_P(existing)); + Z_PTR_P(old_tp) = NULL; + OBJ_RELEASE(Z_OBJ_P(existing)); + ZVAL_OBJ(existing, Z_OBJ_P(EX_VAR(opline->result.var))); + Z_ADDREF_P(existing); + } else { + zval *entry = base - count - 1; + ZVAL_OBJ(entry, Z_OBJ_P(EX_VAR(opline->result.var))); + Z_ADDREF_P(entry); + Z_EXTRA_P(entry) = tracked_temporary_data; + Z_EXTRA_P(base) = ((count + 1) << 8) | (Z_EXTRA_P(base) & 0xFF); + Z_LVAL_P(tracked_tmp_cache) = count; + } + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_DIV_SPEC_TMP_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -37413,7 +37939,140 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FUNC_GET_ARGS result_size = arg_count; } - if (result_size) { + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (0 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!0 && result_size) { + SAVE_OPLINE(); + uint32_t first_extra_arg = EX(func)->op_array.num_args; + + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + zval *p, *q; + uint32_t i = skip; + p = EX_VAR_NUM(i); + if (arg_count > first_extra_arg) { + while (i < first_extra_arg) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + if (skip < first_extra_arg) { + skip = 0; + } else { + skip -= first_extra_arg; + } + p = EX_VAR_NUM(EX(func)->op_array.last_var + EX(func)->op_array.T + skip); + } + while (i < arg_count) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + } ZEND_HASH_FILL_END(); + ht->nNumOfElements = result_size; + } else { + ZVAL_EMPTY_ARRAY(EX_VAR(opline->result.var)); + } + ZEND_VM_NEXT_OPCODE(); +} + +/* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_array *ht; + uint32_t arg_count, result_size, skip; + + arg_count = EX_NUM_ARGS(); + if (IS_UNUSED == IS_CONST) { + skip = Z_LVAL_P(RT_CONSTANT(opline, opline->op1)); + if (arg_count < skip) { + result_size = 0; + } else { + result_size = arg_count - skip; + } + } else { + skip = 0; + result_size = arg_count; + } + + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (1 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!1 && result_size) { SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; @@ -53926,7 +54585,8 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend SAVE_OPLINE(); #endif - if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) { +observed_fn_try_again: + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); i_free_compiled_variables(execute_data); @@ -53948,8 +54608,11 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP)) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD @@ -53959,14 +54622,16 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend zend_clean_and_cache_symbol_table(EX(symbol_table)); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { - zend_free_extra_named_params(EX(extra_named_params)); - } - /* Free extra args before releasing the closure, * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -53984,12 +54649,16 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & ZEND_CALL_TOP) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; } zend_destroy_static_vars(&EX(func)->op_array); + destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; @@ -54011,28 +54680,83 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); } else { - if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) { + if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS))) { if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) { zend_clean_and_cache_symbol_table(EX(symbol_table)); } zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); } if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } ZEND_VM_RETURN(); + } else if (UNEXPECTED(call_info & ZEND_CALL_SCOPE_FN)) { + /* ZEND_CALL_SCOPE_FN is aliased to ZEND_CALL_OBSERVED. Slow path. */ + if (UNEXPECTED((EX(func)->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) == 0)) { + call_info &= ~ZEND_CALL_SCOPE_FN; + goto observed_fn_try_again; + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + + if (UNEXPECTED(Z_EXTRA_P(scope_ex_t0))) { + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr != NULL); + *attached_object_ptr = NULL; + } + + zend_fiber *unwind_fiber = NULL; + if (EG(active_fiber) && EG(active_fiber)->forced_unwind_target == execute_data) { + unwind_fiber = zend_scope_fn_consume_forced_unwind(); + } + + EG(current_execute_data) = EX(prev_execute_data); + zend_scope_fn_detach(execute_data); + + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + OBJ_RELEASE(closure_obj); + zend_scope_ex_pop_original_call_frame(original_call_frame); + + if (UNEXPECTED(unwind_fiber != NULL)) { + /* the error will be thrown here on the user's next resume. */ + zend_fiber_suspend(unwind_fiber, NULL, NULL); + } + + if (UNEXPECTED(call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + + execute_data = EG(current_execute_data); + if (UNEXPECTED(EG(exception) != NULL)) { + zend_rethrow_exception(execute_data); + HANDLE_EXCEPTION_LEAVE(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); } else /* if (call_kind == ZEND_CALL_TOP_CODE) */ { zend_array *symbol_table = EX(symbol_table); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; @@ -54114,8 +54838,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_ICA zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -54184,8 +54908,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_ICA zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -54253,8 +54977,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_IC zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -54448,8 +55172,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -54566,8 +55290,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -54682,8 +55406,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FC zend_vm_stack_free_args(call); uint32_t call_info = ZEND_CALL_INFO(call); - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { - if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (UNEXPECTED(call_info & (ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) { + if (call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_free_extra_named_params(call->extra_named_params); } zend_vm_stack_free_call_frame_ex(call_info, call); @@ -54817,7 +55541,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA fcall_end: zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } @@ -54952,7 +55676,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA fcall_end: zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } @@ -55083,7 +55807,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FC fcall_end: zend_vm_stack_free_args(call); - if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } @@ -55116,29 +55840,172 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_GENERATOR_CREATE_S uint32_t num_args, used_stack, call_info; SAVE_OPLINE(); + + /* SPEC(SCOPE_FN) selects the variant via opline->extended_value bit 0. + * Scope-fn variant: EX is the scope_ex, which lives inside the parent + * function's frame. We must NOT memcpy/emalloc — the body's CV + * accesses use negative offsets that only work when the frame stays + * in place. The scope_ex itself IS the generator's execute_data. The + * generator is attached to the closure so parent-exit cleanup can + * force-destruct it. */ + bool is_scope_fn = 0; + ZEND_ASSERT(!is_scope_fn || zend_is_scope_ex(execute_data)); + + /* Capture EX(This)'s original call_info before we OR in TOP_FUNCTION | + * GENERATOR. The scope-fn leave path checks ZEND_CALL_TOP against the + * pre-modification value (TOP_FUNCTION includes TOP). */ + uint32_t orig_call_info = EX_CALL_INFO(); + object_init_ex(return_value, zend_ce_generator); - /* - * Normally the execute_data is allocated on the VM stack (because it does - * not actually do any allocation and thus is faster). For generators - * though this behavior would be suboptimal, because the (rather large) - * structure would have to be copied back and forth every time execution is - * suspended or resumed. That's why for generators the execution context - * is allocated on heap. - */ - num_args = EX_NUM_ARGS(); - if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { - used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); - gen_execute_data = (zend_execute_data*)emalloc(used_stack); - used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + if (is_scope_fn) { + gen_execute_data = execute_data; + } else { + /* + * Normally the execute_data is allocated on the VM stack (because it does + * not actually do any allocation and thus is faster). For generators + * though this behavior would be suboptimal, because the (rather large) + * structure would have to be copied back and forth every time execution is + * suspended or resumed. That's why for generators the execution context + * is allocated on heap. + */ + num_args = EX_NUM_ARGS(); + if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + } else { + used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + } + memcpy(gen_execute_data, execute_data, used_stack); + } + + /* Save execution context in generator object. */ + generator = (zend_generator *) Z_OBJ_P(return_value); + generator->func = gen_execute_data->func; + generator->execute_data = gen_execute_data; + generator->frozen_call_stack = NULL; + generator->execute_fake.opline = NULL; + generator->execute_fake.func = NULL; + generator->execute_fake.prev_execute_data = NULL; + ZVAL_OBJ(&generator->execute_fake.This, (zend_object *) generator); + + gen_execute_data->opline = opline; + gen_execute_data->return_value = (zval*)generator; + call_info = Z_TYPE_INFO(EX(This)); + if (!is_scope_fn) { + if ((call_info & Z_TYPE_MASK) == IS_OBJECT + && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) + /* Bug #72523 */ + || UNEXPECTED(zend_execute_ex != execute_ex))) { + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); + Z_ADDREF(gen_execute_data->This); + } + /* scope_ex lives in parent's TMP, not heap-allocated. */ + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_ALLOCATED); + /* scope-fn keeps prev_execute_data — body CV access traverses it. */ + gen_execute_data->prev_execute_data = NULL; + } + ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_GENERATOR)); + Z_TYPE_INFO(gen_execute_data->This) = call_info; + + if (is_scope_fn) { + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zend_object **attached_object_ptr = + zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &generator->std; + + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to generator */ + + EG(current_execute_data) = EX(prev_execute_data); + execute_data = EX(prev_execute_data); + zend_scope_ex_pop_original_call_frame(original_call_frame); + if (UNEXPECTED(orig_call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } + + call_info = EX_CALL_INFO(); + EG(current_execute_data) = EX(prev_execute_data); + if (EXPECTED(!(call_info & (ZEND_CALL_TOP|ZEND_CALL_ALLOCATED)))) { + EG(vm_stack_top) = (zval*)execute_data; + execute_data = EX(prev_execute_data); + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } else if (EXPECTED(!(call_info & ZEND_CALL_TOP))) { + zend_execute_data *old_execute_data = execute_data; + execute_data = EX(prev_execute_data); + zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } else { + ZEND_VM_RETURN(); + } + } else { + ZEND_VM_DISPATCH_TO_LEAVE_HELPER(zend_leave_helper_SPEC_TAILCALL); + } +} + +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + zval *return_value = EX(return_value); + + if (EXPECTED(return_value)) { + USE_OPLINE + zend_generator *generator; + zend_execute_data *gen_execute_data; + uint32_t num_args, used_stack, call_info; + + SAVE_OPLINE(); + + /* SPEC(SCOPE_FN) selects the variant via opline->extended_value bit 0. + * Scope-fn variant: EX is the scope_ex, which lives inside the parent + * function's frame. We must NOT memcpy/emalloc — the body's CV + * accesses use negative offsets that only work when the frame stays + * in place. The scope_ex itself IS the generator's execute_data. The + * generator is attached to the closure so parent-exit cleanup can + * force-destruct it. */ + bool is_scope_fn = 1; + ZEND_ASSERT(!is_scope_fn || zend_is_scope_ex(execute_data)); + + /* Capture EX(This)'s original call_info before we OR in TOP_FUNCTION | + * GENERATOR. The scope-fn leave path checks ZEND_CALL_TOP against the + * pre-modification value (TOP_FUNCTION includes TOP). */ + uint32_t orig_call_info = EX_CALL_INFO(); + + object_init_ex(return_value, zend_ce_generator); + + if (is_scope_fn) { + gen_execute_data = execute_data; } else { - used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); - gen_execute_data = (zend_execute_data*)emalloc(used_stack); + /* + * Normally the execute_data is allocated on the VM stack (because it does + * not actually do any allocation and thus is faster). For generators + * though this behavior would be suboptimal, because the (rather large) + * structure would have to be copied back and forth every time execution is + * suspended or resumed. That's why for generators the execution context + * is allocated on heap. + */ + num_args = EX_NUM_ARGS(); + if (EXPECTED(num_args <= EX(func)->op_array.num_args)) { + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var + EX(func)->op_array.T) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + used_stack = (ZEND_CALL_FRAME_SLOT + EX(func)->op_array.last_var) * sizeof(zval); + } else { + used_stack = (ZEND_CALL_FRAME_SLOT + num_args + EX(func)->op_array.last_var + EX(func)->op_array.T - EX(func)->op_array.num_args) * sizeof(zval); + gen_execute_data = (zend_execute_data*)emalloc(used_stack); + } + memcpy(gen_execute_data, execute_data, used_stack); } - memcpy(gen_execute_data, execute_data, used_stack); /* Save execution context in generator object. */ - generator = (zend_generator *) Z_OBJ_P(EX(return_value)); + generator = (zend_generator *) Z_OBJ_P(return_value); generator->func = gen_execute_data->func; generator->execute_data = gen_execute_data; generator->frozen_call_stack = NULL; @@ -55148,19 +56015,44 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_GENERATOR_CREATE_S ZVAL_OBJ(&generator->execute_fake.This, (zend_object *) generator); gen_execute_data->opline = opline; - /* EX(return_value) keeps pointer to zend_object (not a real zval) */ gen_execute_data->return_value = (zval*)generator; call_info = Z_TYPE_INFO(EX(This)); - if ((call_info & Z_TYPE_MASK) == IS_OBJECT - && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) - /* Bug #72523 */ - || UNEXPECTED(zend_execute_ex != execute_ex))) { - ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); - Z_ADDREF(gen_execute_data->This); - } - ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_ALLOCATED | ZEND_CALL_GENERATOR)); + if (!is_scope_fn) { + if ((call_info & Z_TYPE_MASK) == IS_OBJECT + && (!(call_info & (ZEND_CALL_CLOSURE|ZEND_CALL_RELEASE_THIS)) + /* Bug #72523 */ + || UNEXPECTED(zend_execute_ex != execute_ex))) { + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_RELEASE_THIS); + Z_ADDREF(gen_execute_data->This); + } + /* scope_ex lives in parent's TMP, not heap-allocated. */ + ZEND_ADD_CALL_FLAG_EX(call_info, ZEND_CALL_ALLOCATED); + /* scope-fn keeps prev_execute_data — body CV access traverses it. */ + gen_execute_data->prev_execute_data = NULL; + } + ZEND_ADD_CALL_FLAG_EX(call_info, (ZEND_CALL_TOP_FUNCTION | ZEND_CALL_GENERATOR)); Z_TYPE_INFO(gen_execute_data->This) = call_info; - gen_execute_data->prev_execute_data = NULL; + + if (is_scope_fn) { + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zend_object **attached_object_ptr = + zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &generator->std; + + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to generator */ + + EG(current_execute_data) = EX(prev_execute_data); + execute_data = EX(prev_execute_data); + zend_scope_ex_pop_original_call_frame(original_call_frame); + if (UNEXPECTED(orig_call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); + } call_info = EX_CALL_INFO(); EG(current_execute_data) = EX(prev_execute_data); @@ -56231,7 +57123,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP zend_array *args = NULL; zend_function *fbc = EX(func); zval *ret = EX(return_value); - uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_EXTRA_NAMED_PARAMS); + uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS); uint32_t num_args = EX_NUM_ARGS(); zend_execute_data *call; @@ -56266,7 +57158,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP } else { ZVAL_EMPTY_ARRAY(call_args); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { if (zend_hash_num_elements(Z_ARRVAL_P(call_args)) == 0) { GC_ADDREF(call->extra_named_params); ZVAL_ARR(call_args, call->extra_named_params); @@ -56348,7 +57240,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } if (ret == &retval) { @@ -56383,7 +57275,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP zend_array *args = NULL; zend_function *fbc = EX(func); zval *ret = EX(return_value); - uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_EXTRA_NAMED_PARAMS); + uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS); uint32_t num_args = EX_NUM_ARGS(); zend_execute_data *call; @@ -56418,7 +57310,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP } else { ZVAL_EMPTY_ARRAY(call_args); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { if (zend_hash_num_elements(Z_ARRVAL_P(call_args)) == 0) { GC_ADDREF(call->extra_named_params); ZVAL_ARR(call_args, call->extra_named_params); @@ -56496,7 +57388,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(call->extra_named_params); } if (ret == &retval) { @@ -56708,6 +57600,83 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend } ZEND_VM_CONTINUE(); } + +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ENTER_SCOPE_FUNC_SPEC_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zval *this_ptr = zend_closure_get_this_ptr_ptr(closure_obj); + + SAVE_OPLINE(); + + zend_execute_data *parent_ex = Z_PTR_P(this_ptr); + if (UNEXPECTED(!parent_ex)) { + zend_throw_error(NULL, "Cannot call scope function: defining scope has exited"); + HANDLE_EXCEPTION(); + } + + if (UNEXPECTED(Z_EXTRA_P(this_ptr) != 0)) { + zend_throw_error(NULL, "Cannot recursively call scope function"); + HANDLE_EXCEPTION(); + } + + uint32_t num_params = opline->op1.num; + uint32_t scope_ex_offset = opline->extended_value; + zend_execute_data *restrict scope_ex = (zend_execute_data *)((char *)parent_ex + scope_ex_offset); + + /* Move args into parent CVs via the literal mapping. */ + if (num_params > 0) { + zval *literals = EX(func)->op_array.literals; + for (uint32_t i = 0; i < num_params; i++) { + zval *src = ZEND_CALL_ARG(execute_data, i + 1); + uint32_t parent_cv_offset = (uint32_t)Z_LVAL(literals[i]); + zval *dst = ZEND_CALL_VAR(parent_ex, parent_cv_offset); + zval garbage; + ZVAL_COPY_VALUE(&garbage, dst); + ZVAL_COPY_VALUE(dst, src); + ZVAL_UNDEF(src); + i_zval_ptr_dtor(&garbage); + } + if (UNEXPECTED(EG(exception))) { + HANDLE_EXCEPTION(); + } + } + + scope_ex->opline = opline + 1; + scope_ex->call = NULL; + scope_ex->return_value = EX(return_value); + scope_ex->This = EX(This); + scope_ex->func = EX(func); + scope_ex->prev_execute_data = EX(prev_execute_data); + scope_ex->symbol_table = parent_ex->symbol_table; + scope_ex->run_time_cache = EX(run_time_cache); + scope_ex->extra_named_params = EX(extra_named_params); + /* Store the original call frame pointer in scope_ex's first temporary for later retrieval / freeing */ + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(scope_ex, 0); + Z_PTR_P(scope_ex_t0) = execute_data; + Z_EXTRA_P(scope_ex_t0) = 0; + if (UNEXPECTED(EG(active_fiber) != NULL && !zend_pointer_in_vm_stack(EG(vm_stack), scope_ex)) && EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) == 0)) { + /* When scope fns run in a fiber different to where they were declared, + * we must cleanup this fiber when they go out of scope on their original VM stack. + * Generator scope fns have their own handling. */ + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr == NULL); + *attached_object_ptr = &EG(active_fiber)->std; + Z_EXTRA_P(scope_ex_t0) = 1; /* mark attached to fiber */ + } + + /* Call flags are copied when moving This, now we update them */ + ZEND_DEL_CALL_FLAG(scope_ex, ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_ALLOCATED); + ZEND_ADD_CALL_FLAG(scope_ex, ZEND_CALL_SCOPE_FN); + + Z_EXTRA_P(this_ptr) = 1; /* recursion guard */ + + execute_data = scope_ex; + EG(current_execute_data) = scope_ex; + + LOAD_OPLINE(); + ZEND_VM_CONTINUE(); +} static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57036,7 +58005,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_VARIADIC_SPEC ZVAL_EMPTY_ARRAY(params); } - if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + if (EX_CALL_INFO() & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS) { zend_string *name; zval *param; zend_arg_info *arg_info = &EX(func)->common.arg_info[EX(func)->common.num_args]; @@ -64472,7 +65441,140 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FUNC_GET_ARGS_SPEC result_size = arg_count; } - if (result_size) { + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (0 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!0 && result_size) { + SAVE_OPLINE(); + uint32_t first_extra_arg = EX(func)->op_array.num_args; + + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + zval *p, *q; + uint32_t i = skip; + p = EX_VAR_NUM(i); + if (arg_count > first_extra_arg) { + while (i < first_extra_arg) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + if (skip < first_extra_arg) { + skip = 0; + } else { + skip -= first_extra_arg; + } + p = EX_VAR_NUM(EX(func)->op_array.last_var + EX(func)->op_array.T + skip); + } + while (i < arg_count) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + } ZEND_HASH_FILL_END(); + ht->nNumOfElements = result_size; + } else { + ZVAL_EMPTY_ARRAY(EX_VAR(opline->result.var)); + } + ZEND_VM_NEXT_OPCODE(); +} + +/* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_array *ht; + uint32_t arg_count, result_size, skip; + + arg_count = EX_NUM_ARGS(); + if (IS_CONST == IS_CONST) { + skip = Z_LVAL_P(RT_CONSTANT(opline, opline->op1)); + if (arg_count < skip) { + result_size = 0; + } else { + result_size = arg_count - skip; + } + } else { + skip = 0; + result_size = arg_count; + } + + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (1 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!1 && result_size) { SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; @@ -70838,6 +71940,88 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FETCH_CLASS_NAME_S ZEND_VM_NEXT_OPCODE(); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_function *func; + zval *object; + zend_class_entry *called_scope; + + func = (zend_function *) EX(func)->op_array.dynamic_func_defs[opline->op2.num]; + if (Z_TYPE(EX(This)) == IS_OBJECT) { + called_scope = Z_OBJCE(EX(This)); + object = &EX(This); + } else { + called_scope = Z_CE(EX(This)); + object = NULL; + } + SAVE_OPLINE(); + zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); + + zend_execute_data *parent_ex = execute_data; + if (UNEXPECTED(zend_is_scope_ex(execute_data))) { + /* CVs live on the top-level parent, hence we need to reference that one */ + parent_ex = zend_scope_fn_parent_ex(execute_data); + } + + /* The closure this_ptr references parent execute_data and a recursion guard in Z_EXTRA. */ + zval *this_ptr = zend_closure_get_this_ptr_ptr(Z_OBJ_P(EX_VAR(opline->result.var))); + Z_PTR_P(this_ptr) = parent_ex; + Z_TYPE_INFO_P(this_ptr) = IS_PTR; + Z_EXTRA_P(this_ptr) = 0; + + /* Register in the parent's tracked-temporaries array (loop re-evaluation + * replaces the existing entry). Z_EXTRA per entry: bits 0-7 = mode, + * bits 8-23 = func_def index. */ + const zend_op_array *parent_op_array = &parent_ex->func->op_array; + zval *base = ZEND_CALL_VAR_NUM(parent_ex, parent_op_array->last_var + parent_op_array->T - 1); + + if (!(ZEND_CALL_INFO(parent_ex) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + /* Bit was not yet set: no real named params on this frame, so it's + * safe to NULL extra_named_params (the bit is now set purely as a + * tracked-temps marker; deref sites NULL-check before using). */ + ZEND_ADD_CALL_FLAG(parent_ex, ZEND_CALL_TRACKED_TEMPORARIES); + parent_ex->extra_named_params = NULL; + Z_EXTRA_P(base) = 0; + } + + uint32_t func_ref = opline->op2.num; + uint32_t count = Z_EXTRA_P(base) >> 8; + zval *existing = NULL; + /* op1 is a TMP that caches our entry's index across re-executions. + * Pass 9 reserves a fresh slot via ++max + permanent bitset so other + * TMPs can't be allocated on top of it. The slot is uninitialized on + * first reach; bounds + entry validation reject garbage. */ + zval *tracked_tmp_cache = EX_VAR(opline->op1.var); + uint32_t cached_i = (uint32_t)Z_LVAL_P(tracked_tmp_cache); + uint32_t tracked_temporary_data = ZEND_TRACKED_TMP_SCOPE_FUNC | (func_ref << 8); + if (cached_i < count) { + zval *e = base - cached_i - 1; + if (Z_EXTRA_P(e) == tracked_temporary_data) { + existing = e; + } + } + + if (existing) { + /* Invalidate the previous closure so any escaped reference + * throws on call instead of reaching a freed parent frame. */ + zval *old_tp = zend_closure_get_this_ptr_ptr(Z_OBJ_P(existing)); + Z_PTR_P(old_tp) = NULL; + OBJ_RELEASE(Z_OBJ_P(existing)); + ZVAL_OBJ(existing, Z_OBJ_P(EX_VAR(opline->result.var))); + Z_ADDREF_P(existing); + } else { + zval *entry = base - count - 1; + ZVAL_OBJ(entry, Z_OBJ_P(EX_VAR(opline->result.var))); + Z_ADDREF_P(entry); + Z_EXTRA_P(entry) = tracked_temporary_data; + Z_EXTRA_P(base) = ((count + 1) << 8) | (Z_EXTRA_P(base) & 0xFF); + Z_LVAL_P(tracked_tmp_cache) = count; + } + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DIV_SPEC_TMP_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -89869,7 +91053,140 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FUNC_GET_ARGS_SPEC result_size = arg_count; } - if (result_size) { + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (0 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!0 && result_size) { + SAVE_OPLINE(); + uint32_t first_extra_arg = EX(func)->op_array.num_args; + + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + zval *p, *q; + uint32_t i = skip; + p = EX_VAR_NUM(i); + if (arg_count > first_extra_arg) { + while (i < first_extra_arg) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + if (skip < first_extra_arg) { + skip = 0; + } else { + skip -= first_extra_arg; + } + p = EX_VAR_NUM(EX(func)->op_array.last_var + EX(func)->op_array.T + skip); + } + while (i < arg_count) { + q = p; + if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + if (Z_OPT_REFCOUNTED_P(q)) { + Z_ADDREF_P(q); + } + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + p++; + i++; + } + } ZEND_HASH_FILL_END(); + ht->nNumOfElements = result_size; + } else { + ZVAL_EMPTY_ARRAY(EX_VAR(opline->result.var)); + } + ZEND_VM_NEXT_OPCODE(); +} + +/* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zend_array *ht; + uint32_t arg_count, result_size, skip; + + arg_count = EX_NUM_ARGS(); + if (IS_UNUSED == IS_CONST) { + skip = Z_LVAL_P(RT_CONSTANT(opline, opline->op1)); + if (arg_count < skip) { + result_size = 0; + } else { + result_size = arg_count - skip; + } + } else { + skip = 0; + result_size = arg_count; + } + + /* SPEC(SCOPE_FN): the scope-fn variant skips the regular path entirely + * via the explicit return below. The non-scope-fn variant collapses this + * if (0) and the C compiler eliminates the body. Both arms — declared + * params via the literal mapping and extras via the original call + * frame's tail — are handled by zend_scope_fn_get_arg_zval, so a single + * loop covers `i < arg_count` without the regular path's + * first_extra_arg boundary. */ + if (1 && result_size) { + SAVE_OPLINE(); + ht = zend_new_array(result_size); + ZVAL_ARR(EX_VAR(opline->result.var), ht); + zend_hash_real_init_packed(ht); + ZEND_HASH_FILL_PACKED(ht) { + for (uint32_t i = skip; i < arg_count; i++) { + zval *q = zend_scope_fn_get_arg_zval(execute_data, i); + if (q && EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { + ZVAL_DEREF(q); + Z_TRY_ADDREF_P(q); + ZEND_HASH_FILL_SET(q); + } else { + ZEND_HASH_FILL_SET_NULL(); + } + ZEND_HASH_FILL_NEXT(); + } + } ZEND_HASH_FILL_END(); + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + } + + if (!1 && result_size) { SAVE_OPLINE(); uint32_t first_extra_arg = EX(func)->op_array.num_args; @@ -108934,6 +110251,7 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_INSTANCEOF_SPEC_CV_UNUSED_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_GENERATOR_CREATE_SPEC_LABEL, + (void*)&&ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_MAKE_REF_SPEC_VAR_UNUSED_LABEL, @@ -109074,9 +110392,14 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_SPACESHIP_SPEC_CV_CV_LABEL, (void*)&&ZEND_FUNC_NUM_ARGS_SPEC_UNUSED_UNUSED_LABEL, (void*)&&ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_LABEL, + (void*)&&ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN_LABEL, + (void*)&&ZEND_NULL_LABEL, + (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_LABEL, + (void*)&&ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN_LABEL, + (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_FETCH_STATIC_PROP_R_SPEC_LABEL, (void*)&&ZEND_FETCH_STATIC_PROP_W_SPEC_LABEL, @@ -109213,6 +110536,8 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_LABEL, (void*)&&ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_LABEL, (void*)&&ZEND_TYPE_ASSERT_SPEC_CONST_LABEL, + (void*)&&ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP_LABEL, + (void*)&&ZEND_ENTER_SCOPE_FUNC_SPEC_LABEL, (void*)&&ZEND_INIT_FCALL_OFFSET_SPEC_CONST_LABEL, (void*)&&ZEND_RECV_NOTYPE_SPEC_LABEL, (void*)&&ZEND_NULL_LABEL, @@ -110244,7 +111569,8 @@ ZEND_API void execute_ex(zend_execute_data *ex) SAVE_OPLINE(); #endif - if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) { +observed_fn_try_again_SPEC: + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); i_free_compiled_variables(execute_data); @@ -110266,8 +111592,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP)) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD @@ -110277,14 +111606,16 @@ ZEND_API void execute_ex(zend_execute_data *ex) zend_clean_and_cache_symbol_table(EX(symbol_table)); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { - zend_free_extra_named_params(EX(extra_named_params)); - } - /* Free extra args before releasing the closure, * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -110302,12 +111633,16 @@ ZEND_API void execute_ex(zend_execute_data *ex) LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); - } else if (EXPECTED((call_info & ZEND_CALL_TOP) == 0)) { + } else if (EXPECTED((call_info & (ZEND_CALL_TOP|ZEND_CALL_SCOPE_FN)) == 0)) { + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; } zend_destroy_static_vars(&EX(func)->op_array); + destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; @@ -110329,28 +111664,83 @@ ZEND_API void execute_ex(zend_execute_data *ex) LOAD_NEXT_OPLINE(); ZEND_VM_LEAVE(); } else { - if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { + if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_SCOPE_FN)) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) { + if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS))) { if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) { zend_clean_and_cache_symbol_table(EX(symbol_table)); } zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); } if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } ZEND_VM_RETURN(); + } else if (UNEXPECTED(call_info & ZEND_CALL_SCOPE_FN)) { + /* ZEND_CALL_SCOPE_FN is aliased to ZEND_CALL_OBSERVED. Slow path. */ + if (UNEXPECTED((EX(func)->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC) == 0)) { + call_info &= ~ZEND_CALL_SCOPE_FN; + goto observed_fn_try_again_SPEC; + } + + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + + zend_object *closure_obj = ZEND_CLOSURE_OBJECT(EX(func)); + zval *scope_ex_t0 = ZEND_CALL_VAR_NUM(execute_data, 0); + zend_execute_data *original_call_frame = Z_PTR_P(scope_ex_t0); + + if (UNEXPECTED(Z_EXTRA_P(scope_ex_t0))) { + zend_object **attached_object_ptr = zend_closure_get_attached_object_ptr(closure_obj); + ZEND_ASSERT(*attached_object_ptr != NULL); + *attached_object_ptr = NULL; + } + + zend_fiber *unwind_fiber = NULL; + if (EG(active_fiber) && EG(active_fiber)->forced_unwind_target == execute_data) { + unwind_fiber = zend_scope_fn_consume_forced_unwind(); + } + + EG(current_execute_data) = EX(prev_execute_data); + zend_scope_fn_detach(execute_data); + + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { + zend_free_extra_named_params(EX(extra_named_params)); + } + + OBJ_RELEASE(closure_obj); + zend_scope_ex_pop_original_call_frame(original_call_frame); + + if (UNEXPECTED(unwind_fiber != NULL)) { + /* the error will be thrown here on the user's next resume. */ + zend_fiber_suspend(unwind_fiber, NULL, NULL); + } + + if (UNEXPECTED(call_info & ZEND_CALL_TOP)) { + ZEND_VM_RETURN(); + } + + execute_data = EG(current_execute_data); + if (UNEXPECTED(EG(exception) != NULL)) { + zend_rethrow_exception(execute_data); + HANDLE_EXCEPTION_LEAVE(); + } + LOAD_NEXT_OPLINE(); + ZEND_VM_LEAVE(); } else /* if (call_kind == ZEND_CALL_TOP_CODE) */ { zend_array *symbol_table = EX(symbol_table); + zend_vm_force_unwind_scope_fn_closures(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (EX(func)->op_array.last_var > 0) { zend_detach_symbol_table(execute_data); call_info |= ZEND_CALL_NEEDS_REATTACH; @@ -110447,6 +111837,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_GENERATOR_CREATE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_GENERATOR_CREATE_SPEC) HYBRID_BREAK(); + HYBRID_CASE(ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN): + VM_TRACE(ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN) + ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN) + HYBRID_BREAK(); HYBRID_CASE(ZEND_SEND_UNPACK_SPEC): VM_TRACE(ZEND_SEND_UNPACK_SPEC) ZEND_SEND_UNPACK_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -110587,6 +111982,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_JMP_FORWARD_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_JMP_FORWARD_SPEC) HYBRID_BREAK(); + HYBRID_CASE(ZEND_ENTER_SCOPE_FUNC_SPEC): + VM_TRACE(ZEND_ENTER_SCOPE_FUNC_SPEC) + ZEND_ENTER_SCOPE_FUNC_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_ENTER_SCOPE_FUNC_SPEC) + HYBRID_BREAK(); HYBRID_CASE(ZEND_INIT_FCALL_BY_NAME_SPEC_CONST): VM_TRACE(ZEND_INIT_FCALL_BY_NAME_SPEC_CONST) ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -111591,6 +112991,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED) HYBRID_BREAK(); + HYBRID_CASE(ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN): + VM_TRACE(ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN) + ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN) + HYBRID_BREAK(); HYBRID_CASE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED): VM_TRACE(ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -112580,6 +113985,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_FETCH_CLASS_NAME_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_FETCH_CLASS_NAME_SPEC_TMP) HYBRID_BREAK(); + HYBRID_CASE(ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP): + VM_TRACE(ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP) + ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP) + HYBRID_BREAK(); HYBRID_CASE(ZEND_DIV_SPEC_TMP_CONST): VM_TRACE(ZEND_DIV_SPEC_TMP_CONST) ZEND_DIV_SPEC_TMP_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -114085,6 +115495,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED) HYBRID_BREAK(); + HYBRID_CASE(ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN): + VM_TRACE(ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN) + ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN) + HYBRID_BREAK(); HYBRID_CASE(ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED): VM_TRACE(ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED) ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -117872,6 +119287,7 @@ void zend_vm_init(void) ZEND_INSTANCEOF_SPEC_CV_UNUSED_HANDLER, ZEND_NULL_HANDLER, ZEND_GENERATOR_CREATE_SPEC_HANDLER, + ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_MAKE_REF_SPEC_VAR_UNUSED_HANDLER, @@ -118012,9 +119428,14 @@ void zend_vm_init(void) ZEND_SPACESHIP_SPEC_CV_CV_HANDLER, ZEND_FUNC_NUM_ARGS_SPEC_UNUSED_UNUSED_HANDLER, ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_HANDLER, + ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_HANDLER, + ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN_HANDLER, + ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_FETCH_STATIC_PROP_R_SPEC_HANDLER, ZEND_FETCH_STATIC_PROP_W_SPEC_HANDLER, @@ -118151,6 +119572,8 @@ void zend_vm_init(void) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_HANDLER, ZEND_TYPE_ASSERT_SPEC_CONST_HANDLER, + ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP_HANDLER, + ZEND_ENTER_SCOPE_FUNC_SPEC_HANDLER, ZEND_INIT_FCALL_OFFSET_SPEC_CONST_HANDLER, ZEND_RECV_NOTYPE_SPEC_HANDLER, ZEND_NULL_HANDLER, @@ -121350,6 +122773,7 @@ void zend_vm_init(void) ZEND_INSTANCEOF_SPEC_CV_UNUSED_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_GENERATOR_CREATE_SPEC_TAILCALL_HANDLER, + ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_MAKE_REF_SPEC_VAR_UNUSED_TAILCALL_HANDLER, @@ -121490,9 +122914,14 @@ void zend_vm_init(void) ZEND_SPACESHIP_SPEC_CV_CV_TAILCALL_HANDLER, ZEND_FUNC_NUM_ARGS_SPEC_UNUSED_UNUSED_TAILCALL_HANDLER, ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_TAILCALL_HANDLER, + ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_TAILCALL_HANDLER, + ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN_TAILCALL_HANDLER, + ZEND_NULL_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_FETCH_STATIC_PROP_R_SPEC_TAILCALL_HANDLER, ZEND_FETCH_STATIC_PROP_W_SPEC_TAILCALL_HANDLER, @@ -121629,6 +123058,8 @@ void zend_vm_init(void) ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_TAILCALL_HANDLER, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST_TAILCALL_HANDLER, ZEND_TYPE_ASSERT_SPEC_CONST_TAILCALL_HANDLER, + ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP_TAILCALL_HANDLER, + ZEND_ENTER_SCOPE_FUNC_SPEC_TAILCALL_HANDLER, ZEND_INIT_FCALL_OFFSET_SPEC_CONST_TAILCALL_HANDLER, ZEND_RECV_NOTYPE_SPEC_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, @@ -122597,7 +124028,7 @@ void zend_vm_init(void) 1255, 1256 | SPEC_RULE_OP1, 1261 | SPEC_RULE_OP1, - 3474, + 3482, 1266 | SPEC_RULE_OP1, 1271 | SPEC_RULE_OP1, 1276 | SPEC_RULE_OP2, @@ -122631,7 +124062,7 @@ void zend_vm_init(void) 1559 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1584 | SPEC_RULE_OP1, 1589, - 3474, + 3482, 1590 | SPEC_RULE_OP1, 1595 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 1620 | SPEC_RULE_OP1 | SPEC_RULE_OP2, @@ -122691,123 +124122,123 @@ void zend_vm_init(void) 2248 | SPEC_RULE_OP1, 2253, 2254 | SPEC_RULE_OP1 | SPEC_RULE_OP2, - 2279, - 2280 | SPEC_RULE_OP1, - 2285, + 2279 | SPEC_RULE_SCOPE_FN, + 2281 | SPEC_RULE_OP1, 2286, 2287, 2288, 2289, 2290, 2291, - 2292 | SPEC_RULE_OP1 | SPEC_RULE_OP2, - 2317, + 2292, + 2293 | SPEC_RULE_OP1 | SPEC_RULE_OP2, 2318, 2319, - 2320 | SPEC_RULE_OP1, - 2325, - 2326 | SPEC_RULE_ISSET, - 2328 | SPEC_RULE_OP2, - 2333, - 2334 | SPEC_RULE_OP1, - 2339 | SPEC_RULE_OBSERVER, - 2341, - 2342 | SPEC_RULE_OP1 | SPEC_RULE_OP2, - 2367 | SPEC_RULE_OP1 | SPEC_RULE_OBSERVER, - 2377, + 2320, + 2321 | SPEC_RULE_OP1, + 2326, + 2327 | SPEC_RULE_ISSET, + 2329 | SPEC_RULE_OP2, + 2334, + 2335 | SPEC_RULE_OP1, + 2340 | SPEC_RULE_OBSERVER, + 2342, + 2343 | SPEC_RULE_OP1 | SPEC_RULE_OP2, + 2368 | SPEC_RULE_OP1 | SPEC_RULE_OBSERVER, 2378, 2379, 2380, - 2381 | SPEC_RULE_OP1, - 2386, + 2381, + 2382 | SPEC_RULE_OP1, 2387, - 2388 | SPEC_RULE_OP1, - 2393 | SPEC_RULE_OP1 | SPEC_RULE_OP2, - 2418, - 2419 | SPEC_RULE_OP1, - 2424, - 2425, - 2426, - 2427, - 2428, - 2429, + 2388, + 2389 | SPEC_RULE_OP1, + 2394 | SPEC_RULE_OP1 | SPEC_RULE_OP2, + 2419, + 2420 | SPEC_RULE_OP1 | SPEC_RULE_SCOPE_FN, 2430, 2431, - 2432 | SPEC_RULE_OP1 | SPEC_RULE_OP2, - 2457, - 2458, - 2459, - 2460 | SPEC_RULE_OP2, + 2432, + 2433, + 2434, + 2435, + 2436, + 2437, + 2438 | SPEC_RULE_OP1 | SPEC_RULE_OP2, + 2463, + 2464, 2465, - 2466 | SPEC_RULE_OP1, - 2471 | SPEC_RULE_OP1, - 2476 | SPEC_RULE_OP1, - 2481 | SPEC_RULE_OP1, - 2486 | SPEC_RULE_OP1, - 2491, + 2466 | SPEC_RULE_OP2, + 2471, + 2472 | SPEC_RULE_OP1, + 2477 | SPEC_RULE_OP1, + 2482 | SPEC_RULE_OP1, + 2487 | SPEC_RULE_OP1, 2492 | SPEC_RULE_OP1, - 2497 | SPEC_RULE_OP1 | SPEC_RULE_OP2, - 2522 | SPEC_RULE_OP1, - 2527 | SPEC_RULE_OP2, - 2532 | SPEC_RULE_OP1, - 2537 | SPEC_RULE_OP1, - 2542, - 2543, - 2544, - 2545, - 2546, - 2547 | SPEC_RULE_OBSERVER, - 2549 | SPEC_RULE_OBSERVER, - 2551 | SPEC_RULE_OBSERVER, + 2497, + 2498 | SPEC_RULE_OP1, + 2503 | SPEC_RULE_OP1 | SPEC_RULE_OP2, + 2528 | SPEC_RULE_OP1, + 2533 | SPEC_RULE_OP2, + 2538 | SPEC_RULE_OP1, + 2543 | SPEC_RULE_OP1, + 2548, + 2549, + 2550, + 2551, + 2552, 2553 | SPEC_RULE_OBSERVER, - 2555, - 2556, - 2557, - 2558, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, - 3474, + 2555 | SPEC_RULE_OBSERVER, + 2557 | SPEC_RULE_OBSERVER, + 2559 | SPEC_RULE_OBSERVER, + 2561, + 2562, + 2563, + 2564, + 2565, + 2566, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, + 3482, }; #if 0 #elif (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) @@ -122917,6 +124348,7 @@ static uint32_t ZEND_FASTCALL zend_vm_get_opcode_handler_idx(uint32_t spec, cons uint32_t offset = 0; if (spec & SPEC_RULE_OP1) offset = offset * 5 + zend_vm_decode[op->op1_type]; if (spec & SPEC_RULE_OP2) offset = offset * 5 + zend_vm_decode[op->op2_type]; + if (spec & SPEC_RULE_SCOPE_FN) offset = offset * 2 + (op->extended_value & 1); if (spec & SPEC_EXTRA_MASK) { if (spec & SPEC_RULE_RETVAL) { offset = offset * 2 + (op->result_type != IS_UNUSED); @@ -122984,7 +124416,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2567 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2575 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -122992,7 +124424,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2592 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2600 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -123000,7 +124432,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2617 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2625 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; if (op->op1_type < op->op2_type) { zend_swap_operands(op); } @@ -123011,17 +124443,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2642 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2650 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2667 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2675 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2692 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 2700 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_MUL: @@ -123032,17 +124464,17 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2717 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2725 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2742 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2750 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2767 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 2775 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_IDENTICAL: @@ -123053,16 +124485,16 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2792 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2800 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2867 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2875 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { - spec = 3092 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3100 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3098 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3106 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_IDENTICAL: @@ -123073,16 +124505,16 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2942 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2950 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3017 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3025 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { - spec = 3095 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3103 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF))) { - spec = 3103 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; + spec = 3111 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_EQUAL: @@ -123093,12 +124525,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2792 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2800 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2867 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2875 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_NOT_EQUAL: @@ -123109,12 +124541,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 2942 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 2950 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3017 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; + spec = 3025 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; } break; case ZEND_IS_SMALLER: @@ -123122,12 +124554,12 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3108 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3116 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3183 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3191 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_IS_SMALLER_OR_EQUAL: @@ -123135,79 +124567,79 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3258 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3266 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } else if (op1_info == MAY_BE_DOUBLE && op2_info == MAY_BE_DOUBLE) { if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3333 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; + spec = 3341 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH; } break; case ZEND_QM_ASSIGN: if (op1_info == MAY_BE_LONG) { - spec = 3420 | SPEC_RULE_OP1; + spec = 3428 | SPEC_RULE_OP1; } else if (op1_info == MAY_BE_DOUBLE) { - spec = 3425 | SPEC_RULE_OP1; + spec = 3433 | SPEC_RULE_OP1; } else if ((op->op1_type == IS_CONST) ? !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)) : (!(op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE))))) { - spec = 3430 | SPEC_RULE_OP1; + spec = 3438 | SPEC_RULE_OP1; } break; case ZEND_PRE_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3408 | SPEC_RULE_RETVAL; + spec = 3416 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3410 | SPEC_RULE_RETVAL; + spec = 3418 | SPEC_RULE_RETVAL; } break; case ZEND_PRE_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3412 | SPEC_RULE_RETVAL; + spec = 3420 | SPEC_RULE_RETVAL; } else if (op1_info == MAY_BE_LONG) { - spec = 3414 | SPEC_RULE_RETVAL; + spec = 3422 | SPEC_RULE_RETVAL; } break; case ZEND_POST_INC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3416; + spec = 3424; } else if (op1_info == MAY_BE_LONG) { - spec = 3417; + spec = 3425; } break; case ZEND_POST_DEC: if (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG) { - spec = 3418; + spec = 3426; } else if (op1_info == MAY_BE_LONG) { - spec = 3419; + spec = 3427; } break; case ZEND_JMP: if (OP_JMP_ADDR(op, op->op1) > op) { - spec = 2566; + spec = 2574; } break; case ZEND_INIT_FCALL: if (Z_EXTRA_P(RT_CONSTANT(op, op->op2)) != 0) { - spec = 2559; + spec = 2567; } break; case ZEND_RECV: if (op->op2.num == MAY_BE_ANY) { - spec = 2560; + spec = 2568; } break; case ZEND_SEND_VAL: if (op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3470; + spec = 3478; } break; case ZEND_SEND_VAR_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3465 | SPEC_RULE_OP1; + spec = 3473 | SPEC_RULE_OP1; } break; case ZEND_FE_FETCH_R: if (op->op2_type == IS_CV && (op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 3472 | SPEC_RULE_RETVAL; + spec = 3480 | SPEC_RULE_RETVAL; } break; case ZEND_FETCH_DIM_R: @@ -123215,22 +124647,22 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t if (op->op1_type == IS_CONST && op->op2_type == IS_CONST) { break; } - spec = 3435 | SPEC_RULE_OP1 | SPEC_RULE_OP2; + spec = 3443 | SPEC_RULE_OP1 | SPEC_RULE_OP2; } break; case ZEND_SEND_VAL_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3471; + spec = 3479; } break; case ZEND_SEND_VAR: if (op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3460 | SPEC_RULE_OP1; + spec = 3468 | SPEC_RULE_OP1; } break; case ZEND_COUNT: if ((op1_info & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 2561 | SPEC_RULE_OP1; + spec = 2569 | SPEC_RULE_OP1; } break; case ZEND_BW_OR: diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index d26d6a166dda..ab812f984976 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -788,6 +788,9 @@ function gen_code($f, $spec, $kind, $code, $op1, $op2, $name, $extra_spec=null) "/ZEND_OBSERVER_FCALL_END\(\s*([^,]*)\s*,\s*(.*)\s*\)/" => isset($extra_spec['OBSERVER']) ? ($extra_spec['OBSERVER'] == 0 ? "" : "zend_observer_fcall_end(\\1, \\2)") : "", + /* SPEC(SCOPE_FN): substitute the scope-fn predicate at handler-gen time + * so the C compiler can DCE the unselected branch. */ + "/ZEND_VM_IS_SCOPE_FN/" => isset($extra_spec['SCOPE_FN']) && $extra_spec['SCOPE_FN'] == 1 ? "1" : "0", ); $code = preg_replace(array_keys($specialized_replacements), array_values($specialized_replacements), $code); @@ -1625,6 +1628,11 @@ function extra_spec_name($extra_spec) { $s .= "_OBSERVER"; } } + if (isset($extra_spec["SCOPE_FN"])) { + if ($extra_spec["SCOPE_FN"]) { + $s .= "_SCOPE_FN"; + } + } return $s; } @@ -1651,6 +1659,9 @@ function extra_spec_flags($extra_spec) { if (isset($extra_spec["OBSERVER"])) { $s[] = "SPEC_RULE_OBSERVER"; } + if (isset($extra_spec["SCOPE_FN"])) { + $s[] = "SPEC_RULE_SCOPE_FN"; + } return $s; } @@ -1882,6 +1893,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"#define SPEC_RULE_COMMUTATIVE 0x00800000\n"); out($f,"#define SPEC_RULE_ISSET 0x01000000\n"); out($f,"#define SPEC_RULE_OBSERVER 0x02000000\n"); + out($f,"#define SPEC_RULE_SCOPE_FN 0x04000000\n"); out($f,"\n"); out($f,"static const uint32_t *zend_spec_handlers;\n"); out($f,"static zend_vm_opcode_handler_t const *zend_opcode_handlers;\n"); @@ -2456,6 +2468,9 @@ function parse_spec_rules($def, $lineno, $str) { case "OBSERVER": $ret["OBSERVER"] = array(0, 1); break; + case "SCOPE_FN": + $ret["SCOPE_FN"] = array(0, 1); + break; default: die("ERROR ($def:$lineno): Wrong specialization rules '$str'\n"); } @@ -2957,6 +2972,14 @@ function gen_vm($def, $skel) { out($f, "\tuint32_t offset = 0;\n"); out($f, "\tif (spec & SPEC_RULE_OP1) offset = offset * 5 + zend_vm_decode[op->op1_type];\n"); out($f, "\tif (spec & SPEC_RULE_OP2) offset = offset * 5 + zend_vm_decode[op->op2_type];\n"); + if (isset($used_extra_spec["SCOPE_FN"])) { + /* SPEC(SCOPE_FN): the scope-fn vs non-scope-fn variant is + * selected from op->extended_value bit 0 — set at compile time + * by the emitter when the body is a scope function. Independent + * axis like OP1/OP2; the SCOPE_FN-specced opcodes do not also + * use the SPEC_EXTRA_MASK chain. */ + out($f, "\tif (spec & SPEC_RULE_SCOPE_FN) offset = offset * 2 + (op->extended_value & 1);\n"); + } if (isset($used_extra_spec["OP_DATA"]) || isset($used_extra_spec["RETVAL"]) || diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index 6f1595195450..47b63ee074ee 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -907,687 +907,692 @@ _(2276, ZEND_INSTANCEOF_SPEC_CV_VAR) \ _(2277, ZEND_INSTANCEOF_SPEC_CV_UNUSED) \ _(2279, ZEND_GENERATOR_CREATE_SPEC) \ - _(2282, ZEND_MAKE_REF_SPEC_VAR_UNUSED) \ - _(2284, ZEND_MAKE_REF_SPEC_CV_UNUSED) \ - _(2285, ZEND_DECLARE_FUNCTION_SPEC) \ - _(2286, ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST) \ - _(2287, ZEND_DECLARE_CONST_SPEC_CONST_CONST) \ - _(2288, ZEND_DECLARE_CLASS_SPEC_CONST) \ - _(2289, ZEND_DECLARE_CLASS_DELAYED_SPEC_CONST_CONST) \ - _(2290, ZEND_DECLARE_ANON_CLASS_SPEC) \ - _(2291, ZEND_ADD_ARRAY_UNPACK_SPEC) \ - _(2292, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CONST_CONST) \ - _(2293, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CONST_TMP) \ - _(2296, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CONST_CV) \ - _(2297, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_TMP_CONST) \ - _(2298, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_TMP_TMP) \ - _(2301, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_TMP_CV) \ - _(2307, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_UNUSED_CONST) \ - _(2308, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_UNUSED_TMP) \ - _(2311, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_UNUSED_CV) \ - _(2312, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CV_CONST) \ - _(2313, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CV_TMP) \ - _(2316, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CV_CV) \ - _(2317, ZEND_HANDLE_EXCEPTION_SPEC) \ - _(2318, ZEND_USER_OPCODE_SPEC) \ - _(2319, ZEND_ASSERT_CHECK_SPEC) \ - _(2320, ZEND_JMP_SET_SPEC_CONST) \ - _(2321, ZEND_JMP_SET_SPEC_TMP) \ - _(2324, ZEND_JMP_SET_SPEC_CV) \ - _(2325, ZEND_UNSET_CV_SPEC_CV_UNUSED) \ - _(2326, ZEND_ISSET_ISEMPTY_CV_SPEC_CV_UNUSED_SET) \ - _(2327, ZEND_ISSET_ISEMPTY_CV_SPEC_CV_UNUSED_EMPTY) \ - _(2328, ZEND_FETCH_LIST_W_SPEC_VAR_CONST) \ - _(2329, ZEND_FETCH_LIST_W_SPEC_VAR_TMP) \ - _(2332, ZEND_FETCH_LIST_W_SPEC_VAR_CV) \ - _(2333, ZEND_SEPARATE_SPEC_VAR_UNUSED) \ - _(2335, ZEND_FETCH_CLASS_NAME_SPEC_TMP) \ - _(2337, ZEND_FETCH_CLASS_NAME_SPEC_UNUSED) \ - _(2338, ZEND_FETCH_CLASS_NAME_SPEC_CV) \ - _(2339, ZEND_CALL_TRAMPOLINE_SPEC) \ - _(2340, ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER) \ - _(2341, ZEND_DISCARD_EXCEPTION_SPEC) \ - _(2342, ZEND_YIELD_SPEC_CONST_CONST) \ - _(2343, ZEND_YIELD_SPEC_CONST_TMP) \ - _(2345, ZEND_YIELD_SPEC_CONST_UNUSED) \ - _(2346, ZEND_YIELD_SPEC_CONST_CV) \ - _(2347, ZEND_YIELD_SPEC_TMP_CONST) \ - _(2348, ZEND_YIELD_SPEC_TMP_TMP) \ - _(2350, ZEND_YIELD_SPEC_TMP_UNUSED) \ - _(2351, ZEND_YIELD_SPEC_TMP_CV) \ - _(2352, ZEND_YIELD_SPEC_VAR_CONST) \ - _(2353, ZEND_YIELD_SPEC_VAR_TMP) \ - _(2355, ZEND_YIELD_SPEC_VAR_UNUSED) \ - _(2356, ZEND_YIELD_SPEC_VAR_CV) \ - _(2357, ZEND_YIELD_SPEC_UNUSED_CONST) \ - _(2358, ZEND_YIELD_SPEC_UNUSED_TMP) \ - _(2360, ZEND_YIELD_SPEC_UNUSED_UNUSED) \ - _(2361, ZEND_YIELD_SPEC_UNUSED_CV) \ - _(2362, ZEND_YIELD_SPEC_CV_CONST) \ - _(2363, ZEND_YIELD_SPEC_CV_TMP) \ - _(2365, ZEND_YIELD_SPEC_CV_UNUSED) \ - _(2366, ZEND_YIELD_SPEC_CV_CV) \ - _(2367, ZEND_GENERATOR_RETURN_SPEC_CONST) \ - _(2368, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ - _(2369, ZEND_GENERATOR_RETURN_SPEC_TMP) \ - _(2370, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ - _(2371, ZEND_GENERATOR_RETURN_SPEC_VAR) \ - _(2372, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ - _(2375, ZEND_GENERATOR_RETURN_SPEC_CV) \ - _(2376, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ - _(2377, ZEND_FAST_CALL_SPEC) \ - _(2378, ZEND_FAST_RET_SPEC) \ - _(2379, ZEND_RECV_VARIADIC_SPEC_UNUSED) \ - _(2380, ZEND_SEND_UNPACK_SPEC) \ - _(2381, ZEND_YIELD_FROM_SPEC_CONST) \ - _(2382, ZEND_YIELD_FROM_SPEC_TMP) \ - _(2385, ZEND_YIELD_FROM_SPEC_CV) \ - _(2386, ZEND_COPY_TMP_SPEC_TMPVAR_UNUSED) \ - _(2387, ZEND_BIND_GLOBAL_SPEC_CV_CONST) \ - _(2388, ZEND_COALESCE_SPEC_CONST) \ - _(2389, ZEND_COALESCE_SPEC_TMP) \ - _(2392, ZEND_COALESCE_SPEC_CV) \ - _(2393, ZEND_SPACESHIP_SPEC_CONST_CONST) \ - _(2394, ZEND_SPACESHIP_SPEC_CONST_TMP) \ - _(2397, ZEND_SPACESHIP_SPEC_CONST_CV) \ - _(2398, ZEND_SPACESHIP_SPEC_TMP_CONST) \ - _(2399, ZEND_SPACESHIP_SPEC_TMP_TMP) \ - _(2402, ZEND_SPACESHIP_SPEC_TMP_CV) \ - _(2413, ZEND_SPACESHIP_SPEC_CV_CONST) \ - _(2414, ZEND_SPACESHIP_SPEC_CV_TMP) \ - _(2417, ZEND_SPACESHIP_SPEC_CV_CV) \ - _(2418, ZEND_FUNC_NUM_ARGS_SPEC_UNUSED_UNUSED) \ - _(2419, ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED) \ - _(2422, ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED) \ - _(2424, ZEND_FETCH_STATIC_PROP_R_SPEC) \ - _(2425, ZEND_FETCH_STATIC_PROP_W_SPEC) \ - _(2426, ZEND_FETCH_STATIC_PROP_RW_SPEC) \ - _(2427, ZEND_FETCH_STATIC_PROP_IS_SPEC) \ - _(2428, ZEND_FETCH_STATIC_PROP_FUNC_ARG_SPEC) \ - _(2429, ZEND_FETCH_STATIC_PROP_UNSET_SPEC) \ - _(2430, ZEND_UNSET_STATIC_PROP_SPEC) \ - _(2431, ZEND_ISSET_ISEMPTY_STATIC_PROP_SPEC) \ - _(2432, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_CONST) \ - _(2433, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_TMPVARCV) \ - _(2434, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_TMPVARCV) \ - _(2436, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_TMPVARCV) \ - _(2442, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_CONST) \ - _(2443, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_TMPVARCV) \ - _(2444, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_TMPVARCV) \ - _(2446, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_TMPVARCV) \ - _(2447, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_CONST) \ - _(2448, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_TMPVARCV) \ - _(2449, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_TMPVARCV) \ - _(2451, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_TMPVARCV) \ - _(2457, ZEND_BIND_LEXICAL_SPEC_TMP_CV) \ - _(2458, ZEND_BIND_STATIC_SPEC_CV) \ - _(2459, ZEND_FETCH_THIS_SPEC_UNUSED_UNUSED) \ - _(2460, ZEND_SEND_FUNC_ARG_SPEC_VAR_CONST) \ - _(2463, ZEND_SEND_FUNC_ARG_SPEC_VAR_UNUSED) \ - _(2465, ZEND_ISSET_ISEMPTY_THIS_SPEC_UNUSED_UNUSED) \ - _(2466, ZEND_SWITCH_LONG_SPEC_CONST_CONST) \ - _(2467, ZEND_SWITCH_LONG_SPEC_TMPVARCV_CONST) \ - _(2468, ZEND_SWITCH_LONG_SPEC_TMPVARCV_CONST) \ - _(2470, ZEND_SWITCH_LONG_SPEC_TMPVARCV_CONST) \ - _(2471, ZEND_SWITCH_STRING_SPEC_CONST_CONST) \ - _(2472, ZEND_SWITCH_STRING_SPEC_TMPVARCV_CONST) \ - _(2473, ZEND_SWITCH_STRING_SPEC_TMPVARCV_CONST) \ - _(2475, ZEND_SWITCH_STRING_SPEC_TMPVARCV_CONST) \ - _(2476, ZEND_IN_ARRAY_SPEC_CONST_CONST) \ - _(2477, ZEND_IN_ARRAY_SPEC_TMP_CONST) \ - _(2480, ZEND_IN_ARRAY_SPEC_CV_CONST) \ - _(2481, ZEND_COUNT_SPEC_CONST_UNUSED) \ - _(2482, ZEND_COUNT_SPEC_TMP_UNUSED) \ - _(2485, ZEND_COUNT_SPEC_CV_UNUSED) \ - _(2486, ZEND_GET_CLASS_SPEC_CONST_UNUSED) \ - _(2487, ZEND_GET_CLASS_SPEC_TMP_UNUSED) \ - _(2489, ZEND_GET_CLASS_SPEC_UNUSED_UNUSED) \ - _(2490, ZEND_GET_CLASS_SPEC_CV_UNUSED) \ - _(2491, ZEND_GET_CALLED_CLASS_SPEC_UNUSED_UNUSED) \ - _(2492, ZEND_GET_TYPE_SPEC_CONST_UNUSED) \ - _(2493, ZEND_GET_TYPE_SPEC_TMP_UNUSED) \ - _(2496, ZEND_GET_TYPE_SPEC_CV_UNUSED) \ - _(2497, ZEND_ARRAY_KEY_EXISTS_SPEC_CONST_CONST) \ - _(2498, ZEND_ARRAY_KEY_EXISTS_SPEC_CONST_TMP) \ - _(2501, ZEND_ARRAY_KEY_EXISTS_SPEC_CONST_CV) \ - _(2502, ZEND_ARRAY_KEY_EXISTS_SPEC_TMP_CONST) \ - _(2503, ZEND_ARRAY_KEY_EXISTS_SPEC_TMP_TMP) \ - _(2506, ZEND_ARRAY_KEY_EXISTS_SPEC_TMP_CV) \ - _(2517, ZEND_ARRAY_KEY_EXISTS_SPEC_CV_CONST) \ - _(2518, ZEND_ARRAY_KEY_EXISTS_SPEC_CV_TMP) \ - _(2521, ZEND_ARRAY_KEY_EXISTS_SPEC_CV_CV) \ - _(2522, ZEND_MATCH_SPEC_CONST_CONST) \ - _(2523, ZEND_MATCH_SPEC_TMPVARCV_CONST) \ - _(2524, ZEND_MATCH_SPEC_TMPVARCV_CONST) \ - _(2526, ZEND_MATCH_SPEC_TMPVARCV_CONST) \ - _(2527, ZEND_CASE_STRICT_SPEC_TMP_CONST) \ - _(2528, ZEND_CASE_STRICT_SPEC_TMP_TMP) \ - _(2531, ZEND_CASE_STRICT_SPEC_TMP_CV) \ - _(2532, ZEND_MATCH_ERROR_SPEC_CONST_UNUSED) \ - _(2533, ZEND_MATCH_ERROR_SPEC_TMPVARCV_UNUSED) \ - _(2534, ZEND_MATCH_ERROR_SPEC_TMPVARCV_UNUSED) \ - _(2536, ZEND_MATCH_ERROR_SPEC_TMPVARCV_UNUSED) \ - _(2537, ZEND_JMP_NULL_SPEC_CONST) \ - _(2538, ZEND_JMP_NULL_SPEC_TMP) \ - _(2541, ZEND_JMP_NULL_SPEC_CV) \ - _(2542, ZEND_CHECK_UNDEF_ARGS_SPEC_UNUSED_UNUSED) \ - _(2543, ZEND_FETCH_GLOBALS_SPEC_UNUSED_UNUSED) \ - _(2544, ZEND_VERIFY_NEVER_TYPE_SPEC_UNUSED_UNUSED) \ - _(2545, ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED) \ - _(2546, ZEND_BIND_INIT_STATIC_OR_JMP_SPEC_CV) \ - _(2547, ZEND_FRAMELESS_ICALL_0_SPEC_UNUSED_UNUSED) \ - _(2548, ZEND_FRAMELESS_ICALL_0_SPEC_OBSERVER) \ - _(2549, ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED) \ - _(2550, ZEND_FRAMELESS_ICALL_1_SPEC_OBSERVER) \ - _(2551, ZEND_FRAMELESS_ICALL_2_SPEC) \ - _(2552, ZEND_FRAMELESS_ICALL_2_SPEC_OBSERVER) \ - _(2553, ZEND_FRAMELESS_ICALL_3_SPEC) \ - _(2554, ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVER) \ - _(2555, ZEND_JMP_FRAMELESS_SPEC_CONST) \ - _(2556, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) \ - _(2557, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST) \ - _(2558, ZEND_TYPE_ASSERT_SPEC_CONST) \ - _(2559, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ - _(2560, ZEND_RECV_NOTYPE_SPEC) \ - _(2562, ZEND_COUNT_ARRAY_SPEC_TMP_UNUSED) \ - _(2565, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ - _(2566, ZEND_JMP_FORWARD_SPEC) \ - _(2572, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2573, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2574, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2576, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2577, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2578, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2579, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2280, ZEND_GENERATOR_CREATE_SPEC_SCOPE_FN) \ + _(2283, ZEND_MAKE_REF_SPEC_VAR_UNUSED) \ + _(2285, ZEND_MAKE_REF_SPEC_CV_UNUSED) \ + _(2286, ZEND_DECLARE_FUNCTION_SPEC) \ + _(2287, ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST) \ + _(2288, ZEND_DECLARE_CONST_SPEC_CONST_CONST) \ + _(2289, ZEND_DECLARE_CLASS_SPEC_CONST) \ + _(2290, ZEND_DECLARE_CLASS_DELAYED_SPEC_CONST_CONST) \ + _(2291, ZEND_DECLARE_ANON_CLASS_SPEC) \ + _(2292, ZEND_ADD_ARRAY_UNPACK_SPEC) \ + _(2293, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CONST_CONST) \ + _(2294, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CONST_TMP) \ + _(2297, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CONST_CV) \ + _(2298, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_TMP_CONST) \ + _(2299, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_TMP_TMP) \ + _(2302, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_TMP_CV) \ + _(2308, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_UNUSED_CONST) \ + _(2309, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_UNUSED_TMP) \ + _(2312, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_UNUSED_CV) \ + _(2313, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CV_CONST) \ + _(2314, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CV_TMP) \ + _(2317, ZEND_ISSET_ISEMPTY_PROP_OBJ_SPEC_CV_CV) \ + _(2318, ZEND_HANDLE_EXCEPTION_SPEC) \ + _(2319, ZEND_USER_OPCODE_SPEC) \ + _(2320, ZEND_ASSERT_CHECK_SPEC) \ + _(2321, ZEND_JMP_SET_SPEC_CONST) \ + _(2322, ZEND_JMP_SET_SPEC_TMP) \ + _(2325, ZEND_JMP_SET_SPEC_CV) \ + _(2326, ZEND_UNSET_CV_SPEC_CV_UNUSED) \ + _(2327, ZEND_ISSET_ISEMPTY_CV_SPEC_CV_UNUSED_SET) \ + _(2328, ZEND_ISSET_ISEMPTY_CV_SPEC_CV_UNUSED_EMPTY) \ + _(2329, ZEND_FETCH_LIST_W_SPEC_VAR_CONST) \ + _(2330, ZEND_FETCH_LIST_W_SPEC_VAR_TMP) \ + _(2333, ZEND_FETCH_LIST_W_SPEC_VAR_CV) \ + _(2334, ZEND_SEPARATE_SPEC_VAR_UNUSED) \ + _(2336, ZEND_FETCH_CLASS_NAME_SPEC_TMP) \ + _(2338, ZEND_FETCH_CLASS_NAME_SPEC_UNUSED) \ + _(2339, ZEND_FETCH_CLASS_NAME_SPEC_CV) \ + _(2340, ZEND_CALL_TRAMPOLINE_SPEC) \ + _(2341, ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER) \ + _(2342, ZEND_DISCARD_EXCEPTION_SPEC) \ + _(2343, ZEND_YIELD_SPEC_CONST_CONST) \ + _(2344, ZEND_YIELD_SPEC_CONST_TMP) \ + _(2346, ZEND_YIELD_SPEC_CONST_UNUSED) \ + _(2347, ZEND_YIELD_SPEC_CONST_CV) \ + _(2348, ZEND_YIELD_SPEC_TMP_CONST) \ + _(2349, ZEND_YIELD_SPEC_TMP_TMP) \ + _(2351, ZEND_YIELD_SPEC_TMP_UNUSED) \ + _(2352, ZEND_YIELD_SPEC_TMP_CV) \ + _(2353, ZEND_YIELD_SPEC_VAR_CONST) \ + _(2354, ZEND_YIELD_SPEC_VAR_TMP) \ + _(2356, ZEND_YIELD_SPEC_VAR_UNUSED) \ + _(2357, ZEND_YIELD_SPEC_VAR_CV) \ + _(2358, ZEND_YIELD_SPEC_UNUSED_CONST) \ + _(2359, ZEND_YIELD_SPEC_UNUSED_TMP) \ + _(2361, ZEND_YIELD_SPEC_UNUSED_UNUSED) \ + _(2362, ZEND_YIELD_SPEC_UNUSED_CV) \ + _(2363, ZEND_YIELD_SPEC_CV_CONST) \ + _(2364, ZEND_YIELD_SPEC_CV_TMP) \ + _(2366, ZEND_YIELD_SPEC_CV_UNUSED) \ + _(2367, ZEND_YIELD_SPEC_CV_CV) \ + _(2368, ZEND_GENERATOR_RETURN_SPEC_CONST) \ + _(2369, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ + _(2370, ZEND_GENERATOR_RETURN_SPEC_TMP) \ + _(2371, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ + _(2372, ZEND_GENERATOR_RETURN_SPEC_VAR) \ + _(2373, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ + _(2376, ZEND_GENERATOR_RETURN_SPEC_CV) \ + _(2377, ZEND_GENERATOR_RETURN_SPEC_OBSERVER) \ + _(2378, ZEND_FAST_CALL_SPEC) \ + _(2379, ZEND_FAST_RET_SPEC) \ + _(2380, ZEND_RECV_VARIADIC_SPEC_UNUSED) \ + _(2381, ZEND_SEND_UNPACK_SPEC) \ + _(2382, ZEND_YIELD_FROM_SPEC_CONST) \ + _(2383, ZEND_YIELD_FROM_SPEC_TMP) \ + _(2386, ZEND_YIELD_FROM_SPEC_CV) \ + _(2387, ZEND_COPY_TMP_SPEC_TMPVAR_UNUSED) \ + _(2388, ZEND_BIND_GLOBAL_SPEC_CV_CONST) \ + _(2389, ZEND_COALESCE_SPEC_CONST) \ + _(2390, ZEND_COALESCE_SPEC_TMP) \ + _(2393, ZEND_COALESCE_SPEC_CV) \ + _(2394, ZEND_SPACESHIP_SPEC_CONST_CONST) \ + _(2395, ZEND_SPACESHIP_SPEC_CONST_TMP) \ + _(2398, ZEND_SPACESHIP_SPEC_CONST_CV) \ + _(2399, ZEND_SPACESHIP_SPEC_TMP_CONST) \ + _(2400, ZEND_SPACESHIP_SPEC_TMP_TMP) \ + _(2403, ZEND_SPACESHIP_SPEC_TMP_CV) \ + _(2414, ZEND_SPACESHIP_SPEC_CV_CONST) \ + _(2415, ZEND_SPACESHIP_SPEC_CV_TMP) \ + _(2418, ZEND_SPACESHIP_SPEC_CV_CV) \ + _(2419, ZEND_FUNC_NUM_ARGS_SPEC_UNUSED_UNUSED) \ + _(2420, ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED) \ + _(2421, ZEND_FUNC_GET_ARGS_SPEC_CONST_UNUSED_SCOPE_FN) \ + _(2426, ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED) \ + _(2427, ZEND_FUNC_GET_ARGS_SPEC_UNUSED_UNUSED_SCOPE_FN) \ + _(2430, ZEND_FETCH_STATIC_PROP_R_SPEC) \ + _(2431, ZEND_FETCH_STATIC_PROP_W_SPEC) \ + _(2432, ZEND_FETCH_STATIC_PROP_RW_SPEC) \ + _(2433, ZEND_FETCH_STATIC_PROP_IS_SPEC) \ + _(2434, ZEND_FETCH_STATIC_PROP_FUNC_ARG_SPEC) \ + _(2435, ZEND_FETCH_STATIC_PROP_UNSET_SPEC) \ + _(2436, ZEND_UNSET_STATIC_PROP_SPEC) \ + _(2437, ZEND_ISSET_ISEMPTY_STATIC_PROP_SPEC) \ + _(2438, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_CONST) \ + _(2439, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_TMPVARCV) \ + _(2440, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_TMPVARCV) \ + _(2442, ZEND_FETCH_CLASS_CONSTANT_SPEC_CONST_TMPVARCV) \ + _(2448, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_CONST) \ + _(2449, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_TMPVARCV) \ + _(2450, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_TMPVARCV) \ + _(2452, ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_TMPVARCV) \ + _(2453, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_CONST) \ + _(2454, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_TMPVARCV) \ + _(2455, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_TMPVARCV) \ + _(2457, ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUSED_TMPVARCV) \ + _(2463, ZEND_BIND_LEXICAL_SPEC_TMP_CV) \ + _(2464, ZEND_BIND_STATIC_SPEC_CV) \ + _(2465, ZEND_FETCH_THIS_SPEC_UNUSED_UNUSED) \ + _(2466, ZEND_SEND_FUNC_ARG_SPEC_VAR_CONST) \ + _(2469, ZEND_SEND_FUNC_ARG_SPEC_VAR_UNUSED) \ + _(2471, ZEND_ISSET_ISEMPTY_THIS_SPEC_UNUSED_UNUSED) \ + _(2472, ZEND_SWITCH_LONG_SPEC_CONST_CONST) \ + _(2473, ZEND_SWITCH_LONG_SPEC_TMPVARCV_CONST) \ + _(2474, ZEND_SWITCH_LONG_SPEC_TMPVARCV_CONST) \ + _(2476, ZEND_SWITCH_LONG_SPEC_TMPVARCV_CONST) \ + _(2477, ZEND_SWITCH_STRING_SPEC_CONST_CONST) \ + _(2478, ZEND_SWITCH_STRING_SPEC_TMPVARCV_CONST) \ + _(2479, ZEND_SWITCH_STRING_SPEC_TMPVARCV_CONST) \ + _(2481, ZEND_SWITCH_STRING_SPEC_TMPVARCV_CONST) \ + _(2482, ZEND_IN_ARRAY_SPEC_CONST_CONST) \ + _(2483, ZEND_IN_ARRAY_SPEC_TMP_CONST) \ + _(2486, ZEND_IN_ARRAY_SPEC_CV_CONST) \ + _(2487, ZEND_COUNT_SPEC_CONST_UNUSED) \ + _(2488, ZEND_COUNT_SPEC_TMP_UNUSED) \ + _(2491, ZEND_COUNT_SPEC_CV_UNUSED) \ + _(2492, ZEND_GET_CLASS_SPEC_CONST_UNUSED) \ + _(2493, ZEND_GET_CLASS_SPEC_TMP_UNUSED) \ + _(2495, ZEND_GET_CLASS_SPEC_UNUSED_UNUSED) \ + _(2496, ZEND_GET_CLASS_SPEC_CV_UNUSED) \ + _(2497, ZEND_GET_CALLED_CLASS_SPEC_UNUSED_UNUSED) \ + _(2498, ZEND_GET_TYPE_SPEC_CONST_UNUSED) \ + _(2499, ZEND_GET_TYPE_SPEC_TMP_UNUSED) \ + _(2502, ZEND_GET_TYPE_SPEC_CV_UNUSED) \ + _(2503, ZEND_ARRAY_KEY_EXISTS_SPEC_CONST_CONST) \ + _(2504, ZEND_ARRAY_KEY_EXISTS_SPEC_CONST_TMP) \ + _(2507, ZEND_ARRAY_KEY_EXISTS_SPEC_CONST_CV) \ + _(2508, ZEND_ARRAY_KEY_EXISTS_SPEC_TMP_CONST) \ + _(2509, ZEND_ARRAY_KEY_EXISTS_SPEC_TMP_TMP) \ + _(2512, ZEND_ARRAY_KEY_EXISTS_SPEC_TMP_CV) \ + _(2523, ZEND_ARRAY_KEY_EXISTS_SPEC_CV_CONST) \ + _(2524, ZEND_ARRAY_KEY_EXISTS_SPEC_CV_TMP) \ + _(2527, ZEND_ARRAY_KEY_EXISTS_SPEC_CV_CV) \ + _(2528, ZEND_MATCH_SPEC_CONST_CONST) \ + _(2529, ZEND_MATCH_SPEC_TMPVARCV_CONST) \ + _(2530, ZEND_MATCH_SPEC_TMPVARCV_CONST) \ + _(2532, ZEND_MATCH_SPEC_TMPVARCV_CONST) \ + _(2533, ZEND_CASE_STRICT_SPEC_TMP_CONST) \ + _(2534, ZEND_CASE_STRICT_SPEC_TMP_TMP) \ + _(2537, ZEND_CASE_STRICT_SPEC_TMP_CV) \ + _(2538, ZEND_MATCH_ERROR_SPEC_CONST_UNUSED) \ + _(2539, ZEND_MATCH_ERROR_SPEC_TMPVARCV_UNUSED) \ + _(2540, ZEND_MATCH_ERROR_SPEC_TMPVARCV_UNUSED) \ + _(2542, ZEND_MATCH_ERROR_SPEC_TMPVARCV_UNUSED) \ + _(2543, ZEND_JMP_NULL_SPEC_CONST) \ + _(2544, ZEND_JMP_NULL_SPEC_TMP) \ + _(2547, ZEND_JMP_NULL_SPEC_CV) \ + _(2548, ZEND_CHECK_UNDEF_ARGS_SPEC_UNUSED_UNUSED) \ + _(2549, ZEND_FETCH_GLOBALS_SPEC_UNUSED_UNUSED) \ + _(2550, ZEND_VERIFY_NEVER_TYPE_SPEC_UNUSED_UNUSED) \ + _(2551, ZEND_CALLABLE_CONVERT_SPEC_UNUSED_UNUSED) \ + _(2552, ZEND_BIND_INIT_STATIC_OR_JMP_SPEC_CV) \ + _(2553, ZEND_FRAMELESS_ICALL_0_SPEC_UNUSED_UNUSED) \ + _(2554, ZEND_FRAMELESS_ICALL_0_SPEC_OBSERVER) \ + _(2555, ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED) \ + _(2556, ZEND_FRAMELESS_ICALL_1_SPEC_OBSERVER) \ + _(2557, ZEND_FRAMELESS_ICALL_2_SPEC) \ + _(2558, ZEND_FRAMELESS_ICALL_2_SPEC_OBSERVER) \ + _(2559, ZEND_FRAMELESS_ICALL_3_SPEC) \ + _(2560, ZEND_FRAMELESS_ICALL_3_SPEC_OBSERVER) \ + _(2561, ZEND_JMP_FRAMELESS_SPEC_CONST) \ + _(2562, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED) \ + _(2563, ZEND_DECLARE_ATTRIBUTED_CONST_SPEC_CONST_CONST) \ + _(2564, ZEND_TYPE_ASSERT_SPEC_CONST) \ + _(2565, ZEND_DECLARE_SCOPE_FUNC_SPEC_TMP) \ + _(2566, ZEND_ENTER_SCOPE_FUNC_SPEC) \ + _(2567, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ + _(2568, ZEND_RECV_NOTYPE_SPEC) \ + _(2570, ZEND_COUNT_ARRAY_SPEC_TMP_UNUSED) \ + _(2573, ZEND_COUNT_ARRAY_SPEC_CV_UNUSED) \ + _(2574, ZEND_JMP_FORWARD_SPEC) \ + _(2580, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2581, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2587, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2588, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2582, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2584, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2585, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2586, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2587, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2589, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2591, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2597, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2598, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2599, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2601, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2602, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2603, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2604, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2595, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2596, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2597, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2599, ZEND_ADD_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2605, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ _(2606, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2612, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ - _(2613, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2607, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2609, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2610, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ + _(2611, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2612, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2614, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2616, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2622, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2623, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2624, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2626, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2627, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2628, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2629, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2620, ZEND_ADD_LONG_SPEC_TMPVARCV_CONST) \ + _(2621, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2622, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2624, ZEND_ADD_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2630, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2631, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2637, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2638, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2632, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2634, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2635, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2636, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2637, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2639, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2641, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2643, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2644, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2646, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ - _(2647, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2648, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2649, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2651, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2652, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2653, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2654, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2645, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2646, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2647, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2649, ZEND_ADD_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2651, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2652, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2654, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_CONST_TMPVARCV) \ + _(2655, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2656, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2662, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2663, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2657, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2659, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2660, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2661, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2662, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2664, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2666, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2668, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2669, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2671, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ - _(2672, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2673, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2674, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2676, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2677, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2678, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2679, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2670, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2671, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2672, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2674, ZEND_SUB_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2676, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2677, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2679, ZEND_SUB_LONG_SPEC_CONST_TMPVARCV) \ + _(2680, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ _(2681, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2687, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ - _(2688, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2682, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2684, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2685, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ + _(2686, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2687, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2689, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2691, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2693, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2694, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2696, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(2697, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2698, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2699, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2701, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2702, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2703, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2704, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2695, ZEND_SUB_LONG_SPEC_TMPVARCV_CONST) \ + _(2696, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2697, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2699, ZEND_SUB_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2701, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2702, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2704, ZEND_SUB_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(2705, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2706, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2712, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2713, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2707, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2709, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2710, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2711, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2712, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2714, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2716, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2722, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2723, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2724, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2726, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2727, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2728, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2729, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2720, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2721, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2722, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2724, ZEND_SUB_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2730, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ _(2731, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2737, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ - _(2738, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2732, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2734, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2735, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2736, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2737, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ _(2739, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2741, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ - _(2747, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2748, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2749, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2751, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2752, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2753, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2754, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2745, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_CONST) \ + _(2746, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2747, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2749, ZEND_MUL_LONG_NO_OVERFLOW_SPEC_TMPVARCV_TMPVARCV) \ + _(2755, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ _(2756, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2762, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ - _(2763, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2757, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2759, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2760, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ + _(2761, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2762, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ _(2764, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2766, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2772, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2773, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2774, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2776, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2777, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2778, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2779, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2770, ZEND_MUL_LONG_SPEC_TMPVARCV_CONST) \ + _(2771, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2772, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2774, ZEND_MUL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2780, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ _(2781, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2787, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2788, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2782, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2784, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2785, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2786, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2787, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ _(2789, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2791, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2807, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2808, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2809, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2810, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2811, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2812, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2813, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2814, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2815, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2819, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2820, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2821, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2822, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2823, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2824, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2825, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2826, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2834, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2835, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2836, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2852, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2853, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2854, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2855, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2856, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2857, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2858, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2859, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2860, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2864, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2865, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2866, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2882, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2883, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2884, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2885, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2886, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2887, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2888, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2889, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2890, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2894, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2895, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2896, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2897, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2898, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2899, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2900, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2901, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2909, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2910, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2911, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2927, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(2928, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2929, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2930, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2931, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2932, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2933, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2934, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2935, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2939, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(2940, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2941, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2957, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2958, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2959, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2960, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2961, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2962, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2963, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2964, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2965, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2969, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2970, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2971, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2972, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(2973, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(2974, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(2975, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2976, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(2984, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(2985, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(2986, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3002, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3003, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3004, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3005, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3006, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3007, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3008, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3009, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3010, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3014, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3015, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3016, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3032, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3033, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3034, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3035, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3036, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3037, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3038, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3039, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3040, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3044, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3045, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3046, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3047, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3048, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3049, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3050, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3051, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3059, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3060, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3061, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3077, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3078, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3079, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3080, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3081, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3082, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3083, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3084, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3085, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3089, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3090, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3091, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3092, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ - _(3093, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3094, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3095, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ - _(3096, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3097, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3098, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3102, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3103, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ - _(3107, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ - _(3111, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3112, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3113, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3114, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3115, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3116, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3120, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ - _(3121, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3122, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3123, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3124, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3125, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3126, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3127, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3128, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3129, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3130, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3131, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3135, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3140, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3141, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3142, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3145, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3146, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3152, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3168, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ - _(3169, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3170, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3171, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3172, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3173, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3174, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3175, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3176, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3180, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3181, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3182, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3186, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3187, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3188, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3189, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3190, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3191, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3195, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3196, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3197, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3198, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3200, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3201, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3202, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3204, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3205, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3206, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3215, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3216, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3217, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3220, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3221, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3243, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3244, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3245, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3246, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3247, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3248, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3249, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3250, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3251, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3255, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3256, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3257, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3261, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3262, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3263, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3264, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3265, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3266, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3270, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ - _(3271, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3272, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3273, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3275, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3276, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3277, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3279, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3280, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3281, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3290, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3291, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3292, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3295, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3296, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3318, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ - _(3319, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3320, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3321, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3322, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3323, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3324, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3325, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3326, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3330, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ - _(3331, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3332, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3336, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3337, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3338, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3339, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3340, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3341, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3345, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ - _(3346, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ - _(3347, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ - _(3348, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3350, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3351, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3352, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3354, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3355, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3356, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3365, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3366, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3367, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3370, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3371, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3393, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ - _(3394, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ - _(3395, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ - _(3396, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3397, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3398, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3399, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3400, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3401, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3405, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ - _(3406, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ - _(3407, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ - _(3408, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3409, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3410, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3411, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ - _(3412, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ - _(3413, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ - _(3414, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ - _(3415, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ - _(3416, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3417, ZEND_POST_INC_LONG_SPEC_CV) \ - _(3418, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ - _(3419, ZEND_POST_DEC_LONG_SPEC_CV) \ - _(3420, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ - _(3421, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3422, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3424, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ - _(3425, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ - _(3426, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3427, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3429, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ - _(3430, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ - _(3431, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3432, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3434, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ - _(3436, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3437, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3439, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ - _(3440, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3441, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3442, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3444, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3445, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ - _(3446, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3447, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(2795, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2796, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2797, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2799, ZEND_MUL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2815, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2816, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2817, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2818, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2819, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2820, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2821, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2822, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2823, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2827, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2828, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2829, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2830, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2831, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2832, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2833, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2834, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2835, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2836, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2837, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2838, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2842, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2843, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2844, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2860, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2861, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2862, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2863, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2864, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2865, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2866, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2867, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2868, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2872, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2873, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2874, ZEND_IS_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2890, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2891, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2892, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2893, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2894, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2895, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2896, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2897, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2898, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2902, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2903, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2904, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2905, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2906, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2907, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2908, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2909, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2910, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2911, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2912, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2913, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2917, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2918, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2919, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2935, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(2936, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2937, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2938, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2939, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2940, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2941, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2942, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2943, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2947, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(2948, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2949, ZEND_IS_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2965, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2966, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2967, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2968, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2969, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2970, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2971, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2972, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2973, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2977, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2978, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2979, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2980, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(2981, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(2982, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(2983, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2984, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2985, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2986, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2987, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2988, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(2992, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(2993, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(2994, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3010, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3011, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3012, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3013, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3014, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3015, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3016, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3017, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3018, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3022, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3023, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3024, ZEND_IS_NOT_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3040, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3041, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3042, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3043, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3044, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3045, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3046, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3047, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3048, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3052, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3053, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3054, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3055, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3056, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3057, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3058, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3059, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3060, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3061, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3062, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3063, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3067, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3068, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3069, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3085, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3086, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3087, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3088, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3089, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3090, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3091, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3092, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3093, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3097, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3098, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3099, ZEND_IS_NOT_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3100, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3101, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3102, ZEND_IS_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3103, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST) \ + _(3104, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3105, ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3106, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3110, ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3111, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CONST) \ + _(3115, ZEND_IS_NOT_IDENTICAL_NOTHROW_SPEC_CV_CV) \ + _(3119, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3120, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3121, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3122, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3123, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3124, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3128, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV) \ + _(3129, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3130, ZEND_IS_SMALLER_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3131, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3132, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3133, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3134, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3135, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3136, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3137, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3138, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3139, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3143, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3144, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3145, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3146, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3147, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3148, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3149, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3150, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3151, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3152, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3153, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3154, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3158, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3159, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3160, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3176, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST) \ + _(3177, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3178, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3179, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3180, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3181, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3182, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3183, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3184, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3188, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3189, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3190, ZEND_IS_SMALLER_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3194, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3195, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3196, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3197, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3198, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3199, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3203, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3204, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3205, ZEND_IS_SMALLER_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3206, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3207, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3208, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3209, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3210, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3211, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3212, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3213, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3214, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3218, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3219, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3220, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3221, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3222, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3223, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3224, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3225, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3226, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3227, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3228, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3229, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3233, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3234, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3235, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3251, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3252, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3253, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3254, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3255, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3256, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3257, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3258, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3259, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3263, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3264, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3265, ZEND_IS_SMALLER_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3269, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3270, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3271, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3272, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3273, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3274, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3278, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV) \ + _(3279, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3280, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3281, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3282, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3283, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3284, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3285, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3286, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3287, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3288, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3289, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3293, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3294, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3295, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3296, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3297, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3298, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3299, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3300, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3301, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3302, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3303, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3304, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3308, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3309, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3310, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3326, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST) \ + _(3327, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3328, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3329, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3330, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3331, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3332, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3333, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3334, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3338, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV) \ + _(3339, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3340, ZEND_IS_SMALLER_OR_EQUAL_LONG_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3344, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3345, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3346, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3347, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3348, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3349, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3353, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV) \ + _(3354, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPZ) \ + _(3355, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_CONST_TMPVARCV_JMPNZ) \ + _(3356, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3357, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3358, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3359, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3360, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3361, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3362, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3363, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3364, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3368, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3369, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3370, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3371, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3372, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3373, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3374, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3375, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3376, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3377, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3378, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3379, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3383, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3384, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3385, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3401, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST) \ + _(3402, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPZ) \ + _(3403, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_CONST_JMPNZ) \ + _(3404, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3405, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3406, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3407, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3408, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3409, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3413, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV) \ + _(3414, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPZ) \ + _(3415, ZEND_IS_SMALLER_OR_EQUAL_DOUBLE_SPEC_TMPVARCV_TMPVARCV_JMPNZ) \ + _(3416, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3417, ZEND_PRE_INC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3418, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3419, ZEND_PRE_INC_LONG_SPEC_CV_RETVAL_USED) \ + _(3420, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_UNUSED) \ + _(3421, ZEND_PRE_DEC_LONG_NO_OVERFLOW_SPEC_CV_RETVAL_USED) \ + _(3422, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_UNUSED) \ + _(3423, ZEND_PRE_DEC_LONG_SPEC_CV_RETVAL_USED) \ + _(3424, ZEND_POST_INC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3425, ZEND_POST_INC_LONG_SPEC_CV) \ + _(3426, ZEND_POST_DEC_LONG_NO_OVERFLOW_SPEC_CV) \ + _(3427, ZEND_POST_DEC_LONG_SPEC_CV) \ + _(3428, ZEND_QM_ASSIGN_LONG_SPEC_CONST) \ + _(3429, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3430, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3432, ZEND_QM_ASSIGN_LONG_SPEC_TMPVARCV) \ + _(3433, ZEND_QM_ASSIGN_DOUBLE_SPEC_CONST) \ + _(3434, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3435, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3437, ZEND_QM_ASSIGN_DOUBLE_SPEC_TMPVARCV) \ + _(3438, ZEND_QM_ASSIGN_NOREF_SPEC_CONST) \ + _(3439, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3440, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3442, ZEND_QM_ASSIGN_NOREF_SPEC_TMPVARCV) \ + _(3444, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3445, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3447, ZEND_FETCH_DIM_R_INDEX_SPEC_CONST_TMPVARCV) \ + _(3448, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ _(3449, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ - _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ - _(3456, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3459, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ - _(3462, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ - _(3464, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ - _(3467, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ - _(3469, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ - _(3470, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ - _(3471, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ - _(3472, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ - _(3473, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ - _(3473+1, ZEND_NULL) + _(3450, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3452, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3453, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_CONST) \ + _(3454, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3457, ZEND_FETCH_DIM_R_INDEX_SPEC_TMPVAR_TMPVARCV) \ + _(3463, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_CONST) \ + _(3464, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3465, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3467, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ + _(3470, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ + _(3472, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ + _(3475, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ + _(3477, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ + _(3478, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ + _(3479, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ + _(3480, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ + _(3481, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ + _(3481+1, ZEND_NULL) diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 0ece3e6f0c66..b4510fd5876d 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -21,7 +21,7 @@ #include #include -static const char *zend_vm_opcodes_names[212] = { +static const char *zend_vm_opcodes_names[214] = { "ZEND_NOP", "ZEND_ADD", "ZEND_SUB", @@ -234,9 +234,11 @@ static const char *zend_vm_opcodes_names[212] = { "ZEND_INIT_PARENT_PROPERTY_HOOK_CALL", "ZEND_DECLARE_ATTRIBUTED_CONST", "ZEND_TYPE_ASSERT", + "ZEND_DECLARE_SCOPE_FUNC", + "ZEND_ENTER_SCOPE_FUNC", }; -static uint32_t zend_vm_opcodes_flags[212] = { +static uint32_t zend_vm_opcodes_flags[214] = { 0x00000000, 0x00000b0b, 0x00000b0b, @@ -449,6 +451,8 @@ static uint32_t zend_vm_opcodes_flags[212] = { 0x01001103, 0x00000303, 0x01000003, + 0x00001001, + 0x00000000, }; ZEND_API const char* ZEND_FASTCALL zend_get_opcode_name(uint8_t opcode) { diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index 92b46e6628f3..5a3141dbe55b 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -331,7 +331,9 @@ END_EXTERN_C() #define ZEND_INIT_PARENT_PROPERTY_HOOK_CALL 209 #define ZEND_DECLARE_ATTRIBUTED_CONST 210 #define ZEND_TYPE_ASSERT 211 +#define ZEND_DECLARE_SCOPE_FUNC 212 +#define ZEND_ENTER_SCOPE_FUNC 213 -#define ZEND_VM_LAST_OPCODE 211 +#define ZEND_VM_LAST_OPCODE 213 #endif diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 738f204c5026..69af1c589b5d 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3216,6 +3216,11 @@ void ZEND_FASTCALL zend_jit_hot_func(zend_execute_data *execute_data, const zend static void zend_jit_setup_hot_counters_ex(zend_op_array *op_array, zend_cfg *cfg) { + /* TODO: Currently we just skip scope-fn op_arrays (negative var offsets) and their parents. */ + if (op_array->fn_flags2 & (ZEND_ACC2_SCOPE_FUNC | ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + return; + } + if (JIT_G(hot_func)) { zend_op *opline = op_array->opcodes; @@ -3302,6 +3307,12 @@ int zend_jit_op_array(zend_op_array *op_array, zend_script *script) return FAILURE; } + /* Scope-fn op_arrays use negative var offsets; parents reserve TMPs and + * carry DECLARE_SCOPE_FUNC opcodes the JIT does not model. Skip both. */ + if (op_array->fn_flags2 & (ZEND_ACC2_SCOPE_FUNC | ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + return SUCCESS; + } + if (JIT_G(trigger) == ZEND_JIT_ON_FIRST_EXEC) { zend_jit_op_array_extension *jit_extension; zend_op *opline = op_array->opcodes; @@ -3415,6 +3426,12 @@ int zend_jit_script(zend_script *script) } } else if (JIT_G(trigger) == ZEND_JIT_ON_SCRIPT_LOAD) { for (i = 0; i < call_graph.op_arrays_count; i++) { + /* TODO: Scope-fn op_arrays use negative offsets and parents reserve fixed TMPs, which breaks current SSA construction. + * Drop FUNC_INFO so the subsequent usages just skip them. */ + if (call_graph.op_arrays[i]->fn_flags2 & (ZEND_ACC2_SCOPE_FUNC | ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); + continue; + } info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); if (info) { if (zend_jit_op_array_analyze1(call_graph.op_arrays[i], script, &info->ssa) != SUCCESS) { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 08873796ad8a..52109561e5b6 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10629,10 +10629,10 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen } if (may_have_extra_named_params) { - // JIT: if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) + // JIT: if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) ir_ref if_has_named = ir_IF(ir_AND_U8( ir_LOAD_U8(ir_ADD_OFFSET(rx, offsetof(zend_execute_data, This.u1.type_info) + 3)), - ir_CONST_U8(ZEND_CALL_HAS_EXTRA_NAMED_PARAMS >> 24))); + ir_CONST_U8(ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS >> 24))); ir_IF_TRUE_cold(if_has_named); ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_free_extra_named_params), @@ -11101,7 +11101,7 @@ static int zend_jit_leave_func(zend_jit_ctx *jit, /* ZEND_CALL_FAKE_CLOSURE handled on slow path to eliminate check for ZEND_CALL_CLOSURE on fast path */ call_info = ir_LOAD_U32(jit_EX(This.u1.type_info)); ref = ir_AND_U32(call_info, - ir_CONST_U32(ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_FAKE_CLOSURE)); + ir_CONST_U32(ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_FAKE_CLOSURE)); if (trace && trace->op != ZEND_JIT_TRACE_END) { ir_ref if_slow = ir_IF(ref); diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index 012edd194e31..396da5306bb6 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -56,12 +56,13 @@ ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_jit_leave_func_helper_tai } zend_vm_stack_free_extra_args_ex(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } @@ -87,7 +88,8 @@ ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_jit_leave_func_helper_tai } zend_vm_stack_free_extra_args_ex(call_info, execute_data); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -107,12 +109,13 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_nested_func_helper(ZEND_OPC } zend_vm_stack_free_extra_args_ex(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } @@ -148,8 +151,9 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(ZEND_OPCODE zend_clean_and_cache_symbol_table(EX(symbol_table)); } zend_vm_stack_free_extra_args_ex(call_info, execute_data); + zend_vm_stack_free_tracked_temporaries(call_info, execute_data); } - if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { + if (UNEXPECTED(call_info & ZEND_CALL_MAYBE_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -1130,6 +1134,11 @@ zend_jit_trace_stop ZEND_FASTCALL zend_jit_trace_execute(zend_execute_data *ex, break; } + if (EX(func)->op_array.fn_flags2 & (ZEND_ACC2_SCOPE_FUNC | ZEND_ACC2_HAS_TRACKED_TEMPORARIES)) { + stop = ZEND_JIT_TRACE_STOP_INTERPRETER; + break; + } + TRACE_RECORD(ZEND_JIT_TRACE_ENTER, EX(return_value) != NULL ? ZEND_JIT_TRACE_RETURN_VALUE_USED : 0, op_array); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 94d8bb7d149c..8552c682fcf2 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1805,6 +1805,19 @@ ZEND_METHOD(ReflectionFunctionAbstract, isClosure) } /* }}} */ +/* {{{ Returns whether this is a scope function */ +ZEND_METHOD(ReflectionFunctionAbstract, isScopeFunction) +{ + reflection_object *intern; + zend_function *fptr; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(fptr); + RETURN_BOOL(fptr->common.fn_flags2 & ZEND_ACC2_SCOPE_FUNC); +} +/* }}} */ + /* {{{ Returns this pointer bound to closure */ ZEND_METHOD(ReflectionFunctionAbstract, getClosureThis) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index dd605100f8ba..95f263f6b371 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -33,6 +33,8 @@ public function inNamespace(): bool {} /** @tentative-return-type */ public function isClosure(): bool {} + public function isScopeFunction(): bool {} + /** @tentative-return-type */ public function isDeprecated(): bool {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 65571f38d43c..b4be8bec9df0 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: c80946cc8c8215bb6527e09bb71b3a97a76a6a98 + * Stub hash: bb882ca4cb8e3972a85cf0c2ff5a7bfc25479551 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) @@ -14,6 +14,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionFunctionAbstract_isClosure arginfo_class_ReflectionFunctionAbstract_inNamespace +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunctionAbstract_isScopeFunction, 0, 0, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionFunctionAbstract_isDeprecated arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionFunctionAbstract_isInternal arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -76,8 +79,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getReturnType, 0, 0, ReflectionType, 1) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType, 0, 0, _IS_BOOL, 0) -ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType arginfo_class_ReflectionFunctionAbstract_isScopeFunction ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getTentativeReturnType, 0, 0, ReflectionType, 1) ZEND_END_ARG_INFO() @@ -94,7 +96,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunction___toString, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionFunction_isAnonymous arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionFunction_isAnonymous arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionFunction_isDisabled arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -131,7 +133,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionGenerator_getExecutingGenerator, 0, 0, Generator, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionGenerator_isClosed arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionGenerator_isClosed arginfo_class_ReflectionFunctionAbstract_isScopeFunction ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionMethod___construct, 0, 0, 1) ZEND_ARG_TYPE_MASK(0, objectOrMethod, MAY_BE_OBJECT|MAY_BE_STRING, NULL) @@ -180,7 +182,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionMethod_getPrototype, 0, 0, ReflectionMethod, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionMethod_hasPrototype arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionMethod_hasPrototype arginfo_class_ReflectionFunctionAbstract_isScopeFunction ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionMethod_setAccessible, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, accessible, _IS_BOOL, 0) @@ -265,13 +267,13 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_isTrait arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionClass_isEnum arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClass_isEnum arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionClass_isAbstract arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionClass_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionClass_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClass_isReadOnly arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionClass_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters @@ -420,23 +422,23 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isProtected arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionProperty_isPrivateSet arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isPrivateSet arginfo_class_ReflectionFunctionAbstract_isScopeFunction -#define arginfo_class_ReflectionProperty_isProtectedSet arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isProtectedSet arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionProperty_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isReadOnly arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionProperty_isDynamic arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isDynamic arginfo_class_ReflectionFunctionAbstract_isScopeFunction -#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isAbstract arginfo_class_ReflectionFunctionAbstract_isScopeFunction -#define arginfo_class_ReflectionProperty_isVirtual arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isVirtual arginfo_class_ReflectionFunctionAbstract_isScopeFunction -#define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionProperty_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters @@ -452,14 +454,14 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_hasType arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_hasDefaultValue arginfo_class_ReflectionFunctionAbstract_isScopeFunction ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_getDefaultValue, 0, 0, IS_MIXED, 0) ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes -#define arginfo_class_ReflectionProperty_hasHooks arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_hasHooks arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionProperty_getHooks arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables @@ -471,7 +473,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionProperty_getHook, ZEND_ARG_OBJ_INFO(0, type, PropertyHookType, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isFinal arginfo_class_ReflectionFunctionAbstract_isScopeFunction ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_isReadable, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, scope, IS_STRING, 1) @@ -499,7 +501,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassConstant_isProtected arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionClassConstant_isFinal arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClassConstant_isFinal arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionClassConstant_getModifiers arginfo_class_ReflectionFunctionAbstract_getNumberOfParameters @@ -509,11 +511,11 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassConstant_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes -#define arginfo_class_ReflectionClassConstant_isEnumCase arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClassConstant_isEnumCase arginfo_class_ReflectionFunctionAbstract_isScopeFunction -#define arginfo_class_ReflectionClassConstant_isDeprecated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClassConstant_isDeprecated arginfo_class_ReflectionFunctionAbstract_isScopeFunction -#define arginfo_class_ReflectionClassConstant_hasType arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionClassConstant_hasType arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionClassConstant_getType arginfo_class_ReflectionFunctionAbstract_getTentativeReturnType @@ -567,7 +569,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionParameter_isVariadic arginfo_class_ReflectionFunctionAbstract_inNamespace -#define arginfo_class_ReflectionParameter_isPromoted arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionParameter_isPromoted arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionParameter_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes @@ -648,7 +650,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionAttribute_getTarget arginfo_class_ReflectionGenerator_getExecutingLine -#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionAttribute_isRepeated arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionAttribute_getArguments arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables @@ -673,7 +675,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionEnum_getCases arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables -#define arginfo_class_ReflectionEnum_isBacked arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionEnum_isBacked arginfo_class_ReflectionFunctionAbstract_isScopeFunction ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionEnum_getBackingType, 0, 0, ReflectionNamedType, 1) ZEND_END_ARG_INFO() @@ -713,7 +715,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionConstant_getName arginfo_class_ReflectionFunction___toString -#define arginfo_class_ReflectionConstant_inNamespace arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionConstant_inNamespace arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionConstant_getNamespaceName arginfo_class_ReflectionFunction___toString @@ -722,7 +724,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionConstant_getValue, 0, 0, IS_MIXED, 0) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionConstant_isDeprecated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionConstant_isDeprecated arginfo_class_ReflectionFunctionAbstract_isScopeFunction #define arginfo_class_ReflectionConstant_getFileName arginfo_class_ReflectionParameter_getDocComment @@ -739,6 +741,7 @@ ZEND_METHOD(Reflection, getModifierNames); ZEND_METHOD(ReflectionClass, __clone); ZEND_METHOD(ReflectionFunctionAbstract, inNamespace); ZEND_METHOD(ReflectionFunctionAbstract, isClosure); +ZEND_METHOD(ReflectionFunctionAbstract, isScopeFunction); ZEND_METHOD(ReflectionFunctionAbstract, isDeprecated); ZEND_METHOD(ReflectionFunctionAbstract, isInternal); ZEND_METHOD(ReflectionFunctionAbstract, isUserDefined); @@ -1019,6 +1022,7 @@ static const zend_function_entry class_ReflectionFunctionAbstract_methods[] = { ZEND_RAW_FENTRY("__clone", zim_ReflectionClass___clone, arginfo_class_ReflectionFunctionAbstract___clone, ZEND_ACC_PRIVATE, NULL, NULL) ZEND_ME(ReflectionFunctionAbstract, inNamespace, arginfo_class_ReflectionFunctionAbstract_inNamespace, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, isClosure, arginfo_class_ReflectionFunctionAbstract_isClosure, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionFunctionAbstract, isScopeFunction, arginfo_class_ReflectionFunctionAbstract_isScopeFunction, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, isDeprecated, arginfo_class_ReflectionFunctionAbstract_isDeprecated, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, isInternal, arginfo_class_ReflectionFunctionAbstract_isInternal, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, isUserDefined, arginfo_class_ReflectionFunctionAbstract_isUserDefined, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index a87e1635419b..c6be5ccf1af3 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,12 +1,12 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: c80946cc8c8215bb6527e09bb71b3a97a76a6a98 */ + * Stub hash: bb882ca4cb8e3972a85cf0c2ff5a7bfc25479551 */ -#ifndef ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H -#define ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H +#ifndef ZEND_PHP_REFLECTION_DECL_bb882ca4cb8e3972a85cf0c2ff5a7bfc25479551_H +#define ZEND_PHP_REFLECTION_DECL_bb882ca4cb8e3972a85cf0c2ff5a7bfc25479551_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, ZEND_ENUM_PropertyHookType_Set = 2, } zend_enum_PropertyHookType; -#endif /* ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_bb882ca4cb8e3972a85cf0c2ff5a7bfc25479551_H */