From 5de3f04d1e762ae66d981846b75825110eba6559 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 7 Jul 2022 18:40:52 -0600 Subject: [PATCH 01/23] Add PyInterpreterState.types. --- Include/internal/pycore_interp.h | 2 +- Include/internal/pycore_typeobject.h | 5 +++++ Objects/typeobject.c | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 6ce2945cd9fb7b..d71386953a0dd0 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -173,7 +173,7 @@ struct _is { struct _Py_exc_state exc_state; struct ast_state ast; - struct type_cache type_cache; + struct types_state types; struct callable_cache callable_cache; /* The following fields are here to avoid allocation during init. diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index c480a3a57b436c..af5deb00bf287b 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -39,6 +39,11 @@ struct type_cache { #endif }; +struct types_state { + struct type_cache type_cache; +}; + + extern PyStatus _PyTypes_InitSlotDefs(void); extern void _PyStaticType_Dealloc(PyTypeObject *type); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 5ebff6084f4a97..be6166610bb78a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -204,7 +204,7 @@ static struct type_cache* get_type_cache(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); - return &interp->type_cache; + return &interp->types.type_cache; } @@ -223,7 +223,7 @@ type_cache_clear(struct type_cache *cache, PyObject *value) void _PyType_InitCache(PyInterpreterState *interp) { - struct type_cache *cache = &interp->type_cache; + struct type_cache *cache = &interp->types.type_cache; for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) { struct type_cache_entry *entry = &cache->hashtable[i]; assert(entry->name == NULL); @@ -240,7 +240,7 @@ _PyType_InitCache(PyInterpreterState *interp) static unsigned int _PyType_ClearCache(PyInterpreterState *interp) { - struct type_cache *cache = &interp->type_cache; + struct type_cache *cache = &interp->types.type_cache; #if MCACHE_STATS size_t total = cache->hits + cache->collisions + cache->misses; fprintf(stderr, "-- Method cache hits = %zd (%d%%)\n", @@ -272,7 +272,7 @@ PyType_ClearCache(void) void _PyTypes_Fini(PyInterpreterState *interp) { - struct type_cache *cache = &interp->type_cache; + struct type_cache *cache = &interp->types.type_cache; type_cache_clear(cache, NULL); if (_Py_IsMainInterpreter(interp)) { clear_slotdefs(); From 24aea2865cc2ebf73996a69fb5ee823aede31153 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Sat, 9 Jul 2022 14:48:55 -0600 Subject: [PATCH 02/23] Add _PyStaticType_InitBuiltin(). --- Include/internal/pycore_typeobject.h | 4 ++++ Objects/object.c | 3 +++ Objects/typeobject.c | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index af5deb00bf287b..05bf40c3a69a80 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -39,13 +39,17 @@ struct type_cache { #endif }; +#define _Py_MAX_STATIC_BUILTIN_TYPES 1000 + struct types_state { struct type_cache type_cache; + ssize_t num_builtins_initialized; }; extern PyStatus _PyTypes_InitSlotDefs(void); +extern int _PyStaticType_InitBuiltin(PyTypeObject *type); extern void _PyStaticType_Dealloc(PyTypeObject *type); diff --git a/Objects/object.c b/Objects/object.c index 75aee909a3b496..f34db63aba1e90 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1975,6 +1975,9 @@ _PyTypes_InitTypes(PyInterpreterState *interp) // All other static types (unless initialized elsewhere) for (size_t i=0; i < Py_ARRAY_LENGTH(static_types); i++) { PyTypeObject *type = static_types[i]; + if (_PyStaticType_InitBuiltin(type) < 0) { + return _PyStatus_ERR("Can't initialize builtin type"); + } if (PyType_Ready(type) < 0) { return _PyStatus_ERR("Can't initialize types"); } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index be6166610bb78a..2192363a14239b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6650,6 +6650,16 @@ PyType_Ready(PyTypeObject *type) return 0; } +int +_PyStaticType_InitBuiltin(PyTypeObject *self) +{ + /* For static types we store them in an array on each interpreter. */ + PyInterpreterState *interp = _PyInterpreterState_GET(); + interp->types.num_builtins_initialized++; + assert(interp->types.num_builtins_initialized < _Py_MAX_STATIC_BUILTIN_TYPES); + return 0; +} + static int add_subclass(PyTypeObject *base, PyTypeObject *type) From b1cc7d27b55f6bc80875183dedfc9ffd21edfcba Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 15 Jul 2022 13:35:47 -0600 Subject: [PATCH 03/23] Call PyType_Ready() in _PyStaticType_InitBuiltin(). --- Objects/exceptions.c | 3 +-- Objects/object.c | 3 --- Objects/typeobject.c | 3 ++- Objects/unicodeobject.c | 6 +++--- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 9aab683fd1ca7f..e06a8f4173cf8b 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -3556,8 +3556,7 @@ _PyExc_InitTypes(PyInterpreterState *interp) for (size_t i=0; i < Py_ARRAY_LENGTH(static_exceptions); i++) { PyTypeObject *exc = static_exceptions[i].exc; - - if (PyType_Ready(exc) < 0) { + if (_PyStaticType_InitBuiltin(exc) < 0) { return -1; } } diff --git a/Objects/object.c b/Objects/object.c index f34db63aba1e90..758b79eff5b3e3 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1978,9 +1978,6 @@ _PyTypes_InitTypes(PyInterpreterState *interp) if (_PyStaticType_InitBuiltin(type) < 0) { return _PyStatus_ERR("Can't initialize builtin type"); } - if (PyType_Ready(type) < 0) { - return _PyStatus_ERR("Can't initialize types"); - } if (type == &PyType_Type) { // Sanitify checks of the two most important types assert(PyBaseObject_Type.tp_base == NULL); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 2192363a14239b..96b0cd5e2db429 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6657,7 +6657,8 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) PyInterpreterState *interp = _PyInterpreterState_GET(); interp->types.num_builtins_initialized++; assert(interp->types.num_builtins_initialized < _Py_MAX_STATIC_BUILTIN_TYPES); - return 0; + + return PyType_Ready(self); } diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 669ffe70727468..7e3caf1af14c5b 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -14604,13 +14604,13 @@ _PyUnicode_InitTypes(PyInterpreterState *interp) return _PyStatus_OK(); } - if (PyType_Ready(&EncodingMapType) < 0) { + if (_PyStaticType_InitBuiltin(&EncodingMapType) < 0) { goto error; } - if (PyType_Ready(&PyFieldNameIter_Type) < 0) { + if (_PyStaticType_InitBuiltin(&PyFieldNameIter_Type) < 0) { goto error; } - if (PyType_Ready(&PyFormatterIter_Type) < 0) { + if (_PyStaticType_InitBuiltin(&PyFormatterIter_Type) < 0) { goto error; } return _PyStatus_OK(); From c63bccb97f44a1f431eb006a40741fac869247eb Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 15 Jul 2022 13:36:45 -0600 Subject: [PATCH 04/23] Add _PyStructSequence_InitBuiltin(). --- Include/internal/pycore_structseq.h | 9 ++- Objects/floatobject.c | 3 +- Objects/longobject.c | 2 +- Objects/structseq.c | 121 +++++++++++++++++++--------- Python/errors.c | 4 +- Python/sysmodule.c | 41 +++++----- Python/thread.c | 3 +- 7 files changed, 114 insertions(+), 69 deletions(-) diff --git a/Include/internal/pycore_structseq.h b/Include/internal/pycore_structseq.h index 0199c790e24cec..d10a921c55ff8b 100644 --- a/Include/internal/pycore_structseq.h +++ b/Include/internal/pycore_structseq.h @@ -15,11 +15,18 @@ PyAPI_FUNC(PyTypeObject *) _PyStructSequence_NewType( PyStructSequence_Desc *desc, unsigned long tp_flags); -PyAPI_FUNC(int) _PyStructSequence_InitType( +PyAPI_FUNC(int) _PyStructSequence_InitBuiltinWithFlags( PyTypeObject *type, PyStructSequence_Desc *desc, unsigned long tp_flags); +static inline int +_PyStructSequence_InitBuiltin(PyTypeObject *type, + PyStructSequence_Desc *desc) +{ + return _PyStructSequence_InitBuiltinWithFlags(type, desc, 0); +} + extern void _PyStructSequence_FiniType(PyTypeObject *type); #ifdef __cplusplus diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 47d308b8eb3702..4b1b24f2e702a4 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -1992,7 +1992,8 @@ _PyFloat_InitTypes(PyInterpreterState *interp) /* Init float info */ if (FloatInfoType.tp_name == NULL) { - if (PyStructSequence_InitType2(&FloatInfoType, &floatinfo_desc) < 0) { + if (_PyStructSequence_InitBuiltin(&FloatInfoType, + &floatinfo_desc) < 0) { return _PyStatus_ERR("can't init float info type"); } } diff --git a/Objects/longobject.c b/Objects/longobject.c index 4a1e5caf5a2843..90ed02b8c27a19 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -6135,7 +6135,7 @@ _PyLong_InitTypes(PyInterpreterState *interp) /* initialize int_info */ if (Int_InfoType.tp_name == NULL) { - if (PyStructSequence_InitType2(&Int_InfoType, &int_info_desc) < 0) { + if (_PyStructSequence_InitBuiltin(&Int_InfoType, &int_info_desc) < 0) { return _PyStatus_ERR("can't init int info type"); } } diff --git a/Objects/structseq.c b/Objects/structseq.c index 229e3d893ff6aa..9c48e2b51998c0 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -432,11 +432,21 @@ initialize_structseq_dict(PyStructSequence_Desc *desc, PyObject* dict, return -1; } -static void -initialize_members(PyStructSequence_Desc *desc, PyMemberDef* members, - Py_ssize_t n_members) { - Py_ssize_t i, k; +static PyMemberDef * +initialize_members(PyStructSequence_Desc *desc, + Py_ssize_t *pn_members, Py_ssize_t *pn_unnamed_members) +{ + PyMemberDef *members; + Py_ssize_t n_members, n_unnamed_members; + + n_members = count_members(desc, &n_unnamed_members); + members = PyMem_NEW(PyMemberDef, n_members - n_unnamed_members + 1); + if (members == NULL) { + PyErr_NoMemory(); + return NULL; + } + Py_ssize_t i, k; for (i = k = 0; i < n_members; ++i) { if (desc->fields[i].name == PyStructSequence_UnnamedField) { continue; @@ -453,30 +463,17 @@ initialize_members(PyStructSequence_Desc *desc, PyMemberDef* members, k++; } members[k].name = NULL; + + *pn_members = n_members; + *pn_unnamed_members = n_members; + return members; } -int -_PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc, - unsigned long tp_flags) +static void +initialize_static_fields(PyTypeObject *type, PyStructSequence_Desc *desc, + PyMemberDef *tp_members, unsigned long tp_flags) { - PyMemberDef *members; - Py_ssize_t n_members, n_unnamed_members; - -#ifdef Py_TRACE_REFS - /* if the type object was chained, unchain it first - before overwriting its storage */ - if (type->ob_base.ob_base._ob_next) { - _Py_ForgetReference((PyObject *)type); - } -#endif - - /* PyTypeObject has already been initialized */ - if (Py_REFCNT(type) != 0) { - PyErr_BadInternalCall(); - return -1; - } - type->tp_name = desc->name; type->tp_basicsize = sizeof(PyStructSequence) - sizeof(PyObject *); type->tp_itemsize = sizeof(PyObject *); @@ -488,25 +485,20 @@ _PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc, type->tp_new = structseq_new; type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | tp_flags; type->tp_traverse = (traverseproc) structseq_traverse; + type->tp_members = tp_members; +} - n_members = count_members(desc, &n_unnamed_members); - members = PyMem_NEW(PyMemberDef, n_members - n_unnamed_members + 1); - if (members == NULL) { - PyErr_NoMemory(); - return -1; - } - initialize_members(desc, members, n_members); - type->tp_members = members; - +static int +initialize_static_type(PyTypeObject *type, PyStructSequence_Desc *desc, + Py_ssize_t n_members, Py_ssize_t n_unnamed_members) { + /* initialize_static_fields() should have been called already. */ if (PyType_Ready(type) < 0) { - PyMem_Free(members); return -1; } Py_INCREF(type); if (initialize_structseq_dict( desc, type->tp_dict, n_members, n_unnamed_members) < 0) { - PyMem_Free(members); Py_DECREF(type); return -1; } @@ -514,10 +506,62 @@ _PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc, return 0; } +int +_PyStructSequence_InitBuiltinWithFlags(PyTypeObject *type, + PyStructSequence_Desc *desc, + unsigned long tp_flags) +{ + PyMemberDef *members; + Py_ssize_t n_members, n_unnamed_members; + + members = initialize_members(desc, &n_members, &n_unnamed_members); + if (members == NULL) { + return -1; + } + initialize_static_fields(type, desc, members, tp_flags); + if (_PyStaticType_InitBuiltin(type) < 0) { + PyErr_Format(PyExc_RuntimeError, + "Can't initialize builtin type %s", + desc->name); + return -1; + } + if (initialize_static_type(type, desc, n_members, n_unnamed_members) < 0) { + PyMem_Free(members); + return -1; + } + return 0; +} + int PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc) { - return _PyStructSequence_InitType(type, desc, 0); + PyMemberDef *members; + Py_ssize_t n_members, n_unnamed_members; + +#ifdef Py_TRACE_REFS + /* if the type object was chained, unchain it first + before overwriting its storage */ + if (type->ob_base.ob_base._ob_next) { + _Py_ForgetReference((PyObject *)type); + } +#endif + + /* PyTypeObject has already been initialized */ + if (Py_REFCNT(type) != 0) { + PyErr_BadInternalCall(); + return -1; + } + + members = initialize_members(desc, &n_members, &n_unnamed_members); + if (members == NULL) { + return -1; + } + initialize_static_fields(type, desc, members, 0); + if (initialize_static_type(type, desc, n_members, n_unnamed_members) < 0) { + PyMem_Free(members); + return -1; + } + return 0; } void @@ -569,13 +613,10 @@ _PyStructSequence_NewType(PyStructSequence_Desc *desc, unsigned long tp_flags) Py_ssize_t n_members, n_unnamed_members; /* Initialize MemberDefs */ - n_members = count_members(desc, &n_unnamed_members); - members = PyMem_NEW(PyMemberDef, n_members - n_unnamed_members + 1); + members = initialize_members(desc, &n_members, &n_unnamed_members); if (members == NULL) { - PyErr_NoMemory(); return NULL; } - initialize_members(desc, members, n_members); /* Initialize Slots */ slots[0] = (PyType_Slot){Py_tp_dealloc, (destructor)structseq_dealloc}; diff --git a/Python/errors.c b/Python/errors.c index b6b5d9b046ce85..2aa748c60c3704 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1229,8 +1229,8 @@ _PyErr_InitTypes(PyInterpreterState *interp) } if (UnraisableHookArgsType.tp_name == NULL) { - if (PyStructSequence_InitType2(&UnraisableHookArgsType, - &UnraisableHookArgs_desc) < 0) { + if (_PyStructSequence_InitBuiltin(&UnraisableHookArgsType, + &UnraisableHookArgs_desc) < 0) { return _PyStatus_ERR("failed to initialize UnraisableHookArgs type"); } } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index a5fa551b95764c..e861d9cbce415c 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -28,7 +28,7 @@ Data members: #include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyThreadState_GET() -#include "pycore_structseq.h" // _PyStructSequence_InitType() +#include "pycore_structseq.h" // _PyStructSequence_InitBuiltinWithFlags() #include "pycore_tuple.h" // _PyTuple_FromArray() #include "frameobject.h" // PyFrame_FastToLocalsWithError() @@ -2921,7 +2921,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) SET_SYS("int_info", PyLong_GetInfo()); /* initialize hash_info */ if (Hash_InfoType.tp_name == NULL) { - if (PyStructSequence_InitType2(&Hash_InfoType, &hash_info_desc) < 0) { + if (_PyStructSequence_InitBuiltin(&Hash_InfoType, &hash_info_desc) < 0) { goto type_init_failed; } } @@ -2943,14 +2943,18 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) SET_SYS_FROM_STRING("abiflags", ABIFLAGS); #endif +#define ENSURE_INFO_TYPE(TYPE, DESC) \ + do { \ + if (TYPE.tp_name == NULL) { \ + if (_PyStructSequence_InitBuiltinWithFlags( \ + &TYPE, &DESC, Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) { \ + goto type_init_failed; \ + } \ + } \ + } while (0) + /* version_info */ - if (VersionInfoType.tp_name == NULL) { - if (_PyStructSequence_InitType(&VersionInfoType, - &version_info_desc, - Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) { - goto type_init_failed; - } - } + ENSURE_INFO_TYPE(VersionInfoType, version_info_desc); version_info = make_version_info(tstate); SET_SYS("version_info", version_info); @@ -2958,27 +2962,18 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) SET_SYS("implementation", make_impl_info(version_info)); // sys.flags: updated in-place later by _PySys_UpdateConfig() - if (FlagsType.tp_name == 0) { - if (_PyStructSequence_InitType(&FlagsType, &flags_desc, - Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) { - goto type_init_failed; - } - } + ENSURE_INFO_TYPE(FlagsType, flags_desc); SET_SYS("flags", make_flags(tstate->interp)); #if defined(MS_WINDOWS) /* getwindowsversion */ - if (WindowsVersionType.tp_name == 0) { - if (_PyStructSequence_InitType(&WindowsVersionType, - &windows_version_desc, - Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) { - goto type_init_failed; - } - } + ENSURE_INFO_TYPE(WindowsVersionType, windows_version_desc); SET_SYS_FROM_STRING("_vpath", VPATH); #endif +#undef ENSURE_INFO_TYPE + /* float repr style: 0.03 (short) vs 0.029999999999999999 (legacy) */ #if _PY_SHORT_FLOAT_REPR == 1 SET_SYS_FROM_STRING("float_repr_style", "short"); @@ -2990,7 +2985,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict) /* initialize asyncgen_hooks */ if (AsyncGenHooksType.tp_name == NULL) { - if (PyStructSequence_InitType2( + if (_PyStructSequence_InitBuiltin( &AsyncGenHooksType, &asyncgen_hooks_desc) < 0) { goto type_init_failed; } diff --git a/Python/thread.c b/Python/thread.c index 846f02545271cf..d3211219dffe6a 100644 --- a/Python/thread.c +++ b/Python/thread.c @@ -155,7 +155,8 @@ PyThread_GetInfo(void) #endif if (ThreadInfoType.tp_name == 0) { - if (PyStructSequence_InitType2(&ThreadInfoType, &threadinfo_desc) < 0) + if (_PyStructSequence_InitBuiltin(&ThreadInfoType, + &threadinfo_desc) < 0) return NULL; } From 0aacc7a4a72305829a03d69f8dc1f0d858e770e6 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 18 Jul 2022 16:44:17 -0600 Subject: [PATCH 05/23] Hard-code _Py_NUM_STATIC_BUILTIN_TYPES. --- Include/internal/pycore_typeobject.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 05bf40c3a69a80..928a8519311845 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -39,7 +39,9 @@ struct type_cache { #endif }; -#define _Py_MAX_STATIC_BUILTIN_TYPES 1000 +/* For now we hard-code this to a value for which we are confident + all the static builtin types will fit (for all builds). */ +#define _Py_MAX_STATIC_BUILTIN_TYPES 200 struct types_state { struct type_cache type_cache; From a006e2136dcb3ae854ed90554090d257ddd0dbe7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 18 Jul 2022 20:09:53 -0600 Subject: [PATCH 06/23] Store per-interpreter state for static builtin types. --- Include/cpython/object.h | 1 + Include/internal/pycore_typeobject.h | 5 +++++ Objects/typeobject.c | 30 +++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 614d6c18ee0b4a..2ee8a203c6952e 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -227,6 +227,7 @@ struct _typeobject { destructor tp_finalize; vectorcallfunc tp_vectorcall; + ssize_t tp_static_builtin_index; /* 0 means "not initialized" */ }; /* This struct is used by the specializer diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 928a8519311845..bca696b38f6284 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -43,9 +43,14 @@ struct type_cache { all the static builtin types will fit (for all builds). */ #define _Py_MAX_STATIC_BUILTIN_TYPES 200 +struct builtin_static_type_state { + PyTypeObject *type; +}; + struct types_state { struct type_cache type_cache; ssize_t num_builtins_initialized; + struct builtin_static_type_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 96b0cd5e2db429..dd371281a22b5f 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -65,6 +65,18 @@ lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound); static int slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value); + +static inline struct builtin_static_type_state * +lookup_static_builtin_type(PyTypeObject *self) +{ + if (self->tp_static_builtin_index == 0) { + return NULL; + } + PyInterpreterState *interp = _PyInterpreterState_GET(); + return &(interp->types.builtins[self->tp_static_builtin_index - 1]); +} + + /* * finds the beginning of the docstring's introspection signature. * if present, returns a pointer pointing to the first '('. @@ -4221,6 +4233,11 @@ _PyStaticType_Dealloc(PyTypeObject *type) return; } + struct builtin_static_type_state *state = lookup_static_builtin_type(type); + if (state != NULL) { + state->type = NULL; + } + type_dealloc_common(type); Py_CLEAR(type->tp_dict); @@ -6653,11 +6670,22 @@ PyType_Ready(PyTypeObject *type) int _PyStaticType_InitBuiltin(PyTypeObject *self) { - /* For static types we store them in an array on each interpreter. */ + /* It should only be called once for each builtin type. */ + assert(self->tp_static_builtin_index == 0); + + /* For static types we store some state in an array on each interpreter. */ PyInterpreterState *interp = _PyInterpreterState_GET(); interp->types.num_builtins_initialized++; assert(interp->types.num_builtins_initialized < _Py_MAX_STATIC_BUILTIN_TYPES); + /* We use 1-based indexing so 0 can mean "not initialized". */ + self->tp_static_builtin_index = interp->types.num_builtins_initialized; + + /* Now we initialize the type's per-interpreter state. */ + struct builtin_static_type_state *state = lookup_static_builtin_type(self); + assert(state != NULL); + state->type = self; + return PyType_Ready(self); } From e3ed4fd7ae593f8e2bf83830e8de773f4fdc1126 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 19 Jul 2022 09:43:34 -0600 Subject: [PATCH 07/23] Export _PyStaticType_GetState(). --- Include/internal/pycore_typeobject.h | 7 ++++--- Objects/typeobject.c | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index bca696b38f6284..26ef070447a6a7 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -43,20 +43,21 @@ struct type_cache { all the static builtin types will fit (for all builds). */ #define _Py_MAX_STATIC_BUILTIN_TYPES 200 -struct builtin_static_type_state { +typedef struct { PyTypeObject *type; -}; +} static_builtin_type_state; struct types_state { struct type_cache type_cache; ssize_t num_builtins_initialized; - struct builtin_static_type_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; + static_builtin_type_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; }; extern PyStatus _PyTypes_InitSlotDefs(void); extern int _PyStaticType_InitBuiltin(PyTypeObject *type); +extern static_builtin_type_state * _PyStaticType_GetState(PyTypeObject *); extern void _PyStaticType_Dealloc(PyTypeObject *type); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index dd371281a22b5f..b3a5541943acb3 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -66,8 +66,8 @@ static int slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value); -static inline struct builtin_static_type_state * -lookup_static_builtin_type(PyTypeObject *self) +static_builtin_type_state * +_PyStaticType_GetState(PyTypeObject *self) { if (self->tp_static_builtin_index == 0) { return NULL; @@ -4233,7 +4233,7 @@ _PyStaticType_Dealloc(PyTypeObject *type) return; } - struct builtin_static_type_state *state = lookup_static_builtin_type(type); + static_builtin_type_state *state = _PyStaticType_GetState(type); if (state != NULL) { state->type = NULL; } @@ -6682,7 +6682,7 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) self->tp_static_builtin_index = interp->types.num_builtins_initialized; /* Now we initialize the type's per-interpreter state. */ - struct builtin_static_type_state *state = lookup_static_builtin_type(self); + static_builtin_type_state *state = _PyStaticType_GetState(self); assert(state != NULL); state->type = self; From 0c695b5293074325539bf0deff5358a7ca023010 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 19 Jul 2022 09:46:02 -0600 Subject: [PATCH 08/23] Move _PyStaticType_GetState() down next to _PyStaticType_InitBuiltin(). --- Objects/typeobject.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b3a5541943acb3..0d9d9317650e49 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -66,17 +66,6 @@ static int slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value); -static_builtin_type_state * -_PyStaticType_GetState(PyTypeObject *self) -{ - if (self->tp_static_builtin_index == 0) { - return NULL; - } - PyInterpreterState *interp = _PyInterpreterState_GET(); - return &(interp->types.builtins[self->tp_static_builtin_index - 1]); -} - - /* * finds the beginning of the docstring's introspection signature. * if present, returns a pointer pointing to the first '('. @@ -6689,6 +6678,16 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) return PyType_Ready(self); } +static_builtin_type_state * +_PyStaticType_GetState(PyTypeObject *self) +{ + if (self->tp_static_builtin_index == 0) { + return NULL; + } + PyInterpreterState *interp = _PyInterpreterState_GET(); + return &(interp->types.builtins[self->tp_static_builtin_index - 1]); +} + static int add_subclass(PyTypeObject *base, PyTypeObject *type) From 2df59c1913a23f7667c19bfe9b790e4f2bb78a3f Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Tue, 19 Jul 2022 17:19:14 +0000 Subject: [PATCH 09/23] make it compile --- Include/cpython/object.h | 2 +- Include/internal/pycore_typeobject.h | 2 +- Objects/typeobject.c | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 2ee8a203c6952e..1ca1a576fb2329 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -227,7 +227,7 @@ struct _typeobject { destructor tp_finalize; vectorcallfunc tp_vectorcall; - ssize_t tp_static_builtin_index; /* 0 means "not initialized" */ + size_t tp_static_builtin_index; /* 0 means "not initialized" */ }; /* This struct is used by the specializer diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 26ef070447a6a7..545e28ac32f5fc 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -49,7 +49,7 @@ typedef struct { struct types_state { struct type_cache type_cache; - ssize_t num_builtins_initialized; + size_t num_builtins_initialized; static_builtin_type_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; }; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0d9d9317650e49..48300edd433cfb 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4241,6 +4241,8 @@ _PyStaticType_Dealloc(PyTypeObject *type) } type->tp_flags &= ~Py_TPFLAGS_READY; + // Reset tp_static_builtin_index after each finalization. + type->tp_static_builtin_index = 0; } From 5d76cfa9a975b2ee424cb6bf75f9892e696a4888 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 19 Jul 2022 12:13:50 -0600 Subject: [PATCH 10/23] Fix a typo. --- Objects/structseq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/structseq.c b/Objects/structseq.c index 9c48e2b51998c0..3ac2276acd725b 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -465,7 +465,7 @@ initialize_members(PyStructSequence_Desc *desc, members[k].name = NULL; *pn_members = n_members; - *pn_unnamed_members = n_members; + *pn_unnamed_members = n_unnamed_members; return members; } From d05be58f529e4a76af09f06f809e98a34838107c Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:16:14 +0000 Subject: [PATCH 11/23] fix test_sys --- Lib/test/test_sys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 1dc10d8b0a39ac..f629dd5f034728 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1507,7 +1507,7 @@ def delx(self): del self.__x check((1,2,3), vsize('') + 3*self.P) # type # static type: PyTypeObject - fmt = 'P2nPI13Pl4Pn9Pn12PIP' + fmt = 'P2nPI13Pl4Pn9Pn12PIPI' s = vsize('2P' + fmt) check(int, s) # class From 160738bc39b4befba78caf455a5232d1feb8b810 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 11:55:55 -0600 Subject: [PATCH 12/23] Ignore non-builtin static types in _PyStaticType_Dealloc(). --- Objects/typeobject.c | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 48300edd433cfb..e446a3cedd7cf4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4212,13 +4212,37 @@ type_dealloc_common(PyTypeObject *type) } +static int +has_lingering_subclasses(PyTypeObject *type) +{ + if (type->tp_subclasses == NULL) { + return 0; + } + // We can ignore non-builtin static types. + Py_ssize_t i = 0; + PyObject *ref; // borrowed ref + while (PyDict_Next((PyObject *)type->tp_subclasses, &i, NULL, &ref)) { + PyTypeObject *sub = (PyTypeObject *)((struct _PyWeakReference *)ref)->wr_object; + if (sub->tp_flags & Py_TPFLAGS_HEAPTYPE) { + return 1; + } + // All static builtin subtypes should have been finalized already. + assert(sub->tp_static_builtin_index == 0); + } + /* At this point, all the subclasses are non-builtin static types. + We can ignore them since they only come from extension modules + and such extension modules are already unsafe if the runtime + is re-used after finalization (or in multiple interpreters). */ + return 0; +} + void _PyStaticType_Dealloc(PyTypeObject *type) { - // If a type still has subtypes, it cannot be deallocated. - // A subtype can inherit attributes and methods of its parent type, - // and a type must no longer be used once it's deallocated. - if (type->tp_subclasses != NULL) { + // If a static type still has non-static subtypes, it cannot be + // deallocated. A subtype can inherit attributes and methods of its + // parent type, and a type must no longer be used once it's deallocated. + if (has_lingering_subclasses(type)) { return; } @@ -4233,7 +4257,7 @@ _PyStaticType_Dealloc(PyTypeObject *type) Py_CLEAR(type->tp_bases); Py_CLEAR(type->tp_mro); Py_CLEAR(type->tp_cache); - // type->tp_subclasses is NULL + Py_CLEAR(type->tp_subclasses); // PyObject_ClearWeakRefs() raises an exception if Py_REFCNT() != 0 if (Py_REFCNT(type) == 0) { From 7655114e4902030a7b7d4b71a4845bf083645ffe Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 13:35:31 -0600 Subject: [PATCH 13/23] Remember if a type is a static builtin. --- Include/object.h | 3 +++ Objects/typeobject.c | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Include/object.h b/Include/object.h index a12b754c8ed8f3..c00b9eb583420a 100644 --- a/Include/object.h +++ b/Include/object.h @@ -352,6 +352,9 @@ given type object has a specified feature. #ifndef Py_LIMITED_API +/* Track types initialized using _PyStaticType_InitBuiltin(). */ +#define _Py_TPFLAGS_STATIC_BUILTIN (1 << 1) + /* Placement of dict (and values) pointers are managed by the VM, not by the type. * The VM will automatically set tp_dictoffset. Should not be used for variable sized * classes, such as classes that extend tuple. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e446a3cedd7cf4..94f178fce148ac 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4218,6 +4218,11 @@ has_lingering_subclasses(PyTypeObject *type) if (type->tp_subclasses == NULL) { return 0; } + if (!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)) { + // XXX This means we'll leak the objects for which the static type + // owns a reference (directly or indirectly). + return 1; + } // We can ignore non-builtin static types. Py_ssize_t i = 0; PyObject *ref; // borrowed ref @@ -4265,8 +4270,12 @@ _PyStaticType_Dealloc(PyTypeObject *type) } type->tp_flags &= ~Py_TPFLAGS_READY; - // Reset tp_static_builtin_index after each finalization. - type->tp_static_builtin_index = 0; + + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { + /* Reset the type's per-interpreter state. + This basically undoes what _PyStaticType_InitBuiltin() did. */ + type->tp_static_builtin_index = 0; + } } @@ -6701,6 +6710,8 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) assert(state != NULL); state->type = self; + self->tp_flags = self->tp_flags | _Py_TPFLAGS_STATIC_BUILTIN; + return PyType_Ready(self); } From 6820fcaabd9ec5eb29b5a1882d9e4d49c896e69e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 15:28:35 -0600 Subject: [PATCH 14/23] Factor out get_static_builtin_index() and set_static_builtin_index(). --- Objects/typeobject.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 94f178fce148ac..5094a20979bc9a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4241,6 +4241,9 @@ has_lingering_subclasses(PyTypeObject *type) return 0; } +static inline size_t get_static_builtin_index(PyTypeObject *); +static inline void set_static_builtin_index(PyTypeObject *, size_t); + void _PyStaticType_Dealloc(PyTypeObject *type) { @@ -4274,7 +4277,7 @@ _PyStaticType_Dealloc(PyTypeObject *type) if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { /* Reset the type's per-interpreter state. This basically undoes what _PyStaticType_InitBuiltin() did. */ - type->tp_static_builtin_index = 0; + set_static_builtin_index(type, 0); } } @@ -6691,11 +6694,24 @@ PyType_Ready(PyTypeObject *type) return 0; } + +static inline size_t +get_static_builtin_index(PyTypeObject *self) +{ + return self->tp_static_builtin_index; +} + +static inline void +set_static_builtin_index(PyTypeObject *self, size_t index) +{ + self->tp_static_builtin_index = index; +} + int _PyStaticType_InitBuiltin(PyTypeObject *self) { /* It should only be called once for each builtin type. */ - assert(self->tp_static_builtin_index == 0); + assert(get_static_builtin_index(self) == 0); /* For static types we store some state in an array on each interpreter. */ PyInterpreterState *interp = _PyInterpreterState_GET(); @@ -6703,7 +6719,7 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) assert(interp->types.num_builtins_initialized < _Py_MAX_STATIC_BUILTIN_TYPES); /* We use 1-based indexing so 0 can mean "not initialized". */ - self->tp_static_builtin_index = interp->types.num_builtins_initialized; + set_static_builtin_index(self, interp->types.num_builtins_initialized); /* Now we initialize the type's per-interpreter state. */ static_builtin_type_state *state = _PyStaticType_GetState(self); @@ -6718,11 +6734,11 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) static_builtin_type_state * _PyStaticType_GetState(PyTypeObject *self) { - if (self->tp_static_builtin_index == 0) { + if (get_static_builtin_index(self) == 0) { return NULL; } PyInterpreterState *interp = _PyInterpreterState_GET(); - return &(interp->types.builtins[self->tp_static_builtin_index - 1]); + return &(interp->types.builtins[get_static_builtin_index(self) - 1]); } From 98f9eafdcb84d0138d401f8f39037a6d9a5e8996 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 15:46:31 -0600 Subject: [PATCH 15/23] Revert to skipping on subclasses for now. --- Objects/typeobject.c | 44 +++++++++++--------------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 5094a20979bc9a..886243c4b313f5 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4212,45 +4212,23 @@ type_dealloc_common(PyTypeObject *type) } -static int -has_lingering_subclasses(PyTypeObject *type) -{ - if (type->tp_subclasses == NULL) { - return 0; - } - if (!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)) { - // XXX This means we'll leak the objects for which the static type - // owns a reference (directly or indirectly). - return 1; - } - // We can ignore non-builtin static types. - Py_ssize_t i = 0; - PyObject *ref; // borrowed ref - while (PyDict_Next((PyObject *)type->tp_subclasses, &i, NULL, &ref)) { - PyTypeObject *sub = (PyTypeObject *)((struct _PyWeakReference *)ref)->wr_object; - if (sub->tp_flags & Py_TPFLAGS_HEAPTYPE) { - return 1; - } - // All static builtin subtypes should have been finalized already. - assert(sub->tp_static_builtin_index == 0); - } - /* At this point, all the subclasses are non-builtin static types. - We can ignore them since they only come from extension modules - and such extension modules are already unsafe if the runtime - is re-used after finalization (or in multiple interpreters). */ - return 0; -} - static inline size_t get_static_builtin_index(PyTypeObject *); static inline void set_static_builtin_index(PyTypeObject *, size_t); void _PyStaticType_Dealloc(PyTypeObject *type) { - // If a static type still has non-static subtypes, it cannot be - // deallocated. A subtype can inherit attributes and methods of its - // parent type, and a type must no longer be used once it's deallocated. - if (has_lingering_subclasses(type)) { + /* At this point in the runtime lifecycle, if a type still has + subtypes then some extension module did not correctly finalize + its objects. We can ignore such sybtypes since such extension + modules are already unsafe if the runtime is re-used after + finalization (or in multiple interpreters). + + Unfortunately, this means we will leak the objects for which + the subtype owns a reference (directly or indirectly). */ + // XXX For now we abandon finalizing the static type here and + // instead leak the type's objects. + if (type->tp_subclasses != NULL) { return; } From a3fd8e3b3b8b0e6f9899f53c9ae553e72acd335b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 17:59:42 -0600 Subject: [PATCH 16/23] Throw away the leaking subclasses. --- Objects/typeobject.c | 55 ++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 886243c4b313f5..733e92fab6b408 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4212,26 +4212,51 @@ type_dealloc_common(PyTypeObject *type) } +static void +clear_static_tp_subclasses(PyTypeObject *type) +{ + if (type->tp_subclasses == NULL) { + return; + } + + /* Normally it would be a problem to finalize the type if its + tp_subclasses wasn't cleared first. However, this is only + ever called at the end of runtime finalization, so we can be + more liberal in cleaning up. If the given type still has + subtypes at this point then some extension module did not + correctly finalize its objects. + + We can safely obliterate such sybtypes since the extension + module and its objects won't be used again, except maybe if + the runtime were re-initialized. In that case the sticky + situation would only happen if the module were re-imported + then and only if the subtype were stored in a global and only + if that global were not overwritten during import. We'd be + fine since the extension is otherwise unsafe and unsupported + in that situation, and likely problematic already. + + In any case, this situation means at least some memory is + going to leak. This mostly only affects embedding scenarios. + */ + + // For now we just do a sanity check and then drop tp_subclasses. + Py_ssize_t i = 0; + PyObject *ref; // borrowed ref + while (PyDict_Next((PyObject *)type->tp_subclasses, &i, NULL, &ref)) { + PyTypeObject *sub = (PyTypeObject *)((struct _PyWeakReference *)ref)->wr_object; + // All static builtin subtypes should have been finalized already. + assert(!(sub->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); + } + + Py_CLEAR(type->tp_subclasses); +} + static inline size_t get_static_builtin_index(PyTypeObject *); static inline void set_static_builtin_index(PyTypeObject *, size_t); void _PyStaticType_Dealloc(PyTypeObject *type) { - /* At this point in the runtime lifecycle, if a type still has - subtypes then some extension module did not correctly finalize - its objects. We can ignore such sybtypes since such extension - modules are already unsafe if the runtime is re-used after - finalization (or in multiple interpreters). - - Unfortunately, this means we will leak the objects for which - the subtype owns a reference (directly or indirectly). */ - // XXX For now we abandon finalizing the static type here and - // instead leak the type's objects. - if (type->tp_subclasses != NULL) { - return; - } - static_builtin_type_state *state = _PyStaticType_GetState(type); if (state != NULL) { state->type = NULL; @@ -4243,7 +4268,7 @@ _PyStaticType_Dealloc(PyTypeObject *type) Py_CLEAR(type->tp_bases); Py_CLEAR(type->tp_mro); Py_CLEAR(type->tp_cache); - Py_CLEAR(type->tp_subclasses); + clear_static_tp_subclasses(type); // PyObject_ClearWeakRefs() raises an exception if Py_REFCNT() != 0 if (Py_REFCNT(type) == 0) { From cb3895cb1a5f250b9ba72bce33d56ff37549c323 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 18:02:51 -0600 Subject: [PATCH 17/23] Reset state->type later on. --- Objects/typeobject.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 733e92fab6b408..cf2a05ce6a4583 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4257,11 +4257,6 @@ static inline void set_static_builtin_index(PyTypeObject *, size_t); void _PyStaticType_Dealloc(PyTypeObject *type) { - static_builtin_type_state *state = _PyStaticType_GetState(type); - if (state != NULL) { - state->type = NULL; - } - type_dealloc_common(type); Py_CLEAR(type->tp_dict); @@ -4280,6 +4275,9 @@ _PyStaticType_Dealloc(PyTypeObject *type) if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { /* Reset the type's per-interpreter state. This basically undoes what _PyStaticType_InitBuiltin() did. */ + static_builtin_type_state *state = _PyStaticType_GetState(type); + assert(state != NULL); + state->type = NULL; set_static_builtin_index(type, 0); } } From 8d7edd73732c90ce9fde211b42c7d52b7756aba8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 18:04:12 -0600 Subject: [PATCH 18/23] Add some asserts. --- Objects/typeobject.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index cf2a05ce6a4583..9ea44b654fe87c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4257,6 +4257,8 @@ static inline void set_static_builtin_index(PyTypeObject *, size_t); void _PyStaticType_Dealloc(PyTypeObject *type) { + assert(!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)); + type_dealloc_common(type); Py_CLEAR(type->tp_dict); @@ -4275,6 +4277,7 @@ _PyStaticType_Dealloc(PyTypeObject *type) if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { /* Reset the type's per-interpreter state. This basically undoes what _PyStaticType_InitBuiltin() did. */ + assert(get_static_builtin_index(type) > 0); static_builtin_type_state *state = _PyStaticType_GetState(type); assert(state != NULL); state->type = NULL; From f057fcffdbc2f4ff7b500c2a3d6e16ae1fcef50c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 20 Jul 2022 18:07:50 -0600 Subject: [PATCH 19/23] Decrement num_builtins_initialized. --- Objects/typeobject.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 9ea44b654fe87c..57559e94b28101 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4282,6 +4282,10 @@ _PyStaticType_Dealloc(PyTypeObject *type) assert(state != NULL); state->type = NULL; set_static_builtin_index(type, 0); + + PyInterpreterState *interp = _PyInterpreterState_GET(); + assert(interp->types.num_builtins_initialized > 0); + interp->types.num_builtins_initialized--; } } From bb8264d9dfbfc1ddf703842f7f932e34474f0643 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 22 Jul 2022 14:50:29 -0600 Subject: [PATCH 20/23] Factor out subclass_from_ref(). --- Objects/typeobject.c | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 57559e94b28101..230f4fb2413df3 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -65,6 +65,7 @@ lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound); static int slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value); +static inline PyTypeObject * subclass_from_ref(PyObject *ref); /* * finds the beginning of the docstring's introspection signature. @@ -310,12 +311,11 @@ PyType_Modified(PyTypeObject *type) Py_ssize_t i = 0; PyObject *ref; while (PyDict_Next(subclasses, &i, NULL, &ref)) { - assert(PyWeakref_CheckRef(ref)); - PyObject *obj = PyWeakref_GET_OBJECT(ref); - if (obj == Py_None) { + PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + if (subclass == NULL) { continue; } - PyType_Modified(_PyType_CAST(obj)); + PyType_Modified(subclass); } } @@ -4243,9 +4243,12 @@ clear_static_tp_subclasses(PyTypeObject *type) Py_ssize_t i = 0; PyObject *ref; // borrowed ref while (PyDict_Next((PyObject *)type->tp_subclasses, &i, NULL, &ref)) { - PyTypeObject *sub = (PyTypeObject *)((struct _PyWeakReference *)ref)->wr_object; + PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + if (subclass == NULL) { + continue; + } // All static builtin subtypes should have been finalized already. - assert(!(sub->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); + assert(!(subclass->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); } Py_CLEAR(type->tp_subclasses); @@ -4348,14 +4351,12 @@ _PyType_GetSubclasses(PyTypeObject *self) Py_ssize_t i = 0; PyObject *ref; // borrowed ref while (PyDict_Next(subclasses, &i, NULL, &ref)) { - assert(PyWeakref_CheckRef(ref)); - PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref - if (obj == Py_None) { + PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + if (subclass == NULL) { continue; } - assert(PyType_Check(obj)); - if (PyList_Append(list, obj) < 0) { + if (PyList_Append(list, _PyObject_CAST(subclass)) < 0) { Py_DECREF(list); return NULL; } @@ -6799,6 +6800,19 @@ add_all_subclasses(PyTypeObject *type, PyObject *bases) return res; } +static inline PyTypeObject * +subclass_from_ref(PyObject *ref) +{ + assert(PyWeakref_CheckRef(ref)); + PyObject *obj = PyWeakref_GET_OBJECT(ref); // borrowed ref + assert(obj != NULL); + if (obj == Py_None) { + return NULL; + } + assert(PyType_Check(obj)); + return _PyType_CAST(obj); +} + static void remove_subclass(PyTypeObject *base, PyTypeObject *type) { @@ -8902,13 +8916,10 @@ recurse_down_subclasses(PyTypeObject *type, PyObject *attr_name, Py_ssize_t i = 0; PyObject *ref; while (PyDict_Next(subclasses, &i, NULL, &ref)) { - assert(PyWeakref_CheckRef(ref)); - PyObject *obj = PyWeakref_GET_OBJECT(ref); - assert(obj != NULL); - if (obj == Py_None) { + PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + if (subclass == NULL) { continue; } - PyTypeObject *subclass = _PyType_CAST(obj); /* Avoid recursing down into unaffected classes */ PyObject *dict = subclass->tp_dict; From d680e8ec771e9c59b5613222f074f0b77c3db008 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 22 Jul 2022 15:29:16 -0600 Subject: [PATCH 21/23] Handle MemoryError in remove_subclass(). --- Objects/typeobject.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 230f4fb2413df3..f5e7318fc6ba3c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -6813,6 +6813,29 @@ subclass_from_ref(PyObject *ref) return _PyType_CAST(obj); } +static PyObject * +get_subclasses_key(PyTypeObject *type, PyTypeObject *base) +{ + PyObject *key = PyLong_FromVoidPtr((void *) type); + if (key != NULL) { + return key; + } + PyErr_Clear(); + + /* This basically means we're out of memory. + We fall back to manually traversing the values. */ + Py_ssize_t i = 0; + PyObject *ref; // borrowed ref + while (PyDict_Next((PyObject *)base->tp_subclasses, &i, &key, &ref)) { + PyTypeObject *subclass = subclass_from_ref(ref); // borrowed + if (subclass == type) { + return Py_NewRef(key); + } + } + /* It wasn't found. */ + return NULL; +} + static void remove_subclass(PyTypeObject *base, PyTypeObject *type) { @@ -6822,8 +6845,8 @@ remove_subclass(PyTypeObject *base, PyTypeObject *type) } assert(PyDict_CheckExact(subclasses)); - PyObject *key = PyLong_FromVoidPtr((void *) type); - if (key == NULL || PyDict_DelItem(subclasses, key)) { + PyObject *key = get_subclasses_key(type, base); + if (key != NULL && PyDict_DelItem(subclasses, key)) { /* This can happen if the type initialization errored out before the base subclasses were updated (e.g. a non-str __qualname__ was passed in the type dict). */ From 41850e88be38da1a1fbe800f8896f7fb7a8d32da Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 22 Jul 2022 15:44:36 -0600 Subject: [PATCH 22/23] Call _PyTypes_FiniTypes() before _PyTypes_Fini(). --- Python/pylifecycle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 65e7f23e963b5f..75aba4a25c41a9 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1672,9 +1672,10 @@ finalize_interp_types(PyInterpreterState *interp) _PyLong_FiniTypes(interp); _PyThread_FiniType(interp); _PyErr_FiniTypes(interp); - _PyTypes_Fini(interp); _PyTypes_FiniTypes(interp); + _PyTypes_Fini(interp); + // Call _PyUnicode_ClearInterned() before _PyDict_Fini() since it uses // a dict internally. _PyUnicode_ClearInterned(interp); From f1bbbd2a4e2be449016b22a71bcc44f8fff05207 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 22 Jul 2022 15:47:26 -0600 Subject: [PATCH 23/23] Verify the per-interpreter type state has been cleared. --- Objects/typeobject.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f5e7318fc6ba3c..c301daca0e86a1 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -279,6 +279,12 @@ _PyTypes_Fini(PyInterpreterState *interp) if (_Py_IsMainInterpreter(interp)) { clear_slotdefs(); } + + assert(interp->types.num_builtins_initialized == 0); + // All the static builtin types should have been finalized already. + for (size_t i = 0; i < _Py_MAX_STATIC_BUILTIN_TYPES; i++) { + assert(interp->types.builtins[i].type == NULL); + } }