Skip to content

Commit d52dd19

Browse files
ericsnowcurrentlyiritkatriel
authored andcommitted
pythongh-94673: Add Per-Interpreter tp_subclasses for Static Builtin Types (pythongh-95301)
1 parent befe5a5 commit d52dd19

File tree

8 files changed

+120
-31
lines changed

8 files changed

+120
-31
lines changed

Doc/c-api/typeobj.rst

+11-3
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ Quick Reference
135135
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
136136
| [:c:member:`~PyTypeObject.tp_cache`] | :c:type:`PyObject` * | | | | |
137137
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
138-
| [:c:member:`~PyTypeObject.tp_subclasses`] | :c:type:`PyObject` * | __subclasses__ | | | |
138+
| [:c:member:`~PyTypeObject.tp_subclasses`] | void * | __subclasses__ | | | |
139139
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
140140
| [:c:member:`~PyTypeObject.tp_weaklist`] | :c:type:`PyObject` * | | | | |
141141
+------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+
@@ -1934,9 +1934,17 @@ and :c:type:`PyType_Type` effectively act as defaults.)
19341934
This field is not inherited.
19351935

19361936

1937-
.. c:member:: PyObject* PyTypeObject.tp_subclasses
1937+
.. c:member:: void* PyTypeObject.tp_subclasses
19381938
1939-
List of weak references to subclasses. Internal use only.
1939+
A collection of subclasses. Internal use only. May be an invalid pointer.
1940+
1941+
To get a list of subclasses, call the Python method
1942+
:py:meth:`~class.__subclasses__`.
1943+
1944+
.. versionchanged:: 3.12
1945+
1946+
For some types, this field does not hold a valid :c:expr:`PyObject*`.
1947+
The type was changed to :c:expr:`void*` to indicate this.
19401948

19411949
**Inheritance:**
19421950

Doc/whatsnew/3.12.rst

+10
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,16 @@ Porting to Python 3.12
440440
using the existing public C-API instead, or, if necessary, the
441441
(internal-only) ``_PyObject_GET_WEAKREFS_LISTPTR()`` macro.
442442

443+
* This internal-only :c:member:`PyTypeObject.tp_subclasses` may now not be
444+
a valid object pointer. Its type was changed to :c:expr:`void *` to
445+
reflect this. We mention this in case someone happens to be accessing the
446+
internal-only field directly.
447+
448+
To get a list of subclasses, call the Python method
449+
:py:meth:`~class.__subclasses__` (using :c:func:`PyObject_CallMethod`,
450+
for example).
451+
452+
443453
Deprecated
444454
----------
445455

Include/cpython/object.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ struct _typeobject {
217217
inquiry tp_is_gc; /* For PyObject_IS_GC */
218218
PyObject *tp_bases;
219219
PyObject *tp_mro; /* method resolution order */
220-
PyObject *tp_cache;
221-
PyObject *tp_subclasses;
220+
PyObject *tp_cache; /* no longer used */
221+
void *tp_subclasses; /* for static builtin types this is an index */
222222
PyObject *tp_weaklist; /* not used for static builtin types */
223223
destructor tp_del;
224224

@@ -227,7 +227,6 @@ struct _typeobject {
227227

228228
destructor tp_finalize;
229229
vectorcallfunc tp_vectorcall;
230-
size_t tp_static_builtin_index; /* 0 means "not initialized" */
231230
};
232231

233232
/* This struct is used by the specializer

Include/internal/pycore_object.h

+1
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ _PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values)
351351
extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
352352
extern void _PyObject_FreeInstanceAttributes(PyObject *obj);
353353
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
354+
extern int _PyType_HasSubclasses(PyTypeObject *);
354355
extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
355356

356357
// Access macro to the members which are floating "behind" the object

Include/internal/pycore_typeobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct type_cache {
4545

4646
typedef struct {
4747
PyTypeObject *type;
48+
PyObject *tp_subclasses;
4849
/* We never clean up weakrefs for static builtin types since
4950
they will effectively never get triggered. However, there
5051
are also some diagnostic uses for the list of weakrefs,

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,7 @@ def delx(self): del self.__x
15071507
check((1,2,3), vsize('') + 3*self.P)
15081508
# type
15091509
# static type: PyTypeObject
1510-
fmt = 'P2nPI13Pl4Pn9Pn12PIPI'
1510+
fmt = 'P2nPI13Pl4Pn9Pn12PIP'
15111511
s = vsize('2P' + fmt)
15121512
check(int, s)
15131513
# class

Objects/structseq.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ _PyStructSequence_FiniType(PyTypeObject *type)
580580
assert(type->tp_base == &PyTuple_Type);
581581

582582
// Cannot delete a type if it still has subclasses
583-
if (type->tp_subclasses != NULL) {
583+
if (_PyType_HasSubclasses(type)) {
584584
return;
585585
}
586586

Objects/typeobject.c

+93-23
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,29 @@ static inline PyTypeObject * subclass_from_ref(PyObject *ref);
7373
static inline int
7474
static_builtin_index_is_set(PyTypeObject *self)
7575
{
76-
return self->tp_static_builtin_index > 0;
76+
return self->tp_subclasses != NULL;
7777
}
7878

7979
static inline size_t
8080
static_builtin_index_get(PyTypeObject *self)
8181
{
8282
assert(static_builtin_index_is_set(self));
8383
/* We store a 1-based index so 0 can mean "not initialized". */
84-
return self->tp_static_builtin_index - 1;
84+
return (size_t)self->tp_subclasses - 1;
8585
}
8686

