Skip to content

Commit 9dec265

Browse files
committed
Short closures with minimal auto capture
1 parent 90a845c commit 9dec265

22 files changed

+750
-31
lines changed

Zend/Optimizer/zend_cfg.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ typedef struct _zend_cfg {
9999
#define ZEND_CFG_RECV_ENTRY (1<<24)
100100
#define ZEND_CALL_TREE (1<<23)
101101
#define ZEND_SSA_USE_CV_RESULTS (1<<22)
102+
#define ZEND_DFG_SHORT_CLOSURE (1<<21)
102103

103104
#define CRT_CONSTANT_EX(op_array, opline, node) \
104105
(((op_array)->fn_flags & ZEND_ACC_DONE_PASS_TWO) ? \

Zend/Optimizer/zend_dfg.c

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,44 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
2525
const zend_op *next;
2626

2727
if (opline->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
28-
var_num = EX_VAR_TO_NUM(opline->op1.var);
29-
if (!zend_bitset_in(def, var_num)) {
30-
zend_bitset_incl(use, var_num);
28+
if (build_flags & ZEND_DFG_SHORT_CLOSURE) {
29+
switch (opline->opcode) {
30+
case ZEND_ASSIGN:
31+
case ZEND_ASSIGN_REF:
32+
case ZEND_BIND_GLOBAL:
33+
case ZEND_BIND_STATIC:
34+
case ZEND_UNSET_CV:
35+
break;
36+
default:
37+
var_num = EX_VAR_TO_NUM(opline->op1.var);
38+
if (!zend_bitset_in(def, var_num)) {
39+
zend_bitset_incl(use, var_num);
40+
}
41+
break;
42+
}
43+
} else {
44+
var_num = EX_VAR_TO_NUM(opline->op1.var);
45+
if (!zend_bitset_in(def, var_num)) {
46+
zend_bitset_incl(use, var_num);
47+
}
3148
}
3249
}
3350
if (((opline->op2_type & (IS_VAR|IS_TMP_VAR)) != 0
3451
&& opline->opcode != ZEND_FE_FETCH_R
3552
&& opline->opcode != ZEND_FE_FETCH_RW)
3653
|| (opline->op2_type == IS_CV)) {
37-
var_num = EX_VAR_TO_NUM(opline->op2.var);
38-
if (!zend_bitset_in(def, var_num)) {
39-
zend_bitset_incl(use, var_num);
54+
if (build_flags & ZEND_DFG_SHORT_CLOSURE) {
55+
if (opline->opcode != ZEND_FE_FETCH_R && opline->opcode != ZEND_FE_FETCH_RW) {
56+
var_num = EX_VAR_TO_NUM(opline->op2.var);
57+
if (!zend_bitset_in(def, var_num)) {
58+
zend_bitset_incl(use, var_num);
59+
}
60+
}
61+
} else {
62+
var_num = EX_VAR_TO_NUM(opline->op2.var);
63+
if (!zend_bitset_in(def, var_num)) {
64+
zend_bitset_incl(use, var_num);
65+
}
4066
}
4167
}
4268
if ((build_flags & ZEND_SSA_USE_CV_RESULTS)

Zend/tests/short_closures/001.phpt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Short closures
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$b = 2;
8+
9+
$f = fn ($x) {
10+
return [$a, $b, $x];
11+
};
12+
13+
echo "Captures:\n";
14+
var_dump((new ReflectionFunction($f))->getStaticVariables());
15+
16+
echo "Result:\n";
17+
var_dump($f(3));
18+
--EXPECT--
19+
Captures:
20+
array(2) {
21+
["a"]=>
22+
int(1)
23+
["b"]=>
24+
int(2)
25+
}
26+
Result:
27+
array(3) {
28+
[0]=>
29+
int(1)
30+
[1]=>
31+
int(2)
32+
[2]=>
33+
int(3)
34+
}

Zend/tests/short_closures/002.phpt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
--TEST--
2+
Short closures syntax variations
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
8+
function printFunctionSignature($f) {
9+
$rf = new ReflectionFunction($f);
10+
if ($rf->isStatic()) {
11+
print 'static ';
12+
}
13+
print 'fn ';
14+
if ($rf->returnsReference()) {
15+
print '& ';
16+
}
17+
print '(...)';
18+
$usedVars = $rf->getClosureUsedVariables();
19+
if (count($usedVars) > 0) {
20+
print ' use (';
21+
$n = 0;
22+
foreach ($usedVars as $var => $_) {
23+
if ($n++ > 0) {
24+
print ', ';
25+
}
26+
print $var;
27+
}
28+
print ')';
29+
}
30+
$type = $rf->getReturnType();
31+
if ($type !== null) {
32+
print ': ' . $type->getName();
33+
}
34+
print "\n";
35+
};
36+
37+
$f = fn () {
38+
return 1;
39+
};
40+
41+
printFunctionSignature($f);
42+
43+
$f = fn & () {
44+
return 1;
45+
};
46+
47+
printFunctionSignature($f);
48+
49+
$f = static fn () {
50+
return 1;
51+
};
52+
53+
printFunctionSignature($f);
54+
55+
$f = fn (): Foo {
56+
return 1;
57+
};
58+
59+
printFunctionSignature($f);
60+
61+
$f = fn () use ($a) {
62+
};
63+
64+
printFunctionSignature($f);
65+
66+
$f = fn () use ($a): Foo {
67+
};
68+
69+
printFunctionSignature($f);
70+
71+
--EXPECTF--
72+
fn (...)
73+
fn & (...)
74+
static fn (...)
75+
fn (...): Foo
76+
fn (...) use (a)
77+
fn (...) use (a): Foo
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
Short closures: explicit use
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$b = 2;
8+
9+
$f = fn ($x) use ($a, &$b) {
10+
$b = 4;
11+
return [$a, $b, $x];
12+
};
13+
14+
echo "Captures:\n";
15+
var_dump((new ReflectionFunction($f))->getStaticVariables());
16+
17+
echo "Result:\n";
18+
var_dump($f(3));
19+
20+
echo "\$b:\n";
21+
var_dump($b);
22+
--EXPECT--
23+
Captures:
24+
array(2) {
25+
["a"]=>
26+
int(1)
27+
["b"]=>
28+
&int(2)
29+
}
30+
Result:
31+
array(3) {
32+
[0]=>
33+
int(1)
34+
[1]=>
35+
int(4)
36+
[2]=>
37+
int(3)
38+
}
39+
$b:
40+
int(4)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Short closures minimal capture: simple case
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$x = 1;
8+
9+
$f = fn () {
10+
$x = 2;
11+
return $x + $a;
12+
};
13+
14+
echo "Captures:\n";
15+
var_dump((new ReflectionFunction($f))->getStaticVariables());
16+
17+
echo "f():\n";
18+
var_dump($f());
19+
--EXPECT--
20+
Captures:
21+
array(1) {
22+
["a"]=>
23+
int(1)
24+
}
25+
f():
26+
int(3)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Short closures minimal capture: loop
3+
--FILE--
4+
<?php
5+
6+
$a = -1;
7+
$b = 2;
8+
9+
// Captures $a because it may be used before definition
10+
$f = fn ($x) {
11+
for ($i = 0; $i < $x; $i++) {
12+
$a = $i;
13+
}
14+
return $a;
15+
};
16+
17+
echo "Captures:\n";
18+
var_dump((new ReflectionFunction($f))->getStaticVariables());
19+
20+
echo "f(3):\n";
21+
var_dump($f(3));
22+
23+
echo "f(0):\n";
24+
var_dump($f(0));
25+
--EXPECT--
26+
Captures:
27+
array(1) {
28+
["a"]=>
29+
int(-1)
30+
}
31+
f(3):
32+
int(2)
33+
f(0):
34+
int(-1)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
Short closures minimal capture: goto
3+
--FILE--
4+
<?php
5+
6+
$a = -1;
7+
$b = 2;
8+
9+
// Does not capture $a because it's always defined
10+
$f = fn ($x) {
11+
goto end;
12+
13+
ret:
14+
return $a + $x;
15+
16+
end:
17+
$a = $b;
18+
goto ret;
19+
};
20+
21+
echo "Captures:\n";
22+
var_dump((new ReflectionFunction($f))->getStaticVariables());
23+
24+
echo "f(3):\n";
25+
var_dump($f(3));
26+
27+
echo "f(0):\n";
28+
var_dump($f(0));
29+
--EXPECT--
30+
Captures:
31+
array(1) {
32+
["b"]=>
33+
int(2)
34+
}
35+
f(3):
36+
int(5)
37+
f(0):
38+
int(2)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Short closures minimal capture: static
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$x = 1;
8+
9+
$f = fn () {
10+
static $a;
11+
$x = 2;
12+
return $x + (int) $a;
13+
};
14+
15+
echo "Captures or statics:\n";
16+
var_dump((new ReflectionFunction($f))->getStaticVariables());
17+
18+
echo "f():\n";
19+
var_dump($f(3));
20+
--EXPECT--
21+
Captures or statics:
22+
array(1) {
23+
["a"]=>
24+
NULL
25+
}
26+
f():
27+
int(2)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Short closures minimal capture: assign ref
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$x = 1;
8+
9+
$f = fn () {
10+
$b = 2;
11+
$x = &$b;
12+
return $x + $a;
13+
};
14+
15+
echo "Captures:\n";
16+
var_dump((new ReflectionFunction($f))->getStaticVariables());
17+
18+
echo "f():\n";
19+
var_dump($f());
20+
--EXPECT--
21+
Captures:
22+
array(1) {
23+
["a"]=>
24+
int(1)
25+
}
26+
f():
27+
int(3)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Short closures minimal capture: global
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
8+
$f = fn () {
9+
global $a;
10+
return $a;
11+
};
12+
13+
echo "Captures:\n";
14+
var_dump((new ReflectionFunction($f))->getStaticVariables());
15+
16+
echo "f():\n";
17+
var_dump($f());
18+
--EXPECT--
19+
Captures:
20+
array(0) {
21+
}
22+
f():
23+
int(1)

0 commit comments

Comments
 (0)