diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index d0aaccec7ce2c..9c30fae9e4e7b 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -740,6 +740,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++; } diff --git a/Zend/tests/exit/exit_as_function.phpt b/Zend/tests/exit/exit_as_function.phpt index fb95b9f288171..726cf1f0d7a94 100644 --- a/Zend/tests/exit/exit_as_function.phpt +++ b/Zend/tests/exit/exit_as_function.phpt @@ -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) "" } } -object(Closure)#2 (2) { +object(Closure)#%d (2) { ["function"]=> string(4) "exit" ["parameter"]=> diff --git a/Zend/tests/first_class_callable/constexpr/namespace_004.phpt b/Zend/tests/first_class_callable/constexpr/namespace_004.phpt index 0fc23422199d5..6fe99593bf95d 100644 --- a/Zend/tests/first_class_callable/constexpr/namespace_004.phpt +++ b/Zend/tests/first_class_callable/constexpr/namespace_004.phpt @@ -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"]=> diff --git a/Zend/tests/first_class_callable/first_class_callable_optimization.phpt b/Zend/tests/first_class_callable/first_class_callable_optimization.phpt index 707b6a7299a4a..169f6dfd62cc2 100644 --- a/Zend/tests/first_class_callable/first_class_callable_optimization.phpt +++ b/Zend/tests/first_class_callable/first_class_callable_optimization.phpt @@ -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" } diff --git a/Zend/tests/magic_methods/trampoline_closure_named_arguments.phpt b/Zend/tests/magic_methods/trampoline_closure_named_arguments.phpt index e4ccaf16e63a6..3be14f6145ace 100644 --- a/Zend/tests/magic_methods/trampoline_closure_named_arguments.phpt +++ b/Zend/tests/magic_methods/trampoline_closure_named_arguments.phpt @@ -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" diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b46b5d5bff8d2..43536061885c8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -3964,7 +3964,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; } diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 91b8c5ab210ef..674da0258ae41 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -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 diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 48b978b535014..fa24128ae20ed 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -319,6 +319,8 @@ struct _zend_executor_globals { zend_strtod_state strtod_state; + HashTable callable_convert_cache; + void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3ebf816cf3817..1193ed86fe0ed 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -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)); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index b0d6f2bc33d96..4f42cdc2af9dc 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -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)); diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 00ad38baaafeb..936a96e55e41f 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -439,7 +439,7 @@ static uint32_t zend_vm_opcodes_flags[211] = { 0x00000101, 0x00000101, 0x00000101, - 0x00000101, + 0x01040101, 0x00002001, 0x00000101, 0x00000100, diff --git a/ext/dom/tests/registerPhpFunctionNS.phpt b/ext/dom/tests/registerPhpFunctionNS.phpt index 4c4fb157000bf..2ee98168251fd 100644 --- a/ext/dom/tests/registerPhpFunctionNS.phpt +++ b/ext/dom/tests/registerPhpFunctionNS.phpt @@ -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) }