diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index d3546119bc9ee0e..1eb4cb6f09043db 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1458,10 +1458,10 @@ _PyOpcode_macro_expansion[256] = { [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 6, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 4, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_ATTR_METHOD_LAZY_DICT, 1, 3 }, { _LOAD_ATTR_METHOD_LAZY_DICT, 4, 5 } } }, [LOAD_ATTR_METHOD_NO_DICT] = { .nuops = 3, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_METHOD_NO_DICT, 4, 5 } } }, - [LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 4, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_METHOD_WITH_VALUES] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _GUARD_SHADOWING_SLOT_EMPTY, 1, 3 }, { _LOAD_ATTR_METHOD_WITH_VALUES, 4, 5 } } }, [LOAD_ATTR_MODULE] = { .nuops = 4, .uops = { { _LOAD_ATTR_MODULE, 2, 1 }, { _LOAD_ATTR_MODULE, OPERAND1_1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_NONDESCRIPTOR_NO_DICT] = { .nuops = 3, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_NONDESCRIPTOR_NO_DICT, 4, 5 } } }, - [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 4, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, + [LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, OPARG_SIMPLE, 3 }, { _GUARD_SHADOWING_SLOT_EMPTY, 1, 3 }, { _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, 4, 5 } } }, [LOAD_ATTR_PROPERTY] = { .nuops = 7, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_PEP_523, OPARG_SIMPLE, 3 }, { _LOAD_ATTR_PROPERTY_FRAME, 2, 3 }, { _LOAD_ATTR_PROPERTY_FRAME, OPERAND1_4, 5 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 9 }, { _PUSH_FRAME, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_SLOT] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_SLOT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, [LOAD_ATTR_WITH_HINT] = { .nuops = 5, .uops = { { _RECORD_TOS_TYPE, OPARG_SIMPLE, 1 }, { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_WITH_HINT, 1, 3 }, { _POP_TOP, OPARG_SIMPLE, 4 }, { _PUSH_NULL_CONDITIONAL, OPARG_SIMPLE, 9 } } }, diff --git a/Include/internal/pycore_uop_ids.h b/Include/internal/pycore_uop_ids.h index ba6e0252024a6df..968eed8e1fd295f 100644 --- a/Include/internal/pycore_uop_ids.h +++ b/Include/internal/pycore_uop_ids.h @@ -191,6 +191,7 @@ enum { _GUARD_NOT_EXHAUSTED_LIST, _GUARD_NOT_EXHAUSTED_RANGE, _GUARD_NOT_EXHAUSTED_TUPLE, + _GUARD_SHADOWING_SLOT_EMPTY, _GUARD_THIRD_NULL, _GUARD_TOS_ANY_DICT, _GUARD_TOS_ANY_SET, @@ -366,7 +367,7 @@ enum { _UNPACK_SEQUENCE_UNIQUE_TWO_TUPLE, _YIELD_VALUE, }; -#define MAX_UOP_ID 654 +#define MAX_UOP_ID 655 #define _BUILD_INTERPOLATION BUILD_INTERPOLATION #define _BUILD_LIST BUILD_LIST @@ -920,6 +921,10 @@ enum { _GUARD_NOT_EXHAUSTED_TUPLE_r12, _GUARD_NOT_EXHAUSTED_TUPLE_r22, _GUARD_NOT_EXHAUSTED_TUPLE_r33, + _GUARD_SHADOWING_SLOT_EMPTY_r01, + _GUARD_SHADOWING_SLOT_EMPTY_r11, + _GUARD_SHADOWING_SLOT_EMPTY_r22, + _GUARD_SHADOWING_SLOT_EMPTY_r33, _GUARD_THIRD_NULL_r03, _GUARD_THIRD_NULL_r13, _GUARD_THIRD_NULL_r23, @@ -1444,7 +1449,7 @@ enum { _WITH_EXCEPT_START_r33, _YIELD_VALUE_r11, }; -#define MAX_UOP_REGS_ID 1655 +#define MAX_UOP_REGS_ID 1660 #ifdef __cplusplus } diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 86b449158f00d0d..722d7486fa6bacb 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -286,6 +286,7 @@ const uint32_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_SPECIAL] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, [_WITH_EXCEPT_START] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_PUSH_EXC_INFO] = 0, + [_GUARD_SHADOWING_SLOT_EMPTY] = HAS_EXIT_FLAG, [_LOAD_ATTR_METHOD_WITH_VALUES] = HAS_ARG_FLAG, [_LOAD_ATTR_METHOD_NO_DICT] = HAS_ARG_FLAG, [_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES] = HAS_ARG_FLAG | HAS_ESCAPES_FLAG, @@ -2706,6 +2707,15 @@ const _PyUopCachingInfo _PyUop_Caching[MAX_UOP_ID+1] = { { -1, -1, -1 }, }, }, + [_GUARD_SHADOWING_SLOT_EMPTY] = { + .best = { 0, 1, 2, 3 }, + .entries = { + { 1, 0, _GUARD_SHADOWING_SLOT_EMPTY_r01 }, + { 1, 1, _GUARD_SHADOWING_SLOT_EMPTY_r11 }, + { 2, 2, _GUARD_SHADOWING_SLOT_EMPTY_r22 }, + { 3, 3, _GUARD_SHADOWING_SLOT_EMPTY_r33 }, + }, + }, [_LOAD_ATTR_METHOD_WITH_VALUES] = { .best = { 0, 1, 2, 2 }, .entries = { @@ -4525,6 +4535,10 @@ const uint16_t _PyUop_Uncached[MAX_UOP_REGS_ID+1] = { [_PUSH_EXC_INFO_r02] = _PUSH_EXC_INFO, [_PUSH_EXC_INFO_r12] = _PUSH_EXC_INFO, [_PUSH_EXC_INFO_r23] = _PUSH_EXC_INFO, + [_GUARD_SHADOWING_SLOT_EMPTY_r01] = _GUARD_SHADOWING_SLOT_EMPTY, + [_GUARD_SHADOWING_SLOT_EMPTY_r11] = _GUARD_SHADOWING_SLOT_EMPTY, + [_GUARD_SHADOWING_SLOT_EMPTY_r22] = _GUARD_SHADOWING_SLOT_EMPTY, + [_GUARD_SHADOWING_SLOT_EMPTY_r33] = _GUARD_SHADOWING_SLOT_EMPTY, [_LOAD_ATTR_METHOD_WITH_VALUES_r02] = _LOAD_ATTR_METHOD_WITH_VALUES, [_LOAD_ATTR_METHOD_WITH_VALUES_r12] = _LOAD_ATTR_METHOD_WITH_VALUES, [_LOAD_ATTR_METHOD_WITH_VALUES_r23] = _LOAD_ATTR_METHOD_WITH_VALUES, @@ -5535,6 +5549,11 @@ const char *const _PyOpcode_uop_name[MAX_UOP_REGS_ID+1] = { [_GUARD_NOT_EXHAUSTED_TUPLE_r12] = "_GUARD_NOT_EXHAUSTED_TUPLE_r12", [_GUARD_NOT_EXHAUSTED_TUPLE_r22] = "_GUARD_NOT_EXHAUSTED_TUPLE_r22", [_GUARD_NOT_EXHAUSTED_TUPLE_r33] = "_GUARD_NOT_EXHAUSTED_TUPLE_r33", + [_GUARD_SHADOWING_SLOT_EMPTY] = "_GUARD_SHADOWING_SLOT_EMPTY", + [_GUARD_SHADOWING_SLOT_EMPTY_r01] = "_GUARD_SHADOWING_SLOT_EMPTY_r01", + [_GUARD_SHADOWING_SLOT_EMPTY_r11] = "_GUARD_SHADOWING_SLOT_EMPTY_r11", + [_GUARD_SHADOWING_SLOT_EMPTY_r22] = "_GUARD_SHADOWING_SLOT_EMPTY_r22", + [_GUARD_SHADOWING_SLOT_EMPTY_r33] = "_GUARD_SHADOWING_SLOT_EMPTY_r33", [_GUARD_THIRD_NULL] = "_GUARD_THIRD_NULL", [_GUARD_THIRD_NULL_r03] = "_GUARD_THIRD_NULL_r03", [_GUARD_THIRD_NULL_r13] = "_GUARD_THIRD_NULL_r13", @@ -6700,6 +6719,8 @@ int _PyUop_num_popped(int opcode, int oparg) return 0; case _PUSH_EXC_INFO: return 1; + case _GUARD_SHADOWING_SLOT_EMPTY: + return 0; case _LOAD_ATTR_METHOD_WITH_VALUES: return 1; case _LOAD_ATTR_METHOD_NO_DICT: diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 60879e2774e7077..e47a46b420d2489 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1760,6 +1760,33 @@ def set_value(n): set_value(_testinternalcapi.SPECIALIZATION_COOLDOWN) self.assert_no_opcode(set_value, "STORE_ATTR_WITH_HINT") + @cpython_only + @requires_specialization + def test_load_attr_class_default_not_set_on_instance(self): + class C: + timeout = 30 + + def set_timeout(self, value): + self.timeout = value # puts "timeout" in C's shared keys + + c = C() # never sets timeout, so c.timeout is the class default + + @reset_code + def get_timeout(n): + for _ in range(n): + value = c.timeout + return value + + self.assertEqual(get_timeout(_testinternalcapi.SPECIALIZATION_THRESHOLD), 30) + # Must load the class attribute, not specialize as an instance + # attribute load that misses on every execution. + self.assert_specialized(get_timeout, "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES") + self.assert_no_opcode(get_timeout, "LOAD_ATTR_INSTANCE_VALUE") + + # Setting the attribute on the instance must be seen. + c.set_timeout(5) + self.assertEqual(get_timeout(1), 5) + @cpython_only @requires_specialization def test_to_bool(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-06-21-39-58.gh-issue-158934.wuXP68.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-06-21-39-58.gh-issue-158934.wuXP68.rst new file mode 100644 index 000000000000000..080fe4f6acf8a0a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-06-21-39-58.gh-issue-158934.wuXP68.rst @@ -0,0 +1,3 @@ +Fix specialization of attribute loads that find a class attribute because the +instance has not set its own value, when the class sets that attribute on +``self`` elsewhere. These loads previously missed on every execution. diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index 3bdc16437e2bc61..08974017f93372b 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -9173,7 +9173,20 @@ JUMP_TO_PREDICTED(LOAD_ATTR); } } - /* Skip 2 cache entries */ + // _GUARD_SHADOWING_SLOT_EMPTY + { + uint16_t slot_offset = read_u16(&this_instr[4].cache); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UPDATE_MISS_STATS(LOAD_ATTR); + assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR)); + JUMP_TO_PREDICTED(LOAD_ATTR); + } + } + } + /* Skip 1 cache entry */ // _LOAD_ATTR_METHOD_WITH_VALUES { PyObject *descr = read_obj(&this_instr[6].cache); @@ -9358,7 +9371,20 @@ JUMP_TO_PREDICTED(LOAD_ATTR); } } - /* Skip 2 cache entries */ + // _GUARD_SHADOWING_SLOT_EMPTY + { + uint16_t slot_offset = read_u16(&this_instr[4].cache); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UPDATE_MISS_STATS(LOAD_ATTR); + assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR)); + JUMP_TO_PREDICTED(LOAD_ATTR); + } + } + } + /* Skip 1 cache entry */ // _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES { PyObject *descr = read_obj(&this_instr[6].cache); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 31eaeab0d67841f..1dca143a5d26810 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -4197,6 +4197,15 @@ dummy_func( DEAD(exc); } + /* Slots are created even if not assigend to */ + op(_GUARD_SHADOWING_SLOT_EMPTY, (slot_offset/1, owner -- owner)) { + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + EXIT_IF(FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL); + } + } + op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self)) { assert(oparg & 1); /* Cached method object */ @@ -4213,7 +4222,8 @@ dummy_func( _RECORD_TOS_TYPE + _GUARD_TYPE_VERSION + _CHECK_MANAGED_OBJECT_HAS_VALUES + - unused/2 + + _GUARD_SHADOWING_SLOT_EMPTY + + unused/1 + _LOAD_ATTR_METHOD_WITH_VALUES; op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self)) { @@ -4247,7 +4257,8 @@ dummy_func( _RECORD_TOS_TYPE + _GUARD_TYPE_VERSION + _CHECK_MANAGED_OBJECT_HAS_VALUES + - unused/2 + + _GUARD_SHADOWING_SLOT_EMPTY + + unused/1 + _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES; op(_LOAD_ATTR_NONDESCRIPTOR_NO_DICT, (descr/4, owner -- attr)) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index c5b2dfcf5f618f3..78648f81bd2a851 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -16491,6 +16491,107 @@ break; } + case _GUARD_SHADOWING_SLOT_EMPTY_r01: { + CHECK_CURRENT_CACHED_VALUES(0); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + _PyStackRef owner; + owner = stack_pointer[-1]; + uint16_t slot_offset = (uint16_t)CURRENT_OPERAND0_16(); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UOP_STAT_INC(uopcode, miss); + SET_CURRENT_CACHED_VALUES(0); + JUMP_TO_JUMP_TARGET(); + } + } + _tos_cache0 = owner; + SET_CURRENT_CACHED_VALUES(1); + stack_pointer += -1; + ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + break; + } + + case _GUARD_SHADOWING_SLOT_EMPTY_r11: { + CHECK_CURRENT_CACHED_VALUES(1); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + _PyStackRef owner; + _PyStackRef _stack_item_0 = _tos_cache0; + owner = _stack_item_0; + uint16_t slot_offset = (uint16_t)CURRENT_OPERAND0_16(); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UOP_STAT_INC(uopcode, miss); + _tos_cache0 = owner; + SET_CURRENT_CACHED_VALUES(1); + JUMP_TO_JUMP_TARGET(); + } + } + _tos_cache0 = owner; + SET_CURRENT_CACHED_VALUES(1); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + break; + } + + case _GUARD_SHADOWING_SLOT_EMPTY_r22: { + CHECK_CURRENT_CACHED_VALUES(2); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + _PyStackRef owner; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + owner = _stack_item_1; + uint16_t slot_offset = (uint16_t)CURRENT_OPERAND0_16(); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UOP_STAT_INC(uopcode, miss); + _tos_cache1 = owner; + _tos_cache0 = _stack_item_0; + SET_CURRENT_CACHED_VALUES(2); + JUMP_TO_JUMP_TARGET(); + } + } + _tos_cache1 = owner; + _tos_cache0 = _stack_item_0; + SET_CURRENT_CACHED_VALUES(2); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + break; + } + + case _GUARD_SHADOWING_SLOT_EMPTY_r33: { + CHECK_CURRENT_CACHED_VALUES(3); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + _PyStackRef owner; + _PyStackRef _stack_item_0 = _tos_cache0; + _PyStackRef _stack_item_1 = _tos_cache1; + _PyStackRef _stack_item_2 = _tos_cache2; + owner = _stack_item_2; + uint16_t slot_offset = (uint16_t)CURRENT_OPERAND0_16(); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UOP_STAT_INC(uopcode, miss); + _tos_cache2 = owner; + _tos_cache1 = _stack_item_1; + _tos_cache0 = _stack_item_0; + SET_CURRENT_CACHED_VALUES(3); + JUMP_TO_JUMP_TARGET(); + } + } + _tos_cache2 = owner; + _tos_cache1 = _stack_item_1; + _tos_cache0 = _stack_item_0; + SET_CURRENT_CACHED_VALUES(3); + ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); + break; + } + case _LOAD_ATTR_METHOD_WITH_VALUES_r02: { CHECK_CURRENT_CACHED_VALUES(0); ASSERT_WITHIN_STACK_BOUNDS_IGNORING_CACHE(__FILE__, __LINE__); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index dd0ce41e4b06b4a..fda377d891443bd 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -9172,7 +9172,20 @@ JUMP_TO_PREDICTED(LOAD_ATTR); } } - /* Skip 2 cache entries */ + // _GUARD_SHADOWING_SLOT_EMPTY + { + uint16_t slot_offset = read_u16(&this_instr[4].cache); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UPDATE_MISS_STATS(LOAD_ATTR); + assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR)); + JUMP_TO_PREDICTED(LOAD_ATTR); + } + } + } + /* Skip 1 cache entry */ // _LOAD_ATTR_METHOD_WITH_VALUES { PyObject *descr = read_obj(&this_instr[6].cache); @@ -9357,7 +9370,20 @@ JUMP_TO_PREDICTED(LOAD_ATTR); } } - /* Skip 2 cache entries */ + // _GUARD_SHADOWING_SLOT_EMPTY + { + uint16_t slot_offset = read_u16(&this_instr[4].cache); + if (slot_offset != 0) { + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset); + if (FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL) { + UPDATE_MISS_STATS(LOAD_ATTR); + assert(_PyOpcode_Deopt[opcode] == (LOAD_ATTR)); + JUMP_TO_PREDICTED(LOAD_ATTR); + } + } + } + /* Skip 1 cache entry */ // _LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES { PyObject *descr = read_obj(&this_instr[6].cache); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 6284885c428832b..91b793ea28ca778 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1028,6 +1028,12 @@ dummy_func(void) { _POP_TOP, _NOP); } + op(_GUARD_SHADOWING_SLOT_EMPTY, (slot_offset/1, owner -- owner)) { + if (slot_offset == 0) { + ADD_OP(_NOP, 0, 0); + } + } + op(_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, (descr/4, owner -- attr)) { (void)descr; PyTypeObject *type = sym_get_type(owner); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 9cd20c495db4418..b9f5308b7d35d87 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -3951,6 +3951,14 @@ break; } + case _GUARD_SHADOWING_SLOT_EMPTY: { + uint16_t slot_offset = (uint16_t)this_instr->operand0; + if (slot_offset == 0) { + ADD_OP(_NOP, 0, 0); + } + break; + } + case _LOAD_ATTR_METHOD_WITH_VALUES: { JitOptRef owner; JitOptRef attr; diff --git a/Python/specialize.c b/Python/specialize.c index f658df3bc5d2228..33d59515b60f837 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -751,9 +751,9 @@ specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr, uint32_t shared_keys_version); static int specialize_class_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name); -/* Returns true if instances of obj's class are - * likely to have `name` in their __dict__. - * For objects with inline values, we check in the shared keys. +/* Returns true if obj may have its own value for `name`. + * For objects with inline values, we check obj's slot for `name` in the + * shared keys: it may be empty even though the name has a slot. * For other objects, we check their actual dictionary. */ static bool @@ -767,7 +767,17 @@ instance_has_key(PyObject *obj, PyObject *name, uint32_t *shared_keys_version) PyDictKeysObject *keys = ((PyHeapTypeObject *)cls)->ht_cached_keys; Py_ssize_t index = _PyDictKeys_StringLookupAndVersion(keys, name, shared_keys_version); - return index >= 0; + if (index < 0) { + return false; + } + bool result; + Py_BEGIN_CRITICAL_SECTION(obj); + PyDictValues *values = _PyObject_InlineValues(obj); + result = !FT_ATOMIC_LOAD_UINT8(values->valid) || + index >= values->capacity || + FT_ATOMIC_LOAD_PTR_RELAXED(values->values[index]) != NULL; + Py_END_CRITICAL_SECTION(); + return result; } PyDictObject *dict = _PyObject_GetManagedDict(obj); if (dict == NULL || !PyDict_CheckExact(dict)) { @@ -1282,14 +1292,28 @@ specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr, unsigned long tp_flags = PyType_GetFlags(owner_cls); if (tp_flags & Py_TPFLAGS_INLINE_VALUES) { - #ifndef Py_GIL_DISABLED - assert(_PyDictKeys_StringLookup( - ((PyHeapTypeObject *)owner_cls)->ht_cached_keys, name) < 0); - #endif if (shared_keys_version == 0) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS); return 0; } + /* The name can have a slot in the shared keys (e.g. from + * __static_attributes__) that is empty in this instance. Store the + * slot's offset so the instruction can check it stays empty; 0 means + * there is no slot to check. */ + uint16_t slot_offset = 0; + Py_ssize_t index = _PyDictKeys_StringLookupSplit( + ((PyHeapTypeObject *)owner_cls)->ht_cached_keys, name); + assert(index != DKIX_ERROR); + if (index >= 0) { + char *value_addr = (char *)&_PyObject_InlineValues(owner)->values[index]; + Py_ssize_t offset = value_addr - (char *)owner; + if (offset != (uint16_t)offset) { + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_RANGE); + return 0; + } + slot_offset = (uint16_t)offset; + } + cache->keys_version[0] = slot_offset; specialize(instr, is_method ? LOAD_ATTR_METHOD_WITH_VALUES : LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES); } else {