gh-94673: Add Per-Interpreter tp_weaklist for Static Builtin Types (#95302)

* Store tp_weaklist on the interpreter state for static builtin types.

* Factor out _PyStaticType_GET_WEAKREFS_LISTPTR().

* Add _PyStaticType_ClearWeakRefs().

* Add a comment about how _PyStaticType_ClearWeakRefs() loops.

* Document the change.

* Update Doc/whatsnew/3.12.rst

* Fix a typo.
This commit is contained in:
Eric Snow 2022-07-28 19:23:47 -06:00 committed by GitHub
parent 6e44bf9558
commit 3e7cad3bca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 59 additions and 1 deletions

View File

@ -1942,6 +1942,13 @@ and :c:type:`PyType_Type` effectively act as defaults.)
Weak reference list head, for weak references to this type object. Not Weak reference list head, for weak references to this type object. Not
inherited. Internal use only. inherited. Internal use only.
.. versionchanged:: 3.12
Internals detail: For the static builtin types this is always ``NULL``,
even if weakrefs are added. Instead, the weakrefs for each are stored
on ``PyInterpreterState``. Use the public C-API or the internal
``_PyObject_GET_WEAKREFS_LISTPTR()`` macro to avoid the distinction.
**Inheritance:** **Inheritance:**
This field is not inherited. This field is not inherited.

View File

@ -413,6 +413,13 @@ Porting to Python 3.12
``Py_UNICODE*`` based format (e.g. ``u``, ``Z``) anymore. Please migrate ``Py_UNICODE*`` based format (e.g. ``u``, ``Z``) anymore. Please migrate
to other formats for Unicode like ``s``, ``z``, ``es``, and ``U``. to other formats for Unicode like ``s``, ``z``, ``es``, and ``U``.
* ``tp_weaklist`` for all static builtin types is always ``NULL``.
This is an internal-only field on ``PyTypeObject``
but we're pointing out the change in case someone happens to be
accessing the field directly anyway. To avoid breakage, consider
using the existing public C-API instead, or, if necessary, the
(internal-only) ``_PyObject_GET_WEAKREFS_LISTPTR()`` macro.
Deprecated Deprecated
---------- ----------

View File

