Skip to content
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

Remove cache slot from ZEND_VERIFY_RETURN_TYPE and arg RECV opcodes #18258

Merged
merged 1 commit into from
Apr 7, 2025
Merged
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
2 changes: 2 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ PHP 8.5 INTERNALS UPGRADE NOTES
char* instead of zend_string*-based smart strings.
. Added php_build_provider() to retrieve the value of PHP_BUILD_PROVIDER at
runtime.
. Removed the cache_slot argument of zend_check_user_type_slow() because
now it only relies on the CE cache.

========================
2. Build system changes
Expand Down
64 changes: 0 additions & 64 deletions Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,50 +43,6 @@ typedef struct _literal_info {
info[n].num_related = (related); \
} while (0)

static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num)
{
zend_arg_info *arg_info;
if (arg_num > 0) {
if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) {
return 0;
}
if (EXPECTED(arg_num <= op_array->num_args)) {
arg_info = &op_array->arg_info[arg_num-1];
} else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
arg_info = &op_array->arg_info[op_array->num_args];
} else {
return 0;
}
} else {
arg_info = op_array->arg_info - 1;
}

if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) {
if (ZEND_TYPE_HAS_LIST(arg_info->type)) {
/* Intersection types cannot have nested list types */
if (ZEND_TYPE_IS_INTERSECTION(arg_info->type)) {
return ZEND_TYPE_LIST(arg_info->type)->num_types;
}
ZEND_ASSERT(ZEND_TYPE_IS_UNION(arg_info->type));
size_t count = 0;
const zend_type *list_type;

ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) {
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
count += ZEND_TYPE_LIST(*list_type)->num_types;
} else {
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
count += 1;
}
} ZEND_TYPE_LIST_FOREACH_END();
return count;
}
return 1;
}

return 0;
}

static uint32_t add_static_slot(HashTable *hash,
zend_op_array *op_array,
uint32_t op1,
Expand Down Expand Up @@ -504,26 +460,6 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
opline->op2.constant = map[opline->op2.constant];
}
switch (opline->opcode) {
case ZEND_RECV_INIT:
case ZEND_RECV:
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 *);
}
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 *);
}
break;
}
case ZEND_ASSIGN_STATIC_PROP_OP:
if (opline->op1_type == IS_CONST) {
// op1 static property
Expand Down
35 changes: 0 additions & 35 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2620,33 +2620,6 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr) /* {{{ */
}
/* }}} */

/* Remember to update type_num_classes() in compact_literals.c when changing this function */
static size_t zend_type_get_num_classes(const zend_type type) {
if (!ZEND_TYPE_IS_COMPLEX(type)) {
return 0;
}
if (ZEND_TYPE_HAS_LIST(type)) {
/* Intersection types cannot have nested list types */
if (ZEND_TYPE_IS_INTERSECTION(type)) {
return ZEND_TYPE_LIST(type)->num_types;
}
ZEND_ASSERT(ZEND_TYPE_IS_UNION(type));
size_t count = 0;
const zend_type *list_type;

ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
count += ZEND_TYPE_LIST(*list_type)->num_types;
} else {
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
count += 1;
}
} ZEND_TYPE_LIST_FOREACH_END();
return count;
}
return 1;
}

static void zend_emit_return_type_check(
znode *expr, zend_arg_info *return_info, bool implicit) /* {{{ */
{
Expand Down Expand Up @@ -2708,8 +2681,6 @@ static void zend_emit_return_type_check(
opline->result_type = expr->op_type = IS_TMP_VAR;
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));
}
}
/* }}} */
Expand Down Expand Up @@ -7754,12 +7725,6 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
SET_NODE(opline->result, &var_node);
opline->op1.num = i + 1;

if (type_ast) {
/* Allocate cache slot to speed-up run-time class resolution */
opline->extended_value =
zend_alloc_cache_slots(zend_type_get_num_classes(arg_info->type));
}

uint32_t arg_info_flags = _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic, /* is_tentative */ 0)
| (is_promoted ? _ZEND_IS_PROMOTED_BIT : 0);
ZEND_TYPE_FULL_MASK(arg_info->type) |= arg_info_flags;
Expand Down
80 changes: 26 additions & 54 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1099,23 +1099,9 @@ static zend_always_inline bool zend_value_instanceof_static(const zval *zv) {
return instanceof_function(Z_OBJCE_P(zv), called_scope);
}

/* The cache_slot may only be NULL in debug builds, where arginfo verification of
* internal functions is enabled. Avoid unnecessary checks in release builds. */
#if ZEND_DEBUG
# define HAVE_CACHE_SLOT (cache_slot != NULL)
#else
# define HAVE_CACHE_SLOT 1
#endif

#define PROGRESS_CACHE_SLOT() if (HAVE_CACHE_SLOT) {cache_slot++;}

static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot(
void **cache_slot, const zend_type *type)
static zend_always_inline zend_class_entry *zend_fetch_ce_from_type(
const zend_type *type)
{
if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) {
return (zend_class_entry *) *cache_slot;
}

zend_string *name = ZEND_TYPE_NAME(*type);
zend_class_entry *ce;
if (ZSTR_HAS_CE_CACHE(name)) {
Expand All @@ -1134,68 +1120,54 @@ static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot(
return NULL;
}
}
if (HAVE_CACHE_SLOT) {
*cache_slot = (void *) ce;
}
return ce;
}

