Skip to content

Commit 3e7cad3

Browse files
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.
1 parent 6e44bf9 commit 3e7cad3

File tree

7 files changed

+59
-1
lines changed

7 files changed

+59
-1
lines changed

Doc/c-api/typeobj.rst

+7
Original file line numberDiff line numberDiff line change
@@ -1942,6 +1942,13 @@ and :c:type:`PyType_Type` effectively act as defaults.)
19421942
Weak reference list head, for weak references to this type object. Not
19431943
inherited. Internal use only.
19441944

1945+
.. versionchanged:: 3.12
1946+
1947+
Internals detail: For the static builtin types this is always ``NULL``,
1948+
even if weakrefs are added. Instead, the weakrefs for each are stored
1949+
on ``PyInterpreterState``. Use the public C-API or the internal
1950+
``_PyObject_GET_WEAKREFS_LISTPTR()`` macro to avoid the distinction.
1951+
19451952
**Inheritance:**
19461953

19471954
This field is not inherited.

Doc/whatsnew/3.12.rst

+7
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,13 @@ Porting to Python 3.12
413413
``Py_UNICODE*`` based format (e.g. ``u``, ``Z``) anymore. Please migrate
414414
to other formats for Unicode like ``s``, ``z``, ``es``, and ``U``.
415415

416+
* ``tp_weaklist`` for all static builtin types is always ``NULL``.
417+
This is an internal-only field on ``PyTypeObject``
418+
but we're pointing out the change in case someone happens to be
419+
accessing the field directly anyway. To avoid breakage, consider
420+
using the existing public C-API instead, or, if necessary, the
421+
(internal-only) ``_PyObject_GET_WEAKREFS_LISTPTR()`` macro.
422+
416423
Deprecated
417424
----------
418425

Include/cpython/object.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ struct _typeobject {
219219
PyObject *tp_mro; /* method resolution order */
220220
PyObject *tp_cache;
221221
PyObject *tp_subclasses;
222-
PyObject *tp_weaklist;
222+
PyObject *tp_weaklist; /* not used for static builtin types */
223223
destructor tp_del;
224224

225225
/* Type attribute cache version tag. Added in version 2.6 */

Include/internal/pycore_object.h

+6
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ extern void _Py_PrintReferenceAddresses(FILE *);
220220
static inline PyObject **
221221
_PyObject_GET_WEAKREFS_LISTPTR(PyObject *op)
222222
{
223+
if (PyType_Check(op) &&
224+
((PyTypeObject *)op)->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
225+
static_builtin_state *state = _PyStaticType_GetState(
226+
(PyTypeObject *)op);
227+
return _PyStaticType_GET_WEAKREFS_LISTPTR(state);
228+
}
223229
Py_ssize_t offset = Py_TYPE(op)->tp_weaklistoffset;
224230
return (PyObject **)((char *)op + offset);
225231
}

Include/internal/pycore_typeobject.h

+13
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,20 @@ struct type_cache {
4545

4646
typedef struct {
4747
PyTypeObject *type;
48+
/* We never clean up weakrefs for static builtin types since
49+
they will effectively never get triggered. However, there
50+
are also some diagnostic uses for the list of weakrefs,
51+
so we still keep it. */
52+
PyObject *tp_weaklist;
4853
} static_builtin_state;
4954

55+
static inline PyObject **
56+
_PyStaticType_GET_WEAKREFS_LISTPTR(static_builtin_state *state)
57+
{
58+
assert(state != NULL);
59+
return &state->tp_weaklist;
60+
}
61+
5062
struct types_state {
5163
struct type_cache type_cache;
5264
size_t num_builtins_initialized;
@@ -58,6 +70,7 @@ extern PyStatus _PyTypes_InitSlotDefs(void);
5870

5971
extern int _PyStaticType_InitBuiltin(PyTypeObject *type);
6072
extern static_builtin_state * _PyStaticType_GetState(PyTypeObject *);
73+
extern void _PyStaticType_ClearWeakRefs(PyTypeObject *type);
6174
extern void _PyStaticType_Dealloc(PyTypeObject *type);
6275

6376

Objects/typeobject.c

+6
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ static_builtin_state_init(PyTypeObject *self)
127127

128128
static_builtin_state *state = static_builtin_state_get(interp, self);
129129
state->type = self;
130+
/* state->tp_weaklist is left NULL until insert_head() or insert_after()
131+
(in weakrefobject.c) sets it. */
130132
}
131133

132134
static void
@@ -138,6 +140,7 @@ static_builtin_state_clear(PyTypeObject *self)
138140

139141
static_builtin_state *state = static_builtin_state_get(interp, self);
140142
state->type = NULL;
143+
assert(state->tp_weaklist == NULL); // It was already cleared out.
141144
static_builtin_index_clear(self);
142145

143146
assert(interp->types.num_builtins_initialized > 0);
@@ -502,6 +505,8 @@ static PyMemberDef type_members[] = {
502505
{"__basicsize__", T_PYSSIZET, offsetof(PyTypeObject,tp_basicsize),READONLY},
503506
{"__itemsize__", T_PYSSIZET, offsetof(PyTypeObject, tp_itemsize), READONLY},
504507
{"__flags__", T_ULONG, offsetof(PyTypeObject, tp_flags), READONLY},
508+
/* Note that this value is misleading for static builtin types,
509+
since the memory at this offset will always be NULL. */
505510
{"__weakrefoffset__", T_PYSSIZET,
506511
offsetof(PyTypeObject, tp_weaklistoffset), READONLY},
507512
{"__base__", T_OBJECT, offsetof(PyTypeObject, tp_base), READONLY},
@@ -4353,6 +4358,7 @@ _PyStaticType_Dealloc(PyTypeObject *type)
43534358
type->tp_flags &= ~Py_TPFLAGS_READY;
43544359

43554360
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
4361+
_PyStaticType_ClearWeakRefs(type);
43564362
static_builtin_state_clear(type);
43574363
/* We leave _Py_TPFLAGS_STATIC_BUILTIN set on tp_flags. */
43584364
}

Objects/weakrefobject.c

+19
Original file line numberDiff line numberDiff line change
@@ -1019,3 +1019,22 @@ PyObject_ClearWeakRefs(PyObject *object)
10191019
PyErr_Restore(err_type, err_value, err_tb);
10201020
}
10211021
}
1022+
1023+
/* This function is called by _PyStaticType_Dealloc() to clear weak references.
1024+
*
1025+
* This is called at the end of runtime finalization, so we can just
1026+
* wipe out the type's weaklist. We don't bother with callbacks
1027+
* or anything else.
1028+
*/
1029+
void
1030+
_PyStaticType_ClearWeakRefs(PyTypeObject *type)
1031+
{
1032+
static_builtin_state *state = _PyStaticType_GetState(type);
1033+
PyObject **list = _PyStaticType_GET_WEAKREFS_LISTPTR(state);
1034+
while (*list != NULL) {
1035+
/* Note that clear_weakref() pops the first ref off the type's
1036+
weaklist before clearing its wr_object and wr_callback.
1037+
That is how we're able to loop over the list. */
1038+
clear_weakref((PyWeakReference *)*list);
1039+
}
1040+
}

0 commit comments

Comments
 (0)