Skip to content

Commit 28fd759

Browse files
committed
Add first-class callable cache
This cache is implemented in two levels: A EG(callable_convert_cache) global that maps zend_function pointers to a shared callable instance, and a CALLABLE_CONVERT cache slot to remember the result of the hash table lookup. Fixes GH-19754 Closes GH-19863
1 parent 3b67680 commit 28fd759

14 files changed

+109
-28
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.6.0alpha1
44

5+
- Core:
6+
. Added first-class callable cache to share instances for the duration of the
7+
request. (ilutov)
8+
59
- Intl:
610
. Added IntlNumberRangeFormatter class to format an interval of two numbers
711
with a given skeleton, locale, collapse type and identity fallback.

Zend/Optimizer/compact_literals.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,12 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
741741
cache_size += 2 * sizeof(void *);
742742
}
743743
break;
744+
case ZEND_CALLABLE_CONVERT:
745+
if (opline->extended_value != (uint32_t)-1) {
746+
opline->extended_value = cache_size;
747+
cache_size += sizeof(void *);
748+
}
749+
break;
744750
}
745751
opline++;
746752
}

Zend/tests/closures/fcc-cache.phpt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--TEST--
2+
FCCs are cached and shared
3+
--FILE--
4+
<?php
5+
var_dump(strlen(...) === strlen(...));
6+
?>
7+
--EXPECT--
8+
bool(true)

Zend/tests/exit/exit_as_function.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ foreach ($values as $value) {
1919
}
2020

2121
?>
22-
--EXPECT--
22+
--EXPECTF--
2323
string(4) "exit"
2424
string(3) "die"
25-
object(Closure)#1 (2) {
25+
object(Closure)#%d (2) {
2626
["function"]=>
2727
string(4) "exit"
2828
["parameter"]=>
@@ -31,7 +31,7 @@ object(Closure)#1 (2) {
3131
string(10) "<optional>"
3232
}
3333
}
34-
object(Closure)#2 (2) {
34+
object(Closure)#%d (2) {
3535
["function"]=>
3636
string(4) "exit"
3737
["parameter"]=>

Zend/tests/first_class_callable/constexpr/namespace_004.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ foo();
2626

2727
?>
2828
--EXPECTF--
29-
object(Closure)#1 (2) {
29+
object(Closure)#%d (2) {
3030
["function"]=>
3131
string(6) "strrev"
3232
["parameter"]=>
@@ -36,7 +36,7 @@ object(Closure)#1 (2) {
3636
}
3737
}
3838
string(3) "cba"
39-
object(Closure)#2 (2) {
39+
object(Closure)#%d (2) {
4040
["function"]=>
4141
string(6) "strrev"
4242
["parameter"]=>
@@ -46,7 +46,7 @@ object(Closure)#2 (2) {
4646
}
4747
}
4848
string(3) "cba"
49-
object(Closure)#2 (2) {
49+
object(Closure)#%d (2) {
5050
["function"]=>
5151
string(6) "strrev"
5252
["parameter"]=>
@@ -56,7 +56,7 @@ object(Closure)#2 (2) {
5656
}
5757
}
5858
string(3) "cba"
59-
object(Closure)#1 (2) {
59+
object(Closure)#%d (2) {
6060
["function"]=>
6161
string(6) "strrev"
6262
["parameter"]=>

Zend/tests/first_class_callable/first_class_callable_optimization.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ var_dump(test1(...));
1010
var_dump(test2(...));
1111

1212
?>
13-
--EXPECT--
14-
object(Closure)#1 (1) {
13+
--EXPECTF--
14+
object(Closure)#%d (1) {
1515
["function"]=>
1616
string(5) "test1"
1717
}
18-
object(Closure)#1 (1) {
18+
object(Closure)#%d (1) {
1919
["function"]=>
2020
string(5) "test2"
2121
}

