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
* 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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ test('Proxy', $obj);
?>
--EXPECT--
# Ghost
ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn
ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on dynamic property C::$dyn
# Proxy
ReflectionException: Can not use setRawValueWithoutLazyInitialization on dynamic property C::$dyn
ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on dynamic property C::$dyn
16 changes: 8 additions & 8 deletions Zend/tests/lazy_objects/skipLazyInitialization.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,10 @@ getValue(): string(5) "value"
## Property [ public static $static = 'static' ]

skipInitializerForProperty():
ReflectionException: Can not use skipLazyInitialization on static property A::$static
ReflectionException: Cannot use skipLazyInitialization() on static property A::$static

setRawValueWithoutLazyInitialization():
ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static
ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on static property A::$static

## Property [ public $noDefault = NULL ]

Expand Down Expand Up @@ -238,10 +238,10 @@ getValue(): string(5) "value"
## Property [ public virtual $virtual { get; set; } ]

skipInitializerForProperty():
ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual
ReflectionException: Cannot use skipLazyInitialization() on virtual property A::$virtual

setRawValueWithoutLazyInitialization():
ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual
ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on virtual property A::$virtual

## Property [ $dynamicProp ]

Expand Down Expand Up @@ -295,10 +295,10 @@ getValue(): string(5) "value"
## Property [ public static $static = 'static' ]

skipInitializerForProperty():
ReflectionException: Can not use skipLazyInitialization on static property A::$static
ReflectionException: Cannot use skipLazyInitialization() on static property A::$static

setRawValueWithoutLazyInitialization():
ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static
ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on static property A::$static

## Property [ public $noDefault = NULL ]

Expand Down Expand Up @@ -335,10 +335,10 @@ getValue(): string(5) "value"
## Property [ public virtual $virtual { get; set; } ]

skipInitializerForProperty():
ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual
ReflectionException: Cannot use skipLazyInitialization() on virtual property A::$virtual

setRawValueWithoutLazyInitialization():
ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual
ReflectionException: Cannot use setRawValueWithoutLazyInitialization() on virtual property A::$virtual

## Property [ $dynamicProp ]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ test('Proxy', $obj);
?>
--EXPECT--
# Ghost
ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn
ReflectionException: Cannot use skipLazyInitialization() on dynamic property C::$dyn
# Proxy
ReflectionException: Can not use skipLazyInitialization on dynamic property C::$dyn
ReflectionException: Cannot use skipLazyInitialization() on dynamic property C::$dyn
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(1);

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);

if (named_positions) {
zend_array_release(named_positions);
Expand Down
Loading
Loading