Skip to content
Open
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
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.
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);

// 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve scaling of :func:`~collections.namedtuple` instantiation in the
free-threaded build.
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.

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
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
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

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) {
_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
151 changes: 103 additions & 48 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6375,102 +6375,153 @@ _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long

*/
PyObject *
_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int * suppress_missing_attribute)
_Py_type_getattro_impl(PyTypeObject *type, PyObject *name, int *suppress_missing_attribute)
{
_PyStackRef ref = _Py_type_getattro_stackref(type, name, suppress_missing_attribute);
if (PyStackRef_IsNull(ref)) {
return NULL;
}
return PyStackRef_AsPyObjectSteal(ref);
}

/* This is similar to PyObject_GenericGetAttr(),
but uses _PyType_LookupRef() instead of just looking in type->tp_dict. */
PyObject *
_Py_type_getattro(PyObject *tp, PyObject *name)
{
PyTypeObject *type = PyTypeObject_CAST(tp);
return _Py_type_getattro_impl(type, name, NULL);
}

/* Like _Py_type_getattro but returns a _PyStackRef.
This can return a deferred reference in the free-threaded build
when the attribute is found without going through a descriptor.

suppress_missing_attribute (optional):
* NULL: do not suppress the exception
* Non-zero pointer: suppress the PyExc_AttributeError and
set *suppress_missing_attribute to 1 to signal we are returning NULL while
having suppressed the exception (other exceptions are not suppressed)
*/
_PyStackRef
_Py_type_getattro_stackref(PyTypeObject *type, PyObject *name,
int *suppress_missing_attribute)
{
PyTypeObject *metatype = Py_TYPE(type);
PyObject *meta_attribute, *attribute;
descrgetfunc meta_get;
PyObject* res;
descrgetfunc meta_get = NULL;

if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
Py_TYPE(name)->tp_name);
return NULL;
return PyStackRef_NULL;
}

/* Initialize this type (we'll assume the metatype is initialized) */
if (!_PyType_IsReady(type)) {
if (PyType_Ready(type) < 0)
return NULL;
return PyStackRef_NULL;
}

/* No readable descriptor found yet */
meta_get = NULL;
/* Set up GC-visible stack refs */
_PyCStackRef result_ref, meta_attribute_ref, attribute_ref;
PyThreadState *tstate = _PyThreadState_GET();
_PyThreadState_PushCStackRef(tstate, &result_ref);
_PyThreadState_PushCStackRef(tstate, &meta_attribute_ref);
_PyThreadState_PushCStackRef(tstate, &attribute_ref);

/* Look for the attribute in the metatype */
meta_attribute = _PyType_LookupRef(metatype, name);
_PyType_LookupStackRefAndVersion(metatype, name, &meta_attribute_ref.ref);