8787
static inline void
8888
static_builtin_index_set(PyTypeObject *self, size_t index)
8989
{
9090
assert(index < _Py_MAX_STATIC_BUILTIN_TYPES);
9191
/* We store a 1-based index so 0 can mean "not initialized". */
92-
self->tp_static_builtin_index = index + 1;
92+
self->tp_subclasses = (PyObject *)(index + 1);
9393
}
9494

9595
static inline void
9696
static_builtin_index_clear(PyTypeObject *self)
9797
{
98-
self->tp_static_builtin_index = 0;
98+
self->tp_subclasses = NULL;
9999
}
100100

101101
static inline static_builtin_state *
@@ -127,6 +127,7 @@ 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_subclasses is left NULL until init_subclasses() sets it. */
130131
/* state->tp_weaklist is left NULL until insert_head() or insert_after()
131132
(in weakrefobject.c) sets it. */
132133
}
@@ -373,6 +374,8 @@ _PyTypes_Fini(PyInterpreterState *interp)
373374
}
374375

375376

377+
static PyObject * lookup_subclasses(PyTypeObject *);
378+
376379
void
377380
PyType_Modified(PyTypeObject *type)
378381
{
@@ -395,7 +398,7 @@ PyType_Modified(PyTypeObject *type)
395398
return;
396399
}
397400

398-
PyObject *subclasses = type->tp_subclasses;
401+
PyObject *subclasses = lookup_subclasses(type);
399402
if (subclasses != NULL) {
400403
assert(PyDict_CheckExact(subclasses));
401404

@@ -783,7 +786,7 @@ mro_hierarchy(PyTypeObject *type, PyObject *temp)
783786
Py_XDECREF(old_mro);
784787

785788
// Avoid creating an empty list if there is no subclass
786-
if (type->tp_subclasses != NULL) {
789+
if (_PyType_HasSubclasses(type)) {
787790
/* Obtain a copy of subclasses list to iterate over.
788791
789792
Otherwise type->tp_subclasses might be altered
@@ -4345,10 +4348,13 @@ type_dealloc_common(PyTypeObject *type)
43454348
}
43464349

43474350

4351+
static void clear_subclasses(PyTypeObject *self);
4352+
43484353
static void
43494354
clear_static_tp_subclasses(PyTypeObject *type)
43504355
{
4351-
if (type->tp_subclasses == NULL) {
4356+
PyObject *subclasses = lookup_subclasses(type);
4357+
if (subclasses == NULL) {
43524358
return;
43534359
}
43544360

@@ -4372,9 +4378,19 @@ clear_static_tp_subclasses(PyTypeObject *type)
43724378
going to leak. This mostly only affects embedding scenarios.
43734379
*/
43744380

4375-
// For now we just clear tp_subclasses.
4381+
// For now we just do a sanity check and then clear tp_subclasses.
4382+
Py_ssize_t i = 0;
4383+
PyObject *key, *ref; // borrowed ref
4384+
while (PyDict_Next(subclasses, &i, &key, &ref)) {
4385+
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
4386+
if (subclass == NULL) {
4387+
continue;
4388+
}
4389+
// All static builtin subtypes should have been finalized already.
4390+
assert(!(subclass->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
4391+
}
43764392

4377-
Py_CLEAR(type->tp_subclasses);
4393+
clear_subclasses(type);
43784394
}
43794395

43804396
void
@@ -4424,7 +4440,7 @@ type_dealloc(PyTypeObject *type)
44244440
Py_XDECREF(type->tp_bases);
44254441
Py_XDECREF(type->tp_mro);
44264442
Py_XDECREF(type->tp_cache);
4427-
Py_XDECREF(type->tp_subclasses);
4443+
clear_subclasses(type);
44284444

44294445
/* A type's tp_doc is heap allocated, unlike the tp_doc slots
44304446
* of most other objects. It's okay to cast it to char *.
@@ -4444,6 +4460,30 @@ type_dealloc(PyTypeObject *type)
44444460
}
44454461

44464462

4463+
static PyObject *
4464+
lookup_subclasses(PyTypeObject *self)
4465+
{
4466+
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
4467+
static_builtin_state *state = _PyStaticType_GetState(self);
4468+
assert(state != NULL);
4469+
return state->tp_subclasses;
4470+
}
4471+
return (PyObject *)self->tp_subclasses;
4472+
}
4473+
4474+
int
4475+
_PyType_HasSubclasses(PyTypeObject *self)
4476+
{
4477+
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN &&
4478+
_PyStaticType_GetState(self) == NULL) {
4479+
return 0;
4480+
}
4481+
if (lookup_subclasses(self) == NULL) {
4482+
return 0;
4483+
}
4484+
return 1;
4485+
}
4486+
44474487
PyObject*
44484488
_PyType_GetSubclasses(PyTypeObject *self)
44494489
{
@@ -4452,7 +4492,7 @@ _PyType_GetSubclasses(PyTypeObject *self)
44524492
return NULL;
44534493
}
44544494

4455-
PyObject *subclasses = self->tp_subclasses; // borrowed ref
4495+
PyObject *subclasses = lookup_subclasses(self); // borrowed ref
44564496
if (subclasses == NULL) {
44574497
return list;
44584498
}
@@ -6830,6 +6870,36 @@ _PyStaticType_InitBuiltin(PyTypeObject *self)
68306870
}
68316871

68326872

6873+
static PyObject *
6874+
init_subclasses(PyTypeObject *self)
6875+
{
6876+
PyObject *subclasses = PyDict_New();
6877+
if (subclasses == NULL) {
6878+
return NULL;
6879+
}
6880+
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
6881+
static_builtin_state *state = _PyStaticType_GetState(self);
6882+
state->tp_subclasses = subclasses;
6883+
return subclasses;
6884+
}
6885+
self->tp_subclasses = (void *)subclasses;
6886+
return subclasses;
6887+
}
6888+
6889+
static void
6890+
clear_subclasses(PyTypeObject *self)
6891+
{
6892+
/* Delete the dictionary to save memory. _PyStaticType_Dealloc()
6893+
callers also test if tp_subclasses is NULL to check if a static type
6894+
has no subclass. */
6895+
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
6896+
static_builtin_state *state = _PyStaticType_GetState(self);
6897+
Py_CLEAR(state->tp_subclasses);
6898+
return;
6899+
}
6900+
Py_CLEAR(self->tp_subclasses);
6901+
}
6902+
68336903
static int
68346904
add_subclass(PyTypeObject *base, PyTypeObject *type)
68356905
{
@@ -6846,9 +6916,9 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
68466916
// Only get tp_subclasses after creating the key and value.
68476917
// PyWeakref_NewRef() can trigger a garbage collection which can execute
68486918
// arbitrary Python code and so modify base->tp_subclasses.
6849-
PyObject *subclasses = base->tp_subclasses;
6919+
PyObject *subclasses = lookup_subclasses(base);
68506920
if (subclasses == NULL) {
6851-
base->tp_subclasses = subclasses = PyDict_New();
6921+
subclasses = init_subclasses(base);
68526922
if (subclasses == NULL) {
68536923
Py_DECREF(key);
68546924
Py_DECREF(ref);
@@ -6905,10 +6975,13 @@ get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
69056975
We fall back to manually traversing the values. */
69066976
Py_ssize_t i = 0;
69076977
PyObject *ref; // borrowed ref
6908-
while (PyDict_Next((PyObject *)base->tp_subclasses, &i, &key, &ref)) {
6909-
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
6910-
if (subclass == type) {
6911-
return Py_NewRef(key);
6978+
PyObject *subclasses = lookup_subclasses(base);
6979+
if (subclasses != NULL) {
6980+
while (PyDict_Next(subclasses, &i, &key, &ref)) {
6981+
PyTypeObject *subclass = subclass_from_ref(ref); // borrowed
6982+
if (subclass == type) {
6983+
return Py_NewRef(key);
6984+
}
69126985
}
69136986
}
69146987
/* It wasn't found. */
@@ -6918,7 +6991,7 @@ get_subclasses_key(PyTypeObject *type, PyTypeObject *base)
69186991
static void
69196992
remove_subclass(PyTypeObject *base, PyTypeObject *type)
69206993
{
6921-
PyObject *subclasses = base->tp_subclasses; // borrowed ref
6994+
PyObject *subclasses = lookup_subclasses(base); // borrowed ref
69226995
if (subclasses == NULL) {
69236996
return;
69246997
}
@@ -6934,10 +7007,7 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type)
69347007
Py_XDECREF(key);
69357008

69367009
if (PyDict_Size(subclasses) == 0) {
6937-
// Delete the dictionary to save memory. _PyStaticType_Dealloc()
6938-
// callers also test if tp_subclasses is NULL to check if a static type
6939-
// has no subclass.
6940-
Py_CLEAR(base->tp_subclasses);
7010+
clear_subclasses(base);
69417011
}
69427012
}
69437013

@@ -9022,7 +9092,7 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name,
90229092
// It is safe to use a borrowed reference because update_subclasses() is
90239093
// only used with update_slots_callback() which doesn't modify
90249094
// tp_subclasses.
9025-
PyObject *subclasses = type->tp_subclasses; // borrowed ref
9095+
PyObject *subclasses = lookup_subclasses(type); // borrowed ref
90269096
if (subclasses == NULL) {
90279097
return 0;
90289098
}

0 commit comments

Comments
 (0)