Skip to content
Draft
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
15 changes: 15 additions & 0 deletions Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,21 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
opline->extended_value = cache_size | (opline->extended_value & ZEND_PARTIAL_FLAGS);
cache_size += 2 * sizeof(void *);
break;
case ZEND_RECV:
/* class-type inline cache */
if (opline->op2.num & _ZEND_TYPE_KIND_MASK) {
opline->extended_value = cache_size;
cache_size += sizeof(void *);
}
break;
case ZEND_VERIFY_RETURN_TYPE:
/* class-type inline cache */
if ((op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)
&& ZEND_TYPE_IS_COMPLEX((op_array->arg_info - 1)->type)) {
opline->extended_value = cache_size;
cache_size += sizeof(void *);
}
break;
}
opline++;
}
Expand Down
8 changes: 8 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2720,6 +2720,10 @@ static void zend_emit_return_type_check(
}

opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL);
if (ZEND_TYPE_IS_COMPLEX(type)) {
/* Cache slot for the class-type inline cache. */
opline->extended_value = zend_alloc_cache_slot();
}
if (expr && expr->op_type == IS_CONST) {
opline->result_type = expr->op_type = IS_TMP_VAR;
opline->result.var = expr->u.op.var = get_temporary_variable();
Expand Down Expand Up @@ -8453,6 +8457,10 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
if (opcode == ZEND_RECV) {
opline->op2.num = type_ast ?
ZEND_TYPE_FULL_MASK(arg_info->type) : MAY_BE_ANY;
if (opline->op2.num & _ZEND_TYPE_KIND_MASK) {
/* Cache slot for the class-type inline cache. */
opline->extended_value = zend_alloc_cache_slot();
}
}

if (is_promoted) {
Expand Down
7 changes: 7 additions & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,13 @@ static zend_always_inline bool zend_check_type(
return zend_check_type_slow(type, arg, ref, current_frame, is_internal);
}

/* If a type check if fully determined by the CE of the object, rather than the scope, then we may cache it. */
static zend_always_inline bool zend_type_may_cache_ce(const zend_type *type)
{
return ZEND_TYPE_IS_COMPLEX(*type)
&& !(ZEND_TYPE_FULL_MASK(*type) & (MAY_BE_CALLABLE|MAY_BE_STATIC));
}

/* We can not expose zend_check_type() directly because it's inline and uses static functions */
ZEND_API bool zend_check_type_ex(
const zend_type *type, zval *arg, bool current_frame, bool is_internal)
Expand Down
34 changes: 32 additions & 2 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -4465,7 +4465,7 @@ ZEND_VM_C_LABEL(fcall_end):
ZEND_VM_CONTINUE();
}

ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED)
ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED, CACHE_SLOT)
{
if (OP1_TYPE == IS_UNUSED) {
SAVE_OPLINE();
Expand Down Expand Up @@ -4495,6 +4495,13 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
ZEND_VM_NEXT_OPCODE();
}

/* Monomorphic inline cache for class types when type declaration contains an object, see ZEND_RECV. */
if (EXPECTED(Z_TYPE_P(retval_ref) == IS_OBJECT)
&& EXPECTED(ZEND_TYPE_IS_COMPLEX(ret_info->type))
&& EXPECTED(CACHED_PTR(opline->extended_value) == (void*)Z_OBJCE_P(retval_ref))) {
ZEND_VM_NEXT_OPCODE();
}

if (OP1_TYPE == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) {
SAVE_OPLINE();
retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1();
Expand Down Expand Up @@ -4530,6 +4537,13 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
zend_verify_return_error(EX(func), retval_ptr);
HANDLE_EXCEPTION();
}
/* SAFETY: See the ZEND_RECV helper for why this is safe and why it fills once. */
if (ZEND_TYPE_IS_COMPLEX(ret_info->type)
&& CACHED_PTR(opline->extended_value) == NULL
&& Z_TYPE_P(retval_ptr) == IS_OBJECT
&& zend_type_may_cache_ce(&ret_info->type)) {
CACHE_PTR(opline->extended_value, (void*)Z_OBJCE_P(retval_ptr));
}
ZEND_VM_NEXT_OPCODE();
#endif
}
Expand Down Expand Up @@ -5723,6 +5737,15 @@ ZEND_VM_HELPER(zend_verify_recv_arg_type_helper, ANY, ANY, zval *op_1)
HANDLE_EXCEPTION();
}

/* Remember the class that satisfied the check, so that the next argument of the same class can take the fast path above.
* Rewriting polymorphic slots is expensive, so fill only once. */
if ((opline->op2.num & _ZEND_TYPE_KIND_MASK)
&& CACHED_PTR(opline->extended_value) == NULL
&& Z_TYPE_P(op_1) == IS_OBJECT
&& zend_type_may_cache_ce(&EX(func)->common.arg_info[opline->op1.num - 1].type)) {
CACHE_PTR(opline->extended_value, (void*)Z_OBJCE_P(op_1));
}

ZEND_VM_NEXT_OPCODE();
}

Expand Down Expand Up @@ -5750,7 +5773,7 @@ ZEND_VM_HANDLER(213, ZEND_SEND_PLACEHOLDER, UNUSED, CONST|UNUSED|NUM)
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HOT_HANDLER(63, ZEND_RECV, NUM, UNUSED)
ZEND_VM_HOT_HANDLER(63, ZEND_RECV, NUM, UNUSED, CACHE_SLOT)
{
USE_OPLINE
uint32_t arg_num = opline->op1.num;
Expand All @@ -5763,6 +5786,13 @@ ZEND_VM_HOT_HANDLER(63, ZEND_RECV, NUM, UNUSED)
param = EX_VAR(opline->result.var);

if (UNEXPECTED(!(opline->op2.num & (1u << Z_TYPE_P(param))))) {
/* Monomorphic cache for CEs.
* The cache slot only exists when the type has a class part. */
if (EXPECTED(Z_TYPE_P(param) == IS_OBJECT)
&& EXPECTED(opline->op2.num & _ZEND_TYPE_KIND_MASK)
&& EXPECTED(CACHED_PTR(opline->extended_value) == (void*)Z_OBJCE_P(param))) {
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_DISPATCH_TO_HELPER(zend_verify_recv_arg_type_helper, op_1, param);
}

Expand Down
Loading
Loading