Commit 2d359a40b74 for php.net
commit 2d359a40b748308403100caec4891c09b9262aa7
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date: Mon Jul 27 17:59:25 2026 +0200
[PFA 3/n] Support PFA in const exprs (#22785)
* Use a pointer to the declaring opline->lineno as unique addr, instead of the declaring opline itself
zend_partial_create() takes a 'zend_op* declaring_opline' parameter for the
purpose of building a cache key: the opline address is unique to this PFA as
long as it's in SHM.
However the declaring opline is not available when evaluating a PFA AST. Replace
the parameter by 'uint32_t* declaring_lineno'. Callers can pass
'&zend_op->lineno' or '&ast->lineno': Both are unique to the related PFA.
* 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()
* Support for PFA in constant expressions
diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c
index a4ecb19c85e..d603fe159e2 100644
--- a/Zend/Optimizer/compact_literals.c
+++ b/Zend/Optimizer/compact_literals.c
@@ -26,6 +26,7 @@
#include "zend_execute.h"
#include "zend_vm.h"
#include "zend_extensions.h"
+#include "zend_partial.h"
#define DEBUG_COMPACT_LITERALS 0
@@ -747,7 +748,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
}
break;
case ZEND_CALLABLE_CONVERT_PARTIAL:
- opline->op1.num = cache_size;
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_PARTIAL_FLAGS);
cache_size += 2 * sizeof(void *);
break;
}
diff --git a/Zend/tests/partial_application/constexpr_001.phpt b/Zend/tests/partial_application/constexpr_001.phpt
new file mode 100644
index 00000000000..8ab5fc881af
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_001.phpt
@@ -0,0 +1,54 @@
+--TEST--
+PFA in constexpr 001
+--FILE--
+<?php
+
+function f($a = g("foo", ?)) {
+ return $a(1);
+}
+
+function g(string $a, int $b) {
+ return [$a, $b];
+}
+
+class C {
+ static function f($a = self::g("foo", ?)) {
+ return $a(1);
+ }
+
+ static function g(string $a, int $b) {
+ return [$a, $b];
+ }
+}
+
+var_dump(f());
+var_dump(f());
+var_dump(C::f());
+var_dump(C::f());
+
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ string(3) "foo"
+ [1]=>
+ int(1)
+}
+array(2) {
+ [0]=>
+ string(3) "foo"
+ [1]=>
+ int(1)
+}
+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 00000000000..05f48b5eb67
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_002.phpt
@@ -0,0 +1,22 @@
+--TEST--
+PFA in constexpr: non-constexpr arg
+--FILE--
+<?php
+
+function f($a = g(new stdClass, $x, ?)) {
+ return $a(1);
+}
+
+function g(string $a, int $b) {
+ return [$a, $b];
+}
+
+try {
+ var_dump(f());
+} catch (Error $e) {
+ echo $e::class, ": ", $e->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 00000000000..4e7b7a17d0b
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_003.phpt
@@ -0,0 +1,25 @@
+--TEST--
+PFA in constexpr: named args
+--FILE--
+<?php
+
+function f($a = g(c: ?, ...)) {
+ return $a(1.5, b: 3);
+}
+
+function g(string $a = 'hello', int $b = 2, float $c = 0) {
+ return [$a, $b, $c];
+}
+
+var_dump(f());
+
+?>
+--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 00000000000..b5fee160a03
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_004.phpt
@@ -0,0 +1,28 @@
+--TEST--
+PFA in constexpr: extra named args
+--FILE--
+<?php
+
+function f($a = g(?, foo: 'bar', ...)) {
+ return $a(1, bar: 'baz');
+}
+
+function g(...$args) {
+ return [$args];
+}
+
+var_dump(f());
+
+?>
+--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 00000000000..48c830a0b83
--- /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--
+<?php
+
+function f($a = g(..., 1)) {
+}
+
+f();
+
+?>
+--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 00000000000..873c0b155fe
--- /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--
+<?php
+
+function f($a = g(..., foo: 1)) {
+}
+
+f();
+
+?>
+--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 00000000000..e932812ddf7
--- /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--
+<?php
+
+function f($a = g(..., ...)) {
+}
+
+f();
+
+?>
+--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 00000000000..c8f5b36ccb6
--- /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--
+<?php
+
+function f($a = g(foo: ...)) {
+}
+
+f();
+
+?>
+--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 00000000000..e4e05cc30a8
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_010.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA in constexpr: error during partial creation
+--FILE--
+<?php
+
+function f($a = g(?)) {
+}
+
+function g($a, $b) {
+}
+
+try {
+ f();
+} catch (Error $e) {
+ echo $e::class, ": ", $e->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 00000000000..a6480f21479
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_011.phpt
@@ -0,0 +1,43 @@
+--TEST--
+PFA in constexpr: error during arg list evaluation
+--FILE--
+<?php
+
+function f($a = g(strlen(?), e: strlen(?), a: strlen(?), ...)) {
+}
+
+function g($a, $b, $c, $d, $e) {
+}
+
+function h($a = i(strlen(?), a: strlen(?), b: invalid(?), ...)) {
+}
+
+function i(...$args) {
+}
+
+function j($a = g(c: new stdClass, ...), $b = g(c: MISSING_CONST + 1, ...)) {
+}
+
+try {
+ f();
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+try {
+ h();
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+try {
+ j();
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Error: Named parameter $a overwrites previous argument
+Error: Call to undefined function invalid()
+Error: Undefined constant "MISSING_CONST"
diff --git a/Zend/tests/partial_application/constexpr_012.phpt b/Zend/tests/partial_application/constexpr_012.phpt
new file mode 100644
index 00000000000..3c941ed6e13
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_012.phpt
@@ -0,0 +1,38 @@
+--TEST--
+PFA in constexpr: nested const expr
+--FILE--
+<?php
+
+function f($a = g(new stdClass, g(...), ...)) {
+ return $a(1);
+}
+
+function g($a, $b, $c) {
+ return [$a, $b, $c];
+}
+
+var_dump(f());
+
+?>
+--EXPECTF--
+array(3) {
+ [0]=>
+ object(stdClass)#%d (0) {
+ }
+ [1]=>
+ object(Closure)#%d (2) {
+ ["function"]=>
+ string(1) "g"
+ ["parameter"]=>
+ array(3) {
+ ["$a"]=>
+ string(10) "<required>"
+ ["$b"]=>
+ string(10) "<required>"
+ ["$c"]=>
+ string(10) "<required>"
+ }
+ }
+ [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 00000000000..c75fdb60716
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_014.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PFA in constexpr: sites
+--FILE--
+<?php
+
+#[Attribute]
+class A {
+ function __construct(public mixed $fn) {}
+}
+
+#[A(strlen(?))]
+class C {
+ const CC = strlen(?);
+
+ #[A(strlen(?))]
+ function f($arg = strlen(?)) {
+ return $arg;
+ }
+}
+
+const CC = strlen(?);
+
+echo "# Class const\n";
+var_dump((C::CC)('hello'));
+echo "# Const\n";
+var_dump((CC)('hello'));
+echo "# Class attr\n";
+var_dump((new ReflectionClass(C::class)->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 00000000000..bd9dd1674f8
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_015.inc
@@ -0,0 +1,20 @@
+<?php
+
+#[Attribute]
+class A {
+ function __construct(public mixed $fn) {}
+}
+
+#[A(strlen(?))]
+class C {
+ const CC = strlen(?);
+
+ #[A(strlen(?))]
+ function f($arg = strlen(?)) {
+ return $arg;
+ }
+}
+
+const CC = strlen(?);
+
+?>
diff --git a/Zend/tests/partial_application/constexpr_015.phpt b/Zend/tests/partial_application/constexpr_015.phpt
new file mode 100644
index 00000000000..2449adb9e4b
--- /dev/null
+++ b/Zend/tests/partial_application/constexpr_015.phpt
@@ -0,0 +1,33 @@
+--TEST--
+PFA in constexpr: preloading
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/constexpr_015.inc
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
+?>
+--FILE--
+<?php
+
+echo "# Class const\n";
+var_dump((C::CC)('hello'));
+echo "# Class attr\n";
+var_dump((new ReflectionClass(C::class)->getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method attr\n";
+var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method default value\n";
+var_dump((new C()->f())('hello'));
+
+?>
+--EXPECT--
+# Class const
+int(5)
+# Class attr
+int(5)
+# Method attr
+int(5)
+# Method default value
+int(5)
diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt
index addee498d81..194f08a9c14 100644
--- a/Zend/tests/partial_application/pipe_optimization_004.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_004.phpt
@@ -37,7 +37,7 @@ function foo($a, $b) {
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 09c3c765d23..be8d773c44c 100644
--- a/Zend/tests/partial_application/pipe_optimization_007.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_007.phpt
@@ -37,7 +37,7 @@ function foo($a, $b) {
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 070074632c7..96f8d88815c 100644
--- a/Zend/tests/partial_application/pipe_optimization_008.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_008.phpt
@@ -36,7 +36,7 @@ function foo($a, $b) {
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 7d1a48b2f2e..6c2be6728e7 100644
--- a/Zend/tests/partial_application/pipe_optimization_013.phpt
+++ b/Zend/tests/partial_application/pipe_optimization_013.phpt
@@ -32,7 +32,7 @@ function foo($a, $b) {
0004 DECLARE_FUNCTION string("foo") 0
0005 INIT_FCALL_BY_NAME 0 string("foo")
0006 SEND_VAL_EX int(2) string("b")
-0007 T0 = CALLABLE_CONVERT_PARTIAL 3
+0007 T0 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}")
0008 INIT_DYNAMIC_CALL 1 T0
0009 SEND_VAL_EX int(1) 1
0010 DO_FCALL
diff --git a/Zend/tests/partial_application/rfc_examples_const_expr.phpt b/Zend/tests/partial_application/rfc_examples_const_expr.phpt
index e2fc5744025..23350f6116c 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--
<?php
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index 1b293c832c9..8e735c6aba7 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -25,6 +25,7 @@
#include "zend_closures.h"
#include "zend_constants.h"
#include "zend_enum.h"
+#include "zend_partial.h"
ZEND_API zend_ast_process_t zend_ast_process = NULL;
@@ -60,6 +61,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);
@@ -671,6 +674,82 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(
return r;
}
+static zend_execute_data *zend_ast_evaluate_arg_list(
+ zend_function *func,
+ zend_ast_list *args_ast,
+ zend_class_entry *scope,
+ void *object_or_called_scope,
+ bool *short_circuited_ptr,
+ zend_ast_evaluate_ctx *ctx,
+ zend_array **named_positions_ptr,
+ bool *uses_variadic_placeholder
+) {
+ zend_execute_data *frame = zend_vm_stack_push_call_frame_ex(
+ zend_vm_calc_used_stack(args_ast->children, func),
+ 0, func, 0, object_or_called_scope);
+
+ 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) {
+ ZVAL_UNDEF(arg);
+ 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,
@@ -1155,10 +1234,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: {
@@ -1248,9 +1323,46 @@ 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, called_scope, short_circuited_ptr, ctx,
+ &named_positions, &uses_variadic_placeholder);
+ if (!frame) {
+ ZEND_ASSERT(EG(exception));
+ return FAILURE;
+ }
+
+ 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, &frame->This, 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:
{
@@ -1425,6 +1537,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 +1647,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;
}
@@ -1543,6 +1671,9 @@ ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *contex
for (i = 0; i < list->children; ++i) {
fn(&list->child[i], context);
}
+ } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
+ zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
+ fn(&fcc_ast->args, context);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index 26f54102a14..5d646f428c1 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 2466710f9ce..420a7cc0865 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);
@@ -11856,7 +11902,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;
}
/* }}} */
@@ -12021,24 +12068,20 @@ 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");
}
- 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) {
@@ -12080,6 +12123,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) {
@@ -12088,11 +12132,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/Zend/zend_compile.h b/Zend/zend_compile.h
index 69d7aeb2f37..3d4e6f3c3f9 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 e7a3e1c92b5..a78db3f51ef 100644
--- a/Zend/zend_partial.c
+++ b/Zend/zend_partial.c
@@ -51,6 +51,7 @@
#include "zend_closures.h"
#include "zend_attributes.h"
#include "zend_exceptions.h"
+#include "zend_partial.h"
#include "ext/opcache/ZendAccelerator.h"
static zend_always_inline bool Z_IS_PLACEHOLDER_P(const zval *p) {
@@ -502,49 +503,6 @@ static zend_ast *zp_param_attributes_to_ast(zend_function *function,
return attributes_ast;
}
-static zend_string *zp_pfa_name(const zend_op_array *declaring_op_array,
- const zend_op *declaring_opline)
-{
- /* We attempt to generate a name that hints at where the PFA was created,
- * similarly to Closures (GH-13550).
- * We do not attempt to make the name unique. */
-
- zend_string *filename = declaring_op_array->filename;
- uint32_t start_lineno = declaring_opline->lineno;
-
- zend_string *class = zend_empty_string;
- zend_string *separator = zend_empty_string;
- zend_string *function = filename;
- const char *parens = "";
-
- if (declaring_op_array->function_name) {
- function = declaring_op_array->function_name;
- if (declaring_op_array->fn_flags & ZEND_ACC_CLOSURE) {
- /* If the parent function is a closure, don't redundantly
- * add the classname and parentheses. */
- } else {
- parens = "()";
-
- if (declaring_op_array->scope && declaring_op_array->scope->name) {
- class = declaring_op_array->scope->name;
- separator = ZSTR_KNOWN(ZEND_STR_PAAMAYIM_NEKUDOTAYIM);
- }
- }
- }
-
- zend_string *name = zend_strpprintf_unchecked(
- 0,
- "{closure:pfa:%S%S%S%s:%" PRIu32 "}",
- class,
- separator,
- function,
- parens,
- start_lineno
- );
-
- return name;
-}
-
/* Generate the AST for calling the actual function */
static zend_ast *zp_compile_forwarding_call(
zval *this_ptr, zend_function *function,
@@ -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,
- const zend_op *declaring_opline, void **cache_slot,
- bool uses_variadic_placeholder) {
+ zend_string *declaring_filename,
+ const uint32_t *declaring_lineno_ptr, void **cache_slot,
+ zend_string *pfa_name, uint32_t flags) {
zend_op_array *op_array = NULL;
@@ -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_opline);
-
- op_array = zend_accel_compile_pfa(closure_ast, declaring_op_array,
- declaring_opline, function, pfa_name);
+ op_array = zend_accel_compile_pfa(closure_ast, declaring_filename,
+ declaring_lineno_ptr, function, pfa_name, flags & ZEND_PARTIAL_CACHEABLE_IN_SHM);
zend_ast_destroy(closure_ast);
@@ -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,
- const zend_op *declaring_opline, void **cache_slot,
- bool uses_variadic_placeholder) {
+ zend_string *declaring_filename,
+ const uint32_t *declaring_lineno_ptr, void **cache_slot,
+ zend_string *pfa_name, uint32_t flags) {
if (EXPECTED(function->type == ZEND_INTERNAL_FUNCTION
? cache_slot[0] == function
@@ -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_opline, function);
+ const zend_op_array *op_array = zend_accel_pfa_cache_get(declaring_lineno_ptr, function,
+ flags & ZEND_PARTIAL_CACHEABLE_IN_SHM);
if (UNEXPECTED(!op_array)) {
op_array = zp_compile(this_ptr, function, argc, argv,
- extra_named_params, named_positions, declaring_op_array, declaring_opline,
- cache_slot, uses_variadic_placeholder);
+ extra_named_params, named_positions, declaring_filename, declaring_lineno_ptr,
+ cache_slot, pfa_name, flags);
}
if (EXPECTED(op_array) && !(function->common.fn_flags & ZEND_ACC_NEVER_CACHE)) {
@@ -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,
- const zend_op *declaring_opline, void **cache_slot,
- bool uses_variadic_placeholder) {
+ zend_string *declaring_filename,
+ const uint32_t *declaring_lineno_ptr, void **cache_slot,
+ zend_string *pfa_name, uint32_t flags) {
+
+ ZEND_ASSERT(pfa_name);
const zend_op_array *op_array = zp_get_op_array(this_ptr, function, argc, argv,
extra_named_params, named_positions,
- declaring_op_array, declaring_opline,
- cache_slot, uses_variadic_placeholder);
+ declaring_filename, declaring_lineno_ptr,
+ cache_slot, pfa_name, flags);
if (UNEXPECTED(!op_array)) {
ZEND_ASSERT(EG(exception));
diff --git a/Zend/zend_partial.h b/Zend/zend_partial.h
index 3c47305ff54..386823261e2 100644
--- a/Zend/zend_partial.h
+++ b/Zend/zend_partial.h
@@ -21,12 +21,22 @@
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
+ * 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,
- bool uses_variadic_placeholder);
+ zend_string *declaring_filename,
+ const uint32_t *declaring_lineno_ptr, void **cache_slot,
+ zend_string *pfa_name, uint32_t flags);
void zend_partial_op_array_dtor(zval *pDest);
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index cd74145799b..3466bc68d69 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -9866,14 +9866,15 @@ ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED, NUM|CACHE_SLOT)
ZEND_VM_NEXT_OPCODE();
}
-ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NUM)
+ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CONST, CONST|UNUSED, NUM)
{
USE_OPLINE
SAVE_OPLINE();
zend_execute_data *call = EX(call);
- void **cache_slot = CACHE_ADDR(opline->op1.num);
+ void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_PARTIAL_FLAGS);
zval *named_positions = GET_OP2_ZVAL_PTR();
+ zend_string *pfa_name = Z_STR_P(GET_OP1_ZVAL_PTR());
zend_partial_create(EX_VAR(opline->result.var),
&call->This, call->func,
@@ -9881,8 +9882,8 @@ ZEND_VM_HANDLER(212, ZEND_CALLABLE_CONVERT_PARTIAL, CACHE_SLOT, CONST|UNUSED, NU
(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) ?
call->extra_named_params : NULL,
OP2_TYPE == IS_CONST ? Z_ARRVAL_P(named_positions) : NULL,
- &EX(func)->op_array, opline, cache_slot,
- opline->extended_value & ZEND_FCALL_USES_VARIADIC_PLACEHOLDER);
+ EX(func)->op_array.filename, &opline->lineno, cache_slot,
+ pfa_name, opline->extended_value & ZEND_PARTIAL_FLAGS);
if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_array_release(call->extra_named_params);
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 8d2e0670ee3..c0a23e49d02 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h
index 7ffe2c220a0..503a6d25634 100644
Binary files a/Zend/zend_vm_handlers.h and b/Zend/zend_vm_handlers.h differ
diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c
index 9846e36037b..f9b30edb5e9 100644
Binary files a/Zend/zend_vm_opcodes.c and b/Zend/zend_vm_opcodes.c differ
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 6f7cc8a6552..8685fb56415 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -2024,7 +2024,7 @@ static char *zend_accel_uintptr_hex(char *dest, uintptr_t n)
* a scheme, except file:// and phar://. */
#define PFA_KEY_PREFIX "pfa://"
-static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline,
+static zend_string *zend_accel_pfa_key(const uint32_t *declaring_lineno_ptr,
const zend_function *called_function)
{
const size_t max_key_len = strlen(PFA_KEY_PREFIX) + (sizeof(uintptr_t)*2) + strlen(":") + (sizeof(uintptr_t)*2);
@@ -2032,7 +2032,7 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline,
char *dest = ZSTR_VAL(key);
dest = zend_mempcpy(dest, PFA_KEY_PREFIX, strlen(PFA_KEY_PREFIX));
- dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_opline);
+ dest = zend_accel_uintptr_hex(dest, (uintptr_t)declaring_lineno_ptr);
*dest++ = ':';
const void *ptr;
@@ -2054,17 +2054,17 @@ static zend_string *zend_accel_pfa_key(const zend_op *declaring_opline,
return key;
}
-const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array,
- const zend_op *declaring_opline, const zend_function *called_function)
+const zend_op_array *zend_accel_pfa_cache_get(
+ const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm)
{
- zend_string *key = zend_accel_pfa_key(declaring_opline, called_function);
+ zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function);
zend_op_array *op_array = NULL;
- /* A PFA is SHM-cacheable if the declaring_op_array and called_function are
+ /* A PFA is SHM-cacheable if the declaring op_array and called_function are
* cached. */
if (ZCG(accelerator_enabled)
&& !file_cache_only
- && !declaring_op_array->refcount
+ && cacheable_in_shm /* declaring op_array is cached */
&& (called_function->type != ZEND_USER_FUNCTION || !called_function->op_array.refcount)) {
zend_persistent_script *persistent_script = zend_accel_hash_find(&ZCSG(hash), key);
if (persistent_script) {
@@ -2084,11 +2084,13 @@ const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_
}
zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
- const zend_op_array *declaring_op_array,
- const zend_op *declaring_opline,
+ zend_string *declaring_filename,
+ const uint32_t *declaring_lineno_ptr,
const zend_function *called_function,
- zend_string *pfa_func_name)
+ zend_string *pfa_func_name, bool cacheable_in_shm)
{
+ ZEND_ASSERT(zend_accel_in_shm((void*)declaring_lineno_ptr) || !cacheable_in_shm);
+
zend_begin_record_errors();
zend_op_array *op_array;
@@ -2106,7 +2108,7 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES;
#endif
- op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_op_array->filename);
+ op_array = zend_compile_ast(ast, ZEND_USER_FUNCTION, declaring_filename);
CG(compiler_options) = orig_compiler_options;
} zend_catch {
@@ -2119,21 +2121,21 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
ZEND_ASSERT(op_array->num_dynamic_func_defs == 1);
zend_string_release(op_array->dynamic_func_defs[0]->function_name);
- op_array->dynamic_func_defs[0]->function_name = pfa_func_name;
+ op_array->dynamic_func_defs[0]->function_name = zend_string_copy(pfa_func_name);
- zend_string *key = zend_accel_pfa_key(declaring_opline, called_function);
+ zend_string *key = zend_accel_pfa_key(declaring_lineno_ptr, called_function);
/* Cache op_array only if the declaring op_array and the called function
* are cached */
if (!ZCG(accelerator_enabled)
|| file_cache_only
- || declaring_op_array->refcount
+ || !cacheable_in_shm /* declaring op_array is not in SHM */
|| (called_function->type == ZEND_USER_FUNCTION && called_function->op_array.refcount)
|| (ZCSG(restart_in_progress) && accel_restart_is_active())
|| (!ZCG(counted) && accel_activate_add() == FAILURE)) {
zend_op_array *script_op_array = op_array;
zend_op_array *op_array = script_op_array->dynamic_func_defs[0];
- GC_ADDREF(op_array->function_name);
+ GC_TRY_ADDREF(op_array->function_name);
(*op_array->refcount)++;
destroy_op_array(script_op_array);
efree(script_op_array);
diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h
index ef7eea433c0..b76e5d1d4be 100644
--- a/ext/opcache/ZendAccelerator.h
+++ b/ext/opcache/ZendAccelerator.h
@@ -334,15 +334,14 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str);
uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name);
-const zend_op_array *zend_accel_pfa_cache_get(const zend_op_array *declaring_op_array,
- const zend_op *declaring_opline,
- const zend_function *called_function);
+const zend_op_array *zend_accel_pfa_cache_get(
+ const uint32_t *declaring_lineno_ptr, const zend_function *called_function, bool cacheable_in_shm);
zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
- const zend_op_array *declaring_op_array,
- const zend_op *declaring_opline,
+ zend_string *declaring_filename,
+ const uint32_t *declaring_lineno_ptr,
const zend_function *called_function,
- zend_string *pfa_func_name);
+ zend_string *pfa_func_name, bool cacheable_in_shm);
END_EXTERN_C()
diff --git a/ext/opcache/tests/array_map_foreach_optimization_008.phpt b/ext/opcache/tests/array_map_foreach_optimization_008.phpt
index 8a406ef11ca..36c80b460e8 100644
--- a/ext/opcache/tests/array_map_foreach_optimization_008.phpt
+++ b/ext/opcache/tests/array_map_foreach_optimization_008.phpt
@@ -33,7 +33,7 @@ function plusn($x, $n) {
0005 INIT_FCALL 2 %d string("array_map")
0006 INIT_FCALL 0 %d string("plusn")
0007 SEND_VAL int(2) string("n")
-0008 T2 = CALLABLE_CONVERT_PARTIAL 2
+0008 T2 = CALLABLE_CONVERT_PARTIAL %d string("{closure:%s}")
0009 SEND_VAL T2 1
0010 SEND_VAR CV0($array) 2
0011 T2 = DO_ICALL
diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c
index 265d5de4147..0b6cabe4201 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 d3e719dbed7..134551781ba 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 9ff37079193..71398024e7c 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. */