static bool zend_check_intersection_type_from_cache_slot(
static bool zend_check_intersection_type_from_list(
const zend_type_list *intersection_type_list,
const zend_class_entry *arg_ce,
void ***cache_slot_ptr)
zend_class_entry *arg_ce)
{
void **cache_slot = *cache_slot_ptr;
zend_class_entry *ce;
const zend_type *list_type;
bool status = true;
ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) {
/* Only check classes if the type might be valid */
if (status) {
zend_class_entry *ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
/* If type is not an instance of one of the types taking part in the
* intersection it cannot be a valid instance of the whole intersection type. */
if (!ce || !instanceof_function(arg_ce, ce)) {
status = false;
}
ce = zend_fetch_ce_from_type(list_type);
/* If type is not an instance of one of the types taking part in the
* intersection it cannot be a valid instance of the whole intersection type. */
if (!ce || !instanceof_function(arg_ce, ce)) {
return false;
}
PROGRESS_CACHE_SLOT();
} ZEND_TYPE_LIST_FOREACH_END();
if (HAVE_CACHE_SLOT) {
*cache_slot_ptr = cache_slot;
}
return status;
return true;
}

static zend_always_inline bool zend_check_type_slow(
const zend_type *type, zval *arg, const zend_reference *ref, void **cache_slot,
const zend_type *type, zval *arg, const zend_reference *ref,
bool is_return_type, bool is_internal)
{
if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
zend_class_entry *ce;
if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) {
if (ZEND_TYPE_IS_INTERSECTION(*type)) {
return zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg), &cache_slot);
return zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg));
} else {
const zend_type *list_type;
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) {
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
if (zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg), &cache_slot)) {
if (zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg))) {
return true;
}
/* The cache_slot is progressed in zend_check_intersection_type_from_cache_slot() */
} else {
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
ce = zend_fetch_ce_from_type(list_type);
/* Instance of a single type part of a union is sufficient to pass the type check */
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
return true;
}
PROGRESS_CACHE_SLOT();
}
} ZEND_TYPE_LIST_FOREACH_END();
}
} else {
ce = zend_fetch_ce_from_cache_slot(cache_slot, type);
ce = zend_fetch_ce_from_type(type);
/* If we have a CE we check if it satisfies the type constraint,
* otherwise it will check if a standard type satisfies it. */
if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
Expand Down Expand Up @@ -1232,7 +1204,7 @@ static zend_always_inline bool zend_check_type_slow(
}

static zend_always_inline bool zend_check_type(
const zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope,
const zend_type *type, zval *arg, zend_class_entry *scope,
bool is_return_type, bool is_internal)
{
const zend_reference *ref = NULL;
Expand All @@ -1247,25 +1219,25 @@ static zend_always_inline bool zend_check_type(
return 1;
}

return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal);
return zend_check_type_slow(type, arg, ref, is_return_type, is_internal);
}

ZEND_API bool zend_check_user_type_slow(
const zend_type *type, zval *arg, const zend_reference *ref, void **cache_slot, bool is_return_type)
const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type)
{
return zend_check_type_slow(
type, arg, ref, cache_slot, is_return_type, /* is_internal */ false);
type, arg, ref, is_return_type, /* is_internal */ false);
}

static zend_always_inline bool zend_verify_recv_arg_type(const zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot)
static zend_always_inline bool zend_verify_recv_arg_type(const zend_function *zf, uint32_t arg_num, zval *arg)
{
const zend_arg_info *cur_arg_info;

ZEND_ASSERT(arg_num <= zf->common.num_args);
cur_arg_info = &zf->common.arg_info[arg_num-1];

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))) {
&& UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, zf->common.scope, 0, 0))) {
zend_verify_arg_error(zf, cur_arg_info, arg_num, arg);
return 0;
}
Expand All @@ -1274,10 +1246,10 @@ static zend_always_inline bool zend_verify_recv_arg_type(const zend_function *zf
}

static zend_always_inline bool zend_verify_variadic_arg_type(
const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *arg, void **cache_slot)
const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *arg)
{
ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type));
if (UNEXPECTED(!zend_check_type(&arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) {
if (UNEXPECTED(!zend_check_type(&arg_info->type, arg, zf->common.scope, 0, 0))) {
zend_verify_arg_error(zf, arg_info, arg_num, arg);
return 0;
}
Expand All @@ -1302,7 +1274,7 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ
}

if (ZEND_TYPE_IS_SET(cur_arg_info->type)
&& UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) {
&& UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, fbc->common.scope, 0, /* is_internal */ 1))) {
return 0;
}
arg++;
Expand Down Expand Up @@ -1508,7 +1480,7 @@ ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, zval *re
return 1;
}

if (UNEXPECTED(!zend_check_type(&ret_info->type, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) {
if (UNEXPECTED(!zend_check_type(&ret_info->type, ret, NULL, 1, /* is_internal */ 1))) {
zend_verify_internal_return_error(zf, ret);
return 0;
}
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ ZEND_API ZEND_COLD void zend_verify_never_error(
const zend_function *zf);
ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref);
ZEND_API bool zend_check_user_type_slow(
const zend_type *type, zval *arg, const zend_reference *ref, void **cache_slot, bool is_return_type);
const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type);

#if ZEND_DEBUG
ZEND_API bool zend_internal_call_should_throw(const zend_function *fbc, zend_execute_data *call);
Expand Down
Loading
Loading