Skip to content

Commit c28c929

Browse files
ericsnowcurrentlymiss-islington
authored andcommitted
pythongh-105020: Share tp_bases and tp_mro Between Interpreters For All Static Builtin Types (pythongh-105115)
In pythongh-103912 we added tp_bases and tp_mro to each PyInterpreterState.types.builtins entry. However, doing so ignored the fact that both PyTypeObject fields are public API, and not documented as internal (as opposed to tp_subclasses). We address that here by reverting back to shared objects, making them immortal in the process. (cherry picked from commit 7be667d) Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
1 parent af7b55d commit c28c929

File tree

6 files changed

+122
-34
lines changed

6 files changed

+122
-34
lines changed

Include/internal/pycore_object.h

+15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ static inline void _Py_SetImmortal(PyObject *op)
7676
}
7777
#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op))
7878

79+
/* _Py_ClearImmortal() should only be used during runtime finalization. */
80+
static inline void _Py_ClearImmortal(PyObject *op)
81+
{
82+
if (op) {
83+
assert(op->ob_refcnt == _Py_IMMORTAL_REFCNT);
84+
op->ob_refcnt = 1;
85+
Py_DECREF(op);
86+
}
87+
}
88+
#define _Py_ClearImmortal(op) \
89+
do { \
90+
_Py_ClearImmortal(_PyObject_CAST(op)); \
91+
op = NULL; \
92+
} while (0)
93+
7994
static inline void
8095
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
8196
{

Include/internal/pycore_typeobject.h

+2-4
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,9 @@ typedef struct {
4646
PyTypeObject *type;
4747
int readying;
4848
int ready;
49-
// XXX tp_dict, tp_bases, and tp_mro can probably be statically
50-
// allocated, instead of dynamically and stored on the interpreter.
49+
// XXX tp_dict can probably be statically allocated,
50+
// instead of dynamically and stored on the interpreter.
5151
PyObject *tp_dict;
52-
PyObject *tp_bases;
53-
PyObject *tp_mro;
5452
PyObject *tp_subclasses;
5553
/* We never clean up weakrefs for static builtin types since
5654
they will effectively never get triggered. However, there

Lib/test/test_capi/test_misc.py

+33
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,39 @@ def test_module_state_shared_in_global(self):
15931593
self.assertEqual(main_attr_id, subinterp_attr_id)
15941594

15951595

1596+
class BuiltinStaticTypesTests(unittest.TestCase):
1597+
1598+
TYPES = [
1599+
object,
1600+
type,
1601+
int,
1602+
str,
1603+
dict,
1604+
type(None),
1605+
bool,
1606+
BaseException,
1607+
Exception,
1608+
Warning,
1609+
DeprecationWarning, # Warning subclass
1610+
]
1611+
1612+
def test_tp_bases_is_set(self):
1613+
# PyTypeObject.tp_bases is documented as public API.
1614+
# See https://github.com/python/cpython/issues/105020.
1615+
for typeobj in self.TYPES:
1616+
with self.subTest(typeobj):
1617+
bases = _testcapi.type_get_tp_bases(typeobj)
1618+
self.assertIsNot(bases, None)
1619+
1620+
def test_tp_mro_is_set(self):
1621+
# PyTypeObject.tp_bases is documented as public API.
1622+
# See https://github.com/python/cpython/issues/105020.
1623+
for typeobj in self.TYPES:
1624+
with self.subTest(typeobj):
1625+
mro = _testcapi.type_get_tp_mro(typeobj)
1626+
self.assertIsNot(mro, None)
1627+
1628+
15961629
class TestThreadState(unittest.TestCase):
15971630

15981631
@threading_helper.reap_threads
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``PyTypeObject.tp_bases`` (and ``tp_mro``) for builtin static types are now
2+
shared by all interpreters, whereas in 3.12-beta1 they were stored on
3+
``PyInterpreterState``. Also note that now the tuples are immortal objects.

Modules/_testcapimodule.c

+23
Original file line numberDiff line numberDiff line change
@@ -2606,6 +2606,27 @@ type_assign_version(PyObject *self, PyObject *type)
26062606
}
26072607

26082608

2609+
static PyObject *
2610+
type_get_tp_bases(PyObject *self, PyObject *type)
2611+
{
2612+
PyObject *bases = ((PyTypeObject *)type)->tp_bases;
2613+
if (bases == NULL) {
2614+
Py_RETURN_NONE;
2615+
}
2616+
return Py_NewRef(bases);
2617+
}
2618+
2619+
static PyObject *
2620+
type_get_tp_mro(PyObject *self, PyObject *type)
2621+
{
2622+
PyObject *mro = ((PyTypeObject *)type)->tp_mro;
2623+
if (mro == NULL) {
2624+
Py_RETURN_NONE;
2625+
}
2626+
return Py_NewRef(mro);
2627+
}
2628+
2629+
26092630
// Test PyThreadState C API
26102631
static PyObject *
26112632
test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
@@ -3361,6 +3382,8 @@ static PyMethodDef TestMethods[] = {
33613382
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
33623383
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
33633384
{"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
3385+
{"type_get_tp_bases", type_get_tp_bases, METH_O},
3386+
{"type_get_tp_mro", type_get_tp_mro, METH_O},
33643387
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
33653388
{"frame_getlocals", frame_getlocals, METH_O, NULL},
33663389
{"frame_getglobals", frame_getglobals, METH_O, NULL},

Objects/typeobject.c

+46-30
Original file line numberDiff line numberDiff line change
@@ -268,12 +268,6 @@ clear_tp_dict(PyTypeObject *self)
268268
static inline PyObject *
269269
lookup_tp_bases(PyTypeObject *self)
270270
{
271-
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
272-
PyInterpreterState *interp = _PyInterpreterState_GET();
273-
static_builtin_state *state = _PyStaticType_GetState(interp, self);
274-
assert(state != NULL);
275-
return state->tp_bases;
276-
}
277271
return self->tp_bases;
278272
}
279273

@@ -287,12 +281,22 @@ _PyType_GetBases(PyTypeObject *self)
287281
static inline void
288282
set_tp_bases(PyTypeObject *self, PyObject *bases)
289283
{
284+
assert(PyTuple_CheckExact(bases));
290285
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
291-
PyInterpreterState *interp = _PyInterpreterState_GET();
292-
static_builtin_state *state = _PyStaticType_GetState(interp, self);
293-
assert(state != NULL);
294-
state->tp_bases = bases;
295-
return;
286+
// XXX tp_bases can probably be statically allocated for each
287+
// static builtin type.
288+
assert(_Py_IsMainInterpreter(_PyInterpreterState_GET()));
289+
assert(self->tp_bases == NULL);
290+
if (PyTuple_GET_SIZE(bases) == 0) {
291+
assert(self->tp_base == NULL);
292+
}
293+
else {
294+
assert(PyTuple_GET_SIZE(bases) == 1);
295+
assert(PyTuple_GET_ITEM(bases, 0) == (PyObject *)self->tp_base);
296+
assert(self->tp_base->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
297+
assert(_Py_IsImmortal(self->tp_base));
298+
}
299+
_Py_SetImmortal(bases);
296300
}
297301
self->tp_bases = bases;
298302
}
@@ -301,10 +305,14 @@ static inline void
301305
clear_tp_bases(PyTypeObject *self)
302306
{
303307
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
304-
PyInterpreterState *interp = _PyInterpreterState_GET();
305-
static_builtin_state *state = _PyStaticType_GetState(interp, self);
306-
assert(state != NULL);
307-
Py_CLEAR(state->tp_bases);
308+
if (_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
309+
if (self->tp_bases != NULL
310+
&& PyTuple_GET_SIZE(self->tp_bases) > 0)
311+
{
312+
assert(_Py_IsImmortal(self->tp_bases));
313+
_Py_ClearImmortal(self->tp_bases);
314+
}
315+
}
308316
return;
309317
}
310318
Py_CLEAR(self->tp_bases);
@@ -314,12 +322,6 @@ clear_tp_bases(PyTypeObject *self)
314322
static inline PyObject *
315323
lookup_tp_mro(PyTypeObject *self)
316324
{
317-
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
318-
PyInterpreterState *interp = _PyInterpreterState_GET();
319-
static_builtin_state *state = _PyStaticType_GetState(interp, self);
320-
assert(state != NULL);
321-
return state->tp_mro;
322-
}
323325
return self->tp_mro;
324326
}
325327

@@ -333,12 +335,14 @@ _PyType_GetMRO(PyTypeObject *self)
333335
static inline void
334336
set_tp_mro(PyTypeObject *self, PyObject *mro)
335337
{
338+
assert(PyTuple_CheckExact(mro));
336339
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
337-
PyInterpreterState *interp = _PyInterpreterState_GET();
338-
static_builtin_state *state = _PyStaticType_GetState(interp, self);
339-
assert(state != NULL);
340-
state->tp_mro = mro;
341-
return;
340+
// XXX tp_mro can probably be statically allocated for each
341+
// static builtin type.
342+
assert(_Py_IsMainInterpreter(_PyInterpreterState_GET()));
343+
assert(self->tp_mro == NULL);
344+
/* Other checks are done via set_tp_bases. */
345+
_Py_SetImmortal(mro);
342346
}
343347
self->tp_mro = mro;
344348
}
@@ -347,10 +351,14 @@ static inline void
347351
clear_tp_mro(PyTypeObject *self)
348352
{
349353
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
350-
PyInterpreterState *interp = _PyInterpreterState_GET();
351-
static_builtin_state *state = _PyStaticType_GetState(interp, self);
352-
assert(state != NULL);
353-
Py_CLEAR(state->tp_mro);
354+
if (_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
355+
if (self->tp_mro != NULL
356+
&& PyTuple_GET_SIZE(self->tp_mro) > 0)
357+
{
358+
assert(_Py_IsImmortal(self->tp_mro));
359+
_Py_ClearImmortal(self->tp_mro);
360+
}
361+
}
354362
return;
355363
}
356364
Py_CLEAR(self->tp_mro);
@@ -7153,6 +7161,14 @@ type_ready_preheader(PyTypeObject *type)
71537161
static int
71547162
type_ready_mro(PyTypeObject *type)
71557163
{
7164+
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
7165+
if (!_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
7166+
assert(lookup_tp_mro(type) != NULL);
7167+
return 0;
7168+
}
7169+
assert(lookup_tp_mro(type) == NULL);
7170+
}
7171+
71567172
/* Calculate method resolution order */
71577173
if (mro_internal(type, NULL) < 0) {
71587174
return -1;

0 commit comments

Comments
 (0)