Skip to content

Commit 17f6194

Browse files
committed
Short closures with minimal auto capture
1 parent 90a845c commit 17f6194

21 files changed

+669
-32
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+
}
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)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
Short closures minimal capture: foreach
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$x = 1;
8+
$k = 1;
9+
$v = 1;
10+
11+
$f = fn () {
12+
$ret = [];
13+
foreach (range(0, $a) as $k => $v) {
14+
$ret[] = [$k, $v];
15+
}
16+
return $ret;
17+
};
18+
19+
echo "Captures:\n";
20+
var_dump((new ReflectionFunction($f))->getStaticVariables());
21+
22+
echo "f():\n";
23+
var_dump($f());
24+
--EXPECT--
25+
Captures:
26+
array(1) {
27+
["a"]=>
28+
int(1)
29+
}
30+
f():
31+
array(2) {
32+
[0]=>
33+
array(2) {
34+
[0]=>
35+
int(0)
36+
[1]=>
37+
int(0)
38+
}
39+
[1]=>
40+
array(2) {
41+
[0]=>
42+
int(1)
43+
[1]=>
44+
int(1)
45+
}
46+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Short closures minimal capture: unset
3+
--FILE--
4+
<?php
5+
6+
$a = 1;
7+
$x = 1;
8+
9+
$f = fn () {
10+
$x = 2;
11+
unset($a);
12+
return $x + ($a ?? 0);
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(0) {
23+
}
24+
f():
25+
int(2)

0 commit comments

Comments
 (0)