Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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 */
Comment thread
arnaud-lb marked this conversation as resolved.
Outdated
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);

Comment thread
arnaud-lb marked this conversation as resolved.
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