Zend/tests/magic_methods/trampoline_closure_named_arguments.phpt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ var_dump($type);
4242
var_dump($type->getName());
4343

4444
?>
45-
--EXPECT--
45+
--EXPECTF--
4646
-- Non-static cases --
4747
string(4) "test"
4848
array(3) {
@@ -69,15 +69,15 @@ array(4) {
6969
["a"]=>
7070
int(123)
7171
["b"]=>
72-
object(Test)#1 (0) {
72+
object(Test)#%d (0) {
7373
}
7474
}
7575
string(4) "test"
7676
array(2) {
7777
["a"]=>
7878
int(123)
7979
["b"]=>
80-
object(Test)#1 (0) {
80+
object(Test)#%d (0) {
8181
}
8282
}
8383
string(4) "test"
@@ -114,15 +114,15 @@ array(4) {
114114
["a"]=>
115115
int(123)
116116
["b"]=>
117-
object(Test)#1 (0) {
117+
object(Test)#%d (0) {
118118
}
119119
}
120120
string(10) "testStatic"
121121
array(2) {
122122
["a"]=>
123123
int(123)
124124
["b"]=>
125-
object(Test)#1 (0) {
125+
object(Test)#%d (0) {
126126
}
127127
}
128128
string(10) "testStatic"
@@ -136,12 +136,12 @@ array(1) {
136136
-- Reflection tests --
137137
array(1) {
138138
[0]=>
139-
object(ReflectionParameter)#4 (1) {
139+
object(ReflectionParameter)#%d (1) {
140140
["name"]=>
141141
string(9) "arguments"
142142
}
143143
}
144144
bool(true)
145-
object(ReflectionNamedType)#5 (0) {
145+
object(ReflectionNamedType)#%d (0) {
146146
}
147147
string(5) "mixed"

Zend/zend_compile.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3965,7 +3965,14 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, zend_fun
39653965
opline->op1.num = zend_vm_calc_used_stack(0, fbc);
39663966
}
39673967

3968-
zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT, NULL, NULL);
3968+
zend_op *callable_convert_op = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT, NULL, NULL);
3969+
if (opline->opcode == ZEND_INIT_FCALL
3970+
|| opline->opcode == ZEND_INIT_FCALL_BY_NAME
3971+
|| opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
3972+
callable_convert_op->extended_value = zend_alloc_cache_slot();
3973+
} else {
3974+
callable_convert_op->extended_value = (uint32_t)-1;
3975+
}
39693976
return true;
39703977
}
39713978

Zend/zend_execute_API.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ void init_executor(void) /* {{{ */
203203
zend_fiber_init();
204204
zend_weakrefs_init();
205205

206+
zend_hash_init(&EG(callable_convert_cache), 8, NULL, ZVAL_PTR_DTOR, 0);
207+
206208
EG(active) = 1;
207209
}
208210
/* }}} */
@@ -420,6 +422,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown)
420422
zend_stack_clean(&EG(user_error_handlers), (void (*)(void *))ZVAL_PTR_DTOR, 1);
421423
zend_stack_clean(&EG(user_exception_handlers), (void (*)(void *))ZVAL_PTR_DTOR, 1);
422424

425+
zend_hash_clean(&EG(callable_convert_cache));
426+
423427
#if ZEND_DEBUG
424428
if (!CG(unclean_shutdown)) {
425429
gc_collect_cycles();
@@ -516,6 +520,8 @@ void shutdown_executor(void) /* {{{ */
516520
if (EG(ht_iterators) != EG(ht_iterators_slots)) {
517521
efree(EG(ht_iterators));
518522
}
523+
524+
zend_hash_destroy(&EG(callable_convert_cache));
519525
}
520526

521527
#if ZEND_DEBUG

Zend/zend_globals.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ struct _zend_executor_globals {
319319

320320
zend_strtod_state strtod_state;
321321

322+
HashTable callable_convert_cache;
323+
322324
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
323325
};
324326

0 commit comments

Comments
 (0)