Skip to content
Merged
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
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
Comment thread
arnaud-lb marked this conversation as resolved.
* 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-const zvals (e.g. objects), and are not idempotent */
continue;
}
const_args |= (UINT32_C(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);
Comment thread
arnaud-lb marked this conversation as resolved.

echo "# Constant pre-bound argument (inverted):\n";
$f = f(?, 2);
print_lexical_vars($f);
$f(1);
Comment thread
arnaud-lb marked this conversation as resolved.

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(1)
[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)
10 changes: 7 additions & 3 deletions Zend/tests/partial_application/references_004.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
--TEST--
PFA receives variadic param by ref if the actual function does
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.file_update_protection=0
--FILE--
<?php

Expand All @@ -26,9 +31,8 @@ var_dump($b, $c, $d);
Closure [ <user> static function {closure:%s:%d} ] {
@@ %sreferences_004.php 13 - 13

- Bound Variables [2] {
Variable #0 [ $a ]
Variable #1 [ $args2 ]
- Bound Variables [1] {
Variable #0 [ $args2 ]
}

- Parameters [2] {
Expand Down
9 changes: 8 additions & 1 deletion Zend/tests/partial_application/variation_debug_001.phpt
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
--TEST--
PFA variation: var_dump(), user function
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.file_update_protection=0
--ENV--
A=20
--FILE--
<?php
function bar($a = 1, $b = 2, ...$c) {

}

var_dump(bar(?, new stdClass, 20, new stdClass, four: 4));
var_dump(bar(?, new stdClass, (int)getenv('A'), new stdClass, four: 4));
?>
--EXPECTF--
object(Closure)#%d (5) {
Expand Down
16 changes: 14 additions & 2 deletions Zend/tests/partial_application/variation_debug_002.phpt
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
--TEST--
PFA variation: var_dump(), internal function
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.file_update_protection=0
--ENV--
A=1
--FILE--
<?php
var_dump(array_map(?, [1, 2, 3], [4, 5, 6], four: new stdClass, ...));

function notconst($value) {
return getenv('A') ? $value : null;
}

var_dump(array_map(?, notconst([1, 2, 3]), notconst([4, 5, 6]), four: new stdClass, ...));
?>
--EXPECTF--
object(Closure)#%d (5) {
Expand All @@ -11,7 +23,7 @@ object(Closure)#%d (5) {
["file"]=>
string(%d) "%svariation_debug_002.php"
["line"]=>
int(2)
int(7)
["static"]=>
array(3) {
["array"]=>
Expand Down
12 changes: 7 additions & 5 deletions Zend/tests/partial_application/variation_variadics_001.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
--TEST--
PFA variation: variadics, user function
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.file_update_protection=0
--ENV--
A=1
--FILE--
<?php
function foo($a, ...$b) {
Expand All @@ -16,11 +23,6 @@ $foo(1000, 10000);
Closure [ <user> static function {closure:%s:%d} ] {
@@ %s 6 - 6

- Bound Variables [2] {
Variable #0 [ $a ]
Variable #1 [ $b2 ]
}

- Parameters [1] {
Parameter #0 [ <optional> ...$b ]
}
Expand Down
10 changes: 5 additions & 5 deletions Zend/tests/partial_application/variation_variadics_002.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
--TEST--
PFA variation: variadics, internal function
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.file_update_protection=0
--FILE--
<?php
$sprintf = sprintf("%d %d %d", 100, ...);
Expand All @@ -12,11 +17,6 @@ echo $sprintf(1000, 10000);
Closure [ <user> static function {closure:%s:%d} ] {
@@ %svariation_variadics_002.php 2 - 2

- Bound Variables [2] {
Variable #0 [ $format ]
Variable #1 [ $values2 ]
}

- Parameters [1] {
Parameter #0 [ <optional> mixed ...$values ]
}
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
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);
(void**)cache_slot, fcc_ast->name, flags, 0);
Comment thread
arnaud-lb marked this conversation as resolved.

if (named_positions) {
zend_array_release(named_positions);
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -4162,6 +4162,8 @@ static void zend_compile_call_partial(znode *result, zend_ast_fcc *fcc_ast, uint
if (!Z_ISUNDEF_P(named_positions)) {
opline->op2.constant = zend_add_literal(named_positions);
opline->op2_type = IS_CONST;
} else {
opline->op2.num = 0;
}
}

Expand Down
Loading
Loading