mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
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
This commit is contained in:
4
NEWS
4
NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|
||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
?? ??? ????, PHP 8.6.0alpha1
|
||||
|
||||
- Core:
|
||||
. Added first-class callable cache to share instances for the duration of the
|
||||
request. (ilutov)
|
||||
|
||||
- Intl:
|
||||
. Added IntlNumberRangeFormatter class to format an interval of two numbers
|
||||
with a given skeleton, locale, collapse type and identity fallback.
|
||||
|
||||
@@ -741,6 +741,12 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
|
||||
cache_size += 2 * sizeof(void *);
|
||||
}
|
||||
break;
|
||||
case ZEND_CALLABLE_CONVERT:
|
||||
if (opline->extended_value != (uint32_t)-1) {
|
||||
opline->extended_value = cache_size;
|
||||
cache_size += sizeof(void *);
|
||||
}
|
||||
break;
|
||||
}
|
||||
opline++;
|
||||
}
|
||||
|
||||
8
Zend/tests/closures/fcc-cache.phpt
Normal file
8
Zend/tests/closures/fcc-cache.phpt
Normal file
@@ -0,0 +1,8 @@
|
||||
--TEST--
|
||||
FCCs are cached and shared
|
||||
--FILE--
|
||||
<?php
|
||||
var_dump(strlen(...) === strlen(...));
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(true)
|
||||
@@ -19,10 +19,10 @@ foreach ($values as $value) {
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
--EXPECTF--
|
||||
string(4) "exit"
|
||||
string(3) "die"
|
||||
object(Closure)#1 (2) {
|
||||
object(Closure)#%d (2) {
|
||||
["function"]=>
|
||||
string(4) "exit"
|
||||
["parameter"]=>
|
||||
@@ -31,7 +31,7 @@ object(Closure)#1 (2) {
|
||||
string(10) "<optional>"
|
||||
}
|
||||
}
|
||||
object(Closure)#2 (2) {
|
||||
object(Closure)#%d (2) {
|
||||
["function"]=>
|
||||
string(4) "exit"
|
||||
["parameter"]=>
|
||||
|
||||
@@ -26,7 +26,7 @@ foo();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Closure)#1 (2) {
|
||||
object(Closure)#%d (2) {
|
||||
["function"]=>
|
||||
string(6) "strrev"
|
||||
["parameter"]=>
|
||||
@@ -36,7 +36,7 @@ object(Closure)#1 (2) {
|
||||
}
|
||||
}
|
||||
string(3) "cba"
|
||||
object(Closure)#2 (2) {
|
||||
object(Closure)#%d (2) {
|
||||
["function"]=>
|
||||
string(6) "strrev"
|
||||
["parameter"]=>
|
||||
@@ -46,7 +46,7 @@ object(Closure)#2 (2) {
|
||||
}
|
||||
}
|
||||
string(3) "cba"
|
||||
object(Closure)#2 (2) {
|
||||
object(Closure)#%d (2) {
|
||||
["function"]=>
|
||||
string(6) "strrev"
|
||||
["parameter"]=>
|
||||
@@ -56,7 +56,7 @@ object(Closure)#2 (2) {
|
||||
}
|
||||
}
|
||||
string(3) "cba"
|
||||
object(Closure)#1 (2) {
|
||||
object(Closure)#%d (2) {
|
||||
["function"]=>
|
||||
string(6) "strrev"
|
||||
["parameter"]=>
|
||||
|
||||
@@ -10,12 +10,12 @@ var_dump(test1(...));
|
||||
var_dump(test2(...));
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(Closure)#1 (1) {
|
||||
--EXPECTF--
|
||||
object(Closure)#%d (1) {
|
||||
["function"]=>
|
||||
string(5) "test1"
|
||||
}
|
||||
object(Closure)#1 (1) {
|
||||
object(Closure)#%d (1) {
|
||||
["function"]=>
|
||||
string(5) "test2"
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ var_dump($type);
|
||||
var_dump($type->getName());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
--EXPECTF--
|
||||
-- Non-static cases --
|
||||
string(4) "test"
|
||||
array(3) {
|
||||
@@ -69,7 +69,7 @@ array(4) {
|
||||
["a"]=>
|
||||
int(123)
|
||||
["b"]=>
|
||||
object(Test)#1 (0) {
|
||||
object(Test)#%d (0) {
|
||||
}
|
||||
}
|
||||
string(4) "test"
|
||||
@@ -77,7 +77,7 @@ array(2) {
|
||||
["a"]=>
|
||||
int(123)
|
||||
["b"]=>
|
||||
object(Test)#1 (0) {
|
||||
object(Test)#%d (0) {
|
||||
}
|
||||
}
|
||||
string(4) "test"
|
||||
@@ -114,7 +114,7 @@ array(4) {
|
||||
["a"]=>
|
||||
int(123)
|
||||
["b"]=>
|
||||
object(Test)#1 (0) {
|
||||
object(Test)#%d (0) {
|
||||
}
|
||||
}
|
||||
string(10) "testStatic"
|
||||
@@ -122,7 +122,7 @@ array(2) {
|
||||
["a"]=>
|
||||
int(123)
|
||||
["b"]=>
|
||||
object(Test)#1 (0) {
|
||||
object(Test)#%d (0) {
|
||||
}
|
||||
}
|
||||
string(10) "testStatic"
|
||||
@@ -136,12 +136,12 @@ array(1) {
|
||||
-- Reflection tests --
|
||||
array(1) {
|
||||
[0]=>
|
||||
object(ReflectionParameter)#4 (1) {
|
||||
object(ReflectionParameter)#%d (1) {
|
||||
["name"]=>
|
||||
string(9) "arguments"
|
||||
}
|
||||
}
|
||||
bool(true)
|
||||
object(ReflectionNamedType)#5 (0) {
|
||||
object(ReflectionNamedType)#%d (0) {
|
||||
}
|
||||
string(5) "mixed"
|
||||
|
||||
@@ -3965,7 +3965,14 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, zend_fun
|
||||
opline->op1.num = zend_vm_calc_used_stack(0, fbc);
|
||||
}
|
||||
|
||||
zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT, NULL, NULL);
|
||||
zend_op *callable_convert_op = zend_emit_op_tmp(result, ZEND_CALLABLE_CONVERT, NULL, NULL);
|
||||
if (opline->opcode == ZEND_INIT_FCALL
|
||||
|| opline->opcode == ZEND_INIT_FCALL_BY_NAME
|
||||
|| opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
|
||||
callable_convert_op->extended_value = zend_alloc_cache_slot();
|
||||
} else {
|
||||
callable_convert_op->extended_value = (uint32_t)-1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -203,6 +203,8 @@ void init_executor(void) /* {{{ */
|
||||
zend_fiber_init();
|
||||
zend_weakrefs_init();
|
||||
|
||||
zend_hash_init(&EG(callable_convert_cache), 8, NULL, ZVAL_PTR_DTOR, 0);
|
||||
|
||||
EG(active) = 1;
|
||||
}
|
||||
/* }}} */
|
||||
@@ -420,6 +422,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown)
|
||||
zend_stack_clean(&EG(user_error_handlers), (void (*)(void *))ZVAL_PTR_DTOR, 1);
|
||||
zend_stack_clean(&EG(user_exception_handlers), (void (*)(void *))ZVAL_PTR_DTOR, 1);
|
||||
|
||||
zend_hash_clean(&EG(callable_convert_cache));
|
||||
|
||||
#if ZEND_DEBUG
|
||||
if (!CG(unclean_shutdown)) {
|
||||
gc_collect_cycles();
|
||||
@@ -516,6 +520,8 @@ void shutdown_executor(void) /* {{{ */
|
||||
if (EG(ht_iterators) != EG(ht_iterators_slots)) {
|
||||
efree(EG(ht_iterators));
|
||||
}
|
||||
|
||||
zend_hash_destroy(&EG(callable_convert_cache));
|
||||
}
|
||||
|
||||
#if ZEND_DEBUG
|
||||
|
||||
@@ -319,6 +319,8 @@ struct _zend_executor_globals {
|
||||
|
||||
zend_strtod_state strtod_state;
|
||||
|
||||
HashTable callable_convert_cache;
|
||||
|
||||
void *reserved[ZEND_MAX_RESERVED_RESOURCES];
|
||||
};
|
||||
|
||||
|
||||
@@ -9709,12 +9709,28 @@ ZEND_VM_HANDLER(167, ZEND_COPY_TMP, TMPVAR, UNUSED)
|
||||
ZEND_VM_NEXT_OPCODE();
|
||||
}
|
||||
|
||||
ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED)
|
||||
ZEND_VM_HANDLER(202, ZEND_CALLABLE_CONVERT, UNUSED, UNUSED, NUM|CACHE_SLOT)
|
||||
{
|
||||
USE_OPLINE
|
||||
zend_execute_data *call = EX(call);
|
||||
|
||||
zend_closure_from_frame(EX_VAR(opline->result.var), call);
|
||||
if (opline->extended_value != (uint32_t)-1) {
|
||||
zend_object *closure = CACHED_PTR(opline->extended_value);
|
||||
if (closure) {
|
||||
ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure);
|
||||
} else {
|
||||
zval *closure_zv = zend_hash_index_lookup(&EG(callable_convert_cache), (zend_ulong)(uintptr_t)call->func);
|
||||
if (Z_TYPE_P(closure_zv) == IS_NULL) {
|
||||
zend_closure_from_frame(closure_zv, call);
|
||||
}
|
||||
ZEND_ASSERT(Z_TYPE_P(closure_zv) == IS_OBJECT);
|
||||
closure = Z_OBJ_P(closure_zv);
|
||||
ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure);
|
||||
CACHE_PTR(opline->extended_value, closure);
|
||||
}
|
||||
} else {
|
||||
zend_closure_from_frame(EX_VAR(opline->result.var), call);
|
||||
}
|
||||
|
||||
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
|
||||
OBJ_RELEASE(Z_OBJ(call->This));
|
||||
|
||||
36
Zend/zend_vm_execute.h
generated
36
Zend/zend_vm_execute.h
generated
@@ -39087,7 +39087,23 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALLABLE_CONV
|
||||
USE_OPLINE
|
||||
zend_execute_data *call = EX(call);
|
||||
|
||||
zend_closure_from_frame(EX_VAR(opline->result.var), call);
|
||||
if (opline->extended_value != (uint32_t)-1) {
|
||||
zend_object *closure = CACHED_PTR(opline->extended_value);
|
||||
if (closure) {
|
||||
ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure);
|
||||
} else {
|
||||
zval *closure_zv = zend_hash_index_lookup(&EG(callable_convert_cache), (zend_ulong)(uintptr_t)call->func);
|
||||
if (Z_TYPE_P(closure_zv) == IS_NULL) {
|
||||
zend_closure_from_frame(closure_zv, call);
|
||||
}
|
||||
ZEND_ASSERT(Z_TYPE_P(closure_zv) == IS_OBJECT);
|
||||
closure = Z_OBJ_P(closure_zv);
|
||||
ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure);
|
||||
CACHE_PTR(opline->extended_value, closure);
|
||||
}
|
||||
} else {
|
||||
zend_closure_from_frame(EX_VAR(opline->result.var), call);
|
||||
}
|
||||
|
||||
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
|
||||
OBJ_RELEASE(Z_OBJ(call->This));
|
||||
@@ -94308,7 +94324,23 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALLABLE_CONVERT_S
|
||||
USE_OPLINE
|
||||
zend_execute_data *call = EX(call);
|
||||
|
||||
zend_closure_from_frame(EX_VAR(opline->result.var), call);
|
||||
if (opline->extended_value != (uint32_t)-1) {
|
||||
zend_object *closure = CACHED_PTR(opline->extended_value);
|
||||
if (closure) {
|
||||
ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure);
|
||||
} else {
|
||||
zval *closure_zv = zend_hash_index_lookup(&EG(callable_convert_cache), (zend_ulong)(uintptr_t)call->func);
|
||||
if (Z_TYPE_P(closure_zv) == IS_NULL) {
|
||||
zend_closure_from_frame(closure_zv, call);
|
||||
}
|
||||
ZEND_ASSERT(Z_TYPE_P(closure_zv) == IS_OBJECT);
|
||||
closure = Z_OBJ_P(closure_zv);
|
||||
ZVAL_OBJ_COPY(EX_VAR(opline->result.var), closure);
|
||||
CACHE_PTR(opline->extended_value, closure);
|
||||
}
|
||||
} else {
|
||||
zend_closure_from_frame(EX_VAR(opline->result.var), call);
|
||||
}
|
||||
|
||||
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
|
||||
OBJ_RELEASE(Z_OBJ(call->This));
|
||||
|
||||
2
Zend/zend_vm_opcodes.c
generated
2
Zend/zend_vm_opcodes.c
generated
@@ -439,7 +439,7 @@ static uint32_t zend_vm_opcodes_flags[211] = {
|
||||
0x00000101,
|
||||
0x00000101,
|
||||
0x00000101,
|
||||
0x00000101,
|
||||
0x01040101,
|
||||
0x00002001,
|
||||
0x00000101,
|
||||
0x00000100,
|
||||
|
||||
@@ -62,14 +62,14 @@ $xpath->registerPhpFunctionNS('urn:bar', 'test', 'strtolower');
|
||||
var_dump($xpath->query('//a[bar:test(string(@href)) = "https://php.net"]'));
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
--EXPECTF--
|
||||
--- Legit cases: global function callable ---
|
||||
object(DOMNodeList)#5 (1) {
|
||||
["length"]=>
|
||||
int(1)
|
||||
}
|
||||
--- Legit cases: string callable ---
|
||||
object(DOMNodeList)#5 (1) {
|
||||
object(DOMNodeList)#%d (1) {
|
||||
["length"]=>
|
||||
int(1)
|
||||
}
|
||||
@@ -79,12 +79,12 @@ array(1) {
|
||||
[0]=>
|
||||
string(15) "https://PHP.net"
|
||||
}
|
||||
object(DOMNodeList)#3 (1) {
|
||||
object(DOMNodeList)#%d (1) {
|
||||
["length"]=>
|
||||
int(0)
|
||||
}
|
||||
--- Legit cases: instance class method callable ---
|
||||
object(DOMNodeList)#6 (1) {
|
||||
object(DOMNodeList)#%d (1) {
|
||||
["length"]=>
|
||||
int(1)
|
||||
}
|
||||
@@ -100,7 +100,7 @@ array(1) {
|
||||
--- Legit cases: global function callable that returns nothing ---
|
||||
string(15) "https://PHP.net"
|
||||
--- Legit cases: multiple namespaces ---
|
||||
object(DOMNodeList)#5 (1) {
|
||||
object(DOMNodeList)#%d (1) {
|
||||
["length"]=>
|
||||
int(1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user