mirror of
https://github.com/python/cpython.git
synced 2025-08-07 18:38:38 +00:00
gh-105020: Share tp_bases and tp_mro Between Interpreters For All Static Builtin Types (gh-105115)
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.
This commit is contained in:
parent
26e7bbf66e
commit
7be667dfaf
6 changed files with 122 additions and 34 deletions
|
@ -76,6 +76,21 @@ static inline void _Py_SetImmortal(PyObject *op)
|
||||||
}
|
}
|
||||||
#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op))
|
#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op))
|
||||||
|
|
||||||
|
/* _Py_ClearImmortal() should only be used during runtime finalization. */
|
||||||
|
static inline void _Py_ClearImmortal(PyObject *op)
|
||||||
|
{
|
||||||
|
if (op) {
|
||||||
|
assert(op->ob_refcnt == _Py_IMMORTAL_REFCNT);
|
||||||
|
op->ob_refcnt = 1;
|
||||||
|
Py_DECREF(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#define _Py_ClearImmortal(op) \
|
||||||
|
do { \
|
||||||
|
_Py_ClearImmortal(_PyObject_CAST(op)); \
|
||||||
|
op = NULL; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
|
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,11 +46,9 @@ typedef struct {
|
||||||
PyTypeObject *type;
|
PyTypeObject *type;
|
||||||
int readying;
|
int readying;
|
||||||
int ready;
|
int ready;
|
||||||
// XXX tp_dict, tp_bases, and tp_mro can probably be statically
|
// XXX tp_dict can probably be statically allocated,
|
||||||
// allocated, instead of dynamically and stored on the interpreter.
|
// instead of dynamically and stored on the interpreter.
|
||||||
PyObject *tp_dict;
|
PyObject *tp_dict;
|
||||||
PyObject *tp_bases;
|
|
||||||
PyObject *tp_mro;
|
|
||||||
PyObject *tp_subclasses;
|
PyObject *tp_subclasses;
|
||||||
/* We never clean up weakrefs for static builtin types since
|
/* We never clean up weakrefs for static builtin types since
|
||||||
they will effectively never get triggered. However, there
|
they will effectively never get triggered. However, there
|
||||||
|
|
|
@ -1593,6 +1593,39 @@ class SubinterpreterTest(unittest.TestCase):
|
||||||
self.assertEqual(main_attr_id, subinterp_attr_id)
|
self.assertEqual(main_attr_id, subinterp_attr_id)
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltinStaticTypesTests(unittest.TestCase):
|
||||||
|
|
||||||
|
TYPES = [
|
||||||
|
object,
|
||||||
|
type,
|
||||||
|
int,
|
||||||
|
str,
|
||||||
|
dict,
|
||||||
|
type(None),
|
||||||
|
bool,
|
||||||
|
BaseException,
|
||||||
|
Exception,
|
||||||
|
Warning,
|
||||||
|
DeprecationWarning, # Warning subclass
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_tp_bases_is_set(self):
|
||||||
|
# PyTypeObject.tp_bases is documented as public API.
|
||||||
|
# See https://github.com/python/cpython/issues/105020.
|
||||||
|
for typeobj in self.TYPES:
|
||||||
|
with self.subTest(typeobj):
|
||||||
|
bases = _testcapi.type_get_tp_bases(typeobj)
|
||||||
|
self.assertIsNot(bases, None)
|
||||||
|
|
||||||
|
def test_tp_mro_is_set(self):
|
||||||
|
# PyTypeObject.tp_bases is documented as public API.
|
||||||
|
# See https://github.com/python/cpython/issues/105020.
|
||||||
|
for typeobj in self.TYPES:
|
||||||
|
with self.subTest(typeobj):
|
||||||
|
mro = _testcapi.type_get_tp_mro(typeobj)
|
||||||
|
self.assertIsNot(mro, None)
|
||||||
|
|
||||||
|
|
||||||
class TestThreadState(unittest.TestCase):
|
class TestThreadState(unittest.TestCase):
|
||||||
|
|
||||||
@threading_helper.reap_threads
|
@threading_helper.reap_threads
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
``PyTypeObject.tp_bases`` (and ``tp_mro``) for builtin static types are now
|
||||||
|
shared by all interpreters, whereas in 3.12-beta1 they were stored on
|
||||||
|
``PyInterpreterState``. Also note that now the tuples are immortal objects.
|
|
@ -2606,6 +2606,27 @@ type_assign_version(PyObject *self, PyObject *type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
type_get_tp_bases(PyObject *self, PyObject *type)
|
||||||
|
{
|
||||||
|
PyObject *bases = ((PyTypeObject *)type)->tp_bases;
|
||||||
|
if (bases == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return Py_NewRef(bases);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
type_get_tp_mro(PyObject *self, PyObject *type)
|
||||||
|
{
|
||||||
|
PyObject *mro = ((PyTypeObject *)type)->tp_mro;
|
||||||
|
if (mro == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return Py_NewRef(mro);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Test PyThreadState C API
|
// Test PyThreadState C API
|
||||||
static PyObject *
|
static PyObject *
|
||||||
test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
|
test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
|
||||||
|
@ -3361,6 +3382,8 @@ static PyMethodDef TestMethods[] = {
|
||||||
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
|
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
|
||||||
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
|
{"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")},
|
||||||
{"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
|
{"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")},
|
||||||
|
{"type_get_tp_bases", type_get_tp_bases, METH_O},
|
||||||
|
{"type_get_tp_mro", type_get_tp_mro, METH_O},
|
||||||
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
|
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
|
||||||
{"frame_getlocals", frame_getlocals, METH_O, NULL},
|
{"frame_getlocals", frame_getlocals, METH_O, NULL},
|
||||||
{"frame_getglobals", frame_getglobals, METH_O, NULL},
|
{"frame_getglobals", frame_getglobals, METH_O, NULL},
|
||||||
|
|
|
@ -268,12 +268,6 @@ clear_tp_dict(PyTypeObject *self)
|
||||||
static inline PyObject *
|
static inline PyObject *
|
||||||
lookup_tp_bases(PyTypeObject *self)
|
lookup_tp_bases(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
|
||||||
static_builtin_state *state = _PyStaticType_GetState(interp, self);
|
|
||||||
assert(state != NULL);
|
|
||||||
return state->tp_bases;
|
|
||||||
}
|
|
||||||
return self->tp_bases;
|
return self->tp_bases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,12 +281,22 @@ _PyType_GetBases(PyTypeObject *self)
|
||||||
static inline void
|
static inline void
|
||||||
set_tp_bases(PyTypeObject *self, PyObject *bases)
|
set_tp_bases(PyTypeObject *self, PyObject *bases)
|
||||||
{
|
{
|
||||||
|
assert(PyTuple_CheckExact(bases));
|
||||||
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
// XXX tp_bases can probably be statically allocated for each
|
||||||
static_builtin_state *state = _PyStaticType_GetState(interp, self);
|
// static builtin type.
|
||||||
assert(state != NULL);
|
assert(_Py_IsMainInterpreter(_PyInterpreterState_GET()));
|
||||||
state->tp_bases = bases;
|
assert(self->tp_bases == NULL);
|
||||||
return;
|
if (PyTuple_GET_SIZE(bases) == 0) {
|
||||||
|
assert(self->tp_base == NULL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(PyTuple_GET_SIZE(bases) == 1);
|
||||||
|
assert(PyTuple_GET_ITEM(bases, 0) == (PyObject *)self->tp_base);
|
||||||
|
assert(self->tp_base->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN);
|
||||||
|
assert(_Py_IsImmortal(self->tp_base));
|
||||||
|
}
|
||||||
|
_Py_SetImmortal(bases);
|
||||||
}
|
}
|
||||||
self->tp_bases = bases;
|
self->tp_bases = bases;
|
||||||
}
|
}
|
||||||
|
@ -301,10 +305,14 @@ static inline void
|
||||||
clear_tp_bases(PyTypeObject *self)
|
clear_tp_bases(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
if (_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
|
||||||
static_builtin_state *state = _PyStaticType_GetState(interp, self);
|
if (self->tp_bases != NULL
|
||||||
assert(state != NULL);
|
&& PyTuple_GET_SIZE(self->tp_bases) > 0)
|
||||||
Py_CLEAR(state->tp_bases);
|
{
|
||||||
|
assert(_Py_IsImmortal(self->tp_bases));
|
||||||
|
_Py_ClearImmortal(self->tp_bases);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Py_CLEAR(self->tp_bases);
|
Py_CLEAR(self->tp_bases);
|
||||||
|
@ -314,12 +322,6 @@ clear_tp_bases(PyTypeObject *self)
|
||||||
static inline PyObject *
|
static inline PyObject *
|
||||||
lookup_tp_mro(PyTypeObject *self)
|
lookup_tp_mro(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
|
||||||
static_builtin_state *state = _PyStaticType_GetState(interp, self);
|
|
||||||
assert(state != NULL);
|
|
||||||
return state->tp_mro;
|
|
||||||
}
|
|
||||||
return self->tp_mro;
|
return self->tp_mro;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,12 +335,14 @@ _PyType_GetMRO(PyTypeObject *self)
|
||||||
static inline void
|
static inline void
|
||||||
set_tp_mro(PyTypeObject *self, PyObject *mro)
|
set_tp_mro(PyTypeObject *self, PyObject *mro)
|
||||||
{
|
{
|
||||||
|
assert(PyTuple_CheckExact(mro));
|
||||||
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
// XXX tp_mro can probably be statically allocated for each
|
||||||
static_builtin_state *state = _PyStaticType_GetState(interp, self);
|
// static builtin type.
|
||||||
assert(state != NULL);
|
assert(_Py_IsMainInterpreter(_PyInterpreterState_GET()));
|
||||||
state->tp_mro = mro;
|
assert(self->tp_mro == NULL);
|
||||||
return;
|
/* Other checks are done via set_tp_bases. */
|
||||||
|
_Py_SetImmortal(mro);
|
||||||
}
|
}
|
||||||
self->tp_mro = mro;
|
self->tp_mro = mro;
|
||||||
}
|
}
|
||||||
|
@ -347,10 +351,14 @@ static inline void
|
||||||
clear_tp_mro(PyTypeObject *self)
|
clear_tp_mro(PyTypeObject *self)
|
||||||
{
|
{
|
||||||
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
if (_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
|
||||||
static_builtin_state *state = _PyStaticType_GetState(interp, self);
|
if (self->tp_mro != NULL
|
||||||
assert(state != NULL);
|
&& PyTuple_GET_SIZE(self->tp_mro) > 0)
|
||||||
Py_CLEAR(state->tp_mro);
|
{
|
||||||
|
assert(_Py_IsImmortal(self->tp_mro));
|
||||||
|
_Py_ClearImmortal(self->tp_mro);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Py_CLEAR(self->tp_mro);
|
Py_CLEAR(self->tp_mro);
|
||||||
|
@ -7153,6 +7161,14 @@ type_ready_preheader(PyTypeObject *type)
|
||||||
static int
|
static int
|
||||||
type_ready_mro(PyTypeObject *type)
|
type_ready_mro(PyTypeObject *type)
|
||||||
{
|
{
|
||||||
|
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||||
|
if (!_Py_IsMainInterpreter(_PyInterpreterState_GET())) {
|
||||||
|
assert(lookup_tp_mro(type) != NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
assert(lookup_tp_mro(type) == NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/* Calculate method resolution order */
|
/* Calculate method resolution order */
|
||||||
if (mro_internal(type, NULL) < 0) {
|
if (mro_internal(type, NULL) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue