]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-123040: Specialize shadowed `LOAD_ATTR`. (GH-123219)
authorMark Shannon <mark@hotpy.org>
Fri, 23 Aug 2024 09:22:35 +0000 (10:22 +0100)
committerGitHub <noreply@github.com>
Fri, 23 Aug 2024 09:22:35 +0000 (10:22 +0100)
Include/internal/pycore_opcode_metadata.h
Include/opcode_ids.h
Lib/_opcode_metadata.py
Python/bytecodes.c
Python/generated_cases.c.h
Python/opcode_targets.h
Python/specialize.c

index 236e6b414611f3d981b7763bee588556c9803c42..97a8e4a00a9d55baad8a6031939e7783fa5a499c 100644 (file)
@@ -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: \
index 1189712e12e21e22376e4dcd2f5ff17f5fd1c5fb..5ded0b41b4830ebe3de5cf364f2ccee77a6a36fb 100644 (file)
@@ -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
index 0f3d9734897559e44bee6e5fd6183cc86f29da6d..6e4b33921863cb15930482d3b6f54ab1255937f6 100644 (file)
@@ -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 = {
index bc418137a9f9e9830aa7bfe1bc09e9724d99615d..62f6853fedf9848f286a9a8591ab51908db02d21 100644 (file)
@@ -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));
index 181940d87fff704530ee6a6f5ee45c059d1e9fe0..feb16ccaad3795a17ac1e246d51f539ae891acdf 100644 (file)
             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;
index 0418105dee5926cc77a2c2849022752a1e4c283f..49f01ca2932ee22e659083618885ebb027129717 100644 (file)
@@ -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,
index 82f1a887bebb4aa2358b4daacf956eb2ddd75b2e..14f0c0756ff390f9a0a5845ffa2d2d3947bae3f6 100644 (file)
@@ -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 (PyUnicode_CompareWithASCIIString(name, "__class__") == 0) {
-            if (descriptor == _PyType_Lookup(&PyBaseObject_Type, name)) {
-                return DUNDER_CLASS;
-            }
-        }
-        if (store) {
-            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;
+    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);
-
-    assert(ENABLE_SPECIALIZATION);
-    assert(_PyOpcode_Caches[LOAD_ATTR] == INLINE_CACHE_ENTRIES_LOAD_ATTR);
-    _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;
+    PyTypeObject *cls = Py_TYPE(obj);
+    if ((cls->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
+        return false;
     }
-    if (PyModule_CheckExact(owner)) {
-        if (specialize_module_load_attr(owner, instr, name))
-        {
-            goto fail;
-        }
-        goto success;
+    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;
     }
-    if (PyType_Check(owner)) {
-        if (specialize_class_load_attr(owner, instr, name)) {
-            goto fail;
-        }
-        goto success;
+    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;
+}
+
+static int
+specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name)
+{
+    _PyAttrCache *cache = (_PyAttrCache *)(instr + 1);
+    PyTypeObject *type = Py_TYPE(owner);
+    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) {