}
+extern PyObject *_PyObject_VectorcallPrepend(
+ PyThreadState *tstate,
+ PyObject *callable,
+ PyObject *arg,
+ PyObject *const *args,
+ size_t nargsf,
+ PyObject *kwnames);
+
/* === Vectorcall protocol (PEP 590) ============================= */
// Call callable using tp_call. Arguments are like PyObject_Vectorcall(),
#define SPECIAL___AEXIT__ 3
#define SPECIAL_MAX 3
+PyAPI_FUNC(_PyStackRef)
+_Py_LoadAttr_StackRefSteal(
+ PyThreadState *tstate, _PyStackRef owner,
+ PyObject *name, _PyStackRef *self_or_null);
+
#ifdef __cplusplus
}
#endif
#define _PyFunction_GET_BUILTINS(func) _PyFunction_GET_BUILTINS(_PyObject_CAST(func))
+/* Get the callable wrapped by a classmethod.
+ Returns a borrowed reference.
+ The caller must ensure 'cm' is a classmethod object. */
+extern PyObject *_PyClassMethod_GetFunc(PyObject *cm);
+
+/* Get the callable wrapped by a staticmethod.
+ Returns a borrowed reference.
+ The caller must ensure 'sm' is a staticmethod object. */
+extern PyObject *_PyStaticMethod_GetFunc(PyObject *sm);
+
+
#ifdef __cplusplus
}
#endif
extern unsigned int
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out);
+extern int _PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
+ PyObject *name, _PyStackRef *method);
+
// Cache the provided init method in the specialization cache of type if the
// provided type version matches the current version of the type.
//
}
#define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj), __FILE__, __LINE__)
+static inline _PyStackRef
+_PyStackRef_FromPyObjectBorrow(PyObject *obj, const char *filename, int linenumber)
+{
+ return _Py_stackref_create(obj, filename, linenumber);
+}
+#define PyStackRef_FromPyObjectBorrow(obj) _PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj), __FILE__, __LINE__)
+
static inline _PyStackRef
_PyStackRef_FromPyObjectImmortal(PyObject *obj, const char *filename, int linenumber)
{
}
# define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj))
+static inline _PyStackRef
+PyStackRef_FromPyObjectBorrow(PyObject *obj)
+{
+ assert(obj != NULL);
+ assert(((uintptr_t)obj & Py_TAG_BITS) == 0);
+ return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED };
+}
+
static inline bool
PyStackRef_IsHeapSafe(_PyStackRef stackref)
{
return ref;
}
+static inline _PyStackRef
+PyStackRef_FromPyObjectBorrow(PyObject *obj)
+{
+ assert(obj != NULL);
+ return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT };
+}
+
static inline _PyStackRef
PyStackRef_FromPyObjectStealMortal(PyObject *obj)
{
PyStackRef_XCLOSE(ref->ref);
}
+static inline _PyStackRef
+_PyThreadState_PopCStackRefSteal(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
+ return ref->ref;
+}
+
#ifdef Py_GIL_DISABLED
static inline int
--- /dev/null
+Improve scaling of :func:`classmethod` and :func:`staticmethod` calls in
+the free-threaded build by avoiding the descriptor ``__get__`` call.
return result;
}
+PyObject *
+_PyObject_VectorcallPrepend(PyThreadState *tstate, PyObject *callable,
+ PyObject *arg, PyObject *const *args,
+ size_t nargsf, PyObject *kwnames)
+{
+ Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
+ assert(nargs == 0 || args[nargs-1]);
+
+ PyObject *result;
+ if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {
+ /* PY_VECTORCALL_ARGUMENTS_OFFSET is set, so we are allowed to mutate the vector */
+ PyObject **newargs = (PyObject**)args - 1;
+ nargs += 1;
+ PyObject *tmp = newargs[0];
+ newargs[0] = arg;
+ assert(newargs[nargs-1]);
+ result = _PyObject_VectorcallTstate(tstate, callable, newargs,
+ nargs, kwnames);
+ newargs[0] = tmp;
+ }
+ else {
+ Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
+ Py_ssize_t totalargs = nargs + nkwargs;
+ if (totalargs == 0) {
+ return _PyObject_VectorcallTstate(tstate, callable, &arg, 1, NULL);
+ }
+
+ PyObject *newargs_stack[_PY_FASTCALL_SMALL_STACK];
+ PyObject **newargs;
+ if (totalargs <= (Py_ssize_t)Py_ARRAY_LENGTH(newargs_stack) - 1) {
+ newargs = newargs_stack;
+ }
+ else {
+ newargs = PyMem_Malloc((totalargs+1) * sizeof(PyObject *));
+ if (newargs == NULL) {
+ _PyErr_NoMemory(tstate);
+ return NULL;
+ }
+ }
+ /* use borrowed references */
+ newargs[0] = arg;
+ /* bpo-37138: since totalargs > 0, it's impossible that args is NULL.
+ * We need this, since calling memcpy() with a NULL pointer is
+ * undefined behaviour. */
+ assert(args != NULL);
+ memcpy(newargs + 1, args, totalargs * sizeof(PyObject *));
+ result = _PyObject_VectorcallTstate(tstate, callable,
+ newargs, nargs+1, kwnames);
+ if (newargs != newargs_stack) {
+ PyMem_Free(newargs);
+ }
+ }
+ return result;
+}
PyObject *
PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
assert(PyVectorcall_NARGS(nargsf) >= 1);
PyThreadState *tstate = _PyThreadState_GET();
- PyObject *callable = NULL;
+ _PyCStackRef self, method;
+ _PyThreadState_PushCStackRef(tstate, &self);
+ _PyThreadState_PushCStackRef(tstate, &method);
/* Use args[0] as "self" argument */
- int unbound = _PyObject_GetMethod(args[0], name, &callable);
- if (callable == NULL) {
+ self.ref = PyStackRef_FromPyObjectBorrow(args[0]);
+ int unbound = _PyObject_GetMethodStackRef(tstate, &self.ref, name, &method.ref);
+ if (unbound < 0) {
+ _PyThreadState_PopCStackRef(tstate, &method);
+ _PyThreadState_PopCStackRef(tstate, &self);
return NULL;
}
- if (unbound) {
+ PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
+ PyObject *self_obj = PyStackRef_AsPyObjectBorrow(self.ref);
+ PyObject *result;
+
+ EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
+ if (self_obj == NULL) {
+ /* Skip "self". We can keep PY_VECTORCALL_ARGUMENTS_OFFSET since
+ * args[-1] in the onward call is args[0] here. */
+ result = _PyObject_VectorcallTstate(tstate, callable,
+ args + 1, nargsf - 1, kwnames);
+ }
+ else if (self_obj == args[0]) {
/* We must remove PY_VECTORCALL_ARGUMENTS_OFFSET since
* that would be interpreted as allowing to change args[-1] */
- nargsf &= ~PY_VECTORCALL_ARGUMENTS_OFFSET;
+ result = _PyObject_VectorcallTstate(tstate, callable, args,
+ nargsf & ~PY_VECTORCALL_ARGUMENTS_OFFSET,
+ kwnames);
}
else {
- /* Skip "self". We can keep PY_VECTORCALL_ARGUMENTS_OFFSET since
- * args[-1] in the onward call is args[0] here. */
- args++;
- nargsf--;
+ /* classmethod: self_obj is the type, not args[0]. Replace
+ * args[0] with self_obj and call the underlying callable. */
+ result = _PyObject_VectorcallPrepend(tstate, callable, self_obj,
+ args + 1, nargsf - 1, kwnames);
}
- EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
- PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
- args, nargsf, kwnames);
- Py_DECREF(callable);
+ _PyThreadState_PopCStackRef(tstate, &method);
+ _PyThreadState_PopCStackRef(tstate, &self);
return result;
}
return null_error(tstate);
}
- PyObject *callable = NULL;
- int is_method = _PyObject_GetMethod(obj, name, &callable);
- if (callable == NULL) {
+ _PyCStackRef self, method;
+ _PyThreadState_PushCStackRef(tstate, &self);
+ _PyThreadState_PushCStackRef(tstate, &method);
+ self.ref = PyStackRef_FromPyObjectBorrow(obj);
+ int res = _PyObject_GetMethodStackRef(tstate, &self.ref, name, &method.ref);
+ if (res < 0) {
+ _PyThreadState_PopCStackRef(tstate, &method);
+ _PyThreadState_PopCStackRef(tstate, &self);
return NULL;
}
- obj = is_method ? obj : NULL;
+ PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
+ PyObject *self_obj = PyStackRef_AsPyObjectBorrow(self.ref);
va_list vargs;
va_start(vargs, name);
- PyObject *result = object_vacall(tstate, obj, callable, vargs);
+ PyObject *result = object_vacall(tstate, self_obj, callable, vargs);
va_end(vargs);
- Py_DECREF(callable);
+ _PyThreadState_PopCStackRef(tstate, &method);
+ _PyThreadState_PopCStackRef(tstate, &self);
return result;
}
PyThreadState *tstate = _PyThreadState_GET();
PyObject *self = PyMethod_GET_SELF(method);
PyObject *func = PyMethod_GET_FUNCTION(method);
- Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
- assert(nargs == 0 || args[nargs-1]);
-
- PyObject *result;
- if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {
- /* PY_VECTORCALL_ARGUMENTS_OFFSET is set, so we are allowed to mutate the vector */
- PyObject **newargs = (PyObject**)args - 1;
- nargs += 1;
- PyObject *tmp = newargs[0];
- newargs[0] = self;
- assert(newargs[nargs-1]);
- result = _PyObject_VectorcallTstate(tstate, func, newargs,
- nargs, kwnames);
- newargs[0] = tmp;
- }
- else {
- Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
- Py_ssize_t totalargs = nargs + nkwargs;
- if (totalargs == 0) {
- return _PyObject_VectorcallTstate(tstate, func, &self, 1, NULL);
- }
-
- PyObject *newargs_stack[_PY_FASTCALL_SMALL_STACK];
- PyObject **newargs;
- if (totalargs <= (Py_ssize_t)Py_ARRAY_LENGTH(newargs_stack) - 1) {
- newargs = newargs_stack;
- }
- else {
- newargs = PyMem_Malloc((totalargs+1) * sizeof(PyObject *));
- if (newargs == NULL) {
- _PyErr_NoMemory(tstate);
- return NULL;
- }
- }
- /* use borrowed references */
- newargs[0] = self;
- /* bpo-37138: since totalargs > 0, it's impossible that args is NULL.
- * We need this, since calling memcpy() with a NULL pointer is
- * undefined behaviour. */
- assert(args != NULL);
- memcpy(newargs + 1, args, totalargs * sizeof(PyObject *));
- result = _PyObject_VectorcallTstate(tstate, func,
- newargs, nargs+1, kwnames);
- if (newargs != newargs_stack) {
- PyMem_Free(newargs);
- }
- }
- return result;
+ return _PyObject_VectorcallPrepend(tstate, func, self, args, nargsf, kwnames);
}
}
cm->cm_callable = Py_None;
cm->cm_dict = NULL;
+ _PyObject_SetDeferredRefcount((PyObject *)cm);
return (PyObject *)cm;
}
}
sm->sm_callable = Py_None;
sm->sm_dict = NULL;
+ _PyObject_SetDeferredRefcount((PyObject *)sm);
return (PyObject *)sm;
}
}
return (PyObject *)sm;
}
+
+PyObject *
+_PyClassMethod_GetFunc(PyObject *self)
+{
+ classmethod *cm = _PyClassMethod_CAST(self);
+ return cm->cm_callable;
+}
+
+PyObject *
+_PyStaticMethod_GetFunc(PyObject *self)
+{
+ staticmethod *sm = _PyStaticMethod_CAST(self);
+ return sm->sm_callable;
+}
#include "pycore_descrobject.h" // _PyMethodWrapper_Type
#include "pycore_dict.h" // _PyObject_MaterializeManagedDict()
#include "pycore_floatobject.h" // _PyFloat_DebugMallocStats()
+#include "pycore_function.h" // _PyClassMethod_GetFunc()
#include "pycore_freelist.h" // _PyObject_ClearFreeLists()
#include "pycore_genobject.h" // _PyAsyncGenAThrow_Type
#include "pycore_hamt.h" // _PyHamtItems_Type
return 0;
}
+// Look up a method on `self` by `name`.
+//
+// On success, `*method` is set and the function returns 0 or 1. If the
+// return value is 1, the call is an unbound method and `*self` is the
+// "self" or "cls" argument to pass. If the return value is 0, the call is
+// a regular function and `*self` is cleared.
+//
+// On error, returns -1, clears `*self`, and sets an exception.
+int
+_PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
+ PyObject *name, _PyStackRef *method)
+{
+ int meth_found = 0;
+ PyObject *obj = PyStackRef_AsPyObjectBorrow(*self);
+
+ assert(PyStackRef_IsNull(*method));
+
+ PyTypeObject *tp = Py_TYPE(obj);
+ if (!_PyType_IsReady(tp)) {
+ if (PyType_Ready(tp) < 0) {
+ PyStackRef_CLEAR(*self);
+ return -1;
+ }
+ }
+
+ if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) {
+ PyObject *res = PyObject_GetAttr(obj, name);
+ PyStackRef_CLEAR(*self);
+ if (res != NULL) {
+ *method = PyStackRef_FromPyObjectSteal(res);
+ return 0;
+ }
+ return -1;
+ }
+
+ _PyType_LookupStackRefAndVersion(tp, name, method);
+ PyObject *descr = PyStackRef_AsPyObjectBorrow(*method);
+ descrgetfunc f = NULL;
+ if (descr != NULL) {
+ if (_PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
+ meth_found = 1;
+ }
+ else {
+ f = Py_TYPE(descr)->tp_descr_get;
+ if (f != NULL && PyDescr_IsData(descr)) {
+ PyObject *value = f(descr, obj, (PyObject *)Py_TYPE(obj));
+ PyStackRef_CLEAR(*method);
+ PyStackRef_CLEAR(*self);
+ if (value != NULL) {
+ *method = PyStackRef_FromPyObjectSteal(value);
+ return 0;
+ }
+ return -1;
+ }
+ }
+ }
+ PyObject *dict, *attr;
+ if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
+ _PyObject_TryGetInstanceAttribute(obj, name, &attr)) {
+ if (attr != NULL) {
+ PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectSteal(attr));
+ PyStackRef_CLEAR(*self);
+ return 0;
+ }
+ dict = NULL;
+ }
+ else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
+ dict = (PyObject *)_PyObject_GetManagedDict(obj);
+ }
+ else {
+ PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
+ if (dictptr != NULL) {
+ dict = FT_ATOMIC_LOAD_PTR_ACQUIRE(*dictptr);
+ }
+ else {
+ dict = NULL;
+ }
+ }
+ if (dict != NULL) {
+ assert(PyUnicode_CheckExact(name));
+ int found = _PyDict_GetMethodStackRef((PyDictObject *)dict, name, method);
+ if (found < 0) {
+ assert(PyStackRef_IsNull(*method));
+ PyStackRef_CLEAR(*self);
+ return -1;
+ }
+ else if (found) {
+ PyStackRef_CLEAR(*self);
+ return 0;
+ }
+ }
+
+ if (meth_found) {
+ assert(!PyStackRef_IsNull(*method));
+ return 1;
+ }
+
+ if (f != NULL) {
+ if (Py_IS_TYPE(descr, &PyClassMethod_Type)) {
+ PyObject *callable = _PyClassMethod_GetFunc(descr);
+ PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectNew(callable));
+ PyStackRef_XSETREF(*self, PyStackRef_FromPyObjectNew((PyObject *)tp));
+ return 1;
+ }
+ else if (Py_IS_TYPE(descr, &PyStaticMethod_Type)) {
+ PyObject *callable = _PyStaticMethod_GetFunc(descr);
+ PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectNew(callable));
+ PyStackRef_CLEAR(*self);
+ return 0;
+ }
+ PyObject *value = f(descr, obj, (PyObject *)tp);
+ PyStackRef_CLEAR(*method);
+ PyStackRef_CLEAR(*self);
+ if (value) {
+ *method = PyStackRef_FromPyObjectSteal(value);
+ return 0;
+ }
+ return -1;
+ }
+
+ if (descr != NULL) {
+ assert(!PyStackRef_IsNull(*method));
+ PyStackRef_CLEAR(*self);
+ return 0;
+ }
+
+ PyErr_Format(PyExc_AttributeError,
+ "'%.100s' object has no attribute '%U'",
+ tp->tp_name, name);
+
+ _PyObject_SetAttributeErrorContext(obj, name);
+ assert(PyStackRef_IsNull(*method));
+ PyStackRef_CLEAR(*self);
+ return -1;
+}
+
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
PyObject *
op(_LOAD_ATTR, (owner -- attr, self_or_null[oparg&1])) {
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
- PyObject *attr_o;
if (oparg & 1) {
/* Designed to work in tandem with CALL, pushes two values. */
- attr_o = NULL;
- int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
- if (is_meth) {
- /* We can bypass temporary bound method object.
- meth is unbound method and obj is self.
- meth | self | arg1 | ... | argN
- */
- assert(attr_o != NULL); // No errors on this branch
- self_or_null[0] = owner; // Transfer ownership
- DEAD(owner);
- }
- else {
- /* meth is not an unbound method (but a regular attr, or
- something was returned by a descriptor protocol). Set
- the second element of the stack to NULL, to signal
- CALL that it's not a method call.
- meth | NULL | arg1 | ... | argN
- */
- PyStackRef_CLOSE(owner);
- ERROR_IF(attr_o == NULL);
- self_or_null[0] = PyStackRef_NULL;
- }
+ attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name, self_or_null);
+ DEAD(owner);
+ ERROR_IF(PyStackRef_IsNull(attr));
}
else {
/* Classic, pushes one value. */
- attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+ PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
PyStackRef_CLOSE(owner);
ERROR_IF(attr_o == NULL);
+ attr = PyStackRef_FromPyObjectSteal(attr_o);
}
- attr = PyStackRef_FromPyObjectSteal(attr_o);
}
macro(LOAD_ATTR) =
PyObject *seen = NULL;
PyObject *dummy = NULL;
PyObject *values = NULL;
- PyObject *get = NULL;
// We use the two argument form of map.get(key, default) for two reasons:
// - Atomically check for a key and get its value without error handling.
// - Don't cause key creation or resizing in dict subclasses like
// collections.defaultdict that define __missing__ (or similar).
- int meth_found = _PyObject_GetMethod(map, &_Py_ID(get), &get);
- if (get == NULL) {
+ _PyCStackRef self, method;
+ _PyThreadState_PushCStackRef(tstate, &self);
+ _PyThreadState_PushCStackRef(tstate, &method);
+ self.ref = PyStackRef_FromPyObjectBorrow(map);
+ int res = _PyObject_GetMethodStackRef(tstate, &self.ref, &_Py_ID(get), &method.ref);
+ if (res < 0) {
goto fail;
}
+ PyObject *get = PyStackRef_AsPyObjectBorrow(method.ref);
seen = PySet_New(NULL);
if (seen == NULL) {
goto fail;
}
goto fail;
}
- PyObject *args[] = { map, key, dummy };
+ PyObject *self_obj = PyStackRef_AsPyObjectBorrow(self.ref);
+ PyObject *args[] = { self_obj, key, dummy };
PyObject *value = NULL;
- if (meth_found) {
+ if (!PyStackRef_IsNull(self.ref)) {
value = PyObject_Vectorcall(get, args, 3, NULL);
}
else {
}
// Success:
done:
- Py_DECREF(get);
+ _PyThreadState_PopCStackRef(tstate, &method);
+ _PyThreadState_PopCStackRef(tstate, &self);
Py_DECREF(seen);
Py_DECREF(dummy);
return values;
fail:
- Py_XDECREF(get);
+ _PyThreadState_PopCStackRef(tstate, &method);
+ _PyThreadState_PopCStackRef(tstate, &self);
Py_XDECREF(seen);
Py_XDECREF(dummy);
Py_XDECREF(values);
#include "ceval_macros.h"
+
+_PyStackRef
+_Py_LoadAttr_StackRefSteal(
+ PyThreadState *tstate, _PyStackRef owner,
+ PyObject *name, _PyStackRef *self_or_null)
+{
+ // Use _PyCStackRefs to ensure that both method and self are visible to
+ // the GC. Even though self_or_null is on the evaluation stack, it may be
+ // after the stackpointer and therefore not visible to the GC.
+ _PyCStackRef method, self;
+ _PyThreadState_PushCStackRef(tstate, &method);
+ _PyThreadState_PushCStackRef(tstate, &self);
+ self.ref = owner; // steal reference to owner
+ // NOTE: method.ref is initialized to PyStackRef_NULL and remains null on
+ // error, so we don't need to explicitly use the return code from the call.
+ _PyObject_GetMethodStackRef(tstate, &self.ref, name, &method.ref);
+ *self_or_null = _PyThreadState_PopCStackRefSteal(tstate, &self);
+ return _PyThreadState_PopCStackRefSteal(tstate, &method);
+}
+
int _Py_CheckRecursiveCallPy(
PyThreadState *tstate)
{
owner = stack_pointer[-1];
self_or_null = &stack_pointer[0];
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
- PyObject *attr_o;
if (oparg & 1) {
- attr_o = NULL;
_PyFrame_SetStackPointer(frame, stack_pointer);
- int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
+ attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name, self_or_null);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (is_meth) {
- assert(attr_o != NULL);
- self_or_null[0] = owner;
- }
- else {
- stack_pointer += -1;
+ if (PyStackRef_IsNull(attr)) {
+ stack_pointer[-1] = attr;
+ stack_pointer += (oparg&1);
assert(WITHIN_STACK_BOUNDS());
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(owner);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (attr_o == NULL) {
- JUMP_TO_ERROR();
- }
- self_or_null[0] = PyStackRef_NULL;
- stack_pointer += 1;
+ JUMP_TO_ERROR();
}
}
else {
_PyFrame_SetStackPointer(frame, stack_pointer);
- attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+ PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
stack_pointer = _PyFrame_GetStackPointer(frame);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
if (attr_o == NULL) {
JUMP_TO_ERROR();
}
+ attr = PyStackRef_FromPyObjectSteal(attr_o);
stack_pointer += 1;
}
- attr = PyStackRef_FromPyObjectSteal(attr_o);
stack_pointer[-1] = attr;
stack_pointer += (oparg&1);
assert(WITHIN_STACK_BOUNDS());
{
self_or_null = &stack_pointer[0];
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg >> 1);
- PyObject *attr_o;
if (oparg & 1) {
- attr_o = NULL;
_PyFrame_SetStackPointer(frame, stack_pointer);
- int is_meth = _PyObject_GetMethod(PyStackRef_AsPyObjectBorrow(owner), name, &attr_o);
+ attr = _Py_LoadAttr_StackRefSteal(tstate, owner, name, self_or_null);
stack_pointer = _PyFrame_GetStackPointer(frame);
- if (is_meth) {
- assert(attr_o != NULL);
- self_or_null[0] = owner;
- }
- else {
- stack_pointer += -1;
- assert(WITHIN_STACK_BOUNDS());
- _PyFrame_SetStackPointer(frame, stack_pointer);
- PyStackRef_CLOSE(owner);
- stack_pointer = _PyFrame_GetStackPointer(frame);
- if (attr_o == NULL) {
- JUMP_TO_LABEL(error);
- }
- self_or_null[0] = PyStackRef_NULL;
- stack_pointer += 1;
+ if (PyStackRef_IsNull(attr)) {
+ JUMP_TO_LABEL(pop_1_error);
}
}
else {
_PyFrame_SetStackPointer(frame, stack_pointer);
- attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
+ PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
stack_pointer = _PyFrame_GetStackPointer(frame);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
if (attr_o == NULL) {
JUMP_TO_LABEL(error);
}
+ attr = PyStackRef_FromPyObjectSteal(attr_o);
stack_pointer += 1;
}
- attr = PyStackRef_FromPyObjectSteal(attr_o);
}
stack_pointer[-1] = attr;
stack_pointer += (oparg&1);
obj.method()
+class MyClassMethod:
+ @classmethod
+ def my_classmethod(cls):
+ return cls
+
+ @staticmethod
+ def my_staticmethod():
+ pass
+
+@register_benchmark
+def classmethod_call():
+ obj = MyClassMethod()
+ for _ in range(1000 * WORK_SCALE):
+ obj.my_classmethod()
+
+@register_benchmark
+def staticmethod_call():
+ obj = MyClassMethod()
+ for _ in range(1000 * WORK_SCALE):
+ obj.my_staticmethod()
+
+
def bench_one_thread(func):
t0 = time.perf_counter_ns()
func()