From: Nikita Popov Date: Fri, 6 Dec 2019 13:15:58 +0000 (+0100) Subject: Optimize return type checking X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0bac7854779595603e0431521c60744665641734;p=php Optimize return type checking Split off the fast-path case and avoid redundant checks. --- diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 153d8e3bc4..0cd4e105e9 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1027,24 +1027,11 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf return zend_assign_to_variable(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES()); } - -static zend_always_inline zend_bool zend_check_type( - zend_type type, zval *arg, void **cache_slot, zend_class_entry *scope, +static zend_always_inline zend_bool zend_check_type_slow( + zend_type type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope, zend_bool is_return_type, zend_bool is_internal) { - zend_reference *ref = NULL; uint32_t type_mask; - ZEND_ASSERT(ZEND_TYPE_IS_SET(type)); - - if (UNEXPECTED(Z_ISREF_P(arg))) { - ref = Z_REF_P(arg); - arg = Z_REFVAL_P(arg); - } - - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) { - return 1; - } - if (ZEND_TYPE_HAS_CLASS(type) && Z_TYPE_P(arg) == IS_OBJECT) { zend_class_entry *ce; if (ZEND_TYPE_HAS_LIST(type)) { @@ -1109,6 +1096,25 @@ builtin_types: * because this case is already checked at compile-time. */ } +static zend_always_inline zend_bool zend_check_type( + zend_type type, zval *arg, void **cache_slot, zend_class_entry *scope, + zend_bool is_return_type, zend_bool is_internal) +{ + zend_reference *ref = NULL; + ZEND_ASSERT(ZEND_TYPE_IS_SET(type)); + + if (UNEXPECTED(Z_ISREF_P(arg))) { + ref = Z_REF_P(arg); + arg = Z_REFVAL_P(arg); + } + + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) { + return 1; + } + + return zend_check_type_slow(type, arg, ref, cache_slot, scope, is_return_type, is_internal); +} + static zend_always_inline int zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) { zend_arg_info *cur_arg_info; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index c6d37e57a0..eed570121d 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4098,15 +4098,15 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV { USE_OPLINE - SAVE_OPLINE(); if (OP1_TYPE == IS_UNUSED) { + SAVE_OPLINE(); zend_verify_missing_return_type(EX(func), CACHE_ADDR(opline->op2.num)); + HANDLE_EXCEPTION(); } else { /* prevents "undefined variable opline" errors */ #if !ZEND_VM_SPEC || (OP1_TYPE != IS_UNUSED) zval *retval_ref, *retval_ptr; zend_arg_info *ret_info = EX(func)->common.arg_info - 1; - retval_ref = retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R); if (OP1_TYPE == IS_CONST) { @@ -4121,24 +4121,35 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) - && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) - && retval_ref != retval_ptr) - ) { - /* A cast might happen - unwrap the reference if this is a by-value return */ - if (Z_REFCOUNT_P(retval_ref) == 1) { - ZVAL_UNREF(retval_ref); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } + + zend_reference *ref = NULL; + 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); } else { - Z_DELREF_P(retval_ref); - ZVAL_COPY(retval_ref, retval_ptr); + /* A cast might happen - unwrap the reference if this is a by-value return */ + if (Z_REFCOUNT_P(retval_ref) == 1) { + ZVAL_UNREF(retval_ref); + } else { + Z_DELREF_P(retval_ref); + ZVAL_COPY(retval_ref, retval_ptr); + } + retval_ptr = retval_ref; } - retval_ptr = retval_ref; } - zend_verify_return_type(EX(func), retval_ptr, CACHE_ADDR(opline->op2.num)); + + 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); + HANDLE_EXCEPTION(); + } + ZEND_VM_NEXT_OPCODE(); #endif } - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } ZEND_VM_INLINE_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 7e173dc2f7..1a50f32c72 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -8719,15 +8719,15 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP { USE_OPLINE - SAVE_OPLINE(); if (IS_CONST == IS_UNUSED) { + SAVE_OPLINE(); zend_verify_missing_return_type(EX(func), CACHE_ADDR(opline->op2.num)); + HANDLE_EXCEPTION(); } else { /* prevents "undefined variable opline" errors */ #if 0 || (IS_CONST != IS_UNUSED) zval *retval_ref, *retval_ptr; zend_arg_info *ret_info = EX(func)->common.arg_info - 1; - retval_ref = retval_ptr = RT_CONSTANT(opline, opline->op1); if (IS_CONST == IS_CONST) { @@ -8742,24 +8742,35 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) - && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) - && retval_ref != retval_ptr) - ) { - /* A cast might happen - unwrap the reference if this is a by-value return */ - if (Z_REFCOUNT_P(retval_ref) == 1) { - ZVAL_UNREF(retval_ref); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } + + zend_reference *ref = NULL; + 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); } else { - Z_DELREF_P(retval_ref); - ZVAL_COPY(retval_ref, retval_ptr); + /* A cast might happen - unwrap the reference if this is a by-value return */ + if (Z_REFCOUNT_P(retval_ref) == 1) { + ZVAL_UNREF(retval_ref); + } else { + Z_DELREF_P(retval_ref); + ZVAL_COPY(retval_ref, retval_ptr); + } + retval_ptr = retval_ref; } - retval_ptr = retval_ref; } - zend_verify_return_type(EX(func), retval_ptr, CACHE_ADDR(opline->op2.num)); + + 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); + HANDLE_EXCEPTION(); + } + ZEND_VM_NEXT_OPCODE(); #endif } - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_NEW_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) @@ -18663,15 +18674,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN { USE_OPLINE - SAVE_OPLINE(); if (IS_TMP_VAR == IS_UNUSED) { + SAVE_OPLINE(); zend_verify_missing_return_type(EX(func), CACHE_ADDR(opline->op2.num)); + HANDLE_EXCEPTION(); } else { /* prevents "undefined variable opline" errors */ #if 0 || (IS_TMP_VAR != IS_UNUSED) zval *retval_ref, *retval_ptr; zend_arg_info *ret_info = EX(func)->common.arg_info - 1; - retval_ref = retval_ptr = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC); if (IS_TMP_VAR == IS_CONST) { @@ -18686,24 +18697,35 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) - && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) - && retval_ref != retval_ptr) - ) { - /* A cast might happen - unwrap the reference if this is a by-value return */ - if (Z_REFCOUNT_P(retval_ref) == 1) { - ZVAL_UNREF(retval_ref); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } + + zend_reference *ref = NULL; + 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); } else { - Z_DELREF_P(retval_ref); - ZVAL_COPY(retval_ref, retval_ptr); + /* A cast might happen - unwrap the reference if this is a by-value return */ + if (Z_REFCOUNT_P(retval_ref) == 1) { + ZVAL_UNREF(retval_ref); + } else { + Z_DELREF_P(retval_ref); + ZVAL_COPY(retval_ref, retval_ptr); + } + retval_ptr = retval_ref; } - retval_ptr = retval_ref; } - zend_verify_return_type(EX(func), retval_ptr, CACHE_ADDR(opline->op2.num)); + + 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); + HANDLE_EXCEPTION(); + } + ZEND_VM_NEXT_OPCODE(); #endif } - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_TMP_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) @@ -26089,15 +26111,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN { USE_OPLINE - SAVE_OPLINE(); if (IS_VAR == IS_UNUSED) { + SAVE_OPLINE(); zend_verify_missing_return_type(EX(func), CACHE_ADDR(opline->op2.num)); + HANDLE_EXCEPTION(); } else { /* prevents "undefined variable opline" errors */ #if 0 || (IS_VAR != IS_UNUSED) zval *retval_ref, *retval_ptr; zend_arg_info *ret_info = EX(func)->common.arg_info - 1; - retval_ref = retval_ptr = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC); if (IS_VAR == IS_CONST) { @@ -26112,24 +26134,35 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) - && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) - && retval_ref != retval_ptr) - ) { - /* A cast might happen - unwrap the reference if this is a by-value return */ - if (Z_REFCOUNT_P(retval_ref) == 1) { - ZVAL_UNREF(retval_ref); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } + + zend_reference *ref = NULL; + 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); } else { - Z_DELREF_P(retval_ref); - ZVAL_COPY(retval_ref, retval_ptr); + /* A cast might happen - unwrap the reference if this is a by-value return */ + if (Z_REFCOUNT_P(retval_ref) == 1) { + ZVAL_UNREF(retval_ref); + } else { + Z_DELREF_P(retval_ref); + ZVAL_COPY(retval_ref, retval_ptr); + } + retval_ptr = retval_ref; } - retval_ptr = retval_ref; } - zend_verify_return_type(EX(func), retval_ptr, CACHE_ADDR(opline->op2.num)); + + 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); + HANDLE_EXCEPTION(); + } + ZEND_VM_NEXT_OPCODE(); #endif } - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_NEW_SPEC_VAR_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) @@ -32710,15 +32743,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED { USE_OPLINE - SAVE_OPLINE(); if (IS_UNUSED == IS_UNUSED) { + SAVE_OPLINE(); zend_verify_missing_return_type(EX(func), CACHE_ADDR(opline->op2.num)); + HANDLE_EXCEPTION(); } else { /* prevents "undefined variable opline" errors */ #if 0 || (IS_UNUSED != IS_UNUSED) zval *retval_ref, *retval_ptr; zend_arg_info *ret_info = EX(func)->common.arg_info - 1; - retval_ref = retval_ptr = NULL; if (IS_UNUSED == IS_CONST) { @@ -32733,24 +32766,35 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) - && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) - && retval_ref != retval_ptr) - ) { - /* A cast might happen - unwrap the reference if this is a by-value return */ - if (Z_REFCOUNT_P(retval_ref) == 1) { - ZVAL_UNREF(retval_ref); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } + + zend_reference *ref = NULL; + 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); } else { - Z_DELREF_P(retval_ref); - ZVAL_COPY(retval_ref, retval_ptr); + /* A cast might happen - unwrap the reference if this is a by-value return */ + if (Z_REFCOUNT_P(retval_ref) == 1) { + ZVAL_UNREF(retval_ref); + } else { + Z_DELREF_P(retval_ref); + ZVAL_COPY(retval_ref, retval_ptr); + } + retval_ptr = retval_ref; } - retval_ptr = retval_ref; } - zend_verify_return_type(EX(func), retval_ptr, CACHE_ADDR(opline->op2.num)); + + 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); + HANDLE_EXCEPTION(); + } + ZEND_VM_NEXT_OPCODE(); #endif } - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_NEW_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) @@ -44156,15 +44200,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU { USE_OPLINE - SAVE_OPLINE(); if (IS_CV == IS_UNUSED) { + SAVE_OPLINE(); zend_verify_missing_return_type(EX(func), CACHE_ADDR(opline->op2.num)); + HANDLE_EXCEPTION(); } else { /* prevents "undefined variable opline" errors */ #if 0 || (IS_CV != IS_UNUSED) zval *retval_ref, *retval_ptr; zend_arg_info *ret_info = EX(func)->common.arg_info - 1; - retval_ref = retval_ptr = _get_zval_ptr_cv_BP_VAR_R(opline->op1.var EXECUTE_DATA_CC); if (IS_CV == IS_CONST) { @@ -44179,24 +44223,35 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) - && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) - && retval_ref != retval_ptr) - ) { - /* A cast might happen - unwrap the reference if this is a by-value return */ - if (Z_REFCOUNT_P(retval_ref) == 1) { - ZVAL_UNREF(retval_ref); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)))) { + ZEND_VM_NEXT_OPCODE(); + } + + zend_reference *ref = NULL; + 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); } else { - Z_DELREF_P(retval_ref); - ZVAL_COPY(retval_ref, retval_ptr); + /* A cast might happen - unwrap the reference if this is a by-value return */ + if (Z_REFCOUNT_P(retval_ref) == 1) { + ZVAL_UNREF(retval_ref); + } else { + Z_DELREF_P(retval_ref); + ZVAL_COPY(retval_ref, retval_ptr); + } + retval_ptr = retval_ref; } - retval_ptr = retval_ref; } - zend_verify_return_type(EX(func), retval_ptr, CACHE_ADDR(opline->op2.num)); + + 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); + HANDLE_EXCEPTION(); + } + ZEND_VM_NEXT_OPCODE(); #endif } - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)