if (meta_attribute != NULL) {
meta_get = Py_TYPE(meta_attribute)->tp_descr_get;
if (!PyStackRef_IsNull(meta_attribute_ref.ref)) {
PyObject *meta_attr_obj = PyStackRef_AsPyObjectBorrow(meta_attribute_ref.ref);
meta_get = Py_TYPE(meta_attr_obj)->tp_descr_get;

if (meta_get != NULL && PyDescr_IsData(meta_attribute)) {
if (meta_get != NULL && PyDescr_IsData(meta_attr_obj)) {
/* Data descriptors implement tp_descr_set to intercept
* writes. Assume the attribute is not overridden in
* type's tp_dict (and bases): call the descriptor now.
*/
res = meta_get(meta_attribute, (PyObject *)type,
(PyObject *)metatype);
Py_DECREF(meta_attribute);
return res;
PyObject *res = meta_get(meta_attr_obj, (PyObject *)type,
(PyObject *)metatype);
if (res != NULL) {
result_ref.ref = PyStackRef_FromPyObjectSteal(res);
}
goto done;
}
}

/* No data descriptor found on metatype. Look in tp_dict of this
* type and its bases */
attribute = _PyType_LookupRef(type, name);
if (attribute != NULL) {
_PyType_LookupStackRefAndVersion(type, name, &attribute_ref.ref);
if (!PyStackRef_IsNull(attribute_ref.ref)) {
/* Implement descriptor functionality, if any */
descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
PyObject *attr_obj = PyStackRef_AsPyObjectBorrow(attribute_ref.ref);
descrgetfunc local_get = Py_TYPE(attr_obj)->tp_descr_get;

Py_XDECREF(meta_attribute);
/* Release meta_attribute early since we found in local dict */
PyStackRef_CLEAR(meta_attribute_ref.ref);

if (local_get != NULL) {
/* Special case staticmethod to avoid descriptor call overhead.
* staticmethod.__get__ just returns the wrapped callable. */
if (Py_TYPE(attr_obj) == &PyStaticMethod_Type) {
PyObject *callable = _PyStaticMethod_GetFunc(attr_obj);
if (callable) {
result_ref.ref = PyStackRef_FromPyObjectNew(callable);
goto done;
}
}
/* NULL 2nd argument indicates the descriptor was
* found on the target object itself (or a base) */
res = local_get(attribute, (PyObject *)NULL,
(PyObject *)type);
Py_DECREF(attribute);
return res;
PyObject *res = local_get(attr_obj, (PyObject *)NULL,
(PyObject *)type);
if (res != NULL) {
result_ref.ref = PyStackRef_FromPyObjectSteal(res);
}
goto done;
}

return attribute;
/* No descriptor, return the attribute directly */
result_ref.ref = attribute_ref.ref;
attribute_ref.ref = PyStackRef_NULL;
goto done;
}

/* No attribute found in local __dict__ (or bases): use the
* descriptor from the metatype, if any */
if (meta_get != NULL) {
PyObject *res;
res = meta_get(meta_attribute, (PyObject *)type,
(PyObject *)metatype);
Py_DECREF(meta_attribute);
return res;
PyObject *meta_attr_obj = PyStackRef_AsPyObjectBorrow(meta_attribute_ref.ref);
PyObject *res = meta_get(meta_attr_obj, (PyObject *)type,
(PyObject *)metatype);
if (res != NULL) {
result_ref.ref = PyStackRef_FromPyObjectSteal(res);
}
goto done;
}

/* If an ordinary attribute was found on the metatype, return it now */
if (meta_attribute != NULL) {
return meta_attribute;
if (!PyStackRef_IsNull(meta_attribute_ref.ref)) {
result_ref.ref = meta_attribute_ref.ref;
meta_attribute_ref.ref = PyStackRef_NULL;
goto done;
}

/* Give up */
if (suppress_missing_attribute == NULL) {
PyErr_Format(PyExc_AttributeError,
"type object '%.100s' has no attribute '%U'",
type->tp_name, name);
} else {
"type object '%.100s' has no attribute '%U'",
type->tp_name, name);
}
else {
// signal the caller we have not set an PyExc_AttributeError and gave up
*suppress_missing_attribute = 1;
}
return NULL;
}

/* This is similar to PyObject_GenericGetAttr(),
but uses _PyType_LookupRef() instead of just looking in type->tp_dict. */
PyObject *
_Py_type_getattro(PyObject *tp, PyObject *name)
{
PyTypeObject *type = PyTypeObject_CAST(tp);
return _Py_type_getattro_impl(type, name, NULL);
done:
_PyThreadState_PopCStackRef(tstate, &attribute_ref);
_PyThreadState_PopCStackRef(tstate, &meta_attribute_ref);
return _PyThreadState_PopCStackRefSteal(tstate, &result_ref);
}

// Called by type_setattro(). Updates both the type dict and
Expand Down Expand Up @@ -10937,15 +10988,19 @@ static PyObject *
slot_tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *func, *result;
PyObject *result;

func = PyObject_GetAttr((PyObject *)type, &_Py_ID(__new__));
if (func == NULL) {
_PyCStackRef func_ref;
_PyThreadState_PushCStackRef(tstate, &func_ref);
func_ref.ref = _PyObject_GetAttrStackRef((PyObject *)type, &_Py_ID(__new__));
if (PyStackRef_IsNull(func_ref.ref)) {
_PyThreadState_PopCStackRef(tstate, &func_ref);
return NULL;
}

PyObject *func = PyStackRef_AsPyObjectBorrow(func_ref.ref);
result = _PyObject_Call_Prepend(tstate, func, (PyObject *)type, args, kwds);
Py_DECREF(func);
_PyThreadState_PopCStackRef(tstate, &func_ref);
return result;
}

Expand Down
5 changes: 2 additions & 3 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2399,10 +2399,9 @@ dummy_func(
}
else {
/* Classic, pushes one value. */
PyObject *attr_o = PyObject_GetAttr(PyStackRef_AsPyObjectBorrow(owner), name);
attr = _PyObject_GetAttrStackRef(PyStackRef_AsPyObjectBorrow(owner), name);
PyStackRef_CLOSE(owner);
ERROR_IF(attr_o == NULL);
attr = PyStackRef_FromPyObjectSteal(attr_o);
ERROR_IF(PyStackRef_IsNull(attr));
}
}

Expand Down
Loading
Loading