From fe6432b9713a14cd311dc3a7772a8fb645e43b69 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 17 Jul 2026 14:42:52 +0200 Subject: [PATCH 1/8] run-tests: Clean the file cache directory in --file-cache-prime --- run-tests.php | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) 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. From f5efdc6a6d4fd2dd129a2cbaeb3785f8cbd2d095 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 17 Jul 2026 14:20:13 +0200 Subject: [PATCH 2/8] Fix preloading FCC const exprs Preloading will try to evaluate class constants during compilation, but evaluated FCCs are objects, which can not be persisted. Apply the same fix as for enums: Fail evaluation when compiling. Preloading will also reset CG(map_ptr_last) after compilation, which results in collisions if map ptrs were allocated during compilation. Move the ZEND_MAP_PTR_NEW() to the persist phase, as a shared map ptr is not necessary before that. --- Zend/zend_ast.c | 6 ++++++ Zend/zend_compile.c | 2 -- ext/opcache/zend_persist.c | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index adcb62a51d1f..1b293c832c91 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1139,6 +1139,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; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f44784239533..0a7a76dcb378 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -12027,8 +12027,6 @@ static void zend_compile_const_expr_fcc(zend_ast **ast_ptr) zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); } - ZEND_MAP_PTR_NEW(((zend_ast_fcc *)*args_ast)->fptr); - switch ((*ast_ptr)->kind) { case ZEND_AST_CALL: { zend_ast *name_ast = (*ast_ptr)->child[0]; diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index c06452e6acf2..ede2c2e67bb7 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -196,6 +196,7 @@ static zend_ast *zend_persist_ast(zend_ast *ast) } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc)); 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. */ From ba99bcbf93032b34a3ebc1253974e6152070a934 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 13 Jan 2026 17:01:01 +0100 Subject: [PATCH 3/8] 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 | 5 ++++- 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, 27 insertions(+), 24 deletions(-) diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index 4095daa1f1a3..ad109275670c 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; @@ -701,7 +701,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; @@ -1011,10 +1011,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); @@ -1040,7 +1040,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 @@ -1051,11 +1051,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); } @@ -1121,12 +1121,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..3ee167fb3338 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -21,11 +21,14 @@ BEGIN_EXTERN_C() +/* 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, + 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 2c467dc1fc8a..da4e17787179 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 58c2e55c6132ba81bac18b68d907204a88970cb5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 14 Jan 2026 13:45:50 +0100 Subject: [PATCH 4/8] 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 filename 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 | 8 +- 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 | 23 ++ ext/opcache/zend_persist.c | 12 + ext/opcache/zend_persist_calc.c | 6 + 20 files changed, 354 insertions(+), 273 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 0a7a76dcb378..b2e634d38486 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); @@ -12021,10 +12067,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 ad109275670c..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 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, @@ -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, + 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; @@ -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_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); @@ -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, + 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 @@ -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_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)) { @@ -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, + 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 3ee167fb3338..646f8685ac29 100644 --- a/Zend/zend_partial.h +++ b/Zend/zend_partial.h @@ -21,15 +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, + 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 da4e17787179..0c5b0c64b012 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 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 ede2c2e67bb7..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,6 +196,13 @@ 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; @@ -622,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. */ From f5a4f79f1ace8736dd0a4b81d7623a6da3f07591 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 14 Jan 2026 18:09:24 +0100 Subject: [PATCH 5/8] 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_009.phpt | 13 ++ .../partial_application/constexpr_010.phpt | 20 +++ .../partial_application/constexpr_011.phpt | 20 +++ .../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 | 45 ++++--- 18 files changed, 492 insertions(+), 23 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_009.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_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..3e979d68d73e --- /dev/null +++ b/Zend/tests/partial_application/constexpr_011.phpt @@ -0,0 +1,20 @@ +--TEST-- +PFA in constexpr: error during arg list evaluation +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Named parameter $a overwrites previous argument 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) { + for (uint32_t j = 0; j < i; j++) { + zval_ptr_dtor(ZEND_CALL_VAR_NUM(frame, j)); + } + 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) { + for (uint32_t j = 0; j < i; j++) { + zval_ptr_dtor(ZEND_CALL_VAR_NUM(frame, j)); + } + goto fail; + } + } + if (!arg_name) { + ZEND_CALL_NUM_ARGS(frame)++; + } + } + + return frame; + +fail: + 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 +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: { @@ -1250,9 +1322,48 @@ 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); + 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 b2e634d38486..712336b191dd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11888,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; } /* }}} */ @@ -12053,17 +12054,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"); } @@ -12116,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) { @@ -12124,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 (arg->kind == ZEND_AST_PLACEHOLDER_ARG && arg->attr == ZEND_PLACEHOLDER_VARIADIC) { + uses_variadic_placeholder = true; + } } } + if (uses_named_args) { list->attr = 1; } From 3c9faabe988d37d49ad1541281a25f5d4c82b9b5 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 17 Jul 2026 18:20:18 +0200 Subject: [PATCH 6/8] Fix test --- Zend/zend_ast.c | 1 + .../tests/array/array_map_foreach_optimization_007.phpt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 3646cf729039..0bf7ca07e3b1 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1341,6 +1341,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( 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 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 From b398da203c03d73d0fdc335f1f61210e1f54c58f Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 17 Jul 2026 19:04:58 +0200 Subject: [PATCH 7/8] Fix memory leaks --- .../partial_application/constexpr_011.phpt | 17 +++++++++++++++-- Zend/zend_ast.c | 12 ++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Zend/tests/partial_application/constexpr_011.phpt b/Zend/tests/partial_application/constexpr_011.phpt index 3e979d68d73e..525d5375103e 100644 --- a/Zend/tests/partial_application/constexpr_011.phpt +++ b/Zend/tests/partial_application/constexpr_011.phpt @@ -3,10 +3,16 @@ 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/zend_ast.c b/Zend/zend_ast.c index 0bf7ca07e3b1..91b792758f0b 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -699,9 +699,6 @@ static zend_execute_data *zend_ast_evaluate_arg_list( 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) { - for (uint32_t j = 0; j < i; j++) { - zval_ptr_dtor(ZEND_CALL_VAR_NUM(frame, j)); - } goto fail; } if (named_positions_ptr) { @@ -728,9 +725,6 @@ static zend_execute_data *zend_ast_evaluate_arg_list( } } else { if (zend_ast_evaluate_ex(arg, arg_ast, scope, short_circuited_ptr, ctx) == FAILURE) { - for (uint32_t j = 0; j < i; j++) { - zval_ptr_dtor(ZEND_CALL_VAR_NUM(frame, j)); - } goto fail; } } @@ -742,6 +736,12 @@ static zend_execute_data *zend_ast_evaluate_arg_list( 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); From 0a6d9cdef06723c6d6308ffa6dc8a68b8bcfd9a9 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 17 Jul 2026 19:14:43 +0200 Subject: [PATCH 8/8] Simplify --- Zend/zend_compile.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 712336b191dd..55b5370b780e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -12138,7 +12138,7 @@ static void zend_compile_const_expr_args(zend_ast **ast_ptr) } } - if (arg->kind == ZEND_AST_PLACEHOLDER_ARG && arg->attr == ZEND_PLACEHOLDER_VARIADIC) { + if (is_variadic_placeholder) { uses_variadic_placeholder = true; } }