Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Include/internal/pycore_call.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ _PyObject_CallMethodIdOneArg(PyObject *self, _Py_Identifier *name, PyObject *arg
}


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(),
Expand Down
5 changes: 5 additions & 0 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,11 @@ extern int _PyRunRemoteDebugger(PyThreadState *tstate);
#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
Expand Down
11 changes: 11 additions & 0 deletions Include/internal/pycore_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ static inline PyObject* _PyFunction_GET_BUILTINS(PyObject *func) {
#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
Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,9 @@ extern PyObject *_PyType_LookupRefAndVersion(PyTypeObject *, PyObject *,
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.
//
Expand Down
33 changes: 33 additions & 0 deletions Include/internal/pycore_stackref.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ _PyStackRef_FromPyObjectSteal(PyObject *obj, const char *filename, int linenumbe
}
#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)
{
Expand Down Expand Up @@ -320,6 +327,14 @@ _PyStackRef_FromPyObjectSteal(PyObject *obj)
}
# 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)
{
Expand Down Expand Up @@ -538,6 +553,13 @@ PyStackRef_FromPyObjectSteal(PyObject *obj)
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)
{
Expand Down Expand Up @@ -753,6 +775,17 @@ _PyThreadState_PopCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve scaling of :func:`classmethod` and :func:`staticmethod` calls in
the free-threaded build by avoiding the descriptor ``__get__`` call.
115 changes: 96 additions & 19 deletions Objects/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,60 @@ object_vacall(PyThreadState *tstate, PyObject *base,
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,
Expand All @@ -835,28 +889,44 @@ 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;
}

Expand All @@ -869,19 +939,26 @@ PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
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;
}

Expand Down
49 changes: 1 addition & 48 deletions Objects/classobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,54 +51,7 @@ method_vectorcall(PyObject *method, PyObject *const *args,
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);
}


Expand Down
16 changes: 16 additions & 0 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1479,6 +1479,7 @@ cm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
}
cm->cm_callable = Py_None;
cm->cm_dict = NULL;
_PyObject_SetDeferredRefcount((PyObject *)cm);
return (PyObject *)cm;
}

Expand Down Expand Up @@ -1722,6 +1723,7 @@ sm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
}
sm->sm_callable = Py_None;
sm->sm_dict = NULL;
_PyObject_SetDeferredRefcount((PyObject *)sm);
return (PyObject *)sm;
}

Expand Down Expand Up @@ -1889,3 +1891,17 @@ PyStaticMethod_New(PyObject *callable)
}
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;
}
Loading
Loading