From b98e2e478c91f9230d007fc840ae2aa53fcf8a63 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 13 Jan 2026 17:01:01 +0100 Subject: [PATCH 1/3] Use a pointer to the declaring opline->lineno as unique addr, instead of the declaring opline itself This makes the API compatible with creating partials from AST: We can pass &ast->lineno to zend_partial_create() --- Zend/zend_partial.c | 20 ++++++++++---------- Zend/zend_partial.h | 6 +++++- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 8 ++++---- ext/opcache/ZendAccelerator.c | 12 ++++++------ ext/opcache/ZendAccelerator.h | 4 ++-- 6 files changed, 28 insertions(+), 24 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index e7a3e1c92b58..d76bdcbccdbe 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -503,14 +503,14 @@ static zend_ast *zp_param_attributes_to_ast(zend_function *function, } static zend_string *zp_pfa_name(const zend_op_array *declaring_op_array, - const zend_op *declaring_opline) + const uint32_t *declaring_lineno_ptr) { /* 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; + uint32_t start_lineno = *declaring_lineno_ptr; zend_string *class = zend_empty_string; zend_string *separator = zend_empty_string; @@ -702,7 +702,7 @@ 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, + const uint32_t *declaring_lineno_ptr, void **cache_slot, bool uses_variadic_placeholder) { zend_op_array *op_array = NULL; @@ -1012,10 +1012,10 @@ 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); + zend_string *pfa_name = zp_pfa_name(declaring_op_array, declaring_lineno_ptr); op_array = zend_accel_compile_pfa(closure_ast, declaring_op_array, - declaring_opline, function, pfa_name); + declaring_lineno_ptr, function, pfa_name); zend_ast_destroy(closure_ast); @@ -1041,7 +1041,7 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct 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, + const uint32_t *declaring_lineno_ptr, void **cache_slot, bool uses_variadic_placeholder) { if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION @@ -1052,11 +1052,11 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct } const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_op_array, - declaring_opline, function); + declaring_lineno_ptr, function); if (UNEXPECTED(!op_array)) { op_array = zp_compile(this_ptr, function, argc, argv, - extra_named_params, named_positions, declaring_op_array, declaring_opline, + extra_named_params, named_positions, declaring_op_array, declaring_lineno_ptr, cache_slot, uses_variadic_placeholder); } @@ -1136,12 +1136,12 @@ 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, + const uint32_t *declaring_lineno_ptr, void **cache_slot, bool uses_variadic_placeholder) { 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, + declaring_op_array, declaring_lineno_ptr, cache_slot, uses_variadic_placeholder); if (UNEXPECTED(!op_array)) { diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h index 3c47305ff543..76b6dceb6abc 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -21,11 +21,15 @@ BEGIN_EXTERN_C() +/* Create a partial application of 'function' + * 'declaring_lineno_ptr' should be a pointer the zend_op.lineno or + * zend_ast.lineno that declares the PFA. The address is used to build a cache + * key. */ 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, + const uint32_t *declaring_lineno_ptr, void **cache_slot, bool uses_variadic_placeholder); void zend_partial_op_array_dtor(zval *pDest); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0e5efa6f826c..ee539cb0d5db 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9881,7 +9881,7 @@ 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, + &EX(func)->op_array, &opline->lineno, cache_slot, opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 6b675837d3a7..dfe11eacd848 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4288,7 +4288,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV (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, + &EX(func)->op_array, &opline->lineno, cache_slot, opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { @@ -4471,7 +4471,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV (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, + &EX(func)->op_array, &opline->lineno, cache_slot, opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { @@ -57104,7 +57104,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P (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, + &EX(func)->op_array, &opline->lineno, cache_slot, opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { @@ -57287,7 +57287,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_P (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, + &EX(func)->op_array, &opline->lineno, cache_slot, opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER); if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 0a7a62eae9a9..1bc30ac3c30e 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; @@ -2055,9 +2055,9 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline, } 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 uint32_t *declaring_lineno_ptr, const zend_function *called_function) { - 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 @@ -2085,7 +2085,7 @@ 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, + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, zend_string *pfa_func_name) { @@ -2121,7 +2121,7 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, zend_string_release(op_array->dynamic_func_defs[0]->function_name); op_array->dynamic_func_defs[0]->function_name = 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 */ diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index ef7eea433c09..0d466cfd5fa3 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -335,12 +335,12 @@ 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 uint32_t *declaring_lineno_ptr, const zend_function *called_function); zend_op_array *zend_accel_compile_pfa(zend_ast *ast, const zend_op_array *declaring_op_array, - const zend_op *declaring_opline, + const uint32_t *declaring_lineno_ptr, const zend_function *called_function, zend_string *pfa_func_name); From 80cffd44f1daa0895d9cb1b972578613236e903a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 14 Jan 2026 13:45:50 +0100 Subject: [PATCH 2/3] zend_partial_create(): Remove the dependency on the declaring op_array The declaring op_array is used for multiple purposes: * Generating a PFA name * Finding the filename * Finding whether op_array is cached, and therefore whether the PFA should be cached too However it's not available when evaluating a PFA AST. Remove the declaring_op_array argument, and pass the relevant information directly: * Generate the PFA name at compile time, pass it as argument to zend_partial_create() * Pass the filename and a cacheable flag as argument to zend_partial_create() --- Zend/Optimizer/compact_literals.c | 3 +- .../pipe_optimization_004.phpt | 2 +- .../pipe_optimization_007.phpt | 2 +- .../pipe_optimization_008.phpt | 2 +- .../pipe_optimization_013.phpt | 2 +- Zend/zend_ast.c | 18 + Zend/zend_ast.h | 2 + Zend/zend_compile.c | 74 +++- Zend/zend_compile.h | 1 - Zend/zend_partial.c | 78 +--- Zend/zend_partial.h | 10 +- Zend/zend_vm_def.h | 9 +- Zend/zend_vm_execute.h | 348 +++++++++--------- Zend/zend_vm_handlers.h | 4 +- Zend/zend_vm_opcodes.c | 2 +- ext/opcache/ZendAccelerator.c | 22 +- ext/opcache/ZendAccelerator.h | 9 +- ext/opcache/zend_file_cache.c | 24 +- ext/opcache/zend_persist.c | 14 + ext/opcache/zend_persist_calc.c | 6 + 20 files changed, 358 insertions(+), 274 deletions(-) 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/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/zend_ast.c b/Zend/zend_ast.c index 1b293c832c91..9b8d31da26bd 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -60,6 +60,8 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args) { ast->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); @@ -1425,6 +1427,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; @@ -1525,6 +1537,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; } 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 6a677e71a6b6..e479ab586b44 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); @@ -12031,10 +12077,14 @@ static void zend_compile_const_expr_fcc(zend_ast **ast_ptr) zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); } - 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 - zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); + 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) { 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 d76bdcbccdbe..a78db3f51ef1 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 uint32_t *declaring_lineno_ptr) -{ - /* 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_ptr; - - 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, @@ -701,9 +659,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, + zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *pfa_name, uint32_t flags) { zend_op_array *op_array = NULL; @@ -713,6 +671,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; @@ -1012,10 +972,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_lineno_ptr); - - op_array = zend_accel_compile_pfa(closure_ast, declaring_op_array, - declaring_lineno_ptr, 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); @@ -1040,9 +998,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, + zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - bool uses_variadic_placeholder) { + zend_string *pfa_name, uint32_t flags) { if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION ? cache_slot[0] == function @@ -1051,13 +1009,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_lineno_ptr, 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_lineno_ptr, - 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)) { @@ -1135,14 +1093,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, + zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - bool uses_variadic_placeholder) { + 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_lineno_ptr, - 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 76b6dceb6abc..386823261e2b 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -21,6 +21,12 @@ BEGIN_EXTERN_C() +#define ZEND_PARTIAL_USES_VARIADIC_PLACEHOLDER (1<<0) +/* Whether the PFA is cacheable in SHM. This flag is set only when the op_array that declares the PFA is itself in SHM, + * for two reasons: Avoids SHM churn, and ensures that the PFA cache key is unique. */ +#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_lineno_ptr' should be a pointer the zend_op.lineno or * zend_ast.lineno that declares the PFA. The address is used to build a cache @@ -28,9 +34,9 @@ BEGIN_EXTERN_C() 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, + zend_string *declaring_filename, const uint32_t *declaring_lineno_ptr, void **cache_slot, - bool uses_variadic_placeholder); + 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 ee539cb0d5db..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->lineno, 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 dfe11eacd848..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->lineno, 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->lineno, 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->lineno, 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->lineno, 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 1bc30ac3c30e..4792527e4afc 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2054,17 +2054,17 @@ static zend_string *zend_accel_pfa_key(const uint32_t *declaring_lineno_ptr, return key; } -const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array, - const uint32_t *declaring_lineno_ptr, 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_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, + 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,7 +2121,7 @@ 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_lineno_ptr, called_function); @@ -2127,13 +2129,13 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast, * 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 0d466cfd5fa3..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 uint32_t *declaring_lineno_ptr, - 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, + 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 265d5de41476..0b6cabe42015 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++; } @@ -1308,9 +1319,13 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast, zend_ast_get_op_array(ast)->op_array = Z_PTR(z); } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc = (zend_ast_fcc*)ast; - if (!script->corrupted) { + if (script->corrupted) { + fcc->attr &= ~ZEND_PARTIAL_CACHEABLE_IN_SHM; + } else { ZEND_MAP_PTR_NEW(fcc->fptr); } + UNSERIALIZE_STR(fcc->filename); + UNSERIALIZE_STR(fcc->name); if (!IS_UNSERIALIZED(fcc->args)) { UNSERIALIZE_PTR(fcc->args); zend_file_cache_unserialize_ast(fcc->args, script, buf); @@ -1541,6 +1556,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 d3e719dbed70..134551781ba2 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" @@ -197,6 +198,13 @@ static zend_ast *zend_persist_ast(zend_ast *ast) zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc)); if (!ZCG(current_persistent_script)->corrupted) { ZEND_MAP_PTR_NEW(copy->fptr); + 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); node = (zend_ast *) copy; @@ -624,6 +632,12 @@ 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) { + if (!ZCG(current_persistent_script)->corrupted) { + 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. */ From 93915569186d02e94403d7e642b018e21687dfdf Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 14 Jan 2026 18:09:24 +0100 Subject: [PATCH 3/3] Support for PFA in constant expressions --- .../partial_application/constexpr_001.phpt | 30 +++++ .../partial_application/constexpr_002.phpt | 22 +++ .../partial_application/constexpr_003.phpt | 25 ++++ .../partial_application/constexpr_004.phpt | 28 ++++ .../partial_application/constexpr_005.phpt | 13 ++ .../partial_application/constexpr_006.phpt | 13 ++ .../partial_application/constexpr_007.phpt | 13 ++ .../partial_application/constexpr_008.phpt | 13 ++ .../partial_application/constexpr_010.phpt | 20 +++ .../partial_application/constexpr_011.phpt | 33 +++++ .../partial_application/constexpr_012.phpt | 38 ++++++ .../partial_application/constexpr_014.phpt | 45 +++++++ .../partial_application/constexpr_015.inc | 20 +++ .../partial_application/constexpr_015.phpt | 29 ++++ .../rfc_examples_const_expr.phpt | 2 - Zend/zend_ast.c | 126 +++++++++++++++++- Zend/zend_compile.c | 44 +++--- .../array_map_foreach_optimization_007.phpt | 2 +- 18 files changed, 492 insertions(+), 24 deletions(-) create mode 100644 Zend/tests/partial_application/constexpr_001.phpt create mode 100644 Zend/tests/partial_application/constexpr_002.phpt create mode 100644 Zend/tests/partial_application/constexpr_003.phpt create mode 100644 Zend/tests/partial_application/constexpr_004.phpt create mode 100644 Zend/tests/partial_application/constexpr_005.phpt create mode 100644 Zend/tests/partial_application/constexpr_006.phpt create mode 100644 Zend/tests/partial_application/constexpr_007.phpt create mode 100644 Zend/tests/partial_application/constexpr_008.phpt create mode 100644 Zend/tests/partial_application/constexpr_010.phpt create mode 100644 Zend/tests/partial_application/constexpr_011.phpt create mode 100644 Zend/tests/partial_application/constexpr_012.phpt create mode 100644 Zend/tests/partial_application/constexpr_014.phpt create mode 100644 Zend/tests/partial_application/constexpr_015.inc create mode 100644 Zend/tests/partial_application/constexpr_015.phpt 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_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/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-- 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, @@ -1157,10 +1232,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: { @@ -1250,9 +1321,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: { @@ -1561,6 +1672,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_compile.c b/Zend/zend_compile.c index e479ab586b44..1581f88d6a96 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11898,7 +11898,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; } /* }}} */ @@ -12063,17 +12064,9 @@ 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_ast *args_ast = zend_ast_call_get_args(*ast_ptr); + + if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) { zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); } @@ -12126,6 +12119,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) { @@ -12134,11 +12128,31 @@ 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/ext/standard/tests/array/array_map_foreach_optimization_007.phpt b/ext/standard/tests/array/array_map_foreach_optimization_007.phpt index 8a406ef11ca6..36c80b460e88 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