Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c38ceb0

Browse filesBrowse files
[3.12] gh-105020: Share tp_bases and tp_mro Between Interpreters For All Static Builtin Types (gh-105115) (gh-105124)
In gh-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 83c7386 commit c38ceb0
Copy full SHA for c38ceb0

File tree

Expand file treeCollapse file tree

7 files changed

+26544
-26462
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+26544
-26462
lines changed

‎Doc/data/python3.12.abi

Copy file name to clipboardExpand all lines: Doc/data/python3.12.abi
+26,422-26,428Lines changed: 26422 additions & 26428 deletions
Large diffs are not rendered by default.

‎Include/internal/pycore_object.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_object.h
+15Lines changed: 15 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Include/internal/pycore_typeobject.h
+2-4Lines changed: 2 additions & 4 deletions
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

Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_misc.py
+33Lines changed: 33 additions & 0 deletions
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
+3Lines changed: 3 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Modules/_testcapimodule.c
+23Lines changed: 23 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: Objects/typeobject.c
+46-30Lines changed: 46 additions & 30 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.