Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_cfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ typedef struct _zend_cfg {
#define ZEND_CFG_RECV_ENTRY (1<<24)
#define ZEND_CALL_TREE (1<<23)
#define ZEND_SSA_USE_CV_RESULTS (1<<22)
#define ZEND_DFG_SHORT_CLOSURE (1<<21)

#define CRT_CONSTANT_EX(op_array, opline, node) \
(((op_array)->fn_flags & ZEND_ACC_DONE_PASS_TWO) ? \
Expand Down
38 changes: 32 additions & 6 deletions Zend/Optimizer/zend_dfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,44 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
const zend_op *next;

if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
var_num = EX_VAR_TO_NUM(opline->op1.var);
if (!zend_bitset_in(def, var_num)) {
zend_bitset_incl(use, var_num);
if (build_flags & ZEND_DFG_SHORT_CLOSURE) {
switch (opline->opcode) {
case ZEND_ASSIGN:
case ZEND_ASSIGN_REF:
case ZEND_BIND_GLOBAL:
case ZEND_BIND_STATIC:
case ZEND_UNSET_CV:
break;
default:
var_num = EX_VAR_TO_NUM(opline->op1.var);
if (!zend_bitset_in(def, var_num)) {
zend_bitset_incl(use, var_num);
}
break;
}
} else {
var_num = EX_VAR_TO_NUM(opline->op1.var);
if (!zend_bitset_in(def, var_num)) {
zend_bitset_incl(use, var_num);
}
}
}
if (((opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0
&& opline->opcode != ZEND_FE_FETCH_R
&& opline->opcode != ZEND_FE_FETCH_RW)
|| (opline->op2_type == IS_CV)) {
var_num = EX_VAR_TO_NUM(opline->op2.var);
if (!zend_bitset_in(def, var_num)) {
zend_bitset_incl(use, var_num);
if (build_flags & ZEND_DFG_SHORT_CLOSURE) {
if (opline->opcode != ZEND_FE_FETCH_R && opline->opcode != ZEND_FE_FETCH_RW) {
var_num = EX_VAR_TO_NUM(opline->op2.var);
if (!zend_bitset_in(def, var_num)) {
zend_bitset_incl(use, var_num);
}
}
} else {
var_num = EX_VAR_TO_NUM(opline->op2.var);
if (!zend_bitset_in(def, var_num)) {
zend_bitset_incl(use, var_num);
}
}
}
if ((build_flags & ZEND_SSA_USE_CV_RESULTS)
Expand Down
34 changes: 34 additions & 0 deletions Zend/tests/short_closures/001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
Short closures
--FILE--
<?php

$a = 1;
$b = 2;

$f = fn ($x) {
return [$a, $b, $x];
};

echo "Captures:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "Result:\n";
var_dump($f(3));
--EXPECT--
Captures:
array(2) {
["a"]=>
int(1)
["b"]=>
int(2)
}
Result:
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
77 changes: 77 additions & 0 deletions Zend/tests/short_closures/002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
--TEST--
Short closures syntax variations
--FILE--
<?php

$a = 1;

function printFunctionSignature($f) {
$rf = new ReflectionFunction($f);
if ($rf->isStatic()) {
print 'static ';
}
print 'fn ';
if ($rf->returnsReference()) {
print '& ';
}
print '(...)';
$usedVars = $rf->getClosureUsedVariables();
if (count($usedVars) > 0) {
print ' use (';
$n = 0;
foreach ($usedVars as $var => $_) {
if ($n++ > 0) {
print ', ';
}
print $var;
}
print ')';
}
$type = $rf->getReturnType();
if ($type !== null) {
print ': ' . $type->getName();
}
print "\n";
};

$f = fn () {
return 1;
};

printFunctionSignature($f);

$f = fn & () {
return 1;
};

printFunctionSignature($f);

$f = static fn () {
return 1;
};

printFunctionSignature($f);

$f = fn (): Foo {
return 1;
};

printFunctionSignature($f);

$f = fn () use ($a) {
};

printFunctionSignature($f);

$f = fn () use ($a): Foo {
};

printFunctionSignature($f);

--EXPECTF--
fn (...)
fn & (...)
static fn (...)
fn (...): Foo
fn (...) use (a)
fn (...) use (a): Foo
40 changes: 40 additions & 0 deletions Zend/tests/short_closures/explicit_use.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
Short closures: explicit use
--FILE--
<?php

$a = 1;
$b = 2;

$f = fn ($x) use ($a, &$b) {
$b = 4;
return [$a, $b, $x];
};

echo "Captures:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "Result:\n";
var_dump($f(3));

echo "\$b:\n";
var_dump($b);
--EXPECT--
Captures:
array(2) {
["a"]=>
int(1)
["b"]=>
&int(2)
}
Result:
array(3) {
[0]=>
int(1)
[1]=>
int(4)
[2]=>
int(3)
}
$b:
int(4)
26 changes: 26 additions & 0 deletions Zend/tests/short_closures/minimal_capture_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Short closures minimal capture: simple case
--FILE--
<?php

$a = 1;
$x = 1;

$f = fn () {
$x = 2;
return $x + $a;
};

echo "Captures:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "f():\n";
var_dump($f());
--EXPECT--
Captures:
array(1) {
["a"]=>
int(1)
}
f():
int(3)
34 changes: 34 additions & 0 deletions Zend/tests/short_closures/minimal_capture_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
Short closures minimal capture: loop
--FILE--
<?php

$a = -1;
$b = 2;

// Captures $a because it may be used before definition
$f = fn ($x) {
for ($i = 0; $i < $x; $i++) {
$a = $i;
}
return $a;
};

echo "Captures:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "f(3):\n";
var_dump($f(3));

echo "f(0):\n";
var_dump($f(0));
--EXPECT--
Captures:
array(1) {
["a"]=>
int(-1)
}
f(3):
int(2)
f(0):
int(-1)
38 changes: 38 additions & 0 deletions Zend/tests/short_closures/minimal_capture_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Short closures minimal capture: goto
--FILE--
<?php

$a = -1;
$b = 2;

// Does not capture $a because it's always defined
$f = fn ($x) {
goto end;

ret:
return $a + $x;

end:
$a = $b;
goto ret;
};

echo "Captures:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "f(3):\n";
var_dump($f(3));

echo "f(0):\n";
var_dump($f(0));
--EXPECT--
Captures:
array(1) {
["b"]=>
int(2)
}
f(3):
int(5)
f(0):
int(2)
27 changes: 27 additions & 0 deletions Zend/tests/short_closures/minimal_capture_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Short closures minimal capture: static
--FILE--
<?php

$a = 1;
$x = 1;

$f = fn () {
static $a;
$x = 2;
return $x + (int) $a;
};

echo "Captures or statics:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "f():\n";
var_dump($f(3));
--EXPECT--
Captures or statics:
array(1) {
["a"]=>
NULL
}
f():
int(2)
27 changes: 27 additions & 0 deletions Zend/tests/short_closures/minimal_capture_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Short closures minimal capture: assign ref
--FILE--
<?php

$a = 1;
$x = 1;

$f = fn () {
$b = 2;
$x = &$b;
return $x + $a;
};

echo "Captures:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "f():\n";
var_dump($f());
--EXPECT--
Captures:
array(1) {
["a"]=>
int(1)
}
f():
int(3)
23 changes: 23 additions & 0 deletions Zend/tests/short_closures/minimal_capture_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Short closures minimal capture: global
--FILE--
<?php

$a = 1;

$f = fn () {
global $a;
return $a;
};

echo "Captures:\n";
var_dump((new ReflectionFunction($f))->getStaticVariables());

echo "f():\n";
var_dump($f());
--EXPECT--
Captures:
array(0) {
}
f():
int(1)
Loading