From fea00b464be5b642788b8f895aa7b2a75ccf3cb2 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Mon, 24 Aug 2026 00:05:23 +0200 Subject: [PATCH] idea: use monomorphic inline cache slot to speed up object arg type check --- Zend/Optimizer/compact_literals.c | 15 +++ Zend/zend_compile.c | 8 ++ Zend/zend_execute.c | 7 ++ Zend/zend_vm_def.h | 34 +++++- Zend/zend_vm_execute.h | 172 ++++++++++++++++++++++++++++++ Zend/zend_vm_opcodes.c | 4 +- 6 files changed, 236 insertions(+), 4 deletions(-) diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index a73b46c0e332..3bb61e787937 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -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++; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 317114265c57..fc606b0aeda2 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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(); @@ -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) { diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 14a340ffee37..2e24bc4d7275 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -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) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 01131b5d3ae0..af6680a6c69c 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -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(); @@ -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(); @@ -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 } @@ -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(); } @@ -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; @@ -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); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5061d772ee82..8cf3248fe412 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2815,6 +2815,15 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV_ 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(); } @@ -4339,6 +4348,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_R 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_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_EX param)); } @@ -11245,6 +11261,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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 (IS_CONST == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -11280,6 +11303,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ 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 } @@ -21604,6 +21634,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -21639,6 +21676,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 } @@ -29652,6 +29696,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 (IS_VAR == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -29687,6 +29738,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 } @@ -37106,6 +37164,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 (IS_UNUSED == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -37141,6 +37206,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 } @@ -49342,6 +49414,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 (IS_CV == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -49377,6 +49456,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_VERIFY_RETURN 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 } @@ -57184,6 +57270,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_RECV_S 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_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_EX param)); } @@ -63988,6 +64081,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIF 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 (IS_CONST == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -64023,6 +64123,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIF 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 } @@ -74247,6 +74354,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -74282,6 +74396,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 } @@ -82295,6 +82416,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 (IS_VAR == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -82330,6 +82458,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 } @@ -89749,6 +89884,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 (IS_UNUSED == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -89784,6 +89926,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 } @@ -101883,6 +102032,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 (IS_CV == IS_CV && UNEXPECTED(Z_ISUNDEF_P(retval_ptr))) { SAVE_OPLINE(); retval_ref = retval_ptr = ZVAL_UNDEFINED_OP1(); @@ -101918,6 +102074,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_VERIFY_RETURN_TYPE 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 } @@ -106573,6 +106736,15 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV_EX z 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(); } diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index cc14862ebedd..7ba85c13f061 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -302,7 +302,7 @@ static uint32_t zend_vm_opcodes_flags[214] = { 0x00000000, 0x01040310, 0x00000003, - 0x00000110, + 0x00040110, 0x00000310, 0x00001307, 0x00001301, @@ -363,7 +363,7 @@ static uint32_t zend_vm_opcodes_flags[214] = { 0x00000003, 0x00040003, 0x09000003, - 0x00000103, + 0x00040103, 0x00002003, 0x03000001, 0x00004005,