From 082d5a4cef3546c2a5b3ddccf1282c27287bddd1 Mon Sep 17 00:00:00 2001 From: Dik Takken Date: Thu, 16 Jan 2020 22:00:32 +0100 Subject: [PATCH 1/2] Introduce type check cache --- Zend/tests/type_check_cache/cache_reads.phpt | 70 +++++++ .../type_check_cache/zero_cache_row_size.phpt | 30 +++ .../type_check_cache/zero_cache_size.phpt | 29 +++ Zend/zend.c | 45 +++++ Zend/zend.h | 1 + Zend/zend_API.c | 6 +- Zend/zend_API.h | 1 + Zend/zend_compile.c | 25 ++- Zend/zend_execute.c | 167 +++++++++++++--- Zend/zend_execute.h | 2 +- Zend/zend_globals.h | 20 ++ Zend/zend_object_handlers.c | 9 +- Zend/zend_opcode.c | 4 + Zend/zend_type_check_cache.c | 178 ++++++++++++++++++ Zend/zend_type_check_cache.h | 29 +++ Zend/zend_vm_def.h | 8 +- Zend/zend_vm_execute.h | 100 +++++----- configure.ac | 2 +- ext/opcache/Optimizer/compact_literals.c | 20 +- ext/reflection/php_reflection.c | 2 +- main/main.c | 33 ++++ 21 files changed, 682 insertions(+), 99 deletions(-) create mode 100644 Zend/tests/type_check_cache/cache_reads.phpt create mode 100644 Zend/tests/type_check_cache/zero_cache_row_size.phpt create mode 100644 Zend/tests/type_check_cache/zero_cache_size.phpt create mode 100644 Zend/zend_type_check_cache.c create mode 100644 Zend/zend_type_check_cache.h diff --git a/Zend/tests/type_check_cache/cache_reads.phpt b/Zend/tests/type_check_cache/cache_reads.phpt new file mode 100644 index 0000000000000..e06e24365e5a6 --- /dev/null +++ b/Zend/tests/type_check_cache/cache_reads.phpt @@ -0,0 +1,70 @@ +--TEST-- +Test if type checks work when TCC is warmed up +--FILE-- +prop = new A; + echo "Writing invalid property value\n"; + try { + $b->prop = new B; + } catch (TypeError $e) { + echo "TypeError\n"; + } +} + +echo "Done\n"; +?> +--EXPECTF-- +Passing valid object: +Passing invalid object: +TypeError +Returning valid object: +Returning invalid object: +TypeError +Writing valid property value +Writing invalid property value +TypeError +Passing valid object: +Passing invalid object: +TypeError +Returning valid object: +Returning invalid object: +TypeError +Writing valid property value +Writing invalid property value +TypeError +Done diff --git a/Zend/tests/type_check_cache/zero_cache_row_size.phpt b/Zend/tests/type_check_cache/zero_cache_row_size.phpt new file mode 100644 index 0000000000000..4d05902e19882 --- /dev/null +++ b/Zend/tests/type_check_cache/zero_cache_row_size.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test if type checks work when TCC row size is zero +--INI-- +tcc_class_slots=0 +--FILE-- + +--EXPECTF-- +Passing valid object: +Passing invalid object: +TypeError +Done diff --git a/Zend/tests/type_check_cache/zero_cache_size.phpt b/Zend/tests/type_check_cache/zero_cache_size.phpt new file mode 100644 index 0000000000000..9a74ad5352642 --- /dev/null +++ b/Zend/tests/type_check_cache/zero_cache_size.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test if type checks work when TCC has no available memory +--INI-- +tcc_memory_limit=0 +--FILE-- + +--EXPECTF-- +Passing valid object: +Passing invalid object: +TypeError +Done diff --git a/Zend/zend.c b/Zend/zend.c index 20e7e85acc94c..8a71495d3c267 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -43,16 +43,25 @@ ZEND_API size_t executor_globals_offset; static HashTable *global_function_table = NULL; static HashTable *global_class_table = NULL; static HashTable *global_constants_table = NULL; +static HashTable *global_type_names_table = NULL; +static uint8_t **global_type_check_cache = NULL; +static uint8_t *global_tcc_dummy_row = NULL; static HashTable *global_auto_globals_table = NULL; static HashTable *global_persistent_list = NULL; ZEND_TSRMLS_CACHE_DEFINE() # define GLOBAL_FUNCTION_TABLE global_function_table # define GLOBAL_CLASS_TABLE global_class_table +# define GLOBAL_TYPE_NAMES_TABLE global_type_names_table +# define GLOBAL_TYPE_CHECK_CACHE global_type_check_cache +# define GLOBAL_TCC_DUMMY_ROW global_tcc_dummy_row # define GLOBAL_CONSTANTS_TABLE global_constants_table # define GLOBAL_AUTO_GLOBALS_TABLE global_auto_globals_table #else # define GLOBAL_FUNCTION_TABLE CG(function_table) # define GLOBAL_CLASS_TABLE CG(class_table) +# define GLOBAL_TYPE_NAMES_TABLE CG(type_names_table) +# define GLOBAL_TYPE_CHECK_CACHE CG(type_check_cache) +# define GLOBAL_TCC_DUMMY_ROW CG(tcc_dummy_row) # define GLOBAL_AUTO_GLOBALS_TABLE CG(auto_globals) # define GLOBAL_CONSTANTS_TABLE EG(zend_constants) #endif @@ -640,6 +649,10 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{ zend_hash_init_ex(compiler_globals->class_table, 64, NULL, ZEND_CLASS_DTOR, 1, 0); zend_hash_copy(compiler_globals->class_table, global_class_table, zend_class_add_ref); + compiler_globals->type_names_table = (HashTable *) malloc(sizeof(HashTable)); + zend_hash_init_ex(compiler_globals->type_names_table, 64, NULL, zval_ptr_dtor, 1, 0); + zend_hash_copy(compiler_globals->type_names_table, global_type_names_table, zend_class_add_ref); + zend_set_default_compile_time_values(); compiler_globals->auto_globals = (HashTable *) malloc(sizeof(HashTable)); @@ -677,6 +690,16 @@ static void compiler_globals_dtor(zend_compiler_globals *compiler_globals) /* {{ zend_hash_destroy(compiler_globals->class_table); free(compiler_globals->class_table); } + if (compiler_globals->type_check_cache != GLOBAL_TYPE_CHECK_CACHE) { + for (int i=0; itype_names_table); i++) { + free(compiler_globals->type_check_cache[i]); + } + free(compiler_globals->type_check_cache); + } + if (compiler_globals->type_names_table != GLOBAL_TYPE_NAMES_TABLE) { + zend_hash_destroy(compiler_globals->type_names_table); + free(compiler_globals->type_names_table); + } if (compiler_globals->auto_globals != GLOBAL_AUTO_GLOBALS_TABLE) { zend_hash_destroy(compiler_globals->auto_globals); free(compiler_globals->auto_globals); @@ -871,11 +894,13 @@ int zend_startup(zend_utility_functions *utility_functions) /* {{{ */ GLOBAL_FUNCTION_TABLE = (HashTable *) malloc(sizeof(HashTable)); GLOBAL_CLASS_TABLE = (HashTable *) malloc(sizeof(HashTable)); + GLOBAL_TYPE_NAMES_TABLE = (HashTable *) malloc(sizeof(HashTable)); GLOBAL_AUTO_GLOBALS_TABLE = (HashTable *) malloc(sizeof(HashTable)); GLOBAL_CONSTANTS_TABLE = (HashTable *) malloc(sizeof(HashTable)); zend_hash_init_ex(GLOBAL_FUNCTION_TABLE, 1024, NULL, ZEND_FUNCTION_DTOR, 1, 0); zend_hash_init_ex(GLOBAL_CLASS_TABLE, 64, NULL, ZEND_CLASS_DTOR, 1, 0); + zend_hash_init_ex(GLOBAL_TYPE_NAMES_TABLE, 64, NULL, zval_ptr_dtor, 1, 0); zend_hash_init_ex(GLOBAL_AUTO_GLOBALS_TABLE, 8, NULL, auto_global_dtor, 1, 0); zend_hash_init_ex(GLOBAL_CONSTANTS_TABLE, 128, NULL, ZEND_CONSTANT_DTOR, 1, 0); @@ -892,11 +917,14 @@ int zend_startup(zend_utility_functions *utility_functions) /* {{{ */ compiler_globals_dtor(compiler_globals); compiler_globals->in_compilation = 0; + compiler_globals->last_assigned_tcc_column = 0; compiler_globals->function_table = (HashTable *) malloc(sizeof(HashTable)); compiler_globals->class_table = (HashTable *) malloc(sizeof(HashTable)); + compiler_globals->type_names_table = (HashTable *) malloc(sizeof(HashTable)); *compiler_globals->function_table = *GLOBAL_FUNCTION_TABLE; *compiler_globals->class_table = *GLOBAL_CLASS_TABLE; + *compiler_globals->type_names_table = *GLOBAL_TYPE_NAMES_TABLE; compiler_globals->auto_globals = GLOBAL_AUTO_GLOBALS_TABLE; zend_hash_destroy(executor_globals->zend_constants); @@ -1024,6 +1052,8 @@ int zend_post_startup(void) /* {{{ */ #ifdef ZTS *GLOBAL_FUNCTION_TABLE = *compiler_globals->function_table; *GLOBAL_CLASS_TABLE = *compiler_globals->class_table; + *GLOBAL_TYPE_NAMES_TABLE = *compiler_globals->type_names_table; + *GLOBAL_CONSTANTS_TABLE = *executor_globals->zend_constants; global_map_ptr_last = compiler_globals->map_ptr_last; @@ -1035,6 +1065,8 @@ int zend_post_startup(void) /* {{{ */ compiler_globals->function_table = NULL; free(compiler_globals->class_table); compiler_globals->class_table = NULL; + free(compiler_globals->type_names_table); + compiler_globals->type_names_table = NULL; if (ZEND_MAP_PTR_REAL_BASE(compiler_globals->map_ptr_base)) { free(ZEND_MAP_PTR_REAL_BASE(compiler_globals->map_ptr_base)); } @@ -1069,8 +1101,13 @@ void zend_shutdown(void) /* {{{ */ virtual_cwd_deactivate(); virtual_cwd_shutdown(); + for (int i=0; itype)) { ZVAL_COPY_VALUE(&tmp, value); - if (!zend_verify_property_type(prop_info, &tmp, /* strict */ 0)) { + if (!zend_verify_property_type(prop_info, &tmp, /* strict */ 0, NULL)) { Z_TRY_DELREF_P(value); return FAILURE; } diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 70879fe2a8013..a4f7b29326b98 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -28,6 +28,7 @@ #include "zend_variables.h" #include "zend_execute.h" #include "zend_type_info.h" +#include "zend_type_check_cache.h" BEGIN_EXTERN_C() diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2a82513016bc6..a9b80481cbf95 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1781,6 +1781,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify zend_bool persistent_hashes = ce->type == ZEND_INTERNAL_CLASS; ce->refcount = 1; + ce->tcc_column_index = 0; ce->ce_flags = ZEND_ACC_CONSTANTS_UPDATED; if (CG(compiler_options) & ZEND_COMPILE_GUARDS) { @@ -2299,7 +2300,11 @@ static void zend_emit_return_type_check( opline->result.var = expr->u.op.var = get_temporary_variable(); } - opline->op2.num = zend_alloc_cache_slots(zend_type_get_num_classes(return_info->type)); + /* Allocate cache slot to speed-up run-time class resolution and type checking. We + * use one slot to store a pointer to the row in the type checking cache that corresponds + * to the argument type and one for each of the classes contained in the argument type. + */ + opline->op2.num = zend_alloc_cache_slots(1 + zend_type_get_num_classes(return_info->type)); } } /* }}} */ @@ -2673,8 +2678,13 @@ static zend_op *zend_delayed_compile_prop(znode *result, zend_ast *ast, uint32_t opline = zend_delayed_emit_op(result, ZEND_FETCH_OBJ_R, &obj_node, &prop_node); if (opline->op2_type == IS_CONST) { + // Allocate cache slots for the offset of the property + // in the object and other property details. We can only + // cache that information when the property that is referenced + // by the opcode is constant. When the property is written we + // also allocate a slot for the type check cache. convert_to_string(CT_CONSTANT(opline->op2)); - opline->extended_value = zend_alloc_cache_slots(3); + opline->extended_value = zend_alloc_cache_slots((type == BP_VAR_W) ? 4 : 3); } zend_adjust_for_fetch_type(opline, result, type); @@ -2712,14 +2722,14 @@ zend_op *zend_compile_static_prop(znode *result, zend_ast *ast, uint32_t type, i } if (opline->op1_type == IS_CONST) { convert_to_string(CT_CONSTANT(opline->op1)); - opline->extended_value = zend_alloc_cache_slots(3); + opline->extended_value = zend_alloc_cache_slots(4); } if (class_node.op_type == IS_CONST) { opline->op2_type = IS_CONST; opline->op2.constant = zend_add_class_name_literal( Z_STR(class_node.u.constant)); if (opline->op1_type != IS_CONST) { - opline->extended_value = zend_alloc_cache_slot(); + opline->extended_value = zend_alloc_cache_slots(4); } } else { SET_NODE(opline->op2, &class_node); @@ -5781,9 +5791,12 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ opline->op1.num = i + 1; if (type_ast) { - /* Allocate cache slot to speed-up run-time class resolution */ + /* Allocate cache slot to speed-up run-time class resolution and type checking. We + * use one slot to store a pointer to the row in the type checking cache that corresponds + * to the argument type and one for each of the classes contained in the argument type. + */ opline->extended_value = - zend_alloc_cache_slots(zend_type_get_num_classes(arg_info->type)); + zend_alloc_cache_slots(1 + zend_type_get_num_classes(arg_info->type)); } ZEND_TYPE_FULL_MASK(arg_info->type) |= _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index deb89bc7e3d97..1b966dc416837 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -938,7 +938,26 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class } static zend_bool zend_check_and_resolve_property_class_type( - zend_property_info *info, zend_class_entry *object_ce) { + zend_property_info *info, zend_class_entry *object_ce, void **tcc_cache_row ) { + uint8_t *tcc = NULL; + + if (ZEND_TYPE_HAS_CLASS(info->type) && tcc_cache_row) { + if (UNEXPECTED(!*tcc_cache_row )) { + // Pointer to the row in the type check cache is not + // in the run time cache yet. Fetch it now. + *tcc_cache_row = tcc_get_row(&info->type); + } + + // Compute address of type check cache entry + tcc = *tcc_cache_row + object_ce->tcc_column_index; + + if (EXPECTED(*tcc == 1)) { + // We did this type check before and it + // was a match. We are done. + return 1; + } + } + zend_class_entry *ce; if (ZEND_TYPE_HAS_LIST(info->type)) { zend_type *list_type; @@ -955,6 +974,10 @@ static zend_bool zend_check_and_resolve_property_class_type( ce = ZEND_TYPE_CE(*list_type); } if (instanceof_function(object_ce, ce)) { + if (tcc && TCC_CONTAINS(*tcc_cache_row, object_ce)) { + // Store type check result in cache + *tcc = 1; + } return 1; } } ZEND_TYPE_LIST_FOREACH_END(); @@ -972,11 +995,19 @@ static zend_bool zend_check_and_resolve_property_class_type( } else { ce = ZEND_TYPE_CE(info->type); } - return instanceof_function(object_ce, ce); + if (instanceof_function(object_ce, ce)) { + if (tcc && TCC_CONTAINS(*tcc_cache_row, object_ce)) { + // Store type check result in cache + *tcc = 1; + } + return 1; + } else { + return 0; + } } } -static zend_always_inline zend_bool i_zend_check_property_type(zend_property_info *info, zval *property, zend_bool strict) +static zend_always_inline zend_bool i_zend_check_property_type(zend_property_info *info, zval *property, zend_bool strict, void **tcc_cache_row) { ZEND_ASSERT(!Z_ISREF_P(property)); if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) { @@ -984,7 +1015,7 @@ static zend_always_inline zend_bool i_zend_check_property_type(zend_property_inf } if (ZEND_TYPE_HAS_CLASS(info->type) && Z_TYPE_P(property) == IS_OBJECT - && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) { + && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property), tcc_cache_row)) { return 1; } @@ -995,9 +1026,9 @@ static zend_always_inline zend_bool i_zend_check_property_type(zend_property_inf return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0); } -static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict) +static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict, void **tcc_cache_row) { - if (i_zend_check_property_type(info, property, strict)) { + if (i_zend_check_property_type(info, property, strict, tcc_cache_row)) { return 1; } @@ -1005,18 +1036,18 @@ static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_in return 0; } -zend_bool zend_never_inline zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict) { - return i_zend_verify_property_type(info, property, strict); +zend_bool zend_never_inline zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict, void **tcc_cache_row) { + return i_zend_verify_property_type(info, property, strict, tcc_cache_row); } -static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *info, zval *property_val, zval *value EXECUTE_DATA_DC) +static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *info, zval *property_val, zval *value EXECUTE_DATA_DC, void **tcc_cache_row) { zval tmp; ZVAL_DEREF(value); ZVAL_COPY(&tmp, value); - if (UNEXPECTED(!i_zend_verify_property_type(info, &tmp, EX_USES_STRICT_TYPES()))) { + if (UNEXPECTED(!i_zend_verify_property_type(info, &tmp, EX_USES_STRICT_TYPES(), tcc_cache_row))) { zval_ptr_dtor(&tmp); return &EG(uninitialized_zval); } @@ -1029,7 +1060,31 @@ static zend_always_inline zend_bool zend_check_type_slow( zend_bool is_return_type, zend_bool is_internal) { uint32_t type_mask; + uint8_t *tcc = NULL; + uint8_t *tcc_cache_row = NULL; if (ZEND_TYPE_HAS_CLASS(type) && Z_TYPE_P(arg) == IS_OBJECT) { + tcc_cache_row = *cache_slot; + if (UNEXPECTED(!tcc_cache_row)) { + // Pointer to the row in the type check cache is not + // in the run time cache yet. Fetch it now. + tcc_cache_row = tcc_get_row(&type); + *cache_slot = tcc_cache_row; + } + + // Compute address of type check cache entry + tcc = tcc_cache_row + Z_OBJCE_P(arg)->tcc_column_index; + + if (EXPECTED(*tcc)) { + // We did this type check before and it + // was a match. We are done. + return 1; + } + + ZEND_ASSERT(*tcc == 0); + + // Jump to the cache slots that contain the CE. + cache_slot++; + zend_class_entry *ce; if (ZEND_TYPE_HAS_LIST(type)) { zend_type *list_type; @@ -1045,7 +1100,13 @@ static zend_always_inline zend_bool zend_check_type_slow( } *cache_slot = ce; } + + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + if (TCC_CONTAINS(tcc_cache_row, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return 1; } cache_slot++; @@ -1061,6 +1122,10 @@ static zend_always_inline zend_bool zend_check_type_slow( *cache_slot = (void *) ce; } if (instanceof_function(Z_OBJCE_P(arg), ce)) { + if (TCC_CONTAINS(tcc_cache_row, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return 1; } } @@ -1069,9 +1134,17 @@ static zend_always_inline zend_bool zend_check_type_slow( builtin_types: type_mask = ZEND_TYPE_FULL_MASK(type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + if (tcc_cache_row && TCC_CONTAINS(tcc_cache_row, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return 1; } if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { + if (tcc_cache_row && TCC_CONTAINS(tcc_cache_row, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return 1; } if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) { @@ -1119,9 +1192,11 @@ static zend_always_inline int zend_verify_recv_arg_type(zend_function *zf, uint3 ZEND_ASSERT(arg_num <= zf->common.num_args); cur_arg_info = &zf->common.arg_info[arg_num-1]; + // Note that cache_slot points to a TCC row, subsequent slots are + // for storing references to class entries. if (ZEND_TYPE_IS_SET(cur_arg_info->type) && UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) { - zend_verify_arg_error(zf, cur_arg_info, arg_num, cache_slot, arg); + zend_verify_arg_error(zf, cur_arg_info, arg_num, cache_slot + 1, arg); return 0; } @@ -1136,9 +1211,11 @@ static zend_always_inline int zend_verify_variadic_arg_type(zend_function *zf, u ZEND_ASSERT(zf->common.fn_flags & ZEND_ACC_VARIADIC); cur_arg_info = &zf->common.arg_info[zf->common.num_args]; + // Note that cache_slot points to a TCC row, subsequent slots are + // for storing references to class entries. if (ZEND_TYPE_IS_SET(cur_arg_info->type) && UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) { - zend_verify_arg_error(zf, cur_arg_info, arg_num, cache_slot, arg); + zend_verify_arg_error(zf, cur_arg_info, arg_num, cache_slot + 1, arg); return 0; } @@ -1153,7 +1230,6 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED int zend_verify_internal_arg_type for (i = 0; i < num_args; ++i) { zend_arg_info *cur_arg_info; - void *dummy_cache_slot = NULL; if (EXPECTED(i < fbc->common.num_args)) { cur_arg_info = &fbc->common.arg_info[i]; @@ -1163,10 +1239,32 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED int zend_verify_internal_arg_type break; } + void **dummy_cache_slot = NULL; + if (ZEND_TYPE_IS_SET(cur_arg_info->type)) { + zend_type type = cur_arg_info->type; + if (ZEND_TYPE_HAS_CLASS(type)) { + // Internal calls do not have any run_time_cache slots, + // so we generate a dummy cache that zend_check_type can use. + if (ZEND_TYPE_HAS_LIST(type)) { + dummy_cache_slot = calloc(1 + ZEND_TYPE_LIST(type)->num_types, sizeof(void *)); + } else { + dummy_cache_slot = calloc(1 + 1, sizeof(void *)); + } + } + } + if (ZEND_TYPE_IS_SET(cur_arg_info->type) - && UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &dummy_cache_slot, fbc->common.scope, 0, /* is_internal */ 1))) { + && UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, dummy_cache_slot, fbc->common.scope, 0, /* is_internal */ 1))) { + if (dummy_cache_slot){ + free(dummy_cache_slot); + } return 0; } + + if (dummy_cache_slot){ + free(dummy_cache_slot); + } + arg++; } return 1; @@ -1280,7 +1378,6 @@ static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, con static int zend_verify_internal_return_type(zend_function *zf, zval *ret) { zend_internal_arg_info *ret_info = zf->internal_function.arg_info - 1; - void *dummy_cache_slot = NULL; if (ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_VOID) { if (UNEXPECTED(Z_TYPE_P(ret) != IS_NULL)) { @@ -1290,19 +1387,43 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) return 1; } - if (UNEXPECTED(!zend_check_type(ret_info->type, ret, &dummy_cache_slot, NULL, 1, /* is_internal */ 1))) { - zend_verify_internal_return_error(zf, &dummy_cache_slot, ret); + void **dummy_cache_slot = NULL; + if (ZEND_TYPE_IS_SET(ret_info->type)) { + zend_type type = ret_info->type; + if (ZEND_TYPE_HAS_CLASS(type)) { + // Internal calls do not have any run_time_cache slots, + // so we generate a dummy cache that zend_check_type can use. + if (ZEND_TYPE_HAS_LIST(type)) { + dummy_cache_slot = calloc(1 + ZEND_TYPE_LIST(type)->num_types, sizeof(void *)); + } else { + dummy_cache_slot = calloc(1 + 1, sizeof(void *)); + } + } + } + + if (UNEXPECTED(!zend_check_type(ret_info->type, ret, dummy_cache_slot, NULL, 1, /* is_internal */ 1))) { + zend_verify_internal_return_error(zf, dummy_cache_slot, ret); + if (dummy_cache_slot){ + free(dummy_cache_slot); + } return 0; } + if (dummy_cache_slot){ + free(dummy_cache_slot); + } + return 1; } #endif static ZEND_COLD int zend_verify_missing_return_type(const zend_function *zf, void **cache_slot) { - /* VERIFY_RETURN_TYPE is not emitted for "void" functions, so this is always an error. */ - zend_verify_return_error(zf, cache_slot, NULL); + /* VERIFY_RETURN_TYPE is not emitted for "void" functions, so this is always an error. + * Note that cache_slot points to a TCC row, subsequent slots are for storing references + * to class entries. + */ + zend_verify_return_error(zf, cache_slot + 1, NULL); return 0; } @@ -1393,7 +1514,7 @@ static zend_never_inline void zend_binary_assign_op_typed_prop(zend_property_inf zval z_copy; zend_binary_op(&z_copy, zptr, value OPLINE_CC); - if (EXPECTED(zend_verify_property_type(prop_info, &z_copy, EX_USES_STRICT_TYPES()))) { + if (EXPECTED(zend_verify_property_type(prop_info, &z_copy, EX_USES_STRICT_TYPES(), NULL))) { zval_ptr_dtor(zptr); ZVAL_COPY_VALUE(zptr, &z_copy); } else { @@ -1744,7 +1865,7 @@ static void zend_incdec_typed_prop(zend_property_info *prop_info, zval *var_ptr, zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); ZVAL_LONG(var_ptr, val); } - } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { + } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES(), NULL))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, copy); ZVAL_UNDEF(copy); @@ -3041,7 +3162,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( } if (ZEND_TYPE_HAS_CLASS(type) && zv_type == IS_OBJECT - && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) { + && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv), NULL)) { return 1; } @@ -3208,7 +3329,7 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert } } else { ZVAL_DEREF(val); - if (i_zend_check_property_type(prop_info, val, strict)) { + if (i_zend_check_property_type(prop_info, val, strict, NULL)) { return 1; } } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index e2782e2127c56..f0248a77d0b6a 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -396,7 +396,7 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) == ZEND_ACC_HAS_TYPE_HINTS) -zend_bool zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict); +zend_bool zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict, void **tcc_cache_row); ZEND_COLD void zend_verify_property_type_error(zend_property_info *info, zval *property); #define ZEND_REF_ADD_TYPE_SOURCE(ref, source) \ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 2dc769a5efe17..82d3b2f76f314 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -77,6 +77,23 @@ struct _zend_compiler_globals { HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ + /* Lookup table for the type check cache (TCC). It is a matrix + * having columns corresponding to class entries and rows + * corresponding to type names like "array|Traversable". Only + * type names for which using the cache is profitable are + * included. The number of columns is limited and configurable + * by means of the tcc_class_slots config variable. Class entries + * are assigned columns while loading them. Rows are added at + * run time whenever new type names are encountered. The total + * size of the matrix is limited to the maximum size controlled + * by the tcc_memory_limit config variable. + */ + uint8_t **type_check_cache; + + uint8_t *tcc_dummy_row; /* TCC cache row for generating cache misses */ + int last_assigned_tcc_column; /* Last TCC column assigned to any CE */ + HashTable *type_names_table; /* List of type names corresponding to rows in type_check_cache */ + HashTable filenames_table; HashTable *auto_globals; @@ -168,6 +185,9 @@ struct _zend_executor_globals { int ticks_count; + zend_long tcc_memory_limit; /* Memory limit for TCC matrix in bytes */ + zend_long tcc_num_class_slots; /* Number of columns in TCC matrix */ + uint32_t persistent_constants_count; uint32_t persistent_functions_count; uint32_t persistent_classes_count; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 668a1ef9d335c..bd4791cf73a82 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -803,6 +803,11 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info); + if (cache_slot) { + // Jump to slot containing TCC row + cache_slot += 3; + } + if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { variable_ptr = OBJ_PROP(zobj, property_offset); if (Z_TYPE_P(variable_ptr) != IS_UNDEF) { @@ -810,7 +815,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (UNEXPECTED(prop_info)) { ZVAL_COPY_VALUE(&tmp, value); - if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, EG(current_execute_data) && ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data))))) { + if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, EG(current_execute_data) && ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)), cache_slot))) { Z_TRY_DELREF_P(value); variable_ptr = &EG(error_zval); goto exit; @@ -875,7 +880,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (UNEXPECTED(prop_info)) { ZVAL_COPY_VALUE(&tmp, value); - if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data))))) { + if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)), cache_slot))) { zval_ptr_dtor(value); goto exit; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index b7423de45b00a..fa6fa9db9a96c 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -26,6 +26,7 @@ #include "zend_extensions.h" #include "zend_API.h" #include "zend_sort.h" +#include "zend_type_check_cache.h" #include "zend_vm.h" @@ -1039,6 +1040,9 @@ ZEND_API int pass_two(zend_op_array *op_array) opline++; } + // TODO: Should we re-assign CE columns in opcache after loading them from cache? + tcc_assign_ce_columns(); + zend_calc_live_ranges(op_array, NULL); return 0; diff --git a/Zend/zend_type_check_cache.c b/Zend/zend_type_check_cache.c new file mode 100644 index 0000000000000..20ad44514ec80 --- /dev/null +++ b/Zend/zend_type_check_cache.c @@ -0,0 +1,178 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Dik Takken | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_globals_macros.h" +#include "zend_compile.h" + +/** + * Configures the type check cache to use no more than the + * specified amount of memory. + */ +ZEND_API int zend_set_tcc_memory_limit(zend_long memory_limit) +{ + EG(tcc_memory_limit) = memory_limit; + return SUCCESS; +} + +/** + * Configures the length of the rows in the type check cache, + * determining how many class entries it can store. + * + * Note: Must not be changed at run time. + */ +ZEND_API int zend_set_tcc_class_slots(zend_long num_classes) +{ + EG(tcc_num_class_slots) = num_classes; + return SUCCESS; +} + +/** + * Clears the type check cache + */ +ZEND_API void tcc_clear() +{ + for (int i=0; i EG(tcc_memory_limit)) { + // Adding the type name would exceed memory limits. + // We return the dummy cache row which yields a cache + // miss for any CE. + if (!CG(tcc_dummy_row)) { + CG(tcc_dummy_row) = calloc(EG(tcc_num_class_slots), sizeof(uint8_t)); + } + zend_string_release(type_name); + return CG(tcc_dummy_row); + } + + if (CG(type_names_table)->nNumOfElements > 0) { + index++; + } + + type_name_copy = zend_string_dup(type_name, 0); + ZVAL_STR(&type_name_z, type_name_copy); + zend_hash_index_add(CG(type_names_table), index, &type_name_z); + zend_string_release(type_name_copy); + + // Now that we have a new type name, we must also add + // a new row for it in the cache matrix. + CG(type_check_cache) = realloc(CG(type_check_cache), zend_array_count(CG(type_names_table)) * sizeof(uint8_t *)); + CG(type_check_cache)[index] = calloc(EG(tcc_num_class_slots), sizeof(uint8_t)); + } + + zend_string_release(type_name); + + return CG(type_check_cache)[index]; +} + +/** + * Assigns column indices to class entries. Classes that have + * been assigned an index before are skipped. + */ +ZEND_API void tcc_assign_ce_columns() +{ + // Below we assign type check cache column indices to + // the classes in the class table. Note that column index + // zero is special: It is assigned to classes which are + // not covered by the cache. This column contains zeros + // indicating a cache miss. It is special because it is + // not written in case of a cache miss and because it may + // be shared by multiple classes. + // Note that we traverse the class table in reverse order + // for efficiency. + zend_class_entry *ce; + ZEND_HASH_REVERSE_FOREACH_PTR(CG(class_table), ce) { + if (ce->tcc_column_index > 0) { + // We arrived at the classes that were + // assigned an index previously, we are done. + break; + } + + // Below we skip interfaces, traits and abstract classes. + // We do not need to assign them a cache column because no + // object can ever be an instance of any of them. + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + ce->tcc_column_index = 0; + continue; + } + if (ce->ce_flags & ZEND_ACC_TRAIT) { + ce->tcc_column_index = 0; + continue; + } + if (ce->ce_flags & ZEND_ACC_ABSTRACT) { + ce->tcc_column_index = 0; + continue; + } + if (ce->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) { + // TODO: This case is never hit in the tests. Remove? + ce->tcc_column_index = 0; + continue; + } + + CG(last_assigned_tcc_column)++; + ce->tcc_column_index = CG(last_assigned_tcc_column); + + // The cache has a fixed number of columns. In case + // there are more classes than there are columns we + // simply exclude them. This means that type + // checks involving objects of these classes will + // always result in a cache miss. + if (CG(last_assigned_tcc_column) > EG(tcc_num_class_slots)) { + CG(last_assigned_tcc_column) = EG(tcc_num_class_slots); + ce->tcc_column_index = 0; + } + } ZEND_HASH_FOREACH_END(); + + tcc_clear(); +} diff --git a/Zend/zend_type_check_cache.h b/Zend/zend_type_check_cache.h new file mode 100644 index 0000000000000..d158db713cd37 --- /dev/null +++ b/Zend/zend_type_check_cache.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Dik Takken | + +----------------------------------------------------------------------+ +*/ + +ZEND_API int zend_set_tcc_memory_limit(zend_long memory_limit); +ZEND_API int zend_set_tcc_class_slots(zend_long num_classes); +ZEND_API uint8_t *tcc_get_row (zend_type *type); +ZEND_API void tcc_assign_ce_columns(); + +// Below macro encapsulates how type checks that are not covered +// by the type check cache are identified: The row is the dummy +// cache row or their column index is zero. This row / column +// contains only zeros (no match) and must not be written into +// in case of a cache miss. This macro is used to achieve that. +#define TCC_CONTAINS(row, ce) (ce->tcc_column_index > 0 && row != CG(tcc_dummy_row)) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 1fbd1030a40ce..62e806bd4e6ca 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2374,7 +2374,7 @@ ZEND_VM_C_LABEL(assign_object): orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (OP_DATA_TYPE == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -2493,7 +2493,7 @@ ZEND_VM_HANDLER(25, ZEND_ASSIGN_STATIC_PROP, ANY, ANY, CACHE_SLOT, SPEC(OP_DATA= value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R); if (UNEXPECTED(ZEND_TYPE_IS_SET(prop_info->type))) { - value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC, CACHE_ADDR(opline->extended_value) + 3); FREE_OP_DATA(); } else { value = zend_assign_to_variable(prop, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES()); @@ -4139,7 +4139,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } zend_reference *ref = NULL; - void *cache_slot = CACHE_ADDR(opline->op2.num); + void **cache_slot = CACHE_ADDR(opline->op2.num); if (UNEXPECTED(retval_ref != retval_ptr)) { if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { ref = Z_REF_P(retval_ref); @@ -4157,7 +4157,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV SAVE_OPLINE(); if (UNEXPECTED(!zend_check_type_slow(ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { - zend_verify_return_error(EX(func), cache_slot, retval_ptr); + zend_verify_return_error(EX(func), cache_slot + 1, retval_ptr); HANDLE_EXCEPTION(); } ZEND_VM_NEXT_OPCODE(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a30b8ed469f83..1982766c2633a 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -952,7 +952,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = RT_CONSTANT((opline+1), (opline+1)->op1); if (UNEXPECTED(ZEND_TYPE_IS_SET(prop_info->type))) { - value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC, CACHE_ADDR(opline->extended_value) + 3); } else { value = zend_assign_to_variable(prop, value, IS_CONST, EX_USES_STRICT_TYPES()); @@ -983,7 +983,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); if (UNEXPECTED(ZEND_TYPE_IS_SET(prop_info->type))) { - value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC, CACHE_ADDR(opline->extended_value) + 3); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable(prop, value, IS_TMP_VAR, EX_USES_STRICT_TYPES()); @@ -1014,7 +1014,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = _get_zval_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); if (UNEXPECTED(ZEND_TYPE_IS_SET(prop_info->type))) { - value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC, CACHE_ADDR(opline->extended_value) + 3); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable(prop, value, IS_VAR, EX_USES_STRICT_TYPES()); @@ -1045,7 +1045,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); if (UNEXPECTED(ZEND_TYPE_IS_SET(prop_info->type))) { - value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value EXECUTE_DATA_CC, CACHE_ADDR(opline->extended_value) + 3); } else { value = zend_assign_to_variable(prop, value, IS_CV, EX_USES_STRICT_TYPES()); @@ -8792,7 +8792,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } zend_reference *ref = NULL; - void *cache_slot = CACHE_ADDR(opline->op2.num); + void **cache_slot = CACHE_ADDR(opline->op2.num); if (UNEXPECTED(retval_ref != retval_ptr)) { if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { ref = Z_REF_P(retval_ref); @@ -8810,7 +8810,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP SAVE_OPLINE(); if (UNEXPECTED(!zend_check_type_slow(ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { - zend_verify_return_error(EX(func), cache_slot, retval_ptr); + zend_verify_return_error(EX(func), cache_slot + 1, retval_ptr); HANDLE_EXCEPTION(); } ZEND_VM_NEXT_OPCODE(); @@ -18741,7 +18741,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } zend_reference *ref = NULL; - void *cache_slot = CACHE_ADDR(opline->op2.num); + void **cache_slot = CACHE_ADDR(opline->op2.num); if (UNEXPECTED(retval_ref != retval_ptr)) { if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { ref = Z_REF_P(retval_ref); @@ -18759,7 +18759,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN SAVE_OPLINE(); if (UNEXPECTED(!zend_check_type_slow(ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { - zend_verify_return_error(EX(func), cache_slot, retval_ptr); + zend_verify_return_error(EX(func), cache_slot + 1, retval_ptr); HANDLE_EXCEPTION(); } ZEND_VM_NEXT_OPCODE(); @@ -21454,7 +21454,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -21599,7 +21599,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -21744,7 +21744,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -21889,7 +21889,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -23724,7 +23724,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -23869,7 +23869,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -24014,7 +24014,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -24159,7 +24159,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -26227,7 +26227,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } zend_reference *ref = NULL; - void *cache_slot = CACHE_ADDR(opline->op2.num); + void **cache_slot = CACHE_ADDR(opline->op2.num); if (UNEXPECTED(retval_ref != retval_ptr)) { if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { ref = Z_REF_P(retval_ref); @@ -26245,7 +26245,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN SAVE_OPLINE(); if (UNEXPECTED(!zend_check_type_slow(ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { - zend_verify_return_error(EX(func), cache_slot, retval_ptr); + zend_verify_return_error(EX(func), cache_slot + 1, retval_ptr); HANDLE_EXCEPTION(); } ZEND_VM_NEXT_OPCODE(); @@ -27175,7 +27175,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -27320,7 +27320,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -27465,7 +27465,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -27610,7 +27610,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -29654,7 +29654,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -29799,7 +29799,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -29944,7 +29944,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -30089,7 +30089,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -31521,7 +31521,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -31666,7 +31666,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -31811,7 +31811,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -31956,7 +31956,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -32932,7 +32932,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } zend_reference *ref = NULL; - void *cache_slot = CACHE_ADDR(opline->op2.num); + void **cache_slot = CACHE_ADDR(opline->op2.num); if (UNEXPECTED(retval_ref != retval_ptr)) { if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { ref = Z_REF_P(retval_ref); @@ -32950,7 +32950,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED SAVE_OPLINE(); if (UNEXPECTED(!zend_check_type_slow(ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { - zend_verify_return_error(EX(func), cache_slot, retval_ptr); + zend_verify_return_error(EX(func), cache_slot + 1, retval_ptr); HANDLE_EXCEPTION(); } ZEND_VM_NEXT_OPCODE(); @@ -33916,7 +33916,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -34061,7 +34061,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -34206,7 +34206,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -34351,7 +34351,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -38165,7 +38165,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -38310,7 +38310,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -38455,7 +38455,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -38600,7 +38600,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -41643,7 +41643,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -41788,7 +41788,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -41933,7 +41933,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -42078,7 +42078,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -44470,7 +44470,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } zend_reference *ref = NULL; - void *cache_slot = CACHE_ADDR(opline->op2.num); + void **cache_slot = CACHE_ADDR(opline->op2.num); if (UNEXPECTED(retval_ref != retval_ptr)) { if (UNEXPECTED(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { ref = Z_REF_P(retval_ref); @@ -44488,7 +44488,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU SAVE_OPLINE(); if (UNEXPECTED(!zend_check_type_slow(ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { - zend_verify_return_error(EX(func), cache_slot, retval_ptr); + zend_verify_return_error(EX(func), cache_slot + 1, retval_ptr); HANDLE_EXCEPTION(); } ZEND_VM_NEXT_OPCODE(); @@ -46451,7 +46451,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CONST == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -46596,7 +46596,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_TMP_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -46741,7 +46741,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_VAR == IS_CONST && Z_TYPE_P(value) == orig_type) { @@ -46886,7 +46886,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ orig_type = Z_TYPE_P(value); } - value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC, cache_slot + 3); /* will remain valid, thus no need to check prop_info in future here */ if (IS_CV == IS_CONST && Z_TYPE_P(value) == orig_type) { diff --git a/configure.ac b/configure.ac index 4d6106581f089..7e1ea09118902 100644 --- a/configure.ac +++ b/configure.ac @@ -1444,7 +1444,7 @@ PHP_ADD_SOURCES(Zend, \ zend_execute_API.c zend_highlight.c zend_llist.c \ zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c zend_stack.c \ zend_variables.c zend.c zend_API.c zend_extensions.c zend_hash.c \ - zend_list.c zend_builtin_functions.c \ + zend_list.c zend_builtin_functions.c zend_type_check_cache.c \ zend_ini.c zend_sort.c zend_multibyte.c zend_ts_hash.c zend_stream.c \ zend_iterators.c zend_interfaces.c zend_exceptions.c zend_strtod.c zend_gc.c \ zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \ diff --git a/ext/opcache/Optimizer/compact_literals.c b/ext/opcache/Optimizer/compact_literals.c index 4aabe04c6e120..eab979351161e 100644 --- a/ext/opcache/Optimizer/compact_literals.c +++ b/ext/opcache/Optimizer/compact_literals.c @@ -513,19 +513,15 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx case ZEND_RECV_VARIADIC: { size_t num_classes = type_num_classes(op_array, opline->op1.num); - if (num_classes) { - opline->extended_value = cache_size; - cache_size += num_classes * sizeof(void *); - } + opline->extended_value = cache_size; + cache_size += (1 + num_classes) * sizeof(void *); break; } case ZEND_VERIFY_RETURN_TYPE: { size_t num_classes = type_num_classes(op_array, 0); - if (num_classes) { - opline->op2.num = cache_size; - cache_size += num_classes * sizeof(void *); - } + opline->op2.num = cache_size; + cache_size += (1 + num_classes) * sizeof(void *); break; } case ZEND_ASSIGN_STATIC_PROP_OP: @@ -588,6 +584,10 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx } else { opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS); cache_size += 3 * sizeof(void *); + if (opline->opcode == ZEND_ASSIGN_OBJ || opline->opcode == ZEND_ASSIGN_OBJ_REF) { + // Property writes use an additional slot for type checking using TCC + cache_size += sizeof(void *); + } if (opline->op1_type == IS_UNUSED) { property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS; } @@ -715,6 +715,10 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx opline->op1.constant, LITERAL_STATIC_PROPERTY, &cache_size) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS); + if (opline->opcode == ZEND_ASSIGN_STATIC_PROP || opline->opcode == ZEND_ASSIGN_STATIC_PROP_REF) { + // Property writes use an additional slot for type checking using TCC + cache_size += sizeof(void *); + } } else { opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS); cache_size += 3 * sizeof(void *); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a3cd824fddaac..cec7d2b72de9a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3943,7 +3943,7 @@ ZEND_METHOD(reflection_class, setStaticPropertyValue) } } - if (ZEND_TYPE_IS_SET(prop_info->type) && !zend_verify_property_type(prop_info, value, 0)) { + if (ZEND_TYPE_IS_SET(prop_info->type) && !zend_verify_property_type(prop_info, value, 0, NULL)) { return; } diff --git a/main/main.c b/main/main.c index 8e41ec9f49797..dd6de53fef4b6 100644 --- a/main/main.c +++ b/main/main.c @@ -303,6 +303,37 @@ static PHP_INI_MH(OnChangeMemoryLimit) } /* }}} */ +/* {{{ PHP_INI_MH + */ +static PHP_INI_MH(OnChangeTccMemoryLimit) +{ + zend_long tcc_memory_limit; + if (new_value) { + tcc_memory_limit = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); + } else { + // TODO: Can new_value ever be NULL? + tcc_memory_limit = 1024 * 1024; + } + return zend_set_tcc_memory_limit(tcc_memory_limit); +} +/* }}} */ + +/* {{{ PHP_INI_MH + */ +static PHP_INI_MH(OnChangeTccClassSlots) +{ + zend_long tcc_class_slots; + if (new_value) { + tcc_class_slots = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); + } else { + // TODO: Can new_value ever be NULL? + tcc_class_slots = 1024; + } + + return zend_set_tcc_class_slots(tcc_class_slots); +} +/* }}} */ + /* {{{ PHP_INI_MH */ static PHP_INI_MH(OnSetLogFilter) @@ -792,6 +823,8 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("mail.log", NULL, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateMailLog, mail_log, php_core_globals, core_globals) PHP_INI_ENTRY("browscap", NULL, PHP_INI_SYSTEM, OnChangeBrowscap) PHP_INI_ENTRY("memory_limit", "128M", PHP_INI_ALL, OnChangeMemoryLimit) + PHP_INI_ENTRY("tcc_memory_limit", "1M", PHP_INI_SYSTEM, OnChangeTccMemoryLimit) + PHP_INI_ENTRY("tcc_class_slots", "1K", PHP_INI_SYSTEM, OnChangeTccClassSlots) PHP_INI_ENTRY("precision", "14", PHP_INI_ALL, OnSetPrecision) PHP_INI_ENTRY("sendmail_from", NULL, PHP_INI_ALL, NULL) PHP_INI_ENTRY("sendmail_path", DEFAULT_SENDMAIL_PATH, PHP_INI_SYSTEM, NULL) From 31930acebf7ad088c1b198ab05324fb08d7ec84b Mon Sep 17 00:00:00 2001 From: Dik Takken Date: Thu, 16 Jan 2020 22:03:30 +0100 Subject: [PATCH 2/2] Extend JIT compiler to use type check cache --- ext/opcache/jit/zend_jit_helpers.c | 43 ++++++++++++++++++ ext/opcache/jit/zend_jit_x86.dasc | 32 +++++++++++++- ext/opcache/tests/jit/tcc_cache_reads.phpt | 44 +++++++++++++++++++ .../tests/jit/tcc_zero_cache_row_size.phpt | 36 +++++++++++++++ .../tests/jit/tcc_zero_cache_size.phpt | 36 +++++++++++++++ 5 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 ext/opcache/tests/jit/tcc_cache_reads.phpt create mode 100644 ext/opcache/tests/jit/tcc_zero_cache_row_size.phpt create mode 100644 ext/opcache/tests/jit/tcc_zero_cache_size.phpt diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 38294d5c27d1d..d5a3977417d8f 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1144,9 +1144,33 @@ static zval* ZEND_FASTCALL zend_jit_fetch_global_helper(zend_execute_data *execu static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot) { uint32_t type_mask; + uint8_t *tcc = NULL; if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { zend_class_entry *ce; + uint8_t *tcc_cache_row = NULL; + tcc_cache_row = *cache_slot; + if (UNEXPECTED(!tcc_cache_row)) { + // Pointer to the row in the type check cache is not + // in the run time cache yet. Fetch it now. + tcc_cache_row = tcc_get_row(&arg_info->type); + *cache_slot = tcc_cache_row; + } + + // Compute address of type check cache entry + tcc = tcc_cache_row + Z_OBJCE_P(arg)->tcc_column_index; + + if (EXPECTED(*tcc)) { + // We did this type check before and it + // was a match. We are done. + return; + } + + ZEND_ASSERT(*tcc == 0); + + // Jump to the cache slots that contain the CE. + cache_slot++; + if (ZEND_TYPE_HAS_LIST(arg_info->type)) { zend_type *list_type; ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { @@ -1162,6 +1186,10 @@ static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_arra *cache_slot = ce; } if (instanceof_function(Z_OBJCE_P(arg), ce)) { + if (TCC_CONTAINS(*cache_slot, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return; } cache_slot++; @@ -1177,17 +1205,32 @@ static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_arra *cache_slot = (void *) ce; } if (instanceof_function(Z_OBJCE_P(arg), ce)) { + if (TCC_CONTAINS(*cache_slot, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return; } } + } else if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + // Jump to the cache slots that contain the CE. + cache_slot++; } builtin_types: type_mask = ZEND_TYPE_FULL_MASK(arg_info->type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + if (tcc && TCC_CONTAINS(*cache_slot, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return; } if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { + if (tcc && TCC_CONTAINS(*cache_slot, Z_OBJCE_P(arg))) { + // Store type check result in cache + *tcc = 1; + } return; } if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) { diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index f28c39f8a40ce..1ecdd75e0c66d 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -8643,18 +8643,46 @@ static int zend_jit_recv(dasm_State **Dst, const zend_op *opline, const zend_op_ if (is_power_of_two(type_mask)) { uint32_t type_code = concrete_type(type_mask); | cmp byte [r0 + 8], type_code - | jne >8 + | jne >7 } else { | mov edx, 1 | mov cl, byte [r0 + 8] | shl edx, cl | test edx, type_mask - | je >8 + | je >7 + } + | jmp >1 + + |7: + + if (ZEND_TYPE_HAS_CLASS(type)) { + uint8_t *tcc_row = tcc_get_row(&type); + | // Make sure that argument is an object. + | IF_NOT_Z_TYPE r0, IS_OBJECT, >8 + | // Find the class entry of the value. + | mov r0, [r0 + offsetof(zval, value.obj)] + | mov r0, [r0 + offsetof(zend_object, ce)] + | // Load the TCC column index. This is a 32-bit int value + | // which we load into the a 32-bit register. + | mov eax, [r0 + offsetof(zend_class_entry, tcc_column_index)] + | // Load pointer to TCC row from run_time_cache + | LOAD_ADDR r2, tcc_row + | // When cache entry equals one, we have a hit and we're done. + | // Else, we don't know and we will continue to do a full check. + | cmp byte [r2 + eax], 1 + | je >1 } + | jmp >8 // Jump into cold_code segment + |.cold_code |8: | SAVE_VALID_OPLINE opline + | LOAD_ZVAL_ADDR r0, res_addr + if (ZEND_ARG_SEND_MODE(arg_info)) { + | GET_Z_PTR r0, r0 + | add r0, offsetof(zend_reference, val) + } | mov FCARG1a, r0 | mov r0, EX->run_time_cache | add r0, opline->extended_value diff --git a/ext/opcache/tests/jit/tcc_cache_reads.phpt b/ext/opcache/tests/jit/tcc_cache_reads.phpt new file mode 100644 index 0000000000000..aae72c19bfde9 --- /dev/null +++ b/ext/opcache/tests/jit/tcc_cache_reads.phpt @@ -0,0 +1,44 @@ +--TEST-- +Test if type checks work when TCC is warmed up +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit_buffer_size=1M +opcache.jit=1205 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Passing valid object: +Passing invalid object: +TypeError +Passing valid object: +Passing invalid object: +TypeError +Done diff --git a/ext/opcache/tests/jit/tcc_zero_cache_row_size.phpt b/ext/opcache/tests/jit/tcc_zero_cache_row_size.phpt new file mode 100644 index 0000000000000..b8e2cc37bf5e2 --- /dev/null +++ b/ext/opcache/tests/jit/tcc_zero_cache_row_size.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test if type checks work when TCC row size is zero +--INI-- +tcc_class_slots=0 +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit_buffer_size=1M +opcache.jit=1205 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Passing valid object: +Passing invalid object: +TypeError +Done diff --git a/ext/opcache/tests/jit/tcc_zero_cache_size.phpt b/ext/opcache/tests/jit/tcc_zero_cache_size.phpt new file mode 100644 index 0000000000000..13c44a8cabb5a --- /dev/null +++ b/ext/opcache/tests/jit/tcc_zero_cache_size.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test if type checks work when TCC has no available memory +--INI-- +tcc_memory_limit=0 +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit_buffer_size=1M +opcache.jit=1205 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Passing valid object: +Passing invalid object: +TypeError +Done