diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index a4ecb19c85ef..d603fe159e20 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -26,6 +26,7 @@ #include "zend_execute.h" #include "zend_vm.h" #include "zend_extensions.h" +#include "zend_partial.h" #define DEBUG_COMPACT_LITERALS 0 @@ -747,7 +748,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx } break; case ZEND_CALLABLE_CONVERT_PARTIAL: - opline->op1.num = cache_size; + opline->extended_value = cache_size | (opline->extended_value & ZEND_PARTIAL_FLAGS); cache_size += 2 * sizeof(void *); break; } diff --git a/Zend/tests/partial_application/constexpr_001.phpt b/Zend/tests/partial_application/constexpr_001.phpt new file mode 100644 index 000000000000..179780d7fdc9 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +PFA in constexpr 001 +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(1) +} +array(2) { + [0]=> + string(3) "foo" + [1]=> + int(1) +} diff --git a/Zend/tests/partial_application/constexpr_002.phpt b/Zend/tests/partial_application/constexpr_002.phpt new file mode 100644 index 000000000000..05f48b5eb67d --- /dev/null +++ b/Zend/tests/partial_application/constexpr_002.phpt @@ -0,0 +1,22 @@ +--TEST-- +PFA in constexpr: non-constexpr arg +--FILE-- +getMessage(), " on line ", $e->getLine(); +} + +?> +--EXPECTF-- +Fatal error: Constant expression contains invalid operations in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_003.phpt b/Zend/tests/partial_application/constexpr_003.phpt new file mode 100644 index 000000000000..4e7b7a17d0b0 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_003.phpt @@ -0,0 +1,25 @@ +--TEST-- +PFA in constexpr: named args +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + string(5) "hello" + [1]=> + int(3) + [2]=> + float(1.5) +} diff --git a/Zend/tests/partial_application/constexpr_004.phpt b/Zend/tests/partial_application/constexpr_004.phpt new file mode 100644 index 000000000000..b5fee160a03e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_004.phpt @@ -0,0 +1,28 @@ +--TEST-- +PFA in constexpr: extra named args +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + array(3) { + [0]=> + int(1) + ["foo"]=> + string(3) "bar" + ["bar"]=> + string(3) "baz" + } +} diff --git a/Zend/tests/partial_application/constexpr_005.phpt b/Zend/tests/partial_application/constexpr_005.phpt new file mode 100644 index 000000000000..48c830a0b830 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_005.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder must be after positional params +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder must be last in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_006.phpt b/Zend/tests/partial_application/constexpr_006.phpt new file mode 100644 index 000000000000..873c0b155fe1 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_006.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder must be after named params +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder must be last in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_007.phpt b/Zend/tests/partial_application/constexpr_007.phpt new file mode 100644 index 000000000000..e932812ddf70 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_007.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder may only appear once +--FILE-- + +--EXPECTF-- +Fatal error: Variadic placeholder may only appear once in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_008.phpt b/Zend/tests/partial_application/constexpr_008.phpt new file mode 100644 index 000000000000..c8f5b36ccb63 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_008.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder not allowed to be named +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "..." in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_009.phpt b/Zend/tests/partial_application/constexpr_009.phpt new file mode 100644 index 000000000000..c8f5b36ccb63 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_009.phpt @@ -0,0 +1,13 @@ +--TEST-- +PFA in constexpr: variadic placeholder not allowed to be named +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "..." in %s on line %d diff --git a/Zend/tests/partial_application/constexpr_010.phpt b/Zend/tests/partial_application/constexpr_010.phpt new file mode 100644 index 000000000000..e4e05cc30a8e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_010.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA in constexpr: error during partial creation +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +ArgumentCountError: Partial application of g() expects exactly 2 arguments, 1 given diff --git a/Zend/tests/partial_application/constexpr_011.phpt b/Zend/tests/partial_application/constexpr_011.phpt new file mode 100644 index 000000000000..525d5375103e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_011.phpt @@ -0,0 +1,33 @@ +--TEST-- +PFA in constexpr: error during arg list evaluation +--FILE-- +getMessage(), "\n"; +} + +try { + h(); +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Named parameter $a overwrites previous argument +Error: Call to undefined function invalid() diff --git a/Zend/tests/partial_application/constexpr_012.phpt b/Zend/tests/partial_application/constexpr_012.phpt new file mode 100644 index 000000000000..3c941ed6e13e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_012.phpt @@ -0,0 +1,38 @@ +--TEST-- +PFA in constexpr: nested const expr +--FILE-- + +--EXPECTF-- +array(3) { + [0]=> + object(stdClass)#%d (0) { + } + [1]=> + object(Closure)#%d (2) { + ["function"]=> + string(1) "g" + ["parameter"]=> + array(3) { + ["$a"]=> + string(10) "" + ["$b"]=> + string(10) "" + ["$c"]=> + string(10) "" + } + } + [2]=> + int(1) +} diff --git a/Zend/tests/partial_application/constexpr_014.phpt b/Zend/tests/partial_application/constexpr_014.phpt new file mode 100644 index 000000000000..c75fdb60716b --- /dev/null +++ b/Zend/tests/partial_application/constexpr_014.phpt @@ -0,0 +1,45 @@ +--TEST-- +PFA in constexpr: sites +--FILE-- +getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method attr\n"; +var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method default value\n"; +var_dump((new C()->f())('hello')); + +?> +--EXPECT-- +# Class const +int(5) +# Const +int(5) +# Class attr +int(5) +# Method attr +int(5) +# Method default value +int(5) diff --git a/Zend/tests/partial_application/constexpr_015.inc b/Zend/tests/partial_application/constexpr_015.inc new file mode 100644 index 000000000000..bd9dd1674f89 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_015.inc @@ -0,0 +1,20 @@ + diff --git a/Zend/tests/partial_application/constexpr_015.phpt b/Zend/tests/partial_application/constexpr_015.phpt new file mode 100644 index 000000000000..abe2cb9ae1a9 --- /dev/null +++ b/Zend/tests/partial_application/constexpr_015.phpt @@ -0,0 +1,29 @@ +--TEST-- +PFA in constexpr: preloading +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/constexpr_015.inc +--FILE-- +getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method attr\n"; +var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method default value\n"; +var_dump((new C()->f())('hello')); + +?> +--EXPECT-- +# Class const +int(5) +# Class attr +int(5) +# Method attr +int(5) +# Method default value +int(5) diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt index addee498d810..194f08a9c143 100644 --- a/Zend/tests/partial_application/pipe_optimization_004.phpt +++ b/Zend/tests/partial_application/pipe_optimization_004.phpt @@ -37,7 +37,7 @@ $_main: 0005 INIT_FCALL_BY_NAME 2 string("foo") 0006 SEND_PLACEHOLDER 1 0007 SEND_PLACEHOLDER 2 -0008 T1 = CALLABLE_CONVERT_PARTIAL %d +0008 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0009 INIT_DYNAMIC_CALL 1 T1 0010 SEND_VAL_EX int(2) 1 0011 DO_FCALL diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt index 09c3c765d237..be8d773c44ce 100644 --- a/Zend/tests/partial_application/pipe_optimization_007.phpt +++ b/Zend/tests/partial_application/pipe_optimization_007.phpt @@ -37,7 +37,7 @@ $_main: 0005 INIT_FCALL_BY_NAME 0 string("foo") 0006 SEND_PLACEHOLDER string("a") 0007 SEND_PLACEHOLDER string("b") -0008 T1 = CALLABLE_CONVERT_PARTIAL %d array(...) +0008 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") array(...) 0009 INIT_DYNAMIC_CALL 1 T1 0010 SEND_VAL_EX int(2) 1 0011 DO_FCALL diff --git a/Zend/tests/partial_application/pipe_optimization_008.phpt b/Zend/tests/partial_application/pipe_optimization_008.phpt index 070074632c77..96f8d88815ca 100644 --- a/Zend/tests/partial_application/pipe_optimization_008.phpt +++ b/Zend/tests/partial_application/pipe_optimization_008.phpt @@ -36,7 +36,7 @@ $_main: 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 0 string("foo") 0006 SEND_VAL_EX int(1) string("a") -0007 T1 = CALLABLE_CONVERT_PARTIAL %d +0007 T1 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0008 INIT_DYNAMIC_CALL 1 T1 0009 SEND_VAL_EX int(2) 1 0010 DO_FCALL diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt index 7d1a48b2f2ed..6c2be6728e73 100644 --- a/Zend/tests/partial_application/pipe_optimization_013.phpt +++ b/Zend/tests/partial_application/pipe_optimization_013.phpt @@ -32,7 +32,7 @@ $_main: 0004 DECLARE_FUNCTION string("foo") 0 0005 INIT_FCALL_BY_NAME 0 string("foo") 0006 SEND_VAL_EX int(2) string("b") -0007 T0 = CALLABLE_CONVERT_PARTIAL 3 +0007 T0 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0008 INIT_DYNAMIC_CALL 1 T0 0009 SEND_VAL_EX int(1) 1 0010 DO_FCALL diff --git a/Zend/tests/partial_application/rfc_examples_const_expr.phpt b/Zend/tests/partial_application/rfc_examples_const_expr.phpt index e2fc57440256..23350f6116cf 100644 --- a/Zend/tests/partial_application/rfc_examples_const_expr.phpt +++ b/Zend/tests/partial_application/rfc_examples_const_expr.phpt @@ -1,7 +1,5 @@ --TEST-- PFA RFC examples: "Constant expressions" section ---XFAIL-- -PFA in constant expressions not implemented yet --FILE-- kind = ZEND_AST_CALLABLE_CONVERT; ast->attr = 0; ast->lineno = CG(zend_lineno); + ast->filename = NULL; + ast->name = NULL; ast->args = args; ZEND_MAP_PTR_INIT(ast->fptr, NULL); @@ -671,6 +674,81 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex( return r; } +static zend_execute_data *zend_ast_evaluate_arg_list( + zend_function *func, + zend_ast_list *args_ast, + zend_class_entry *scope, + bool *short_circuited_ptr, + zend_ast_evaluate_ctx *ctx, + zend_array **named_positions_ptr, + bool *uses_variadic_placeholder +) { + zend_execute_data *frame = zend_vm_stack_push_call_frame_ex( + zend_vm_calc_used_stack(args_ast->children, func), + 0, func, 0, NULL); + + for (uint32_t i = 0; i < args_ast->children; i++) { + + zend_ast *arg_ast = args_ast->child[i]; + uint32_t arg_num = i + 1; + zval *arg; + zend_string *arg_name = NULL; + + if (arg_ast->kind == ZEND_AST_NAMED_ARG) { + void *cache_slot[2] = {0}; + arg_name = zend_ast_get_str(arg_ast->child[0]); + arg = zend_handle_named_arg(&frame, arg_name, &arg_num, (void**)&cache_slot); + if (!arg) { + goto fail; + } + if (named_positions_ptr) { + if (!*named_positions_ptr) { + *named_positions_ptr = zend_new_array(0); + } + zval tmp; + ZVAL_LONG(&tmp, zend_hash_num_elements(*named_positions_ptr)); + zend_hash_add(*named_positions_ptr, arg_name, &tmp); + } + arg_ast = arg_ast->child[1]; + } else { + arg = ZEND_CALL_VAR_NUM(frame, ZEND_CALL_NUM_ARGS(frame)); + } + + if (arg_ast->kind == ZEND_AST_PLACEHOLDER_ARG) { + if (arg_ast->attr == ZEND_PLACEHOLDER_VARIADIC) { + if (uses_variadic_placeholder) { + *uses_variadic_placeholder = true; + } + continue; + } else { + Z_TYPE_INFO_P(arg) = _IS_PLACEHOLDER; + } + } else { + if (zend_ast_evaluate_ex(arg, arg_ast, scope, short_circuited_ptr, ctx) == FAILURE) { + goto fail; + } + } + if (!arg_name) { + ZEND_CALL_NUM_ARGS(frame)++; + } + } + + return frame; + +fail: + for (uint32_t i = 0, num_args = ZEND_CALL_NUM_ARGS(frame); i < num_args; i++) { + zval_ptr_dtor(ZEND_CALL_VAR_NUM(frame, i)); + } + if (ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_destroy(frame->extra_named_params); + } + zend_vm_stack_free_call_frame(frame); + if (named_positions_ptr && *named_positions_ptr) { + zend_array_destroy(*named_positions_ptr); + } + return NULL; +} + static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( zval *result, zend_ast *ast, @@ -1139,6 +1217,12 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( case ZEND_AST_CALL: case ZEND_AST_STATIC_CALL: { + // Preloading will attempt to resolve constants but objects can't be stored in shm + // Aborting here to store the const AST instead + if (CG(in_compilation)) { + return FAILURE; + } + zend_function *fptr; zend_class_entry *called_scope = NULL; @@ -1149,10 +1233,6 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( zend_ast_list *args = zend_ast_get_list(fcc_ast->args); ZEND_ASSERT(args->children > 0); - if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { - /* TODO: PFAs */ - zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); - } switch (ast->kind) { case ZEND_AST_CALL: { @@ -1242,9 +1322,49 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( default: ZEND_UNREACHABLE(); } - zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL); + bool is_fcc = args->children == 1 && args->child[0]->attr == ZEND_PLACEHOLDER_VARIADIC; - return SUCCESS; + if (is_fcc) { + zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL); + return SUCCESS; + } + + zend_array *named_positions = NULL; + bool uses_variadic_placeholder = false; + zend_execute_data *frame = zend_ast_evaluate_arg_list( + fptr, args, scope, short_circuited_ptr, ctx, + &named_positions, &uses_variadic_placeholder); + if (!frame) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + + zval this_ptr; + ZVAL_UNDEF(&this_ptr); + Z_PTR(this_ptr) = NULL; + void *cache_slot[2] = {0}; + zend_array *extra_named_params = ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS + ? frame->extra_named_params + : NULL; + uint32_t flags = (fcc_ast->attr & ZEND_PARTIAL_CACHEABLE_IN_SHM); + if (uses_variadic_placeholder) { + flags |= ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; + } + zend_partial_create(result, &this_ptr, fptr, + ZEND_CALL_NUM_ARGS(frame), ZEND_CALL_ARG(frame, 1), + extra_named_params, named_positions, + fcc_ast->filename, &ast->lineno, + (void**)cache_slot, fcc_ast->name, flags); + + if (named_positions) { + zend_array_release(named_positions); + } + if (extra_named_params) { + zend_array_release(extra_named_params); + } + zend_vm_stack_free_call_frame(frame); + + return EG(exception) ? FAILURE : SUCCESS; } case ZEND_AST_OP_ARRAY: { @@ -1419,6 +1539,16 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf) new->kind = old->kind; new->attr = old->attr; new->lineno = old->lineno; + if (old->filename) { + new->filename = zend_string_copy(old->filename); + } else { + new->filename = NULL; + } + if (old->name) { + new->name = zend_string_copy(old->name); + } else { + new->name = NULL; + } ZEND_MAP_PTR_INIT(new->fptr, ZEND_MAP_PTR(old->fptr)); buf = (void*)((char*)buf + sizeof(zend_ast_fcc)); new->args = buf; @@ -1519,6 +1649,12 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast) } else if (EXPECTED(ast->kind == ZEND_AST_CALLABLE_CONVERT)) { zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast; + if (fcc_ast->filename) { + zend_string_release_ex(fcc_ast->filename, 0); + } + if (fcc_ast->name) { + zend_string_release_ex(fcc_ast->name, 0); + } ast = fcc_ast->args; goto tail_call; } @@ -1537,6 +1673,9 @@ ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *contex for (i = 0; i < list->children; ++i) { fn(&list->child[i], context); } + } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { + zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast; + fn(&fcc_ast->args, context); } else if (zend_ast_is_decl(ast)) { /* Not implemented. */ ZEND_UNREACHABLE(); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 26f54102a143..5d646f428c1b 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -233,6 +233,8 @@ typedef struct _zend_ast_fcc { zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */ zend_ast_attr attr; /* Additional attribute, use depending on node type */ uint32_t lineno; /* Line number */ + zend_string *filename; + zend_string *name; zend_ast *args; ZEND_MAP_PTR_DEF(zend_function *, fptr); } zend_ast_fcc; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f44784239533..55b5370b780e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -40,6 +40,7 @@ #include "zend_call_stack.h" #include "zend_frameless_function.h" #include "zend_property_hooks.h" +#include "zend_partial.h" #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ @@ -4088,7 +4089,50 @@ ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, const zend_function *f } /* }}} */ -static void zend_compile_call_partial(znode *result, uint32_t arg_count, +static zend_string *zend_compile_partial_name(const zend_op_array *declaring_op_array, + const uint32_t declaring_lineno) +{ + /* We attempt to generate a name that hints at where the PFA was created, + * similarly to Closures (GH-13550). + * We do not attempt to make the name unique. */ + + zend_string *filename = declaring_op_array->filename; + uint32_t start_lineno = declaring_lineno; + + zend_string *class = zend_empty_string; + zend_string *separator = zend_empty_string; + zend_string *function = filename; + const char *parens = ""; + + if (declaring_op_array->function_name) { + function = declaring_op_array->function_name; + if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) { + /* If the parent function is a closure, don't redundantly + * add the classname and parentheses. */ + } else { + parens = "()"; + + if (declaring_op_array->scope && declaring_op_array->scope->name) { + class = declaring_op_array->scope->name; + separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM); + } + } + } + + zend_string *name = zend_strpprintf_unchecked( + 0, + "{closure:pfa:%S%S%S%s:%" PRIu32 "}", + class, + separator, + function, + parens, + start_lineno + ); + + return name; +} + +static void zend_compile_call_partial(znode *result, zend_ast_fcc *fcc_ast, uint32_t arg_count, bool may_have_extra_named_args, bool uses_variadic_placeholder, zval *named_positions, uint32_t opnum_init, const zend_function *fbc) { @@ -4105,15 +4149,16 @@ static void zend_compile_call_partial(znode *result, uint32_t arg_count, zend_op *opline = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT_PARTIAL, NULL, NULL); - opline->op1.num = zend_alloc_cache_slots(2); + opline->extended_value = zend_alloc_cache_slots(2); - if (may_have_extra_named_args) { - opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS; - } if (uses_variadic_placeholder) { - opline->extended_value |= ZEND_FCALL_USES_VARIADIC_PLACEHOLDER; + opline->extended_value |= ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; } + zend_string *name = zend_compile_partial_name(CG(active_op_array), opline->lineno); + opline->op1.constant = zend_add_literal_string(&name); + opline->op1_type = IS_CONST; + if (!Z_ISUNDEF_P(named_positions)) { opline->op2.constant = zend_add_literal(named_positions); opline->op2_type = IS_CONST; @@ -4156,7 +4201,8 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze return true; } - args_ast = ((zend_ast_fcc*)args_ast)->args; + zend_ast_fcc *fcc_ast = (zend_ast_fcc*)args_ast; + args_ast = fcc_ast->args; bool may_have_extra_named_args; bool uses_variadic_placeholder; @@ -4168,7 +4214,7 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze &may_have_extra_named_args, true, &uses_variadic_placeholder, &named_positions); - zend_compile_call_partial(result, arg_count, + zend_compile_call_partial(result, fcc_ast, arg_count, may_have_extra_named_args, uses_variadic_placeholder, &named_positions, opnum_init, fbc); @@ -11842,7 +11888,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */ || kind == ZEND_AST_NAMED_ARG || kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP || kind == ZEND_AST_CLOSURE - || kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT; + || kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT + || kind == ZEND_AST_PLACEHOLDER_ARG; } /* }}} */ @@ -12007,27 +12054,21 @@ static void zend_compile_const_expr_closure(zend_ast **ast_ptr) static void zend_compile_const_expr_fcc(zend_ast **ast_ptr) { - zend_ast **args_ast; - switch ((*ast_ptr)->kind) { - case ZEND_AST_CALL: - args_ast = &(*ast_ptr)->child[1]; - break; - case ZEND_AST_STATIC_CALL: - args_ast = &(*ast_ptr)->child[2]; - break; - default: ZEND_UNREACHABLE(); - } - if ((*args_ast)->kind != ZEND_AST_CALLABLE_CONVERT) { - zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); - } + zend_ast *args_ast = zend_ast_call_get_args(*ast_ptr); - zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)*args_ast)->args); - if (args->children != 1 || args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { - // TODO: PFAs + if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) { zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); } - ZEND_MAP_PTR_NEW(((zend_ast_fcc *)*args_ast)->fptr); + zend_ast_fcc *fcc = (zend_ast_fcc*)args_ast; + + zend_ast_list *args = zend_ast_get_list(fcc->args); + bool is_fcc = args->children == 1 && args->child[0]->attr == ZEND_PLACEHOLDER_VARIADIC; + + if (!is_fcc) { + fcc->filename = zend_string_copy(CG(active_op_array)->filename); + fcc->name = zend_compile_partial_name(CG(active_op_array), fcc->lineno); + } switch ((*ast_ptr)->kind) { case ZEND_AST_CALL: { @@ -12068,6 +12109,7 @@ static void zend_compile_const_expr_args(zend_ast **ast_ptr) { zend_ast_list *list = zend_ast_get_list(*ast_ptr); bool uses_named_args = false; + bool uses_variadic_placeholder = false; for (uint32_t i = 0; i < list->children; i++) { const zend_ast *arg = list->child[i]; if (arg->kind == ZEND_AST_UNPACK) { @@ -12076,11 +12118,32 @@ static void zend_compile_const_expr_args(zend_ast **ast_ptr) } if (arg->kind == ZEND_AST_NAMED_ARG) { uses_named_args = true; - } else if (uses_named_args) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use positional argument after named argument"); + if (uses_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder must be last"); + } + } else { + + bool is_variadic_placeholder = arg->kind == ZEND_AST_PLACEHOLDER_ARG + && arg->attr == ZEND_PLACEHOLDER_VARIADIC; + + if (uses_named_args && !is_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use positional argument after named argument"); + } + + if (uses_variadic_placeholder) { + if (is_variadic_placeholder) { + zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder may only appear once"); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder must be last"); + } + } + + if (is_variadic_placeholder) { + uses_variadic_placeholder = true; + } } } + if (uses_named_args) { list->attr = 1; } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 69d7aeb2f373..3d4e6f3c3f9f 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1121,7 +1121,6 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_THROW_IS_EXPR 1u #define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS (1<<0) -#define ZEND_FCALL_USES_VARIADIC_PLACEHOLDER (1<<1) /* The send mode, the is_variadic, the is_promoted, and the is_tentative flags are stored as part of zend_type */ #define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 4095daa1f1a3..b14245e2d05f 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -51,6 +51,7 @@ #include "zend_closures.h" #include "zend_attributes.h" #include "zend_exceptions.h" +#include "zend_partial.h" #include "ext/opcache/ZendAccelerator.h" static zend_always_inline bool Z_IS_PLACEHOLDER_P(const zval *p) { @@ -502,49 +503,6 @@ static zend_ast *zp_param_attributes_to_ast(zend_function *function, return attributes_ast; } -static zend_string *zp_pfa_name(const zend_op_array *declaring_op_array, - const zend_op *declaring_opline) -{ - /* We attempt to generate a name that hints at where the PFA was created, - * similarly to Closures (GH-13550). - * We do not attempt to make the name unique. */ - - zend_string *filename = declaring_op_array->filename; - uint32_t start_lineno = declaring_opline->lineno; - - zend_string *class = zend_empty_string; - zend_string *separator = zend_empty_string; - zend_string *function = filename; - const char *parens = ""; - - if (declaring_op_array->function_name) { - function = declaring_op_array->function_name; - if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) { - /* If the parent function is a closure, don't redundantly - * add the classname and parentheses. */ - } else { - parens = "()"; - - if (declaring_op_array->scope && declaring_op_array->scope->name) { - class = declaring_op_array->scope->name; - separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM); - } - } - } - - zend_string *name = zend_strpprintf_unchecked( - 0, - "{closure:pfa:%S%S%S%s:%" PRIu32 "}", - class, - separator, - function, - parens, - start_lineno - ); - - return name; -} - /* Generate the AST for calling the actual function */ static zend_ast *zp_compile_forwarding_call( zval *this_ptr, zend_function *function, @@ -700,9 +658,9 @@ static uint32_t zp_compute_num_required(const zend_function *function, static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags) { zend_op_array *op_array = NULL; @@ -712,6 +670,8 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, return NULL; } + bool uses_variadic_placeholder = flags & ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER; + if (UNEXPECTED(zp_args_check(function, argc, argv, extra_named_params, uses_variadic_placeholder) != SUCCESS)) { ZEND_ASSERT(EG(exception)); return NULL; @@ -1011,10 +971,8 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, } #endif - zend_string *pfa_name = zp_pfa_name(declaring_op_array, declaring_opline); - - op_array = zend_accel_compile_pfa(closure_ast, declaring_op_array, - declaring_opline, function, pfa_name); + op_array = zend_accel_compile_pfa(closure_ast, declaring_filename, + declaring_lineno_ptr, function, pfa_name, flags & ZEND_PARTIAL_CACHEABLE_IN_SHM); zend_ast_destroy(closure_ast); @@ -1039,9 +997,9 @@ static zend_op_array *zp_compile(zval *this_ptr, zend_function *function, static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags) { if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION ? cache_slot[0] == function @@ -1050,13 +1008,13 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct return cache_slot[1]; } - const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_op_array, - declaring_opline, function); + const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_lineno_ptr, function, + flags & ZEND_PARTIAL_CACHEABLE_IN_SHM); if (UNEXPECTED(!op_array)) { op_array = zp_compile(this_ptr, function, argc, argv, - extra_named_params, named_positions, declaring_op_array, declaring_opline, - cache_slot, uses_variadic_placeholder); + extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr, + cache_slot, pfa_name, flags); } if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) { @@ -1120,14 +1078,16 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval * void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags) { + + ZEND_ASSERT(pfa_name); const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv, extra_named_params, named_positions, - declaring_op_array, declaring_opline, - cache_slot, uses_variadic_placeholder); + declaring_filename, declaring_lineno_ptr, + cache_slot, pfa_name, flags); if (UNEXPECTED(!op_array)) { ZEND_ASSERT(EG(exception)); diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h index 3c47305ff543..646f8685ac29 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -21,12 +21,19 @@ BEGIN_EXTERN_C() +#define ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER (1<<0) +#define ZEND_PARTIAL_CACHEABLE_IN_SHM (1<<1) +#define ZEND_PARTIAL_FLAGS (ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER|ZEND_PARTIAL_CACHEABLE_IN_SHM) + +/* Create a partial application of 'function' + * 'declaring_op_array' and 'declaring_lineno_ptr' are used to build a cache key. + * The latter should be a pointer to a zend_op.lineno or a zend_ast.lineno. */ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function, uint32_t argc, zval *argv, zend_array *extra_named_params, const zend_array *named_positions, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, void **cache_slot, - bool uses_variadic_placeholder); + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, void **cache_slot, + zend_string *pfa_name, uint32_t flags); void zend_partial_op_array_dtor(zval *pDest); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0e5efa6f826c..99d9d735f31a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9866,14 +9866,15 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED, NUM|CACHE_SLOT) ZEND_VM_NEXT_OPCODE(); } -ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NUM) +ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CONST, CONST|UNUSED, NUM) { USE_OPLINE SAVE_OPLINE(); zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); zval *named_positions = GET_OP2_ZVAL_PTR(); + zend_string *pfa_name = Z_STR_P(GET_OP1_ZVAL_PTR()); zend_partial_create(EX_VAR(opline->result.var), &call->This, call->func, @@ -9881,8 +9882,8 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NU (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? call->extra_named_params : NULL, OP2_TYPE == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { zend_array_release(call->extra_named_params); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6b675837d3a7..0a3f18abe272 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4273,45 +4273,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_R ZEND_VM_NEXT_OPCODE(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = RT_CONSTANT(opline, opline->op2); - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -4456,45 +4417,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_RECV_VARIADIC ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = NULL; - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -8741,6 +8663,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ ZEND_VM_SMART_BRANCH(0, 1); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = RT_CONSTANT(opline, opline->op2); + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ADD_SPEC_CONST_TMPVARCV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -12067,6 +12029,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_FUNC_GET_ARGS } /* 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_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = NULL; + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57089,45 +57091,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_I ZEND_VM_NEXT_OPCODE(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = RT_CONSTANT(opline, opline->op2); - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -57272,45 +57235,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_VARIADIC_SPEC ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } -static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) -{ - USE_OPLINE - SAVE_OPLINE(); - - zend_execute_data *call = EX(call); - void **cache_slot = CACHE_ADDR(opline->op1.num); - zval *named_positions = NULL; - - zend_partial_create(EX_VAR(opline->result.var), - &call->This, call->func, - ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), - (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? - call->extra_named_params : NULL, - IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, - &EX(func)->op_array, opline, cache_slot, - opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); - - if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zend_array_release(call->extra_named_params); - } - - if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - zend_free_trampoline(call->func); - } - - if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { - OBJ_RELEASE(Z_OBJ(call->This)); - } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { - OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); - } - - EX(call) = call->prev_execute_data; - - zend_vm_stack_free_call_frame(call); - - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); -} - static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -61557,6 +61481,46 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_IN_AR ZEND_VM_SMART_BRANCH(0, 1); } +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = RT_CONSTANT(opline, opline->op2); + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_CONST == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ADD_SPEC_CONST_TMPVARCV_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -64781,6 +64745,46 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_FUNC_GET_ARGS_SPEC } /* Contrary to what its name indicates, ZEND_COPY_TMP may receive and define references. */ +static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + SAVE_OPLINE(); + + zend_execute_data *call = EX(call); + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS); + zval *named_positions = NULL; + zend_string *pfa_name = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + + zend_partial_create(EX_VAR(opline->result.var), + &call->This, call->func, + ZEND_CALL_NUM_ARGS(call), ZEND_CALL_ARG(call, 1), + (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ? + call->extra_named_params : NULL, + IS_UNUSED == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL, + EX(func)->op_array.filename, &opline->lineno, cache_slot, + pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS); + + if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zend_array_release(call->extra_named_params); + } + + if ((call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_free_trampoline(call->func); + } + + if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) { + OBJ_RELEASE(Z_OBJ(call->This)); + } else if (ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE) { + OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); + } + + EX(call) = call->prev_execute_data; + + zend_vm_stack_free_call_frame(call); + + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_PARENT_PROPERTY_HOOK_CALL_SPEC_CONST_UNUSED_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -109528,10 +109532,10 @@ 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_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_LABEL, + (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, - (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_LABEL, + (void*)&&ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_LABEL, (void*)&&ZEND_NULL_LABEL, @@ -110942,11 +110946,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_RECV_INIT_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_RECV_INIT_SPEC_CONST) HYBRID_BREAK(); - HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST): - VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); - VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) - HYBRID_BREAK(); HYBRID_CASE(ZEND_INIT_DYNAMIC_CALL_SPEC_TMP): VM_TRACE(ZEND_INIT_DYNAMIC_CALL_SPEC_TMP) ZEND_INIT_DYNAMIC_CALL_SPEC_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -110962,11 +110961,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_RECV_VARIADIC_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_RECV_VARIADIC_SPEC_UNUSED) HYBRID_BREAK(); - HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED): - VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); - VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) - HYBRID_BREAK(); HYBRID_CASE(ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED): VM_TRACE(ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED) ZEND_FRAMELESS_ICALL_1_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -111566,6 +111560,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) ZEND_IN_ARRAY_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); VM_TRACE_OP_END(ZEND_IN_ARRAY_SPEC_CONST_CONST) HYBRID_BREAK(); + HYBRID_CASE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST): + VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST) + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST) + HYBRID_BREAK(); HYBRID_CASE(ZEND_ADD_SPEC_CONST_TMPVARCV): VM_TRACE(ZEND_ADD_SPEC_CONST_TMPVARCV) ZEND_ADD_SPEC_CONST_TMPVARCV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -111926,6 +111925,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_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED): + VM_TRACE(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED) + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + VM_TRACE_OP_END(ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED) + 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); @@ -118496,10 +118500,10 @@ 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_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_HANDLER, ZEND_NULL_HANDLER, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_HANDLER, ZEND_NULL_HANDLER, @@ -121984,10 +121988,10 @@ 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_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_TAILCALL_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, - ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED_TAILCALL_HANDLER, + ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST_TAILCALL_HANDLER, ZEND_NULL_TAILCALL_HANDLER, diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index 7ffe2c220a02..503a6d25634b 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1087,8 +1087,8 @@ _(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_CALLABLE_CONVERT_PARTIAL_SPEC_CONST) \ - _(2562, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_UNUSED) \ + _(2559, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_CONST) \ + _(2562, ZEND_CALLABLE_CONVERT_PARTIAL_SPEC_CONST_UNUSED) \ _(2564, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_CONST) \ _(2567, ZEND_SEND_PLACEHOLDER_SPEC_UNUSED_UNUSED) \ _(2569, ZEND_INIT_FCALL_OFFSET_SPEC_CONST) \ diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 9846e36037b9..f9b30edb5e9b 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -451,7 +451,7 @@ static uint32_t zend_vm_opcodes_flags[214] = { 0x01001103, 0x00000303, 0x01000003, - 0x010003a0, + 0x01000303, 0x00001301, }; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 2c467dc1fc8a..0c5b0c64b012 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2024,7 +2024,7 @@ static char *zend_accel_uintptr_hex(char *dest, uintptr_t n) * a scheme, except file:// and phar://. */ #define PFA_KEY_PREFIX "pfa://" -static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, +static zend_string *zend_accel_pfa_key(const uint32_t *declaring_lineno_ptr, const zend_function *called_function) { const size_t max_key_len = strlen(PFA_KEY_PREFIX) + (sizeof(uintptr_t)*2) + strlen(":") + (sizeof(uintptr_t)*2); @@ -2032,7 +2032,7 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, char *dest = ZSTR_VAL(key); dest = zend_mempcpy(dest, PFA_KEY_PREFIX, strlen(PFA_KEY_PREFIX)); - dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_opline); + dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_lineno_ptr); *dest++ = ':'; const void *ptr; @@ -2054,17 +2054,17 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, return key; } -const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, const zend_function *called_function) +const zend_op_array *zend_accel_pfa_cache_get( + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm) { - zend_string *key = zend_accel_pfa_key(declaring_opline, called_function); + zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function); zend_op_array *op_array = NULL; - /* A PFA is SHM-cacheable if the declaring_op_array and called_function are + /* A PFA is SHM-cacheable if the declaring op_array and called_function are * cached. */ if (ZCG(accelerator_enabled) && !file_cache_only - && !declaring_op_array->refcount + && cacheable_in_shm /* declaring op_array is cached */ && (called_function->type != ZEND_USER_FUNCTION || !called_function->op_array.refcount)) { zend_persistent_script *persistent_script = zend_accel_hash_find(&ZCSG(hash), key); if (persistent_script) { @@ -2084,11 +2084,13 @@ const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_ } zend_op_array *zend_accel_compile_pfa(zend_ast *ast, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, - zend_string *pfa_func_name) + zend_string *pfa_func_name, bool cacheable_in_shm) { + ZEND_ASSERT(zend_accel_in_shm((void*)declaring_lineno_ptr) || !cacheable_in_shm); + zend_begin_record_errors(); zend_op_array *op_array; @@ -2106,7 +2108,7 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES; #endif - op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_op_array->filename); + op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_filename); CG(compiler_options) = orig_compiler_options; } zend_catch { @@ -2119,21 +2121,21 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, ZEND_ASSERT(op_array->num_dynamic_func_defs == 1); zend_string_release(op_array->dynamic_func_defs[0]->function_name); - op_array->dynamic_func_defs[0]->function_name = pfa_func_name; + op_array->dynamic_func_defs[0]->function_name = zend_string_copy(pfa_func_name); - zend_string *key = zend_accel_pfa_key(declaring_opline, called_function); + zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function); /* Cache op_array only if the declaring op_array and the called function * are cached */ if (!ZCG(accelerator_enabled) || file_cache_only - || declaring_op_array->refcount + || !cacheable_in_shm /* declaring op_array is not in SHM */ || (called_function->type == ZEND_USER_FUNCTION && called_function->op_array.refcount) || (ZCSG(restart_in_progress) && accel_restart_is_active()) || (!ZCG(counted) && accel_activate_add() == FAILURE)) { zend_op_array *script_op_array = op_array; zend_op_array *op_array = script_op_array->dynamic_func_defs[0]; - GC_ADDREF(op_array->function_name); + GC_TRY_ADDREF(op_array->function_name); (*op_array->refcount)++; destroy_op_array(script_op_array); efree(script_op_array); diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index ef7eea433c09..b76e5d1d4be7 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -334,15 +334,14 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str); uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name); -const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, - const zend_function *called_function); +const zend_op_array *zend_accel_pfa_cache_get( + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm); zend_op_array *zend_accel_compile_pfa(zend_ast *ast, - const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, + zend_string *declaring_filename, + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, - zend_string *pfa_func_name); + zend_string *pfa_func_name, bool cacheable_in_shm); END_EXTERN_C() diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index af59b9b2c34a..14d5d519ad93 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -22,6 +22,7 @@ #include "zend_attributes.h" #include "zend_system_id.h" #include "zend_enum.h" +#include "zend_partial.h" #include "php.h" #ifdef ZEND_WIN32 @@ -382,6 +383,10 @@ static void zend_file_cache_serialize_ast(zend_ast *ast, } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc = (zend_ast_fcc*)ast; ZEND_MAP_PTR_INIT(fcc->fptr, NULL); + /* Will be reset if needed during unserialize */ + fcc->attr |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + SERIALIZE_STR(fcc->filename); + SERIALIZE_STR(fcc->name); if (!IS_SERIALIZED(fcc->args)) { SERIALIZE_PTR(fcc->args); tmp = fcc->args; @@ -636,6 +641,12 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra break; } #endif + + if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { + /* Will be reset if needed during unserialize */ + opline->extended_value |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + } + zend_serialize_opcode_handler(opline); opline++; } @@ -1309,6 +1320,11 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast, } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc = (zend_ast_fcc*)ast; ZEND_MAP_PTR_NEW(fcc->fptr); + UNSERIALIZE_STR(fcc->filename); + UNSERIALIZE_STR(fcc->name); + if (script->corrupted) { + fcc->attr &= ~ZEND_PARTIAL_CACHEABLE_IN_SHM; + } if (!IS_UNSERIALIZED(fcc->args)) { UNSERIALIZE_PTR(fcc->args); zend_file_cache_unserialize_ast(fcc->args, script, buf); @@ -1539,6 +1555,13 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr zval *literal = RT_CONSTANT(opline, opline->op1); UNSERIALIZE_ATTRIBUTES(Z_PTR_P(literal)); } + + if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { + if (script->corrupted) { + opline->extended_value &= ~ZEND_PARTIAL_CACHEABLE_IN_SHM; + } + } + zend_deserialize_opcode_handler(opline); opline++; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index c06452e6acf2..c5f3713bfd93 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -27,6 +27,7 @@ #include "zend_operators.h" #include "zend_interfaces.h" #include "zend_attributes.h" +#include "zend_partial.h" #ifdef HAVE_JIT # include "Optimizer/zend_func_info.h" @@ -195,7 +196,15 @@ static zend_ast *zend_persist_ast(zend_ast *ast) node = (zend_ast *) copy; } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc)); + copy->attr |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + if (copy->filename) { + zend_accel_store_interned_string(copy->filename); + } + if (copy->name) { + zend_accel_store_interned_string(copy->name); + } copy->args = zend_persist_ast(copy->args); + ZEND_MAP_PTR_NEW(copy->fptr); node = (zend_ast *) copy; } else if (zend_ast_is_decl(ast)) { /* Not implemented. */ @@ -621,6 +630,10 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc attributes = zend_persist_attributes(attributes); ZVAL_PTR(literal, attributes); } + + if (opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) { + opline->extended_value |= ZEND_PARTIAL_CACHEABLE_IN_SHM; + } } efree(op_array->opcodes); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 9ff37079193b..71398024e7c6 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -92,6 +92,12 @@ static void zend_persist_ast_calc(zend_ast *ast) } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast; ADD_SIZE(sizeof(zend_ast_fcc)); + if (fcc_ast->filename) { + ADD_INTERNED_STRING(fcc_ast->filename); + } + if (fcc_ast->name) { + ADD_INTERNED_STRING(fcc_ast->name); + } zend_persist_ast_calc(fcc_ast->args); } else if (zend_ast_is_decl(ast)) { /* Not implemented. */ diff --git a/ext/standard/tests/array/array_map_foreach_optimization_007.phpt b/ext/standard/tests/array/array_map_foreach_optimization_007.phpt index 67654b9e7640..bce14caf9bc6 100644 --- a/ext/standard/tests/array/array_map_foreach_optimization_007.phpt +++ b/ext/standard/tests/array/array_map_foreach_optimization_007.phpt @@ -33,7 +33,7 @@ $_main: 0005 INIT_FCALL 2 %d string("array_map") 0006 INIT_FCALL 0 %d string("plusn") 0007 SEND_VAL int(2) string("n") -0008 T2 = CALLABLE_CONVERT_PARTIAL 2 +0008 T2 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}") 0009 SEND_VAL T2 1 0010 SEND_VAR CV0($array) 2 0011 T2 = DO_ICALL diff --git a/run-tests.php b/run-tests.php index f5c7be8b4f45..1d8be660388d 100755 --- a/run-tests.php +++ b/run-tests.php @@ -1054,6 +1054,32 @@ function find_files(string $dir, bool $is_ext_dir = false, bool $ignore = false) closedir($o); } +function rmdir_recursive($dir) +{ + if (!file_exists($dir)) { + return; + } + if (!is_dir($dir)) { + unlink($dir); + return; + } + + $dh = opendir($dir); + if (!$dh) { + return; + } + + while (($entry = readdir($dh)) !== false) { + if ($entry === '.' || $entry === '..') { + continue; + } + rmdir_recursive($dir . DIRECTORY_SEPARATOR . $entry); + } + + closedir($dh); + rmdir($dir); +} + /** * @param array|string $name */ @@ -2065,7 +2091,11 @@ function run_test(string $php, $file, array $env): string $orig_ini_settings = settings2params($ini_settings); if ($file_cache !== null) { - $ini_settings['opcache.file_cache'] = '/tmp'; + $ini_settings['opcache.file_cache'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'php-run-tests-file-cache'; + if ($file_cache === 'prime') { + rmdir_recursive($ini_settings['opcache.file_cache']); + mkdir($ini_settings['opcache.file_cache'], recursive: true); + } // Make sure warnings still show up on the second run. $ini_settings['opcache.record_warnings'] = '1'; // File cache is currently incompatible with JIT.