From 5d3201fe3f76aeba33e9e5956ba80a116e422bd0 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 23 Aug 2024 10:22:35 +0100 Subject: [PATCH] GH-123040: Specialize shadowed `LOAD_ATTR`. (GH-123219) --- Include/internal/pycore_opcode_metadata.h | 9 +- Include/opcode_ids.h | 63 ++-- Lib/_opcode_metadata.py | 64 ++-- Python/bytecodes.c | 8 +- Python/generated_cases.c.h | 41 +++ Python/opcode_targets.h | 2 +- Python/specialize.c | 348 +++++++++++++--------- 7 files changed, 334 insertions(+), 201 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 236e6b41461..97a8e4a00a9 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -279,6 +279,8 @@ int _PyOpcode_num_popped(int opcode, int oparg) { return 1; case LOAD_ATTR_CLASS: return 1; + case LOAD_ATTR_CLASS_WITH_METACLASS_CHECK: + return 1; case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: return 1; case LOAD_ATTR_INSTANCE_VALUE: @@ -734,6 +736,8 @@ int _PyOpcode_num_pushed(int opcode, int oparg) { return 1 + (oparg & 1); case LOAD_ATTR_CLASS: return 1 + (oparg & 1); + case LOAD_ATTR_CLASS_WITH_METACLASS_CHECK: + return 1 + (oparg & 1); case LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN: return 1; case LOAD_ATTR_INSTANCE_VALUE: @@ -1125,6 +1129,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[264] = { [LIST_EXTEND] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG }, + [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_EXIT_FLAG }, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG }, @@ -1329,6 +1334,7 @@ _PyOpcode_macro_expansion[256] = { [LIST_EXTEND] = { .nuops = 1, .uops = { { _LIST_EXTEND, 0, 0 } } }, [LOAD_ATTR] = { .nuops = 1, .uops = { { _LOAD_ATTR, 0, 0 } } }, [LOAD_ATTR_CLASS] = { .nuops = 2, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _LOAD_ATTR_CLASS, 4, 5 } } }, + [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = { .nuops = 3, .uops = { { _CHECK_ATTR_CLASS, 2, 1 }, { _GUARD_TYPE_VERSION, 2, 3 }, { _LOAD_ATTR_CLASS, 4, 5 } } }, [LOAD_ATTR_INSTANCE_VALUE] = { .nuops = 3, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _CHECK_MANAGED_OBJECT_HAS_VALUES, 0, 0 }, { _LOAD_ATTR_INSTANCE_VALUE, 1, 3 } } }, [LOAD_ATTR_METHOD_LAZY_DICT] = { .nuops = 3, .uops = { { _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 = 2, .uops = { { _GUARD_TYPE_VERSION, 2, 1 }, { _LOAD_ATTR_METHOD_NO_DICT, 4, 5 } } }, @@ -1542,6 +1548,7 @@ const char *_PyOpcode_OpName[264] = { [LIST_EXTEND] = "LIST_EXTEND", [LOAD_ATTR] = "LOAD_ATTR", [LOAD_ATTR_CLASS] = "LOAD_ATTR_CLASS", + [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = "LOAD_ATTR_CLASS_WITH_METACLASS_CHECK", [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", [LOAD_ATTR_INSTANCE_VALUE] = "LOAD_ATTR_INSTANCE_VALUE", [LOAD_ATTR_METHOD_LAZY_DICT] = "LOAD_ATTR_METHOD_LAZY_DICT", @@ -1794,6 +1801,7 @@ const uint8_t _PyOpcode_Deopt[256] = { [LIST_EXTEND] = LIST_EXTEND, [LOAD_ATTR] = LOAD_ATTR, [LOAD_ATTR_CLASS] = LOAD_ATTR, + [LOAD_ATTR_CLASS_WITH_METACLASS_CHECK] = LOAD_ATTR, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = LOAD_ATTR, [LOAD_ATTR_INSTANCE_VALUE] = LOAD_ATTR, [LOAD_ATTR_METHOD_LAZY_DICT] = LOAD_ATTR, @@ -1924,7 +1932,6 @@ const uint8_t _PyOpcode_Deopt[256] = { case 146: \ case 147: \ case 148: \ - case 226: \ case 227: \ case 228: \ case 229: \ diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index 1189712e12e..5ded0b41b48 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -173,37 +173,38 @@ extern "C" { #define FOR_ITER_RANGE 192 #define FOR_ITER_TUPLE 193 #define LOAD_ATTR_CLASS 194 -#define LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN 195 -#define LOAD_ATTR_INSTANCE_VALUE 196 -#define LOAD_ATTR_METHOD_LAZY_DICT 197 -#define LOAD_ATTR_METHOD_NO_DICT 198 -#define LOAD_ATTR_METHOD_WITH_VALUES 199 -#define LOAD_ATTR_MODULE 200 -#define LOAD_ATTR_NONDESCRIPTOR_NO_DICT 201 -#define LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 202 -#define LOAD_ATTR_PROPERTY 203 -#define LOAD_ATTR_SLOT 204 -#define LOAD_ATTR_WITH_HINT 205 -#define LOAD_GLOBAL_BUILTIN 206 -#define LOAD_GLOBAL_MODULE 207 -#define LOAD_SUPER_ATTR_ATTR 208 -#define LOAD_SUPER_ATTR_METHOD 209 -#define RESUME_CHECK 210 -#define SEND_GEN 211 -#define STORE_ATTR_INSTANCE_VALUE 212 -#define STORE_ATTR_SLOT 213 -#define STORE_ATTR_WITH_HINT 214 -#define STORE_SUBSCR_DICT 215 -#define STORE_SUBSCR_LIST_INT 216 -#define TO_BOOL_ALWAYS_TRUE 217 -#define TO_BOOL_BOOL 218 -#define TO_BOOL_INT 219 -#define TO_BOOL_LIST 220 -#define TO_BOOL_NONE 221 -#define TO_BOOL_STR 222 -#define UNPACK_SEQUENCE_LIST 223 -#define UNPACK_SEQUENCE_TUPLE 224 -#define UNPACK_SEQUENCE_TWO_TUPLE 225 +#define LOAD_ATTR_CLASS_WITH_METACLASS_CHECK 195 +#define LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN 196 +#define LOAD_ATTR_INSTANCE_VALUE 197 +#define LOAD_ATTR_METHOD_LAZY_DICT 198 +#define LOAD_ATTR_METHOD_NO_DICT 199 +#define LOAD_ATTR_METHOD_WITH_VALUES 200 +#define LOAD_ATTR_MODULE 201 +#define LOAD_ATTR_NONDESCRIPTOR_NO_DICT 202 +#define LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES 203 +#define LOAD_ATTR_PROPERTY 204 +#define LOAD_ATTR_SLOT 205 +#define LOAD_ATTR_WITH_HINT 206 +#define LOAD_GLOBAL_BUILTIN 207 +#define LOAD_GLOBAL_MODULE 208 +#define LOAD_SUPER_ATTR_ATTR 209 +#define LOAD_SUPER_ATTR_METHOD 210 +#define RESUME_CHECK 211 +#define SEND_GEN 212 +#define STORE_ATTR_INSTANCE_VALUE 213 +#define STORE_ATTR_SLOT 214 +#define STORE_ATTR_WITH_HINT 215 +#define STORE_SUBSCR_DICT 216 +#define STORE_SUBSCR_LIST_INT 217 +#define TO_BOOL_ALWAYS_TRUE 218 +#define TO_BOOL_BOOL 219 +#define TO_BOOL_INT 220 +#define TO_BOOL_LIST 221 +#define TO_BOOL_NONE 222 +#define TO_BOOL_STR 223 +#define UNPACK_SEQUENCE_LIST 224 +#define UNPACK_SEQUENCE_TUPLE 225 +#define UNPACK_SEQUENCE_TWO_TUPLE 226 #define INSTRUMENTED_END_FOR 236 #define INSTRUMENTED_END_SEND 237 #define INSTRUMENTED_LOAD_SUPER_ATTR 238 diff --git a/Lib/_opcode_metadata.py b/Lib/_opcode_metadata.py index 0f3d9734897..6e4b3392186 100644 --- a/Lib/_opcode_metadata.py +++ b/Lib/_opcode_metadata.py @@ -62,6 +62,7 @@ _specializations = { "LOAD_ATTR_WITH_HINT", "LOAD_ATTR_SLOT", "LOAD_ATTR_CLASS", + "LOAD_ATTR_CLASS_WITH_METACLASS_CHECK", "LOAD_ATTR_PROPERTY", "LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN", "LOAD_ATTR_METHOD_WITH_VALUES", @@ -161,37 +162,38 @@ _specialized_opmap = { 'FOR_ITER_RANGE': 192, 'FOR_ITER_TUPLE': 193, 'LOAD_ATTR_CLASS': 194, - 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 195, - 'LOAD_ATTR_INSTANCE_VALUE': 196, - 'LOAD_ATTR_METHOD_LAZY_DICT': 197, - 'LOAD_ATTR_METHOD_NO_DICT': 198, - 'LOAD_ATTR_METHOD_WITH_VALUES': 199, - 'LOAD_ATTR_MODULE': 200, - 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 201, - 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 202, - 'LOAD_ATTR_PROPERTY': 203, - 'LOAD_ATTR_SLOT': 204, - 'LOAD_ATTR_WITH_HINT': 205, - 'LOAD_GLOBAL_BUILTIN': 206, - 'LOAD_GLOBAL_MODULE': 207, - 'LOAD_SUPER_ATTR_ATTR': 208, - 'LOAD_SUPER_ATTR_METHOD': 209, - 'RESUME_CHECK': 210, - 'SEND_GEN': 211, - 'STORE_ATTR_INSTANCE_VALUE': 212, - 'STORE_ATTR_SLOT': 213, - 'STORE_ATTR_WITH_HINT': 214, - 'STORE_SUBSCR_DICT': 215, - 'STORE_SUBSCR_LIST_INT': 216, - 'TO_BOOL_ALWAYS_TRUE': 217, - 'TO_BOOL_BOOL': 218, - 'TO_BOOL_INT': 219, - 'TO_BOOL_LIST': 220, - 'TO_BOOL_NONE': 221, - 'TO_BOOL_STR': 222, - 'UNPACK_SEQUENCE_LIST': 223, - 'UNPACK_SEQUENCE_TUPLE': 224, - 'UNPACK_SEQUENCE_TWO_TUPLE': 225, + 'LOAD_ATTR_CLASS_WITH_METACLASS_CHECK': 195, + 'LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN': 196, + 'LOAD_ATTR_INSTANCE_VALUE': 197, + 'LOAD_ATTR_METHOD_LAZY_DICT': 198, + 'LOAD_ATTR_METHOD_NO_DICT': 199, + 'LOAD_ATTR_METHOD_WITH_VALUES': 200, + 'LOAD_ATTR_MODULE': 201, + 'LOAD_ATTR_NONDESCRIPTOR_NO_DICT': 202, + 'LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES': 203, + 'LOAD_ATTR_PROPERTY': 204, + 'LOAD_ATTR_SLOT': 205, + 'LOAD_ATTR_WITH_HINT': 206, + 'LOAD_GLOBAL_BUILTIN': 207, + 'LOAD_GLOBAL_MODULE': 208, + 'LOAD_SUPER_ATTR_ATTR': 209, + 'LOAD_SUPER_ATTR_METHOD': 210, + 'RESUME_CHECK': 211, + 'SEND_GEN': 212, + 'STORE_ATTR_INSTANCE_VALUE': 213, + 'STORE_ATTR_SLOT': 214, + 'STORE_ATTR_WITH_HINT': 215, + 'STORE_SUBSCR_DICT': 216, + 'STORE_SUBSCR_LIST_INT': 217, + 'TO_BOOL_ALWAYS_TRUE': 218, + 'TO_BOOL_BOOL': 219, + 'TO_BOOL_INT': 220, + 'TO_BOOL_LIST': 221, + 'TO_BOOL_NONE': 222, + 'TO_BOOL_STR': 223, + 'UNPACK_SEQUENCE_LIST': 224, + 'UNPACK_SEQUENCE_TUPLE': 225, + 'UNPACK_SEQUENCE_TWO_TUPLE': 226, } opmap = { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index bc418137a9f..62f6853fedf 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1936,6 +1936,7 @@ dummy_func( LOAD_ATTR_WITH_HINT, LOAD_ATTR_SLOT, LOAD_ATTR_CLASS, + LOAD_ATTR_CLASS_WITH_METACLASS_CHECK, LOAD_ATTR_PROPERTY, LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN, LOAD_ATTR_METHOD_WITH_VALUES, @@ -2119,7 +2120,6 @@ dummy_func( EXIT_IF(!PyType_Check(owner_o)); assert(type_version != 0); EXIT_IF(((PyTypeObject *)owner_o)->tp_version_tag != type_version); - } split op(_LOAD_ATTR_CLASS, (descr/4, owner -- attr, null if (oparg & 1))) { @@ -2136,6 +2136,12 @@ dummy_func( unused/2 + _LOAD_ATTR_CLASS; + macro(LOAD_ATTR_CLASS_WITH_METACLASS_CHECK) = + unused/1 + + _CHECK_ATTR_CLASS + + _GUARD_TYPE_VERSION + + _LOAD_ATTR_CLASS; + op(_LOAD_ATTR_PROPERTY_FRAME, (fget/4, owner -- new_frame: _PyInterpreterFrame *)) { assert((oparg & 1) == 0); assert(Py_IS_TYPE(fget, &PyFunction_Type)); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 181940d87ff..feb16ccaad3 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -4937,6 +4937,47 @@ DISPATCH(); } + TARGET(LOAD_ATTR_CLASS_WITH_METACLASS_CHECK) { + _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; + next_instr += 10; + INSTRUCTION_STATS(LOAD_ATTR_CLASS_WITH_METACLASS_CHECK); + static_assert(INLINE_CACHE_ENTRIES_LOAD_ATTR == 9, "incorrect cache size"); + _PyStackRef owner; + _PyStackRef attr; + _PyStackRef null = PyStackRef_NULL; + /* Skip 1 cache entry */ + // _CHECK_ATTR_CLASS + owner = stack_pointer[-1]; + { + uint32_t type_version = read_u32(&this_instr[2].cache); + PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner); + DEOPT_IF(!PyType_Check(owner_o), LOAD_ATTR); + assert(type_version != 0); + DEOPT_IF(((PyTypeObject *)owner_o)->tp_version_tag != type_version, LOAD_ATTR); + } + // _GUARD_TYPE_VERSION + { + uint32_t type_version = read_u32(&this_instr[4].cache); + PyTypeObject *tp = Py_TYPE(PyStackRef_AsPyObjectBorrow(owner)); + assert(type_version != 0); + DEOPT_IF(tp->tp_version_tag != type_version, LOAD_ATTR); + } + // _LOAD_ATTR_CLASS + { + PyObject *descr = read_obj(&this_instr[6].cache); + STAT_INC(LOAD_ATTR, hit); + assert(descr != NULL); + attr = PyStackRef_FromPyObjectNew(descr); + stack_pointer[-1] = attr; + null = PyStackRef_NULL; + PyStackRef_CLOSE(owner); + } + if (oparg & 1) stack_pointer[0] = null; + stack_pointer += (oparg & 1); + assert(WITHIN_STACK_BOUNDS()); + DISPATCH(); + } + TARGET(LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN) { _Py_CODEUNIT *this_instr = frame->instr_ptr = next_instr; next_instr += 10; diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index 0418105dee5..49f01ca2932 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -194,6 +194,7 @@ static void *opcode_targets[256] = { &&TARGET_FOR_ITER_RANGE, &&TARGET_FOR_ITER_TUPLE, &&TARGET_LOAD_ATTR_CLASS, + &&TARGET_LOAD_ATTR_CLASS_WITH_METACLASS_CHECK, &&TARGET_LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN, &&TARGET_LOAD_ATTR_INSTANCE_VALUE, &&TARGET_LOAD_ATTR_METHOD_LAZY_DICT, @@ -234,7 +235,6 @@ static void *opcode_targets[256] = { &&_unknown_opcode, &&_unknown_opcode, &&_unknown_opcode, - &&_unknown_opcode, &&TARGET_INSTRUMENTED_END_FOR, &&TARGET_INSTRUMENTED_END_SEND, &&TARGET_INSTRUMENTED_LOAD_SUPER_ATTR, diff --git a/Python/specialize.c b/Python/specialize.c index 82f1a887beb..14f0c0756ff 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -490,7 +490,7 @@ _PyCode_Quicken(PyCodeObject *code) #define SPEC_FAIL_ATTR_READ_ONLY 16 #define SPEC_FAIL_ATTR_AUDITED_SLOT 17 #define SPEC_FAIL_ATTR_NOT_MANAGED_DICT 18 -#define SPEC_FAIL_ATTR_NON_STRING_OR_SPLIT 19 +#define SPEC_FAIL_ATTR_NON_STRING 19 #define SPEC_FAIL_ATTR_MODULE_ATTR_NOT_FOUND 20 #define SPEC_FAIL_ATTR_SHADOWED 21 #define SPEC_FAIL_ATTR_BUILTIN_CLASS_METHOD 22 @@ -505,6 +505,8 @@ _PyCode_Quicken(PyCodeObject *code) #define SPEC_FAIL_ATTR_CLASS_ATTR_SIMPLE 31 #define SPEC_FAIL_ATTR_CLASS_ATTR_DESCRIPTOR 32 #define SPEC_FAIL_ATTR_BUILTIN_CLASS_METHOD_OBJ 33 +#define SPEC_FAIL_ATTR_METACLASS_OVERRIDDEN 34 +#define SPEC_FAIL_ATTR_SPLIT_DICT 35 /* Binary subscr and store subscr */ @@ -647,7 +649,7 @@ specialize_module_load_attr( return -1; } if (dict->ma_keys->dk_kind != DICT_KEYS_UNICODE) { - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_NON_STRING_OR_SPLIT); + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_NON_STRING); return -1; } Py_ssize_t index = _PyDict_LookupIndex(dict, &_Py_ID(__getattr__)); @@ -730,6 +732,48 @@ typedef enum { } DescriptorClassification; +static DescriptorClassification +classify_descriptor(PyObject *descriptor, bool has_getattr) +{ + if (descriptor == NULL) { + return ABSENT; + } + PyTypeObject *desc_cls = Py_TYPE(descriptor); + if (!(desc_cls->tp_flags & Py_TPFLAGS_IMMUTABLETYPE)) { + return MUTABLE; + } + if (desc_cls->tp_descr_set) { + if (desc_cls == &PyMemberDescr_Type) { + PyMemberDescrObject *member = (PyMemberDescrObject *)descriptor; + struct PyMemberDef *dmem = member->d_member; + if (dmem->type == Py_T_OBJECT_EX || dmem->type == _Py_T_OBJECT) { + return OBJECT_SLOT; + } + return OTHER_SLOT; + } + if (desc_cls == &PyProperty_Type) { + /* We can't detect at runtime whether an attribute exists + with property. So that means we may have to call + __getattr__. */ + return has_getattr ? GETSET_OVERRIDDEN : PROPERTY; + } + return OVERRIDING; + } + if (desc_cls->tp_descr_get) { + if (desc_cls->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR) { + return METHOD; + } + if (Py_IS_TYPE(descriptor, &PyClassMethodDescr_Type)) { + return BUILTIN_CLASSMETHOD; + } + if (Py_IS_TYPE(descriptor, &PyClassMethod_Type)) { + return PYTHON_CLASSMETHOD; + } + return NON_OVERRIDING; + } + return NON_DESCRIPTOR; +} + static DescriptorClassification analyze_descriptor(PyTypeObject *type, PyObject *name, PyObject **descr, int store) { @@ -783,50 +827,12 @@ analyze_descriptor(PyTypeObject *type, PyObject *name, PyObject **descr, int sto } PyObject *descriptor = _PyType_Lookup(type, name); *descr = descriptor; - if (descriptor == NULL) { - return ABSENT; - } - PyTypeObject *desc_cls = Py_TYPE(descriptor); - if (!(desc_cls->tp_flags & Py_TPFLAGS_IMMUTABLETYPE)) { - return MUTABLE; - } - if (desc_cls->tp_descr_set) { - if (desc_cls == &PyMemberDescr_Type) { - PyMemberDescrObject *member = (PyMemberDescrObject *)descriptor; - struct PyMemberDef *dmem = member->d_member; - if (dmem->type == Py_T_OBJECT_EX || dmem->type == _Py_T_OBJECT) { - return OBJECT_SLOT; - } - return OTHER_SLOT; - } - if (desc_cls == &PyProperty_Type) { - /* We can't detect at runtime whether an attribute exists - with property. So that means we may have to call - __getattr__. */ - return has_getattr ? GETSET_OVERRIDDEN : PROPERTY; - } - if (PyUnicode_CompareWithASCIIString(name, "__class__") == 0) { - if (descriptor == _PyType_Lookup(&PyBaseObject_Type, name)) { - return DUNDER_CLASS; - } - } - if (store) { - return OVERRIDING; + if (PyUnicode_CompareWithASCIIString(name, "__class__") == 0) { + if (descriptor == _PyType_Lookup(&PyBaseObject_Type, name)) { + return DUNDER_CLASS; } } - if (desc_cls->tp_descr_get) { - if (desc_cls->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR) { - return METHOD; - } - if (Py_IS_TYPE(descriptor, &PyClassMethodDescr_Type)) { - return BUILTIN_CLASSMETHOD; - } - if (Py_IS_TYPE(descriptor, &PyClassMethod_Type)) { - return PYTHON_CLASSMETHOD; - } - return NON_OVERRIDING; - } - return NON_DESCRIPTOR; + return classify_descriptor(descriptor, has_getattr); } static int @@ -836,7 +842,8 @@ specialize_dict_access( int base_op, int values_op, int hint_op) { assert(kind == NON_OVERRIDING || kind == NON_DESCRIPTOR || kind == ABSENT || - kind == BUILTIN_CLASSMETHOD || kind == PYTHON_CLASSMETHOD); + kind == BUILTIN_CLASSMETHOD || kind == PYTHON_CLASSMETHOD || + kind == METHOD); // No descriptor, or non overriding. if ((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) { SPECIALIZATION_FAIL(base_op, SPEC_FAIL_ATTR_NOT_MANAGED_DICT); @@ -871,7 +878,7 @@ specialize_dict_access( } // We found an instance with a __dict__. if (dict->ma_values) { - SPECIALIZATION_FAIL(base_op, SPEC_FAIL_ATTR_NON_STRING_OR_SPLIT); + SPECIALIZATION_FAIL(base_op, SPEC_FAIL_ATTR_SPLIT_DICT); return 0; } Py_ssize_t index = @@ -894,57 +901,69 @@ static int specialize_attr_loadclassattr(PyObject* owner, _Py_CODEUNIT* instr, P PyObject* descr, DescriptorClassification kind, bool is_method); static int specialize_class_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name); -void -_Py_Specialize_LoadAttr(_PyStackRef owner_st, _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. + * For other objects, we check their actual dictionary. + */ +static bool +instance_has_key(PyObject *obj, PyObject* name) { - PyObject *owner = PyStackRef_AsPyObjectBorrow(owner_st); + PyTypeObject *cls = Py_TYPE(obj); + if ((cls->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) { + return false; + } + if (cls->tp_flags & Py_TPFLAGS_INLINE_VALUES) { + PyDictKeysObject *keys = ((PyHeapTypeObject *)cls)->ht_cached_keys; + Py_ssize_t index = _PyDictKeys_StringLookup(keys, name); + return index >= 0; + } + PyDictObject *dict = _PyObject_GetManagedDict(obj); + if (dict == NULL || !PyDict_CheckExact(dict)) { + return false; + } + if (dict->ma_values) { + return false; + } + Py_ssize_t index = _PyDict_LookupIndex(dict, name); + if (index < 0) { + return false; + } + return true; +} - assert(ENABLE_SPECIALIZATION); - assert(_PyOpcode_Caches[LOAD_ATTR] == INLINE_CACHE_ENTRIES_LOAD_ATTR); +static int +specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name) +{ _PyAttrCache *cache = (_PyAttrCache *)(instr + 1); PyTypeObject *type = Py_TYPE(owner); - if (!_PyType_IsReady(type)) { - // We *might* not really need this check, but we inherited it from - // PyObject_GenericGetAttr and friends... and this way we still do the - // right thing if someone forgets to call PyType_Ready(type): - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER); - goto fail; - } - if (PyModule_CheckExact(owner)) { - if (specialize_module_load_attr(owner, instr, name)) - { - goto fail; - } - goto success; - } - if (PyType_Check(owner)) { - if (specialize_class_load_attr(owner, instr, name)) { - goto fail; - } - goto success; - } + bool shadow = instance_has_key(owner, name); PyObject *descr = NULL; DescriptorClassification kind = analyze_descriptor(type, name, &descr, 0); assert(descr != NULL || kind == ABSENT || kind == GETSET_OVERRIDDEN); if (type_get_version(type, LOAD_ATTR) == 0) { - goto fail; + return -1; } switch(kind) { case OVERRIDING: SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_OVERRIDING_DESCRIPTOR); - goto fail; + return -1; case METHOD: { + if (shadow) { + goto try_instance; + } int oparg = instr->op.arg; if (oparg & 1) { if (specialize_attr_loadclassattr(owner, instr, name, descr, kind, true)) { - goto success; + return 0; + } + else { + return -1; } } - else { - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METHOD); - } - goto fail; + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METHOD); + return -1; } case PROPERTY: { @@ -953,29 +972,29 @@ _Py_Specialize_LoadAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *nam PyObject *fget = ((_PyPropertyObject *)descr)->prop_get; if (fget == NULL) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR); - goto fail; + return -1; } if (!Py_IS_TYPE(fget, &PyFunction_Type)) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_PROPERTY_NOT_PY_FUNCTION); - goto fail; + return -1; } if (!function_check_args(fget, 1, LOAD_ATTR)) { - goto fail; + return -1; } if (instr->op.arg & 1) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METHOD); - goto fail; + return -1; } if (_PyInterpreterState_GET()->eval_frame) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER); - goto fail; + return -1; } assert(type->tp_version_tag != 0); write_u32(lm_cache->type_version, type->tp_version_tag); /* borrowed */ write_obj(lm_cache->descr, fget); instr->op.code = LOAD_ATTR_PROPERTY; - goto success; + return 0; } case OBJECT_SLOT: { @@ -984,22 +1003,22 @@ _Py_Specialize_LoadAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *nam Py_ssize_t offset = dmem->offset; if (!PyObject_TypeCheck(owner, member->d_common.d_type)) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR); - goto fail; + return -1; } if (dmem->flags & Py_AUDIT_READ) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_AUDITED_SLOT); - goto fail; + return -1; } if (offset != (uint16_t)offset) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_RANGE); - goto fail; + return -1; } assert(dmem->type == Py_T_OBJECT_EX || dmem->type == _Py_T_OBJECT); assert(offset > 0); cache->index = (uint16_t)offset; write_u32(cache->version, type->tp_version_tag); instr->op.code = LOAD_ATTR_SLOT; - goto success; + return 0; } case DUNDER_CLASS: { @@ -1008,83 +1027,115 @@ _Py_Specialize_LoadAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *nam cache->index = (uint16_t)offset; write_u32(cache->version, type->tp_version_tag); instr->op.code = LOAD_ATTR_SLOT; - goto success; + return 0; } case OTHER_SLOT: SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_NON_OBJECT_SLOT); - goto fail; + return -1; case MUTABLE: SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_MUTABLE_CLASS); - goto fail; + return -1; case GETSET_OVERRIDDEN: SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OVERRIDDEN); - goto fail; + return -1; case GETATTRIBUTE_IS_PYTHON_FUNCTION: { assert(type->tp_getattro == _Py_slot_tp_getattro); assert(Py_IS_TYPE(descr, &PyFunction_Type)); _PyLoadMethodCache *lm_cache = (_PyLoadMethodCache *)(instr + 1); if (!function_check_args(descr, 2, LOAD_ATTR)) { - goto fail; + return -1; } if (instr->op.arg & 1) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METHOD); - goto fail; + return -1; } uint32_t version = function_get_version(descr, LOAD_ATTR); if (version == 0) { - goto fail; + return -1; } if (_PyInterpreterState_GET()->eval_frame) { SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER); - goto fail; + return -1; } write_u32(lm_cache->keys_version, version); /* borrowed */ write_obj(lm_cache->descr, descr); write_u32(lm_cache->type_version, type->tp_version_tag); instr->op.code = LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN; - goto success; + return 0; } case BUILTIN_CLASSMETHOD: - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_BUILTIN_CLASS_METHOD_OBJ); - goto fail; case PYTHON_CLASSMETHOD: - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_CLASS_METHOD_OBJ); - goto fail; case NON_OVERRIDING: - SPECIALIZATION_FAIL(LOAD_ATTR, - (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) ? - SPEC_FAIL_ATTR_CLASS_ATTR_DESCRIPTOR : - SPEC_FAIL_ATTR_NOT_MANAGED_DICT); - goto fail; + if (shadow) { + goto try_instance; + } + return -1; case NON_DESCRIPTOR: + if (shadow) { + goto try_instance; + } if ((instr->op.arg & 1) == 0) { if (specialize_attr_loadclassattr(owner, instr, name, descr, kind, false)) { - goto success; + return 0; } } - else { - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_CLASS_ATTR_SIMPLE); - } - goto fail; + return -1; case ABSENT: - if (specialize_dict_access(owner, instr, type, kind, name, LOAD_ATTR, - LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_WITH_HINT)) - { - goto success; + if (shadow) { + goto try_instance; } + return 0; + } + Py_UNREACHABLE(); +try_instance: + if (specialize_dict_access(owner, instr, type, kind, name, LOAD_ATTR, + LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_WITH_HINT)) + { + return 0; + } + return -1; +} + +void +_Py_Specialize_LoadAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *name) +{ + _PyAttrCache *cache = (_PyAttrCache *)(instr + 1); + PyObject *owner = PyStackRef_AsPyObjectBorrow(owner_st); + + assert(ENABLE_SPECIALIZATION); + assert(_PyOpcode_Caches[LOAD_ATTR] == INLINE_CACHE_ENTRIES_LOAD_ATTR); + PyTypeObject *type = Py_TYPE(owner); + bool fail; + if (!_PyType_IsReady(type)) { + // We *might* not really need this check, but we inherited it from + // PyObject_GenericGetAttr and friends... and this way we still do the + // right thing if someone forgets to call PyType_Ready(type): + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OTHER); + fail = true; + } + else if (PyModule_CheckExact(owner)) { + fail = specialize_module_load_attr(owner, instr, name); + } + else if (PyType_Check(owner)) { + fail = specialize_class_load_attr(owner, instr, name); + } + else { + fail = specialize_instance_load_attr(owner, instr, name); + } + + if (fail) { + STAT_INC(LOAD_ATTR, failure); + assert(!PyErr_Occurred()); + instr->op.code = LOAD_ATTR; + cache->counter = adaptive_counter_backoff(cache->counter); + } + else { + STAT_INC(LOAD_ATTR, success); + assert(!PyErr_Occurred()); + cache->counter = adaptive_counter_cooldown(); } -fail: - STAT_INC(LOAD_ATTR, failure); - assert(!PyErr_Occurred()); - instr->op.code = LOAD_ATTR; - cache->counter = adaptive_counter_backoff(cache->counter); - return; -success: - STAT_INC(LOAD_ATTR, success); - assert(!PyErr_Occurred()); - cache->counter = adaptive_counter_cooldown(); } void @@ -1230,23 +1281,52 @@ static int specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name) { + assert(PyType_Check(owner)); + PyTypeObject *cls = (PyTypeObject *)owner; _PyLoadMethodCache *cache = (_PyLoadMethodCache *)(instr + 1); - if (!PyType_CheckExact(owner) || _PyType_Lookup(Py_TYPE(owner), name)) { - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_ATTRIBUTE); + if (Py_TYPE(cls)->tp_getattro != _Py_type_getattro) { + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_OVERRIDDEN); return -1; } + PyObject *metadescriptor = _PyType_Lookup(Py_TYPE(cls), name); + DescriptorClassification metakind = classify_descriptor(metadescriptor, false); + switch (metakind) { + case METHOD: + case NON_DESCRIPTOR: + case NON_OVERRIDING: + case BUILTIN_CLASSMETHOD: + case PYTHON_CLASSMETHOD: + case ABSENT: + break; + default: + SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_METACLASS_ATTRIBUTE); + return -1; + } PyObject *descr = NULL; DescriptorClassification kind = 0; - kind = analyze_descriptor((PyTypeObject *)owner, name, &descr, 0); - if (type_get_version((PyTypeObject *)owner, LOAD_ATTR) == 0) { + kind = analyze_descriptor(cls, name, &descr, 0); + if (type_get_version(cls, LOAD_ATTR) == 0) { return -1; } + bool metaclass_check = false; + if ((Py_TYPE(cls)->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) == 0) { + metaclass_check = true; + if (type_get_version(Py_TYPE(cls), LOAD_ATTR) == 0) { + return -1; + } + } switch (kind) { case METHOD: case NON_DESCRIPTOR: - write_u32(cache->type_version, ((PyTypeObject *)owner)->tp_version_tag); + write_u32(cache->type_version, cls->tp_version_tag); write_obj(cache->descr, descr); - instr->op.code = LOAD_ATTR_CLASS; + if (metaclass_check) { + write_u32(cache->keys_version, Py_TYPE(cls)->tp_version_tag); + instr->op.code = LOAD_ATTR_CLASS_WITH_METACLASS_CHECK; + } + else { + instr->op.code = LOAD_ATTR_CLASS; + } return 0; #ifdef Py_STATS case ABSENT: @@ -1273,11 +1353,7 @@ PyObject *descr, DescriptorClassification kind, bool is_method) assert((is_method && kind == METHOD) || (!is_method && kind == NON_DESCRIPTOR)); if (owner_cls->tp_flags & Py_TPFLAGS_INLINE_VALUES) { PyDictKeysObject *keys = ((PyHeapTypeObject *)owner_cls)->ht_cached_keys; - Py_ssize_t index = _PyDictKeys_StringLookup(keys, name); - if (index != DKIX_EMPTY) { - SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_ATTR_SHADOWED); - return 0; - } + assert(_PyDictKeys_StringLookup(keys, name) < 0); uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState( _PyInterpreterState_GET(), keys); if (keys_version == 0) {