@ -219,7 +219,7 @@ struct _typeobject {
PyObject *tp_mro; /* method resolution order */ PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache; PyObject *tp_cache;
PyObject *tp_subclasses; PyObject *tp_subclasses;
PyObject *tp_weaklist; PyObject *tp_weaklist; /* not used for static builtin types */
destructor tp_del; destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6 */ /* Type attribute cache version tag. Added in version 2.6 */

View File

@ -220,6 +220,12 @@ extern void _Py_PrintReferenceAddresses(FILE *);
static inline PyObject ** static inline PyObject **
_PyObject_GET_WEAKREFS_LISTPTR(PyObject *op) _PyObject_GET_WEAKREFS_LISTPTR(PyObject *op)
{ {
if (PyType_Check(op) &&
((PyTypeObject *)op)->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
static_builtin_state *state = _PyStaticType_GetState(
(PyTypeObject *)op);
return _PyStaticType_GET_WEAKREFS_LISTPTR(state);
}
Py_ssize_t offset = Py_TYPE(op)->tp_weaklistoffset; Py_ssize_t offset = Py_TYPE(op)->tp_weaklistoffset;
return (PyObject **)((char *)op + offset); return (PyObject **)((char *)op + offset);
} }

View File

@ -45,8 +45,20 @@ struct type_cache {
typedef struct { typedef struct {
PyTypeObject *type; PyTypeObject *type;
/* We never clean up weakrefs for static builtin types since
they will effectively never get triggered. However, there
are also some diagnostic uses for the list of weakrefs,
so we still keep it. */
PyObject *tp_weaklist;
} static_builtin_state; } static_builtin_state;
static inline PyObject **
_PyStaticType_GET_WEAKREFS_LISTPTR(static_builtin_state *state)
{
assert(state != NULL);
return &state->tp_weaklist;
}
struct types_state { struct types_state {
struct type_cache type_cache; struct type_cache type_cache;
size_t num_builtins_initialized; size_t num_builtins_initialized;
@ -58,6 +70,7 @@ extern PyStatus _PyTypes_InitSlotDefs(void);
extern int _PyStaticType_InitBuiltin(PyTypeObject *type); extern int _PyStaticType_InitBuiltin(PyTypeObject *type);
extern static_builtin_state * _PyStaticType_GetState(PyTypeObject *); extern static_builtin_state * _PyStaticType_GetState(PyTypeObject *);
extern void _PyStaticType_ClearWeakRefs(PyTypeObject *type);
extern void _PyStaticType_Dealloc(PyTypeObject *type); extern void _PyStaticType_Dealloc(PyTypeObject *type);

View File

@ -127,6 +127,8 @@ static_builtin_state_init(PyTypeObject *self)
static_builtin_state *state = static_builtin_state_get(interp, self); static_builtin_state *state = static_builtin_state_get(interp, self);
state->type = self; state->type = self;
/* state->tp_weaklist is left NULL until insert_head() or insert_after()
(in weakrefobject.c) sets it. */
} }
static void static void
@ -138,6 +140,7 @@ static_builtin_state_clear(PyTypeObject *self)
static_builtin_state *state = static_builtin_state_get(interp, self); static_builtin_state *state = static_builtin_state_get(interp, self);
state->type = NULL; state->type = NULL;
assert(state->tp_weaklist == NULL); // It was already cleared out.
static_builtin_index_clear(self); static_builtin_index_clear(self);
assert(interp->types.num_builtins_initialized > 0); assert(interp->types.num_builtins_initialized > 0);
@ -502,6 +505,8 @@ static PyMemberDef type_members[] = {
{"__basicsize__", T_PYSSIZET, offsetof(PyTypeObject,tp_basicsize),READONLY}, {"__basicsize__", T_PYSSIZET, offsetof(PyTypeObject,tp_basicsize),READONLY},
{"__itemsize__", T_PYSSIZET, offsetof(PyTypeObject, tp_itemsize), READONLY}, {"__itemsize__", T_PYSSIZET, offsetof(PyTypeObject, tp_itemsize), READONLY},
{"__flags__", T_ULONG, offsetof(PyTypeObject, tp_flags), READONLY}, {"__flags__", T_ULONG, offsetof(PyTypeObject, tp_flags), READONLY},
/* Note that this value is misleading for static builtin types,
since the memory at this offset will always be NULL. */
{"__weakrefoffset__", T_PYSSIZET, {"__weakrefoffset__", T_PYSSIZET,
offsetof(PyTypeObject, tp_weaklistoffset), READONLY}, offsetof(PyTypeObject, tp_weaklistoffset), READONLY},
{"__base__", T_OBJECT, offsetof(PyTypeObject, tp_base), READONLY}, {"__base__", T_OBJECT, offsetof(PyTypeObject, tp_base), READONLY},
@ -4353,6 +4358,7 @@ _PyStaticType_Dealloc(PyTypeObject *type)
type->tp_flags &= ~Py_TPFLAGS_READY; type->tp_flags &= ~Py_TPFLAGS_READY;
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
_PyStaticType_ClearWeakRefs(type);
static_builtin_state_clear(type); static_builtin_state_clear(type);
/* We leave _Py_TPFLAGS_STATIC_BUILTIN set on tp_flags. */ /* We leave _Py_TPFLAGS_STATIC_BUILTIN set on tp_flags. */
} }

View File

@ -1019,3 +1019,22 @@ PyObject_ClearWeakRefs(PyObject *object)
PyErr_Restore(err_type, err_value, err_tb); PyErr_Restore(err_type, err_value, err_tb);
} }
} }
/* This function is called by _PyStaticType_Dealloc() to clear weak references.
*
* This is called at the end of runtime finalization, so we can just
* wipe out the type's weaklist. We don't bother with callbacks
* or anything else.
*/
void
_PyStaticType_ClearWeakRefs(PyTypeObject *type)
{
static_builtin_state *state = _PyStaticType_GetState(type);
PyObject **list = _PyStaticType_GET_WEAKREFS_LISTPTR(state);
while (*list != NULL) {
/* Note that clear_weakref() pops the first ref off the type's
weaklist before clearing its wr_object and wr_callback.
That is how we're able to loop over the list. */
clear_weakref((PyWeakReference *)*list);
}
}