Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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;
}
Expand Down
28 changes: 28 additions & 0 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,34 @@ static uint32_t zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
}
}
}

if (call_info->caller_call_opline && call_info->caller_call_opline->opcode == ZEND_CALLABLE_CONVERT_PARTIAL) {
/* Build a bitset of constant pre-bound PFA args: These are args whose value is alway the same for all
* instances of a PFA. */
uint32_t const_args = 0;
for (uint32_t i = 0, l = MIN(sizeof(const_args)*CHAR_BIT, call_info->num_args); i < l; i++) {
zend_op *send_opline = call_info->arg_info[i].opline;
if (send_opline->op1_type == IS_CONST) {
zval *value = CT_CONSTANT_EX(op_array, send_opline->op1.constant);
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
/* Const exprs can evaluate to non-cont zvals (e.g. objects), and are not idempotent */
continue;
}
const_args |= (1 << i);
}
}

/* Pass the bitset to the ZEND_CALLABLE_CONVERT_PARTIAL opline. */
zend_op *call_opline = call_info->caller_call_opline;
if (call_opline->op2_type == IS_UNUSED) {
call_opline->op2.num = const_args;
} else {
ZEND_ASSERT(call_opline->op2_type == IS_CONST);
zval *zv = CT_CONSTANT_EX(op_array, call_opline->op2.constant);
Z_EXTRA_P(zv) = const_args;
}
}

call_info = call_info->next_callee;
} while (call_info);
}
Expand Down
162 changes: 162 additions & 0 deletions Zend/tests/partial_application/const_arg_opt.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
--TEST--
Constant argument optimization
--DESCRIPTION--
Pre-bound arguments that are constant can be burned into the generated
op_array instead of being passed via the Closure's lexical vars.
--ENV--
A=1
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
--FILE--
<?php

function f($a, $b) {
var_dump([$a, $b]);
}

function g() {
return 3;
}

function h($a, $b, $c) {
var_dump([$a, $b, $c]);
}

function i($a1, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9, $a10, $a11, $a12, $a13, $a14, $a15, $a16, $a17, $a18, $a19, $a20, $a21, $a22, $a23, $a24, $a25, $a26, $a27, $a28, $a29, $a30, $a31, $a32, $a33) {
var_dump($a33);
}

function print_lexical_vars($f) {
$vars = new ReflectionFunction($f)->getClosureUsedVariables();
if ($vars === []) {
echo "no lexical vars\n";
} else {
$varNames = array_keys($vars);
echo 'lexical vars: ', implode(', ', $varNames), "\n";
}
}

echo "# Non-constant pre-bound argument:\n";
$f = f(getenv('A'), ?);
print_lexical_vars($f);
$f(2);

echo "# Constant pre-bound argument:\n";
$f = f(2, ?);
print_lexical_vars($f);
$f(2);

echo "# Constant pre-bound argument (inverted):\n";
$f = f(?, 2);
print_lexical_vars($f);
$f(2);

echo "# Inlined pre-bound argument:\n";
$f = f(g(), ?);
print_lexical_vars($f);
$f(2);

echo "# Constexpr pre-bound argument:\n";
const B = 4;
$f = f(B, ?);
print_lexical_vars($f);
$f(2);

echo "# Mixed arguments:\n";
$f = h(5, getenv('A'), ?);
print_lexical_vars($f);
$f(2);

echo "# Constant array pre-bound argument:\n";
$f = f(['foo' => 'bar'], ?);
print_lexical_vars($f);
$f(2);

echo "# Named arguments:\n";
$f = h(1, c: ?, b: ?);
print_lexical_vars($f);
$f(2, 3);

echo "# Many arguments (optimization can not be applied for all args) :\n";
$f = i(?, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33);
print_lexical_vars($f);
$f(33);

?>
--EXPECT--
# Non-constant pre-bound argument:
lexical vars: a
array(2) {
[0]=>
string(1) "1"
[1]=>
int(2)
}
# Constant pre-bound argument:
no lexical vars
array(2) {
[0]=>
int(2)
[1]=>
int(2)
}
# Constant pre-bound argument (inverted):
no lexical vars
array(2) {
[0]=>
int(2)
[1]=>
int(2)
}
# Inlined pre-bound argument:
no lexical vars
array(2) {
[0]=>
int(3)
[1]=>
int(2)
}
# Constexpr pre-bound argument:
lexical vars: a
array(2) {
[0]=>
int(4)
[1]=>
int(2)
}
# Mixed arguments:
lexical vars: b
array(3) {
[0]=>
int(5)
[1]=>
string(1) "1"
[2]=>
int(2)
}
# Constant array pre-bound argument:
no lexical vars
array(2) {
[0]=>
array(1) {
["foo"]=>
string(3) "bar"
}
[1]=>
int(2)
}
# Named arguments:
no lexical vars
array(3) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(2)
}
# Many arguments (optimization can not be applied for all args) :
lexical vars: a33
int(33)
30 changes: 30 additions & 0 deletions Zend/tests/partial_application/constexpr_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
PFA in constexpr 001
--FILE--
<?php

function f($a = g("foo", ?)) {
return $a(1);
}

function g(string $a, int $b) {
return [$a, $b];
}

var_dump(f());
var_dump(f());

?>
--EXPECT--
array(2) {
[0]=>
string(3) "foo"
[1]=>
int(1)
}
array(2) {
[0]=>
string(3) "foo"
[1]=>
int(1)
}
22 changes: 22 additions & 0 deletions Zend/tests/partial_application/constexpr_002.phpt
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions Zend/tests/partial_application/constexpr_003.phpt
Original file line number Diff line number Diff line change
@@ -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)
}
28 changes: 28 additions & 0 deletions Zend/tests/partial_application/constexpr_004.phpt
Original file line number Diff line number Diff line change
@@ -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"
}
}
13 changes: 13 additions & 0 deletions Zend/tests/partial_application/constexpr_005.phpt
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions Zend/tests/partial_application/constexpr_006.phpt
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions Zend/tests/partial_application/constexpr_007.phpt
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions Zend/tests/partial_application/constexpr_008.phpt
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading