Skip to content

Introduce a type check cache (TCC) #5096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Zend/tests/type_check_cache/cache_reads.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
--TEST--
Test if type checks work when TCC is warmed up
--FILE--
<?php

class A {}
class B {
public A $prop;
}

function test1(A $arg) {
return $arg;
}

function test2($arg): A {
return $arg;
}

foreach ([1,2] as $iteration) {
echo "Passing valid object:\n";
test1(new A);
echo "Passing invalid object:\n";
try {
test1(new B);
} catch (TypeError $e) {
echo "TypeError\n";
}

echo "Returning valid object:\n";
test2(new A);
echo "Returning invalid object:\n";
try {
test2(new B);
} catch (TypeError $e) {
echo "TypeError\n";
}

echo "Writing valid property value\n";
$b = new B;
$b->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
30 changes: 30 additions & 0 deletions Zend/tests/type_check_cache/zero_cache_row_size.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
Test if type checks work when TCC row size is zero
--INI--
tcc_class_slots=0
--FILE--
<?php

class A {}
class B {}

function test(A $arg) {
is_object($arg);
}

echo "Passing valid object:\n";
test(new A);
echo "Passing invalid object:\n";
try {
test(new B);
} catch (TypeError $e) {
echo "TypeError\n";
}

echo "Done\n";
?>
--EXPECTF--
Passing valid object:
Passing invalid object:
TypeError
Done
29 changes: 29 additions & 0 deletions Zend/tests/type_check_cache/zero_cache_size.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
Test if type checks work when TCC has no available memory
--INI--
tcc_memory_limit=0
--FILE--
<?php

class A {}
class B {}

function test(A $arg) {
is_object($arg);
}

echo "Passing valid object:\n";
test(new A);
echo "Passing invalid object:\n";
try {
test(new B);
} catch (TypeError $e) {
echo "TypeError\n";
}
echo "Done\n";
?>
--EXPECTF--
Passing valid object:
Passing invalid object:
TypeError
Done
45 changes: 45 additions & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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; i<zend_array_count(compiler_globals->type_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);
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -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;

Expand All @@ -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));
}
Expand Down Expand Up @@ -1069,8 +1101,13 @@ void zend_shutdown(void) /* {{{ */
virtual_cwd_deactivate();
virtual_cwd_shutdown();

for (int i=0; i<zend_array_count(GLOBAL_TYPE_NAMES_TABLE); i++) {
free(GLOBAL_TYPE_CHECK_CACHE[i]);
}

zend_hash_destroy(GLOBAL_FUNCTION_TABLE);
zend_hash_destroy(GLOBAL_CLASS_TABLE);
zend_hash_destroy(GLOBAL_TYPE_NAMES_TABLE);

zend_hash_destroy(GLOBAL_AUTO_GLOBALS_TABLE);
free(GLOBAL_AUTO_GLOBALS_TABLE);
Expand All @@ -1080,6 +1117,11 @@ void zend_shutdown(void) /* {{{ */

free(GLOBAL_FUNCTION_TABLE);
free(GLOBAL_CLASS_TABLE);
free(GLOBAL_TYPE_NAMES_TABLE);
free(GLOBAL_TYPE_CHECK_CACHE);
if (GLOBAL_TCC_DUMMY_ROW) {
free(GLOBAL_TCC_DUMMY_ROW);
}

zend_hash_destroy(GLOBAL_CONSTANTS_TABLE);
free(GLOBAL_CONSTANTS_TABLE);
Expand All @@ -1088,6 +1130,9 @@ void zend_shutdown(void) /* {{{ */
#ifdef ZTS
GLOBAL_FUNCTION_TABLE = NULL;
GLOBAL_CLASS_TABLE = NULL;
GLOBAL_TYPE_NAMES_TABLE = NULL;
GLOBAL_TYPE_CHECK_CACHE = NULL;
GLOBAL_TCC_DUMMY_ROW = NULL;
GLOBAL_AUTO_GLOBALS_TABLE = NULL;
GLOBAL_CONSTANTS_TABLE = NULL;
ts_free_id(executor_globals_id);
Expand Down
1 change: 1 addition & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ struct _zend_class_entry {
zend_string *parent_name;
};
int refcount;
int tcc_column_index; /* Assigned column in type check cache */
uint32_t ce_flags;

int default_properties_count;
Expand Down
6 changes: 3 additions & 3 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,7 @@ ZEND_API int zend_update_class_constants(zend_class_entry *class_type) /* {{{ */
return FAILURE;
}
/* property initializers must always be evaluated with strict types */;
if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, /* strict */ 1))) {
if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, /* strict */ 1, NULL))) {
zval_ptr_dtor(&tmp);
return FAILURE;
}
Expand Down Expand Up @@ -1147,7 +1147,7 @@ ZEND_API void object_properties_init_ex(zend_object *object, HashTable *properti
zval tmp;

ZVAL_COPY_VALUE(&tmp, prop);
if (UNEXPECTED(!zend_verify_property_type(property_info, &tmp, 0))) {
if (UNEXPECTED(!zend_verify_property_type(property_info, &tmp, 0, NULL))) {
continue;
}
ZVAL_COPY_VALUE(slot, &tmp);
Expand Down Expand Up @@ -4032,7 +4032,7 @@ ZEND_API int zend_update_static_property_ex(zend_class_entry *scope, zend_string
Z_TRY_ADDREF_P(value);
if (ZEND_TYPE_IS_SET(prop_info->type)) {
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;
}
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
25 changes: 19 additions & 6 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
}
}
/* }}} */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading