https://github.com/python/cpython/commit/67fbfb42bd5dfe861d0c58d9e6c48d8eef033d24
commit: 67fbfb42bd5dfe861d0c58d9e6c48d8eef033d24
branch: main
author: Sam Gross <colesb...@gmail.com>
committer: colesbury <colesb...@gmail.com>
date: 2025-03-26T14:38:47-04:00
summary:

gh-131586: Avoid refcount contention in some "special" calls (#131588)

In the free threaded build, the `_PyObject_LookupSpecial()` call can lead to
reference count contention on the returned function object becuase it
doesn't use stackrefs. Refactor some of the callers to use
`_PyObject_MaybeCallSpecialNoArgs`, which uses stackrefs internally.

This fixes the scaling bottleneck in the "lookup_special" microbenchmark
in `ftscalingbench.py`. However, the are still some uses of
`_PyObject_LookupSpecial()` that need to be addressed in future PRs.

files:
M Include/internal/pycore_global_objects_fini_generated.h
M Include/internal/pycore_global_strings.h
M Include/internal/pycore_object.h
M Include/internal/pycore_runtime_init_generated.h
M Include/internal/pycore_stackref.h
M Include/internal/pycore_structs.h
M Include/internal/pycore_tstate.h
M Include/internal/pycore_unicodeobject_generated.h
M Lib/test/test_bool.py
M Lib/test/test_builtin.py
M Lib/test/test_math.py
M Modules/mathmodule.c
M Objects/object.c
M Objects/typeobject.c
M Python/bltinmodule.c
M Python/gc_free_threading.c

diff --git a/Include/internal/pycore_global_objects_fini_generated.h 
b/Include/internal/pycore_global_objects_fini_generated.h
index 3579481ba43ba6..1a2e387f894865 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -598,6 +598,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__bytes__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__call__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__cantrace__));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__ceil__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__class__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__class_getitem__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__classcell__));
@@ -622,6 +623,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__file__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__firstlineno__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__float__));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__floor__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__floordiv__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__format__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__fspath__));
@@ -727,6 +729,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__subclasscheck__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__subclasshook__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__truediv__));
+    _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__trunc__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__type_params__));
     _PyStaticObject_CheckRefcnt((PyObject 
*)&_Py_ID(__typing_is_unpacked_typevartuple__));
     _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(__typing_prepare_subst__));
diff --git a/Include/internal/pycore_global_strings.h 
b/Include/internal/pycore_global_strings.h
index d83732fc43d65d..2177f8dc1a6a06 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -89,6 +89,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(__bytes__)
         STRUCT_FOR_ID(__call__)
         STRUCT_FOR_ID(__cantrace__)
+        STRUCT_FOR_ID(__ceil__)
         STRUCT_FOR_ID(__class__)
         STRUCT_FOR_ID(__class_getitem__)
         STRUCT_FOR_ID(__classcell__)
@@ -113,6 +114,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(__file__)
         STRUCT_FOR_ID(__firstlineno__)
         STRUCT_FOR_ID(__float__)
+        STRUCT_FOR_ID(__floor__)
         STRUCT_FOR_ID(__floordiv__)
         STRUCT_FOR_ID(__format__)
         STRUCT_FOR_ID(__fspath__)
@@ -218,6 +220,7 @@ struct _Py_global_strings {
         STRUCT_FOR_ID(__subclasscheck__)
         STRUCT_FOR_ID(__subclasshook__)
         STRUCT_FOR_ID(__truediv__)
+        STRUCT_FOR_ID(__trunc__)
         STRUCT_FOR_ID(__type_params__)
         STRUCT_FOR_ID(__typing_is_unpacked_typevartuple__)
         STRUCT_FOR_ID(__typing_prepare_subst__)
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 5e900f5bb91c99..e5034ff4dcc42b 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -891,6 +891,12 @@ extern bool _PyObject_TryGetInstanceAttribute(PyObject 
*obj, PyObject *name,
 extern PyObject *_PyType_LookupRefAndVersion(PyTypeObject *, PyObject *,
                                              unsigned int *);
 
+// Internal API to look for a name through the MRO.
+// This stores a stack reference in out and returns the value of
+// type->tp_version or zero if name is missing. It doesn't set an exception!
+extern unsigned int
+_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, 
_PyStackRef *out);
+
 // Cache the provided init method in the specialization cache of type if the
 // provided type version matches the current version of the type.
 //
@@ -946,6 +952,14 @@ extern int _PyObject_IsInstanceDictEmpty(PyObject *);
 PyAPI_FUNC(PyObject*) _PyObject_LookupSpecial(PyObject *, PyObject *);
 PyAPI_FUNC(PyObject*) _PyObject_LookupSpecialMethod(PyObject *self, PyObject 
*attr, PyObject **self_or_null);
 
+// Calls the method named `attr` on `self`, but does not set an exception if
+// the attribute does not exist.
+PyAPI_FUNC(PyObject *)
+_PyObject_MaybeCallSpecialNoArgs(PyObject *self, PyObject *attr);
+
+PyAPI_FUNC(PyObject *)
+_PyObject_MaybeCallSpecialOneArg(PyObject *self, PyObject *attr, PyObject 
*arg);
+
 extern int _PyObject_IsAbstract(PyObject *);
 
 PyAPI_FUNC(int) _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject 
**method);
diff --git a/Include/internal/pycore_runtime_init_generated.h 
b/Include/internal/pycore_runtime_init_generated.h
index 554fbdac5b0f51..2582d32b88372a 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -596,6 +596,7 @@ extern "C" {
     INIT_ID(__bytes__), \
     INIT_ID(__call__), \
     INIT_ID(__cantrace__), \
+    INIT_ID(__ceil__), \
     INIT_ID(__class__), \
     INIT_ID(__class_getitem__), \
     INIT_ID(__classcell__), \
@@ -620,6 +621,7 @@ extern "C" {
     INIT_ID(__file__), \
     INIT_ID(__firstlineno__), \
     INIT_ID(__float__), \
+    INIT_ID(__floor__), \
     INIT_ID(__floordiv__), \
     INIT_ID(__format__), \
     INIT_ID(__fspath__), \
@@ -725,6 +727,7 @@ extern "C" {
     INIT_ID(__subclasscheck__), \
     INIT_ID(__subclasshook__), \
     INIT_ID(__truediv__), \
+    INIT_ID(__trunc__), \
     INIT_ID(__type_params__), \
     INIT_ID(__typing_is_unpacked_typevartuple__), \
     INIT_ID(__typing_prepare_subst__), \
diff --git a/Include/internal/pycore_stackref.h 
b/Include/internal/pycore_stackref.h
index 9eba92df5cae55..1a68e0e29da8f1 100644
--- a/Include/internal/pycore_stackref.h
+++ b/Include/internal/pycore_stackref.h
@@ -592,7 +592,7 @@ PyStackRef_XCLOSE(_PyStackRef ref)
 
 // Note: this is a macro because MSVC (Windows) has trouble inlining it.
 
-#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_REFCNT)) == ((b).bits & 
(~Py_TAG_REFCNT)))
+#define PyStackRef_Is(a, b) (((a).bits & (~Py_TAG_BITS)) == ((b).bits & 
(~Py_TAG_BITS)))
 
 #endif // !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
 
@@ -640,6 +640,28 @@ PyStackRef_FunctionCheck(_PyStackRef stackref)
     return PyFunction_Check(PyStackRef_AsPyObjectBorrow(stackref));
 }
 
+static inline void
+_PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
+{
+#ifdef Py_GIL_DISABLED
+    _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
+    ref->next = tstate_impl->c_stack_refs;
+    tstate_impl->c_stack_refs = ref;
+#endif
+    ref->ref = PyStackRef_NULL;
+}
+
+static inline void
+_PyThreadState_PopCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
+{
+#ifdef Py_GIL_DISABLED
+    _PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
+    assert(tstate_impl->c_stack_refs == ref);
+    tstate_impl->c_stack_refs = ref->next;
+#endif
+    PyStackRef_XCLOSE(ref->ref);
+}
+
 #ifdef Py_GIL_DISABLED
 
 static inline int
@@ -656,6 +678,17 @@ _Py_TryIncrefCompareStackRef(PyObject **src, PyObject *op, 
_PyStackRef *out)
     return 0;
 }
 
+static inline int
+_Py_TryXGetStackRef(PyObject **src, _PyStackRef *out)
+{
+    PyObject *op = _Py_atomic_load_ptr_relaxed(src);
+    if (op == NULL) {
+        *out = PyStackRef_NULL;
+        return 1;
+    }
+    return _Py_TryIncrefCompareStackRef(src, op, out);
+}
+
 #endif
 
 // Like Py_VISIT but for _PyStackRef fields
diff --git a/Include/internal/pycore_structs.h 
b/Include/internal/pycore_structs.h
index 90963f0f8b0dfb..b54d61197ad789 100644
--- a/Include/internal/pycore_structs.h
+++ b/Include/internal/pycore_structs.h
@@ -65,6 +65,16 @@ typedef union _PyStackRef {
 #endif
 } _PyStackRef;
 
+// A stackref that can be stored in a regular C local variable and be visible
+// to the GC in the free threading build.
+// Used in combination with _PyThreadState_PushCStackRef().
+typedef struct _PyCStackRef {
+    _PyStackRef ref;
+#ifdef Py_GIL_DISABLED
+    struct _PyCStackRef *next;
+#endif
+} _PyCStackRef;
+
 
 #ifdef __cplusplus
 }
diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h
index b73cd6828b5199..bad968428c73a1 100644
--- a/Include/internal/pycore_tstate.h
+++ b/Include/internal/pycore_tstate.h
@@ -47,8 +47,9 @@ typedef struct _PyThreadStateImpl {
     struct _qsbr_thread_state *qsbr;  // only used by free-threaded build
     struct llist_node mem_free_queue; // delayed free queue
 
-
 #ifdef Py_GIL_DISABLED
+    // Stack references for the current thread that exist on the C stack
+    struct _PyCStackRef *c_stack_refs;
     struct _gc_thread_state gc;
     struct _mimalloc_thread_state mimalloc;
     struct _Py_freelists freelists;
diff --git a/Include/internal/pycore_unicodeobject_generated.h 
b/Include/internal/pycore_unicodeobject_generated.h
index 58b36977485988..2d26ca6949551e 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -144,6 +144,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(__ceil__);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(__class__);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -240,6 +244,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(__floor__);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(__floordiv__);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
@@ -660,6 +668,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
     assert(PyUnicode_GET_LENGTH(string) != 1);
+    string = &_Py_ID(__trunc__);
+    _PyUnicode_InternStatic(interp, &string);
+    assert(_PyUnicode_CheckConsistency(string, 1));
+    assert(PyUnicode_GET_LENGTH(string) != 1);
     string = &_Py_ID(__type_params__);
     _PyUnicode_InternStatic(interp, &string);
     assert(_PyUnicode_CheckConsistency(string, 1));
diff --git a/Lib/test/test_bool.py b/Lib/test/test_bool.py
index 34ecb45f161dfe..dcdf7bdce03b80 100644
--- a/Lib/test/test_bool.py
+++ b/Lib/test/test_bool.py
@@ -383,6 +383,10 @@ def __len__(self):
             __bool__ = None
         self.assertRaises(TypeError, bool, B())
 
+        class C:
+            __len__ = None
+        self.assertRaises(TypeError, bool, C())
+
     def test_real_and_imag(self):
         self.assertEqual(True.real, 1)
         self.assertEqual(True.imag, 0)
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py
index 0cc172c8b77237..5546e6345e9208 100644
--- a/Lib/test/test_builtin.py
+++ b/Lib/test/test_builtin.py
@@ -1746,6 +1746,11 @@ def test_repr(self):
         a[0] = a
         self.assertEqual(repr(a), '{0: {...}}')
 
+    def test_repr_blocked(self):
+        class C:
+            __repr__ = None
+        self.assertRaises(TypeError, repr, C())
+
     def test_round(self):
         self.assertEqual(round(0.0), 0.0)
         self.assertEqual(type(round(0.0)), int)
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index b4f5dd80f55f86..bfc55e7bbaba0a 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -573,6 +573,8 @@ def testFloor(self):
         #self.assertEqual(math.ceil(NINF), NINF)
         #self.assertTrue(math.isnan(math.floor(NAN)))
 
+        class TestFloorIsNone(float):
+            __floor__ = None
         class TestFloor:
             def __floor__(self):
                 return 42
@@ -588,6 +590,7 @@ class TestBadFloor:
         self.assertEqual(math.floor(FloatLike(41.9)), 41)
         self.assertRaises(TypeError, math.floor, TestNoFloor())
         self.assertRaises(ValueError, math.floor, TestBadFloor())
+        self.assertRaises(TypeError, math.floor, TestFloorIsNone(3.5))
 
         t = TestNoFloor()
         t.__floor__ = lambda *args: args
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index b4c15a143f9838..20d3c3192ba2e4 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -78,19 +78,6 @@ module math
 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=76bc7002685dd942]*/
 
 
-typedef struct {
-    PyObject *str___ceil__;
-    PyObject *str___floor__;
-    PyObject *str___trunc__;
-} math_module_state;
-
-static inline math_module_state*
-get_math_module_state(PyObject *module)
-{
-    void *state = _PyModule_GetState(module);
-    assert(state != NULL);
-    return (math_module_state *)state;
-}
 
 /*
 Double and triple length extended precision algorithms from:
@@ -1140,18 +1127,17 @@ math_ceil(PyObject *module, PyObject *number)
         x = PyFloat_AS_DOUBLE(number);
     }
     else {
-        math_module_state *state = get_math_module_state(module);
-        PyObject *method = _PyObject_LookupSpecial(number, 
state->str___ceil__);
-        if (method != NULL) {
-            PyObject *result = _PyObject_CallNoArgs(method);
-            Py_DECREF(method);
+        PyObject *result = _PyObject_MaybeCallSpecialNoArgs(number, 
&_Py_ID(__ceil__));
+        if (result != NULL) {
             return result;
         }
-        if (PyErr_Occurred())
+        else if (PyErr_Occurred()) {
             return NULL;
+        }
         x = PyFloat_AsDouble(number);
-        if (x == -1.0 && PyErr_Occurred())
+        if (x == -1.0 && PyErr_Occurred()) {
             return NULL;
+        }
     }
     return PyLong_FromDouble(ceil(x));
 }
@@ -1209,18 +1195,17 @@ math_floor(PyObject *module, PyObject *number)
         x = PyFloat_AS_DOUBLE(number);
     }
     else {
-        math_module_state *state = get_math_module_state(module);
-        PyObject *method = _PyObject_LookupSpecial(number, 
state->str___floor__);
-        if (method != NULL) {
-            PyObject *result = _PyObject_CallNoArgs(method);
-            Py_DECREF(method);
+        PyObject *result = _PyObject_MaybeCallSpecialNoArgs(number, 
&_Py_ID(__floor__));
+        if (result != NULL) {
             return result;
         }
-        if (PyErr_Occurred())
+        else if (PyErr_Occurred()) {
             return NULL;
+        }
         x = PyFloat_AsDouble(number);
-        if (x == -1.0 && PyErr_Occurred())
+        if (x == -1.0 && PyErr_Occurred()) {
             return NULL;
+        }
     }
     return PyLong_FromDouble(floor(x));
 }
@@ -2074,24 +2059,20 @@ static PyObject *
 math_trunc(PyObject *module, PyObject *x)
 /*[clinic end generated code: output=34b9697b707e1031 input=2168b34e0a09134d]*/
 {
-    PyObject *trunc, *result;
-
     if (PyFloat_CheckExact(x)) {
         return PyFloat_Type.tp_as_number->nb_int(x);
     }
 
-    math_module_state *state = get_math_module_state(module);
-    trunc = _PyObject_LookupSpecial(x, state->str___trunc__);
-    if (trunc == NULL) {
-        if (!PyErr_Occurred())
-            PyErr_Format(PyExc_TypeError,
-                         "type %.100s doesn't define __trunc__ method",
-                         Py_TYPE(x)->tp_name);
-        return NULL;
+    PyObject *result = _PyObject_MaybeCallSpecialNoArgs(x, &_Py_ID(__trunc__));
+    if (result != NULL) {
+        return result;
     }
-    result = _PyObject_CallNoArgs(trunc);
-    Py_DECREF(trunc);
-    return result;
+    else if (!PyErr_Occurred()) {
+        PyErr_Format(PyExc_TypeError,
+            "type %.100s doesn't define __trunc__ method",
+            Py_TYPE(x)->tp_name);
+    }
+    return NULL;
 }
 
 
@@ -4084,19 +4065,6 @@ static int
 math_exec(PyObject *module)
 {
 
-    math_module_state *state = get_math_module_state(module);
-    state->str___ceil__ = PyUnicode_InternFromString("__ceil__");
-    if (state->str___ceil__ == NULL) {
-        return -1;
-    }
-    state->str___floor__ = PyUnicode_InternFromString("__floor__");
-    if (state->str___floor__ == NULL) {
-        return -1;
-    }
-    state->str___trunc__ = PyUnicode_InternFromString("__trunc__");
-    if (state->str___trunc__ == NULL) {
-        return -1;
-    }
     if (PyModule_Add(module, "pi", PyFloat_FromDouble(Py_MATH_PI)) < 0) {
         return -1;
     }
@@ -4116,22 +4084,6 @@ math_exec(PyObject *module)
     return 0;
 }
 
-static int
-math_clear(PyObject *module)
-{
-    math_module_state *state = get_math_module_state(module);
-    Py_CLEAR(state->str___ceil__);
-    Py_CLEAR(state->str___floor__);
-    Py_CLEAR(state->str___trunc__);
-    return 0;
-}
-
-static void
-math_free(void *module)
-{
-    math_clear((PyObject *)module);
-}
-
 static PyMethodDef math_methods[] = {
     {"acos",            math_acos,      METH_O,         math_acos_doc},
     {"acosh",           math_acosh,     METH_O,         math_acosh_doc},
@@ -4208,11 +4160,9 @@ static struct PyModuleDef mathmodule = {
     PyModuleDef_HEAD_INIT,
     .m_name = "math",
     .m_doc = module_doc,
-    .m_size = sizeof(math_module_state),
+    .m_size = 0,
     .m_methods = math_methods,
     .m_slots = math_slots,
-    .m_clear = math_clear,
-    .m_free = math_free,
 };
 
 PyMODINIT_FUNC
diff --git a/Objects/object.c b/Objects/object.c
index ecc5a86901a347..974cf609cfccb4 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1680,14 +1680,20 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, 
PyObject *name,
                      Py_TYPE(name)->tp_name);
         return NULL;
     }
-    Py_INCREF(name);
 
     if (!_PyType_IsReady(tp)) {
         if (PyType_Ready(tp) < 0)
-            goto done;
+            return NULL;
     }
 
-    descr = _PyType_LookupRef(tp, name);
+    Py_INCREF(name);
+
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+
+    _PyType_LookupStackRefAndVersion(tp, name, &cref.ref);
+    descr = PyStackRef_AsPyObjectBorrow(cref.ref);
 
     f = NULL;
     if (descr != NULL) {
@@ -1758,8 +1764,8 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject 
*name,
     }
 
     if (descr != NULL) {
-        res = descr;
-        descr = NULL;
+        res = PyStackRef_AsPyObjectSteal(cref.ref);
+        cref.ref = PyStackRef_NULL;
         goto done;
     }
 
@@ -1771,7 +1777,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject 
*name,
         _PyObject_SetAttributeErrorContext(obj, name);
     }
   done:
-    Py_XDECREF(descr);
+    _PyThreadState_PopCStackRef(tstate, &cref);
     Py_DECREF(name);
     return res;
 }
@@ -1805,7 +1811,13 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject 
*name,
 
     Py_INCREF(name);
     Py_INCREF(tp);
