From d23861b992fa6b040f05a2dadb784be0a74c75dc Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 12 Apr 2023 21:04:04 -0600 Subject: [PATCH 1/6] Immortalize in _PyStructSequence_InitBuiltinWithFlags(). --- Objects/structseq.c | 42 ++++++++++++++++++++++++------------------ Objects/typeobject.c | 1 + 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 2a5343815866d3..e3cafc5dbdd37e 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -517,15 +517,27 @@ _PyStructSequence_InitBuiltinWithFlags(PyTypeObject *type, return -1; } initialize_static_fields(type, desc, members, tp_flags); - if (_PyStaticType_InitBuiltin(type) < 0) { + + PyObject *dict = PyDict_New(); + if (dict == NULL) { PyMem_Free(members); - PyErr_Format(PyExc_RuntimeError, - "Can't initialize builtin type %s", - desc->name); return -1; } + type->tp_dict = dict; + if (initialize_static_type(type, desc, n_members, n_unnamed_members) < 0) { PyMem_Free(members); + Py_CLEAR(type->tp_dict); + return -1; + } + + _Py_SetImmortal(type); + if (_PyStaticType_InitBuiltin(type) < 0) { + PyMem_Free(members); + Py_CLEAR(type->tp_dict); + PyErr_Format(PyExc_RuntimeError, + "Can't initialize builtin type %s", + desc->name); return -1; } return 0; @@ -570,35 +582,29 @@ PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc) } +/* This is exposed in the internal API, not the public API. + It is only called on builtin static types, which are all + initialized via _PyStructSequence_InitBuiltinWithFlags(). */ + void _PyStructSequence_FiniType(PyTypeObject *type) { // Ensure that the type is initialized assert(type->tp_name != NULL); assert(type->tp_base == &PyTuple_Type); + assert((type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); + assert(_Py_IsImmortal(type)); // Cannot delete a type if it still has subclasses if (_PyType_HasSubclasses(type)) { + // XXX Shouldn't this be an error? return; } - // Undo PyStructSequence_NewType() - type->tp_name = NULL; + // Undo _PyStructSequence_InitBuiltinWithFlags() PyMem_Free(type->tp_members); _PyStaticType_Dealloc(type); - assert(Py_REFCNT(type) == 1); - // Undo Py_INCREF(type) of _PyStructSequence_InitType(). - // Don't use Py_DECREF(): static type must not be deallocated - Py_SET_REFCNT(type, 0); -#ifdef Py_REF_DEBUG - _Py_DecRefTotal(_PyInterpreterState_GET()); -#endif - - // Make sure that _PyStructSequence_InitType() will initialize - // the type again - assert(Py_REFCNT(type) == 0); - assert(type->tp_name == NULL); } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 07c900932b4c24..6a0f0478509089 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7030,6 +7030,7 @@ PyType_Ready(PyTypeObject *type) int _PyStaticType_InitBuiltin(PyTypeObject *self) { + assert(_Py_IsImmortal((PyObject *)self)); self->tp_flags |= _Py_TPFLAGS_STATIC_BUILTIN; assert(NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG); From 00e714a5f750a1c5f3642623532ac5478713571a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 25 Apr 2023 12:36:21 -0600 Subject: [PATCH 2/6] Un-initialize the builtin structseq types. --- Objects/structseq.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Objects/structseq.c b/Objects/structseq.c index e3cafc5dbdd37e..344ab9fd18b390 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -512,6 +512,8 @@ _PyStructSequence_InitBuiltinWithFlags(PyTypeObject *type, PyMemberDef *members; Py_ssize_t n_members, n_unnamed_members; + assert(type->tp_name == NULL); + assert(type->tp_members == NULL); members = initialize_members(desc, &n_members, &n_unnamed_members); if (members == NULL) { return -1; @@ -603,8 +605,12 @@ _PyStructSequence_FiniType(PyTypeObject *type) // Undo _PyStructSequence_InitBuiltinWithFlags() PyMem_Free(type->tp_members); + type->tp_members = NULL; _PyStaticType_Dealloc(type); + + // Mark the type as un-initialized. + type->tp_name = NULL; } From c3d292cbadc49bd7642deb9657f0bc4050d0e042 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 12 Apr 2023 10:13:04 -0600 Subject: [PATCH 3/6] Immortalize tp_dict, tp_bases, and tp_mro for builtin static types. --- Include/internal/pycore_object.h | 10 ++++++++++ Objects/typeobject.c | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 2ca047846e0935..1a0d3d27aced14 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -73,6 +73,16 @@ static inline void _Py_SetImmortal(PyObject *op) } #define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) +static inline void +_Py_EnsureImmortal(PyObject *op) +{ + if (_Py_IsImmortal(op)) { + return; + } + assert(op->ob_type != &PyTuple_Type || PyTuple_GET_SIZE(op) > 0); + _Py_SetImmortal(op); +} + static inline void _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 6a0f0478509089..ad7f5422ec6d3f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2357,6 +2357,9 @@ mro_internal(PyTypeObject *type, PyObject **p_old_mro) } type->tp_mro = new_mro; + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { + _Py_EnsureImmortal(new_mro); + } type_mro_modified(type, type->tp_mro); /* corner case: the super class might have been hidden @@ -4498,6 +4501,17 @@ _PyStaticType_Dealloc(PyTypeObject *type) type_dealloc_common(type); + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { + if (type->tp_dict != NULL) { + type->tp_dict->ob_refcnt = 1; + } + if (type->tp_bases != NULL && PyTuple_GET_SIZE(type->tp_bases) > 0) { + type->tp_bases->ob_refcnt = 1; + } + if (type->tp_mro != NULL && PyTuple_GET_SIZE(type->tp_mro) > 0) { + type->tp_mro->ob_refcnt = 1; + } + } Py_CLEAR(type->tp_dict); Py_CLEAR(type->tp_bases); Py_CLEAR(type->tp_mro); @@ -6585,6 +6599,9 @@ type_ready_set_bases(PyTypeObject *type) return -1; } type->tp_bases = bases; + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { + _Py_EnsureImmortal(bases); + } } return 0; } @@ -6602,6 +6619,9 @@ type_ready_set_dict(PyTypeObject *type) return -1; } type->tp_dict = dict; + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { + _Py_SetImmortal(dict); + } return 0; } From 018ee57b0026e536657f411a9169c64fe24e6648 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 12 Apr 2023 10:18:34 -0600 Subject: [PATCH 4/6] Do the work in _PyStaticType_InitBuiltin(). --- Include/internal/pycore_object.h | 10 ---------- Objects/typeobject.c | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 1a0d3d27aced14..2ca047846e0935 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -73,16 +73,6 @@ static inline void _Py_SetImmortal(PyObject *op) } #define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) -static inline void -_Py_EnsureImmortal(PyObject *op) -{ - if (_Py_IsImmortal(op)) { - return; - } - assert(op->ob_type != &PyTuple_Type || PyTuple_GET_SIZE(op) > 0); - _Py_SetImmortal(op); -} - static inline void _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ad7f5422ec6d3f..c251698346c37b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -2357,9 +2357,6 @@ mro_internal(PyTypeObject *type, PyObject **p_old_mro) } type->tp_mro = new_mro; - if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { - _Py_EnsureImmortal(new_mro); - } type_mro_modified(type, type->tp_mro); /* corner case: the super class might have been hidden @@ -6599,9 +6596,6 @@ type_ready_set_bases(PyTypeObject *type) return -1; } type->tp_bases = bases; - if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { - _Py_EnsureImmortal(bases); - } } return 0; } @@ -6619,9 +6613,6 @@ type_ready_set_dict(PyTypeObject *type) return -1; } type->tp_dict = dict; - if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { - _Py_SetImmortal(dict); - } return 0; } @@ -7063,6 +7054,17 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) if (res < 0) { static_builtin_state_clear(self); } + + _Py_SetImmortal(self->tp_dict); + if (!_Py_IsImmortal(self->tp_bases)) { + assert(PyTuple_GET_SIZE(self->tp_bases) > 0); + _Py_SetImmortal(self->tp_bases); + } + if (!_Py_IsImmortal(self->tp_mro)) { + assert(PyTuple_GET_SIZE(self->tp_mro) > 0); + _Py_SetImmortal(self->tp_mro); + } + return res; } From 1d1393a691cfe0dad0f3595649a6b914dee7b426 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 12 Apr 2023 14:51:57 -0600 Subject: [PATCH 5/6] Add _Py_EnsureImmortal() and _Py_ImmortalObjectsFini(). --- Include/internal/pycore_global_objects.h | 18 ++++++ Include/internal/pycore_runtime.h | 2 + Objects/typeobject.c | 12 +--- Python/pylifecycle.c | 1 + Python/pystate.c | 76 ++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 9 deletions(-) diff --git a/Include/internal/pycore_global_objects.h b/Include/internal/pycore_global_objects.h index 64d9384df9c5c5..4c5b704531c387 100644 --- a/Include/internal/pycore_global_objects.h +++ b/Include/internal/pycore_global_objects.h @@ -20,6 +20,24 @@ extern "C" { #define _PY_NSMALLNEGINTS 5 +struct _Py_immortalized_object { + PyObject *obj; + int final_refcnt; + struct _Py_immortalized_object *next; +}; + +struct _Py_immortalized_objects { + Py_ssize_t count; + struct _Py_immortalized_object *head; + struct _Py_immortalized_object *last; +#define _Py_IMMORTALIZED_ARRAY_SIZE 100 + struct _Py_immortalized_object _objects[_Py_IMMORTALIZED_ARRAY_SIZE]; +}; + +extern void _Py_EnsureImmortal(PyObject *); +extern void _Py_ImmortalObjectsFini(void); + + // Only immutable objects should be considered runtime-global. // All others must be per-interpreter. diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index d1b165d0ab9c38..fc36456f86ba5b 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -152,6 +152,8 @@ typedef struct pyruntimestate { struct _Py_unicode_runtime_state unicode_state; struct _types_runtime_state types; + /* All non-static immortal objects (need to be cleaned up during fini). */ + struct _Py_immortalized_objects immortalized_objects; /* All the objects that are shared by the runtime's interpreters. */ struct _Py_static_objects static_objects; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index c251698346c37b..85722b78f1631b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7055,15 +7055,9 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) static_builtin_state_clear(self); } - _Py_SetImmortal(self->tp_dict); - if (!_Py_IsImmortal(self->tp_bases)) { - assert(PyTuple_GET_SIZE(self->tp_bases) > 0); - _Py_SetImmortal(self->tp_bases); - } - if (!_Py_IsImmortal(self->tp_mro)) { - assert(PyTuple_GET_SIZE(self->tp_mro) > 0); - _Py_SetImmortal(self->tp_mro); - } + _Py_EnsureImmortal(self->tp_dict); + _Py_EnsureImmortal(self->tp_bases); + _Py_EnsureImmortal(self->tp_mro); return res; } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index ebf1a0bff54eb0..0ac7ec8e294f2f 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1733,6 +1733,7 @@ finalize_interp_clear(PyThreadState *tstate) _Py_ClearFileSystemEncoding(); _Py_Deepfreeze_Fini(); _PyPerfTrampoline_Fini(); + _Py_ImmortalObjectsFini(); } finalize_interp_types(tstate->interp); diff --git a/Python/pystate.c b/Python/pystate.c index b2ef7e2dddeeba..2f1d97d28c4f82 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2758,6 +2758,82 @@ _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) } +/******************/ +/* global objects */ +/******************/ + +static void +immortalized_add(struct _Py_immortalized_objects *state, PyObject *obj) +{ + struct _Py_immortalized_object *entry; + if (state->count < _Py_IMMORTALIZED_ARRAY_SIZE) { + entry = &state->_objects[state->count]; + } + else { + entry = PyMem_RawMalloc(sizeof(*entry)); + if (entry == NULL) { + Py_FatalError("could not allocate immortalized list entry"); + } + } + + entry->obj = obj; + entry->final_refcnt = Py_REFCNT(obj); + entry->next = NULL; + + if (state->head == NULL) { + assert(state->count == 0); + assert(state->last == NULL); + state->head = entry; + } + else { + state->last->next = entry; + } + state->count += 1; + state->last = entry; +} + +static void +immortalized_fini(struct _Py_immortalized_objects *state) +{ + struct _Py_immortalized_object *next = state->head; + state->head = NULL; + state->last = NULL; + for (int i = 0; i < _Py_IMMORTALIZED_ARRAY_SIZE && next != NULL; i++) { + struct _Py_immortalized_object *entry = next; + next = entry->next; + entry->obj->ob_refcnt = entry->final_refcnt; + } + while (next != NULL) { + struct _Py_immortalized_object *entry = next; + next = next->next; + entry->obj->ob_refcnt = entry->final_refcnt; + PyMem_RawFree(entry); + } +} + +void +_Py_EnsureImmortal(PyObject *obj) +{ + assert(_Py_IsMainInterpreter(_PyInterpreterState_GET())); + if (_Py_IsImmortal(obj)) { + return; + } + + _Py_SetImmortal(obj); + immortalized_add(&_PyRuntime.immortalized_objects, obj); +} + +void +_Py_ImmortalObjectsFini(void) +{ + immortalized_fini(&_PyRuntime.immortalized_objects); +} + + +/*************/ +/* other API */ +/*************/ + _PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) { From 3eebdffdfd93ac6329f43985aed4ea2f83abed74 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 12 Apr 2023 21:20:34 -0600 Subject: [PATCH 6/6] Recursively immortalize. --- Python/pystate.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index 2f1d97d28c4f82..d014efc62049dc 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2821,6 +2821,23 @@ _Py_EnsureImmortal(PyObject *obj) _Py_SetImmortal(obj); immortalized_add(&_PyRuntime.immortalized_objects, obj); + + if (Py_TYPE(obj) == &PyDict_Type) { + Py_ssize_t i = 0; + PyObject *key, *value; // borrowed ref + while (PyDict_Next(obj, &i, &key, &value)) { + _Py_EnsureImmortal(key); + _Py_EnsureImmortal(value); + } + } + else if (Py_TYPE(obj) == &PyTuple_Type) { + assert(PyTuple_GET_SIZE(obj) > 0); + Py_ssize_t size = PyTuple_GET_SIZE(obj); + assert(size > 0); + for (Py_ssize_t i = 0; i < size; i++) { + _Py_EnsureImmortal(PyTuple_GET_ITEM(obj, i)); + } + } } void