Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions Include/internal/pycore_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ 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 staticmethod.
Returns a borrowed reference, or NULL if uninitialized.
Comment thread
colesbury marked this conversation as resolved.
Outdated
The caller must ensure 'sm' is a staticmethod object. */
extern PyObject *_PyStaticMethod_GetFunc(PyObject *sm);


#ifdef __cplusplus
}
#endif
Expand Down
4 changes: 4 additions & 0 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,10 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef
PyAPI_FUNC(int) _PyObject_GetMethodStackRef(PyThreadState *ts, PyObject *obj,
PyObject *name, _PyStackRef *method);

// Like PyObject_GetAttr but returns a _PyStackRef. For types, this can
// return a deferred reference to reduce reference count contention.
PyAPI_FUNC(_PyStackRef) PyObject_GetAttrStackRef(PyObject *obj, PyObject *name);
Comment thread
colesbury marked this conversation as resolved.
Outdated

// 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
3 changes: 3 additions & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ extern "C" {

#include "pycore_interp_structs.h" // managed_static_type_state
#include "pycore_moduleobject.h" // PyModuleObject
#include "pycore_structs.h" // _PyStackRef


/* state */
Expand Down Expand Up @@ -112,6 +113,8 @@ _PyType_IsReady(PyTypeObject *type)
extern PyObject* _Py_type_getattro_impl(PyTypeObject *type, PyObject *name,
int *suppress_missing_attribute);
extern PyObject* _Py_type_getattro(PyObject *type, PyObject *name);
extern _PyStackRef _Py_type_getattro_stackref(PyTypeObject *type, PyObject *name,
int *suppress_missing_attribute);

extern PyObject* _Py_BaseObject_RichCompare(PyObject* self, PyObject* other, int op);

Expand Down
10 changes: 5 additions & 5 deletions Modules/_testinternalcapi/test_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions Objects/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,10 @@ _PyStack_AsDict(PyObject *const *values, PyObject *kwnames)

The newly allocated argument vector supports PY_VECTORCALL_ARGUMENTS_OFFSET.

The positional arguments are borrowed references from the input array
(which must be kept alive by the caller). The keyword argument values
are new references.

When done, you must call _PyStack_UnpackDict_Free(stack, nargs, kwnames) */
PyObject *const *
_PyStack_UnpackDict(PyThreadState *tstate,
Expand Down Expand Up @@ -970,9 +974,9 @@ _PyStack_UnpackDict(PyThreadState *tstate,

stack++; /* For PY_VECTORCALL_ARGUMENTS_OFFSET */

/* Copy positional arguments */
/* Copy positional arguments (borrowed references) */
for (Py_ssize_t i = 0; i < nargs; i++) {
stack[i] = Py_NewRef(args[i]);
stack[i] = args[i];
}

PyObject **kwstack = stack + nargs;
Expand Down Expand Up @@ -1009,9 +1013,10 @@ void
_PyStack_UnpackDict_Free(PyObject *const *stack, Py_ssize_t nargs,
PyObject *kwnames)
{
Py_ssize_t n = PyTuple_GET_SIZE(kwnames) + nargs;
for (Py_ssize_t i = 0; i < n; i++) {
Py_DECREF(stack[i]);
/* Only decref kwargs values, positional args are borrowed */
Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwnames);
for (Py_ssize_t i = 0; i < nkwargs; i++) {
Py_DECREF(stack[nargs + i]);
}
_PyStack_UnpackDict_FreeNoDecRef(stack, kwnames);
}
Expand Down
9 changes: 9 additions & 0 deletions Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "pycore_long.h" // _PyLong_GetOne()
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
#include "pycore_object_deferred.h" // _PyObject_SetDeferredRefcount()
#include "pycore_pyerrors.h" // _PyErr_Occurred()
#include "pycore_setobject.h" // _PySet_NextEntry()
#include "pycore_stats.h"
Expand Down Expand Up @@ -1868,7 +1869,15 @@ PyStaticMethod_New(PyObject *callable)
staticmethod *sm = (staticmethod *)
PyType_GenericAlloc(&PyStaticMethod_Type, 0);
if (sm != NULL) {
_PyObject_SetDeferredRefcount((PyObject *)sm);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also call it in sm_init()? If not, should we move this _PyObject_SetDeferredRefcount() call to typeobject.c after the two PyStaticMethod_New() calls?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... maybe? We'll need to guard the call to _PyObject_SetDeferredRefcount() because calling it multiple times on the same object will trigger an assertion failure.

I'll update this after your PR is merged

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged for classmethod/staticmethod fix in the main branch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've put the calls to _PyObject_SetDeferredRefcount() in sm_new() and PyStaticMethod_New() so that it's called exactly once during construction.

sm->sm_callable = Py_NewRef(callable);
}
return (PyObject *)sm;
}

PyObject *
_PyStaticMethod_GetFunc(PyObject *self)
{
staticmethod *sm = _PyStaticMethod_CAST(self);
return sm->sm_callable;
}
49 changes: 49 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "pycore_tuple.h" // _PyTuple_DebugMallocStats()
#include "pycore_typeobject.h" // _PyBufferWrapper_Type
#include "pycore_typevarobject.h" // _PyTypeAlias_Type
#include "pycore_stackref.h" // PyStackRef_FromPyObjectSteal
#include "pycore_unionobject.h" // _PyUnion_Type


Expand Down Expand Up @@ -1334,6 +1335,54 @@ PyObject_GetAttr(PyObject *v, PyObject *name)
return result;
}

/* Like PyObject_GetAttr but returns a _PyStackRef.
For types (tp_getattro == _Py_type_getattro), this can return
a deferred reference to reduce reference count contention. */
_PyStackRef
PyObject_GetAttrStackRef(PyObject *v, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(v);
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
Py_TYPE(name)->tp_name);
return PyStackRef_NULL;
}

/* Fast path for types - can return deferred references */
if (tp->tp_getattro == _Py_type_getattro) {
Comment thread
mpage marked this conversation as resolved.
_PyStackRef result = _Py_type_getattro_stackref((PyTypeObject *)v, name, NULL);
if (PyStackRef_IsNull(result)) {
_PyObject_SetAttributeErrorContext(v, name);
}
return result;
}

/* Fall back to regular PyObject_GetAttr and convert to stackref */
PyObject *result = NULL;
if (tp->tp_getattro != NULL) {
result = (*tp->tp_getattro)(v, name);
}
else if (tp->tp_getattr != NULL) {
const char *name_str = PyUnicode_AsUTF8(name);
if (name_str == NULL) {
return PyStackRef_NULL;
}
result = (*tp->tp_getattr)(v, (char *)name_str);
}
else {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
tp->tp_name, name);
}

if (result == NULL) {
_PyObject_SetAttributeErrorContext(v, name);
return PyStackRef_NULL;
}
return PyStackRef_FromPyObjectSteal(result);
}

int
PyObject_GetOptionalAttr(PyObject *v, PyObject *name, PyObject **result)
{
Expand Down
Loading
Loading