-    descr = _PyType_LookupRef(tp, name);
+
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+
+    _PyType_LookupStackRefAndVersion(tp, name, &cref.ref);
+    descr = PyStackRef_AsPyObjectBorrow(cref.ref);
 
     if (descr != NULL) {
         f = Py_TYPE(descr)->tp_descr_set;
@@ -1872,7 +1884,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject 
*name,
         _PyObject_SetAttributeErrorContext(obj, name);
     }
   done:
-    Py_XDECREF(descr);
+    _PyThreadState_PopCStackRef(tstate, &cref);
     Py_DECREF(tp);
     Py_DECREF(name);
     return res;
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 4f44d590fb49aa..b92eaefc90d0af 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -103,13 +103,9 @@ releasebuffer_call_python(PyObject *self, Py_buffer 
*buffer);
 static PyObject *
 slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
 
-static PyObject *
-lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound);
-
 static int
 slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value);
 
-
 static inline PyTypeObject *
 type_from_ref(PyObject *ref)
 {
@@ -1146,8 +1142,29 @@ PyType_Modified(PyTypeObject *type)
 static int
 is_subtype_with_mro(PyObject *a_mro, PyTypeObject *a, PyTypeObject *b);
 
+// Check if the `mro` method on `type` is overridden, i.e.,
+// `type(tp).mro is not type.mro`.
+static int
+has_custom_mro(PyTypeObject *tp)
+{
+    _PyCStackRef c_ref1, c_ref2;
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyThreadState_PushCStackRef(tstate, &c_ref1);
+    _PyThreadState_PushCStackRef(tstate, &c_ref2);
+
+    _PyType_LookupStackRefAndVersion(Py_TYPE(tp), &_Py_ID(mro), &c_ref1.ref);
+    _PyType_LookupStackRefAndVersion(&PyType_Type, &_Py_ID(mro), &c_ref2.ref);
+
+    int custom = !PyStackRef_Is(c_ref1.ref, c_ref2.ref);
+
+    _PyThreadState_PopCStackRef(tstate, &c_ref2);
+    _PyThreadState_PopCStackRef(tstate, &c_ref1);
+    return custom;
+}
+
 static void
-type_mro_modified(PyTypeObject *type, PyObject *bases) {
+type_mro_modified(PyTypeObject *type, PyObject *bases)
+{
     /*
        Check that all base classes or elements of the MRO of type are
        able to be cached.  This function is called after the base
@@ -1161,29 +1178,10 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
        each subclass when their mro is recursively updated.
      */
     Py_ssize_t i, n;
-    int custom = !Py_IS_TYPE(type, &PyType_Type);
-    int unbound;
 
     ASSERT_TYPE_LOCK_HELD();
-    if (custom) {
-        PyObject *mro_meth, *type_mro_meth;
-        mro_meth = lookup_maybe_method(
-            (PyObject *)type, &_Py_ID(mro), &unbound);
-        if (mro_meth == NULL) {
-            goto clear;
-        }
-        type_mro_meth = lookup_maybe_method(
-            (PyObject *)&PyType_Type, &_Py_ID(mro), &unbound);
-        if (type_mro_meth == NULL) {
-            Py_DECREF(mro_meth);
-            goto clear;
-        }
-        int custom_mro = (mro_meth != type_mro_meth);
-        Py_DECREF(mro_meth);
-        Py_DECREF(type_mro_meth);
-        if (custom_mro) {
-            goto clear;
-        }
+    if (!Py_IS_TYPE(type, &PyType_Type) && has_custom_mro(type)) {
+        goto clear;
     }
     n = PyTuple_GET_SIZE(bases);
     for (i = 0; i < n; i++) {
@@ -1224,7 +1222,6 @@ This is similar to func_version_cache.
 void
 _PyType_SetVersion(PyTypeObject *tp, unsigned int version)
 {
-
     BEGIN_TYPE_LOCK();
     set_version_unlocked(tp, version);
     END_TYPE_LOCK();
@@ -2805,36 +2802,51 @@ _PyObject_LookupSpecialMethod(PyObject *self, PyObject 
*attr, PyObject **self_or
     return res;
 }
 
-static PyObject *
-lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound)
+static int
+lookup_method_ex(PyObject *self, PyObject *attr, _PyStackRef *out,
+                 int raise_attribute_error)
 {
-    PyObject *res = _PyType_LookupRef(Py_TYPE(self), attr);
-    if (res == NULL) {
-        return NULL;
+    _PyType_LookupStackRefAndVersion(Py_TYPE(self), attr, out);
+    if (PyStackRef_IsNull(*out)) {
+        if (raise_attribute_error) {
+            PyErr_SetObject(PyExc_AttributeError, attr);
+        }
+        return -1;
     }
 
-    if (_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
+    PyObject *value = PyStackRef_AsPyObjectBorrow(*out);
+    if (_PyType_HasFeature(Py_TYPE(value), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
         /* Avoid temporary PyMethodObject */
-        *unbound = 1;
+        return 1;
     }
-    else {
-        *unbound = 0;
-        descrgetfunc f = Py_TYPE(res)->tp_descr_get;
-        if (f != NULL) {
-            Py_SETREF(res, f(res, self, (PyObject *)(Py_TYPE(self))));
+
+    descrgetfunc f = Py_TYPE(value)->tp_descr_get;
+    if (f != NULL) {
+        value = f(value, self, (PyObject *)(Py_TYPE(self)));
+        PyStackRef_CLEAR(*out);
+        if (value == NULL) {
+            if (!raise_attribute_error &&
+                PyErr_ExceptionMatches(PyExc_AttributeError))
+            {
+                PyErr_Clear();
+            }
+            return -1;
         }
+        *out = PyStackRef_FromPyObjectSteal(value);
     }
-    return res;
+    return 0;
 }
 
-static PyObject *
-lookup_method(PyObject *self, PyObject *attr, int *unbound)
+static int
+lookup_maybe_method(PyObject *self, PyObject *attr, _PyStackRef *out)
 {
-    PyObject *res = lookup_maybe_method(self, attr, unbound);
-    if (res == NULL && !PyErr_Occurred()) {
-        PyErr_SetObject(PyExc_AttributeError, attr);
-    }
-    return res;
+    return lookup_method_ex(self, attr, out, 0);
+}
+
+static int
+lookup_method(PyObject *self, PyObject *attr, _PyStackRef *out)
+{
+    return lookup_method_ex(self, attr, out, 1);
 }
 
 
@@ -2864,6 +2876,45 @@ call_unbound_noarg(int unbound, PyObject *func, PyObject 
*self)
     }
 }
 
+// Call the method with the name `attr` on `self`. Returns NULL with an
+// exception set if the method is missing or an error occurs.
+static PyObject *
+call_method_noarg(PyObject *self, PyObject *attr)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+    PyObject *res = NULL;
+    int unbound = lookup_method(self, attr, &cref.ref);
+    if (unbound >= 0) {
+        PyObject *func = PyStackRef_AsPyObjectBorrow(cref.ref);
+        res = call_unbound_noarg(unbound, func, self);
+    }
+    _PyThreadState_PopCStackRef(tstate, &cref);
+    return res;
+}
+
+static PyObject *
+call_method(PyObject *self, PyObject *attr, PyObject *args, PyObject *kwds)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+    PyObject *res = NULL;
+    int unbound = lookup_method(self, attr, &cref.ref);
+    if (unbound >= 0) {
+        PyObject *meth = PyStackRef_AsPyObjectBorrow(cref.ref);
+        if (unbound) {
+            res = _PyObject_Call_Prepend(tstate, meth, self, args, kwds);
+        }
+        else {
+            res = _PyObject_Call(tstate, meth, args, kwds);
+        }
+    }
+    _PyThreadState_PopCStackRef(tstate, &cref);
+    return res;
+}
+
 /* A variation of PyObject_CallMethod* that uses lookup_method()
    instead of PyObject_GetAttrString().
 
@@ -2875,14 +2926,16 @@ vectorcall_method(PyObject *name, PyObject *const 
*args, Py_ssize_t nargs)
     assert(nargs >= 1);
 
     PyThreadState *tstate = _PyThreadState_GET();
-    int unbound;
+    PyObject *retval = NULL;
     PyObject *self = args[0];
-    PyObject *func = lookup_method(self, name, &unbound);
-    if (func == NULL) {
-        return NULL;
-    }
-    PyObject *retval = vectorcall_unbound(tstate, unbound, func, args, nargs);
-    Py_DECREF(func);
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+    int unbound = lookup_method(self, name, &cref.ref);
+    if (unbound >= 0) {
+        PyObject *func = PyStackRef_AsPyObjectBorrow(cref.ref);
+        retval = vectorcall_unbound(tstate, unbound, func, args, nargs);
+    }
+    _PyThreadState_PopCStackRef(tstate, &cref);
     return retval;
 }
 
@@ -2894,19 +2947,81 @@ vectorcall_maybe(PyThreadState *tstate, PyObject *name,
 {
     assert(nargs >= 1);
 
-    int unbound;
     PyObject *self = args[0];
-    PyObject *func = lookup_maybe_method(self, name, &unbound);
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+    int unbound = lookup_maybe_method(self, name, &cref.ref);
+    PyObject *func = PyStackRef_AsPyObjectBorrow(cref.ref);
     if (func == NULL) {
-        if (!PyErr_Occurred())
+        _PyThreadState_PopCStackRef(tstate, &cref);
+        if (!PyErr_Occurred()) {
             Py_RETURN_NOTIMPLEMENTED;
+        }
         return NULL;
     }
     PyObject *retval = vectorcall_unbound(tstate, unbound, func, args, nargs);
-    Py_DECREF(func);
+    _PyThreadState_PopCStackRef(tstate, &cref);
     return retval;
 }
 
+/* Call the method with the name `attr` on `self`. Returns NULL if the
+   method is missing or an error occurs.  No exception is set if
+   the method is missing. If attr_is_none is not NULL, it is set to 1 if
+   the attribute was found and was None, or 0 if it was not found. */
+static PyObject *
+maybe_call_special_no_args(PyObject *self, PyObject *attr, int *attr_is_none)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+
+    PyObject *res = NULL;
+    int unbound = lookup_maybe_method(self, attr, &cref.ref);
+    PyObject *func = PyStackRef_AsPyObjectBorrow(cref.ref);
+    if (attr_is_none != NULL) {
+        *attr_is_none = (func == Py_None);
+    }
+    if (func != NULL && (func != Py_None || attr_is_none == NULL)) {
+        res = call_unbound_noarg(unbound, func, self);
+    }
+    _PyThreadState_PopCStackRef(tstate, &cref);
+    return res;
+}
+
+static PyObject *
+maybe_call_special_one_arg(PyObject *self, PyObject *attr, PyObject *arg,
+                           int *attr_is_none)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
+
+    PyObject *res = NULL;
+    int unbound = lookup_maybe_method(self, attr, &cref.ref);
+    PyObject *func = PyStackRef_AsPyObjectBorrow(cref.ref);
+    if (attr_is_none != NULL) {
+        *attr_is_none = (func == Py_None);
+    }
+    if (func != NULL && (func != Py_None || attr_is_none == NULL)) {
+        PyObject *args[] = { self, arg };
+        res = vectorcall_unbound(tstate, unbound, func, args, 2);
+    }
+    _PyThreadState_PopCStackRef(tstate, &cref);
+    return res;
+}
+
+PyObject *
+_PyObject_MaybeCallSpecialNoArgs(PyObject *self, PyObject *attr)
+{
+    return maybe_call_special_no_args(self, attr, NULL);
+}
+
+PyObject *
+_PyObject_MaybeCallSpecialOneArg(PyObject *self, PyObject *attr, PyObject *arg)
+{
+    return maybe_call_special_one_arg(self, attr, arg, NULL);
+}
+
 /*
     Method resolution order algorithm C3 described in
     "A Monotonic Superclass Linearization for Dylan",
@@ -3288,13 +3403,7 @@ mro_invoke(PyTypeObject *type)
     const int custom = !Py_IS_TYPE(type, &PyType_Type);
 
     if (custom) {
-        int unbound;
-        PyObject *mro_meth = lookup_method(
-            (PyObject *)type, &_Py_ID(mro), &unbound);
-        if (mro_meth == NULL)
-            return NULL;
-        mro_result = call_unbound_noarg(unbound, mro_meth, (PyObject *)type);
-        Py_DECREF(mro_meth);
+        mro_result = call_method_noarg((PyObject *)type, &_Py_ID(mro));
     }
     else {
         mro_result = mro_implementation_unlocked(type);
@@ -5611,10 +5720,20 @@ _PyTypes_AfterFork(void)
 PyObject *
 _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int 
*version)
 {
-    PyObject *res;
-    int error;
-    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyStackRef out;
+    unsigned int ver = _PyType_LookupStackRefAndVersion(type, name, &out);
+    if (version) {
+        *version = ver;
+    }
+    if (PyStackRef_IsNull(out)) {
+        return NULL;
+    }
+    return PyStackRef_AsPyObjectSteal(out);
+}
 
+unsigned int
+_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, 
_PyStackRef *out)
+{
     unsigned int h = MCACHE_HASH_METHOD(type, name);
     struct type_cache *cache = get_type_cache();
     struct type_cache_entry *entry = &cache->hashtable[h];
@@ -5628,16 +5747,12 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, 
PyObject *name, unsigned int *ve
             _Py_atomic_load_ptr_relaxed(&entry->name) == name) {
             OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name));
             OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name));
-            PyObject *value = _Py_atomic_load_ptr_relaxed(&entry->value);
-            // If the sequence is still valid then we're done
-            if (value == NULL || _Py_TryIncref(value)) {
+            if (_Py_TryXGetStackRef(&entry->value, out)) {
+                // If the sequence is still valid then we're done
                 if (_PySeqLock_EndRead(&entry->sequence, sequence)) {
-                    if (version != NULL) {
-                        *version = entry_version;
-                    }
-                    return value;
+                    return entry_version;
                 }
-                Py_XDECREF(value);
+                PyStackRef_XCLOSE(*out);
             }
             else {
                 // If we can't incref the object we need to fallback to locking
@@ -5650,16 +5765,12 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, 
PyObject *name, unsigned int *ve
         }
     }
 #else
-    if (entry->version == type->tp_version_tag &&
-        entry->name == name) {
+    if (entry->version == type->tp_version_tag && entry->name == name) {
         assert(type->tp_version_tag);
         OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name));
         OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name));
-        Py_XINCREF(entry->value);
-        if (version != NULL) {
-            *version = entry->version;
-        }
-        return entry->value;
+        *out = entry->value ? PyStackRef_FromPyObjectNew(entry->value) : 
PyStackRef_NULL;
+        return entry->version;
     }
 #endif
     OBJECT_STAT_INC_COND(type_cache_misses, !is_dunder_name(name));
@@ -5671,6 +5782,9 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject 
*name, unsigned int *ve
     // We need to atomically do the lookup and capture the version before
     // anyone else can modify our mro or mutate the type.
 
+    PyObject *res;
+    int error;
+    PyInterpreterState *interp = _PyInterpreterState_GET();
     int has_version = 0;
     unsigned int assigned_version = 0;
     BEGIN_TYPE_LOCK();
@@ -5694,11 +5808,8 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject 
*name, unsigned int *ve
         if (error == -1) {
             PyErr_Clear();
         }
-        if (version != NULL) {
-            // 0 is not a valid version
-            *version = 0;
-        }
-        return NULL;
+        *out = PyStackRef_NULL;
+        return 0;
     }
 
     if (has_version) {
@@ -5709,11 +5820,8 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject 
*name, unsigned int *ve
         Py_DECREF(old_value);
 #endif
     }
-    if (version != NULL) {
-        // 0 is not a valid version
-        *version = has_version ? assigned_version : 0;
-    }
-    return res;
+    *out = res ? PyStackRef_FromPyObjectSteal(res) : PyStackRef_NULL;
+    return has_version ? assigned_version : 0;
 }
 
 /* Internal API to look for a name through the MRO.
@@ -9802,32 +9910,23 @@ slot_sq_ass_item(PyObject *self, Py_ssize_t index, 
PyObject *value)
 static int
 slot_sq_contains(PyObject *self, PyObject *value)
 {
-    PyThreadState *tstate = _PyThreadState_GET();
-    PyObject *func, *res;
-    int result = -1, unbound;
-
-    func = lookup_maybe_method(self, &_Py_ID(__contains__), &unbound);
-    if (func == Py_None) {
-        Py_DECREF(func);
+    int attr_is_none = 0;
+    PyObject *res = maybe_call_special_one_arg(self, &_Py_ID(__contains__), 
value,
+                                               &attr_is_none);
+    if (attr_is_none) {
         PyErr_Format(PyExc_TypeError,
-                     "'%.200s' object is not a container",
-                     Py_TYPE(self)->tp_name);
+            "'%.200s' object is not a container",
+            Py_TYPE(self)->tp_name);
         return -1;
     }
-    if (func != NULL) {
-        PyObject *args[2] = {self, value};
-        res = vectorcall_unbound(tstate, unbound, func, args, 2);
-        Py_DECREF(func);
-        if (res != NULL) {
-            result = PyObject_IsTrue(res);
-            Py_DECREF(res);
-        }
+    else if (res == NULL && PyErr_Occurred()) {
+        return -1;
     }
-    else if (! PyErr_Occurred()) {
-        /* Possible results: -1 and 1 */
-        result = (int)_PySequence_IterSearch(self, value,
-                                         PY_ITERSEARCH_CONTAINS);
+    else if (res == NULL) {
+        return (int)_PySequence_IterSearch(self, value, 
PY_ITERSEARCH_CONTAINS);
     }
+    int result = PyObject_IsTrue(res);
+    Py_DECREF(res);
     return result;
 }
 
@@ -9891,31 +9990,29 @@ SLOT0(slot_nb_absolute, __abs__)
 static int
 slot_nb_bool(PyObject *self)
 {
-    PyObject *func, *value;
-    int result, unbound;
     int using_len = 0;
-
-    func = lookup_maybe_method(self, &_Py_ID(__bool__), &unbound);
-    if (func == NULL) {
-        if (PyErr_Occurred()) {
-            return -1;
-        }
-
-        func = lookup_maybe_method(self, &_Py_ID(__len__), &unbound);
-        if (func == NULL) {
-            if (PyErr_Occurred()) {
-                return -1;
-            }
+    int attr_is_none = 0;
+    PyObject *value = maybe_call_special_no_args(self, &_Py_ID(__bool__),
+                                                 &attr_is_none);
+    if (attr_is_none) {
+        PyErr_Format(PyExc_TypeError,
+                     "'%.200s' cannot be interpreted as a boolean",
+                     Py_TYPE(self)->tp_name);
+        return -1;
+    }
+    else if (value == NULL && !PyErr_Occurred()) {
+        value = _PyObject_MaybeCallSpecialNoArgs(self, &_Py_ID(__len__));
+        if (value == NULL && !PyErr_Occurred()) {
             return 1;
         }
         using_len = 1;
     }
 
-    value = call_unbound_noarg(unbound, func, self);
     if (value == NULL) {
-        goto error;
+        return -1;
     }
 
+    int result;
     if (using_len) {
         /* bool type enforced by slot_nb_len */
         result = PyObject_IsTrue(value);
@@ -9930,14 +10027,8 @@ slot_nb_bool(PyObject *self)
                      Py_TYPE(value)->tp_name);
         result = -1;
     }
-
     Py_DECREF(value);
-    Py_DECREF(func);
     return result;
-
-error:
-    Py_DECREF(func);
-    return -1;
 }
 
 
@@ -9984,18 +10075,15 @@ SLOT1(slot_nb_inplace_true_divide, __itruediv__, 
PyObject *)
 static PyObject *
 slot_tp_repr(PyObject *self)
 {
-    PyObject *func, *res;
-    int unbound;
-
-    func = lookup_maybe_method(self, &_Py_ID(__repr__), &unbound);
-    if (func != NULL) {
-        res = call_unbound_noarg(unbound, func, self);
-        Py_DECREF(func);
+    PyObject *res = _PyObject_MaybeCallSpecialNoArgs(self, &_Py_ID(__repr__));
+    if (res != NULL) {
         return res;
     }
-    PyErr_Clear();
+    else if (PyErr_Occurred()) {
+        return NULL;
+    }
     return PyUnicode_FromFormat("<%s object at %p>",
-                               Py_TYPE(self)->tp_name, self);
+                                Py_TYPE(self)->tp_name, self);
 }
 
 SLOT0(slot_tp_str, __str__)
@@ -10003,25 +10091,15 @@ SLOT0(slot_tp_str, __str__)
 static Py_hash_t
 slot_tp_hash(PyObject *self)
 {
-    PyObject *func, *res;
-    Py_ssize_t h;
-    int unbound;
-
-    func = lookup_maybe_method(self, &_Py_ID(__hash__), &unbound);
-
-    if (func == Py_None) {
-        Py_SETREF(func, NULL);
-    }
-
-    if (func == NULL) {
+    PyObject *res;
+    int attr_is_none = 0;
+    res  = maybe_call_special_no_args(self, &_Py_ID(__hash__), &attr_is_none);
+    if (attr_is_none || res == NULL) {
+        if (PyErr_Occurred()) {
+            return -1;
+        }
         return PyObject_HashNotImplemented(self);
     }
-
-    res = call_unbound_noarg(unbound, func, self);
-    Py_DECREF(func);
-    if (res == NULL)
-        return -1;
-
     if (!PyLong_Check(res)) {
         PyErr_SetString(PyExc_TypeError,
                         "__hash__ method should return an integer");
@@ -10032,7 +10110,7 @@ slot_tp_hash(PyObject *self)
        Py_hash_t.  Therefore our transformation must preserve values that
        already lie within this range, to ensure that if x.__hash__() returns
        hash(y) then hash(x) == hash(y). */
-    h = PyLong_AsSsize_t(res);
+    Py_ssize_t h = PyLong_AsSsize_t(res);
     if (h == -1 && PyErr_Occurred()) {
         /* res was not within the range of a Py_hash_t, so we're free to
            use any sufficiently bit-mixing transformation;
@@ -10050,24 +10128,7 @@ slot_tp_hash(PyObject *self)
 static PyObject *
 slot_tp_call(PyObject *self, PyObject *args, PyObject *kwds)
 {
-    PyThreadState *tstate = _PyThreadState_GET();
-    int unbound;
-
-    PyObject *meth = lookup_method(self, &_Py_ID(__call__), &unbound);
-    if (meth == NULL) {
-        return NULL;
-    }
-
-    PyObject *res;
-    if (unbound) {
-        res = _PyObject_Call_Prepend(tstate, meth, self, args, kwds);
-    }
-    else {
-        res = _PyObject_Call(tstate, meth, args, kwds);
-    }
-
-    Py_DECREF(meth);
-    return res;
+    return call_method(self, &_Py_ID(__call__), args, kwds);
 }
 
 /* There are two slot dispatch functions for tp_getattro.
@@ -10194,51 +10255,46 @@ static PyObject *name_op[] = {
 static PyObject *
 slot_tp_richcompare(PyObject *self, PyObject *other, int op)
 {
-    PyThreadState *tstate = _PyThreadState_GET();
-
-    int unbound;
-    PyObject *func = lookup_maybe_method(self, name_op[op], &unbound);
-    if (func == NULL) {
-        PyErr_Clear();
+    PyObject *res = _PyObject_MaybeCallSpecialOneArg(self, name_op[op], other);
+    if (res == NULL) {
+        if (PyErr_Occurred()) {
+            return NULL;
+        }
         Py_RETURN_NOTIMPLEMENTED;
     }
-
-    PyObject *stack[2] = {self, other};
-    PyObject *res = vectorcall_unbound(tstate, unbound, func, stack, 2);
-    Py_DECREF(func);
     return res;
 }
 
+static int
+has_dunder_getitem(PyObject *self)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef c_ref;
+    _PyThreadState_PushCStackRef(tstate, &c_ref);
+    lookup_maybe_method(self, &_Py_ID(__getitem__), &c_ref.ref);
+    int has_dunder_getitem = !PyStackRef_IsNull(c_ref.ref);
+    _PyThreadState_PopCStackRef(tstate, &c_ref);
+    return has_dunder_getitem;
+}
+
 static PyObject *
 slot_tp_iter(PyObject *self)
 {
-    int unbound;
-    PyObject *func, *res;
-
-    func = lookup_maybe_method(self, &_Py_ID(__iter__), &unbound);
-    if (func == Py_None) {
-        Py_DECREF(func);
-        PyErr_Format(PyExc_TypeError,
-                     "'%.200s' object is not iterable",
-                     Py_TYPE(self)->tp_name);
-        return NULL;
-    }
-
-    if (func != NULL) {
-        res = call_unbound_noarg(unbound, func, self);
-        Py_DECREF(func);
+    int attr_is_none = 0;
+    PyObject *res = maybe_call_special_no_args(self, &_Py_ID(__iter__),
+                                               &attr_is_none);
+    if (res != NULL) {
         return res;
     }
-
-    PyErr_Clear();
-    func = lookup_maybe_method(self, &_Py_ID(__getitem__), &unbound);
-    if (func == NULL) {
+    else if (PyErr_Occurred()) {
+        return NULL;
+    }
+    else if (attr_is_none || !has_dunder_getitem(self)) {
         PyErr_Format(PyExc_TypeError,
-                     "'%.200s' object is not iterable",
-                     Py_TYPE(self)->tp_name);
+            "'%.200s' object is not iterable",
+            Py_TYPE(self)->tp_name);
         return NULL;
     }
-    Py_DECREF(func);
     return PySeqIter_New(self);
 }
 
@@ -10296,22 +10352,7 @@ slot_tp_descr_set(PyObject *self, PyObject *target, 
PyObject *value)
 static int
 slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
 {
-    PyThreadState *tstate = _PyThreadState_GET();
-
-    int unbound;
-    PyObject *meth = lookup_method(self, &_Py_ID(__init__), &unbound);
-    if (meth == NULL) {
-        return -1;
-    }
-
-    PyObject *res;
-    if (unbound) {
-        res = _PyObject_Call_Prepend(tstate, meth, self, args, kwds);
-    }
-    else {
-        res = _PyObject_Call(tstate, meth, args, kwds);
-    }
-    Py_DECREF(meth);
+    PyObject *res = call_method(self, &_Py_ID(__init__), args, kwds);
     if (res == NULL)
         return -1;
     if (res != Py_None) {
@@ -10344,16 +10385,18 @@ slot_tp_new(PyTypeObject *type, PyObject *args, 
PyObject *kwds)
 static void
 slot_tp_finalize(PyObject *self)
 {
-    int unbound;
-    PyObject *del, *res;
-
     /* Save the current exception, if any. */
-    PyObject *exc = PyErr_GetRaisedException();
+    PyThreadState *tstate = _PyThreadState_GET();
+    PyObject *exc = _PyErr_GetRaisedException(tstate);
+
+    _PyCStackRef cref;
+    _PyThreadState_PushCStackRef(tstate, &cref);
 
     /* Execute __del__ method, if any. */
-    del = lookup_maybe_method(self, &_Py_ID(__del__), &unbound);
-    if (del != NULL) {
-        res = call_unbound_noarg(unbound, del, self);
+    int unbound = lookup_maybe_method(self, &_Py_ID(__del__), &cref.ref);
+    if (unbound >= 0) {
+        PyObject *del = PyStackRef_AsPyObjectBorrow(cref.ref);
+        PyObject *res = call_unbound_noarg(unbound, del, self);
         if (res == NULL) {
             PyErr_FormatUnraisable("Exception ignored while "
                                    "calling deallocator %R", del);
@@ -10361,11 +10404,12 @@ slot_tp_finalize(PyObject *self)
         else {
             Py_DECREF(res);
         }
-        Py_DECREF(del);
     }
 
+    _PyThreadState_PopCStackRef(tstate, &cref);
+
     /* Restore the saved exception. */
-    PyErr_SetRaisedException(exc);
+    _PyErr_SetRaisedException(tstate, exc);
 }
 
 typedef struct _PyBufferWrapper {
@@ -10614,57 +10658,33 @@ slot_bf_releasebuffer(PyObject *self, Py_buffer 
*buffer)
 }
 
 static PyObject *
-slot_am_await(PyObject *self)
+slot_am_generic(PyObject *self, PyObject *name)
 {
-    int unbound;
-    PyObject *func, *res;
-
-    func = lookup_maybe_method(self, &_Py_ID(__await__), &unbound);
-    if (func != NULL) {
-        res = call_unbound_noarg(unbound, func, self);
-        Py_DECREF(func);
-        return res;
+    PyObject *res = _PyObject_MaybeCallSpecialNoArgs(self, name);
+    if (res == NULL && !PyErr_Occurred()) {
+        PyErr_Format(PyExc_AttributeError,
+            "object %.50s does not have %U method",
+            Py_TYPE(self)->tp_name, name);
     }
-    PyErr_Format(PyExc_AttributeError,
-                 "object %.50s does not have __await__ method",
-                 Py_TYPE(self)->tp_name);
-    return NULL;
+    return res;
 }
 
 static PyObject *
-slot_am_aiter(PyObject *self)
+slot_am_await(PyObject *self)
 {
-    int unbound;
-    PyObject *func, *res;
+    return slot_am_generic(self, &_Py_ID(__await__));
+}
 
-    func = lookup_maybe_method(self, &_Py_ID(__aiter__), &unbound);
-    if (func != NULL) {
-        res = call_unbound_noarg(unbound, func, self);
-        Py_DECREF(func);
-        return res;
-    }
-    PyErr_Format(PyExc_AttributeError,
-                 "object %.50s does not have __aiter__ method",
-                 Py_TYPE(self)->tp_name);
-    return NULL;
+static PyObject *
+slot_am_aiter(PyObject *self)
+{
+    return slot_am_generic(self, &_Py_ID(__aiter__));
 }
 
 static PyObject *
 slot_am_anext(PyObject *self)
 {
-    int unbound;
-    PyObject *func, *res;
-
-    func = lookup_maybe_method(self, &_Py_ID(__anext__), &unbound);
-    if (func != NULL) {
-        res = call_unbound_noarg(unbound, func, self);
-        Py_DECREF(func);
-        return res;
-    }
-    PyErr_Format(PyExc_AttributeError,
-                 "object %.50s does not have __anext__ method",
-                 Py_TYPE(self)->tp_name);
-    return NULL;
+    return slot_am_generic(self, &_Py_ID(__anext__));
 }
 
 /*
diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c
index f7761207950705..7a12a86e22e71c 100644
--- a/Python/bltinmodule.c
+++ b/Python/bltinmodule.c
@@ -2537,22 +2537,19 @@ static PyObject *
 builtin_round_impl(PyObject *module, PyObject *number, PyObject *ndigits)
 /*[clinic end generated code: output=ff0d9dd176c02ede input=275678471d7aca15]*/
 {
-    PyObject *round, *result;
-
-    round = _PyObject_LookupSpecial(number, &_Py_ID(__round__));
-    if (round == NULL) {
-        if (!PyErr_Occurred())
-            PyErr_Format(PyExc_TypeError,
-                         "type %.100s doesn't define __round__ method",
-                         Py_TYPE(number)->tp_name);
-        return NULL;
+    PyObject *result;
+    if (ndigits == Py_None) {
+        result = _PyObject_MaybeCallSpecialNoArgs(number, &_Py_ID(__round__));
+    }
+    else {
+        result = _PyObject_MaybeCallSpecialOneArg(number, &_Py_ID(__round__),
+                                                  ndigits);
+    }
+    if (result == NULL && !PyErr_Occurred()) {
+        PyErr_Format(PyExc_TypeError,
+                     "type %.100s doesn't define __round__ method",
+                     Py_TYPE(number)->tp_name);
     }
-
-    if (ndigits == Py_None)
-        result = _PyObject_CallNoArgs(round);
-    else
-        result = PyObject_CallOneArg(round, ndigits);
-    Py_DECREF(round);
     return result;
 }
 
diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c
index 70dace9fe9eff2..4c459b02ce2b8f 100644
--- a/Python/gc_free_threading.c
+++ b/Python/gc_free_threading.c
@@ -433,6 +433,12 @@ static void
 gc_visit_thread_stacks(PyInterpreterState *interp, struct collection_state 
*state)
 {
     _Py_FOR_EACH_TSTATE_BEGIN(interp, p) {
+        _PyCStackRef *c_ref = ((_PyThreadStateImpl *)p)->c_stack_refs;
+        while (c_ref != NULL) {
+            gc_visit_stackref(c_ref->ref);
+            c_ref = c_ref->next;
+        }
+
         for (_PyInterpreterFrame *f = p->current_frame; f != NULL; f = 
f->previous) {
             if (f->owner >= FRAME_OWNED_BY_INTERPRETER) {
                 continue;

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to