summaryrefslogtreecommitdiffstats
path: root/Objects/typeobject.c
diff options
context:
space:
mode:
authorSam Gross <colesbury@gmail.com>2025-03-26 18:38:47 (GMT)
committerGitHub <noreply@github.com>2025-03-26 18:38:47 (GMT)
commit67fbfb42bd5dfe861d0c58d9e6c48d8eef033d24 (patch)
tree282178fa55571c055c8327dc3d77fa6a882a404b /Objects/typeobject.c
parent3d4ac1a2c2b610f35a9e164878d67185e4a3546f (diff)
downloadcpython-67fbfb42bd5dfe861d0c58d9e6c48d8eef033d24.zip
cpython-67fbfb42bd5dfe861d0c58d9e6c48d8eef033d24.tar.gz
cpython-67fbfb42bd5dfe861d0c58d9e6c48d8eef033d24.tar.bz2
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.
Diffstat (limited to 'Objects/typeobject.c')
-rw-r--r--Objects/typeobject.c574
1 files changed, 297 insertions, 277 deletions
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 4f44d59..b92eaef 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__));
}
/*