From eca53620e3ff1f2e7d621360a513ac34a1b35aa3 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 12 Apr 2024 16:39:27 -0600 Subject: [PATCH] gh-94673: Clarify About Runtime State Related to Static Builtin Types (gh-117761) Guido pointed out to me that some details about the per-interpreter state for the builtin types aren't especially clear. I'm addressing that by: * adding a comment explaining that state * adding some asserts to point out the relationship between each index and the interp/global runtime state --- Include/internal/pycore_typeobject.h | 37 ++++++++++++++++++++++++++++ Objects/typeobject.c | 7 +++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 1693119ffec..09c4501c38c 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -68,6 +68,43 @@ struct types_state { unsigned int next_version_tag; struct type_cache type_cache; + + /* Every static builtin type is initialized for each interpreter + during its own initialization, including for the main interpreter + during global runtime initialization. This is done by calling + _PyStaticType_InitBuiltin(). + + The first time a static builtin type is initialized, all the + normal PyType_Ready() stuff happens. The only difference from + normal is that there are three PyTypeObject fields holding + objects which are stored here (on PyInterpreterState) rather + than in the corresponding PyTypeObject fields. Those are: + tp_dict (cls.__dict__), tp_subclasses (cls.__subclasses__), + and tp_weaklist. + + When a subinterpreter is initialized, each static builtin type + is still initialized, but only the interpreter-specific portion, + namely those three objects. + + Those objects are stored in the PyInterpreterState.types.builtins + array, at the index corresponding to each specific static builtin + type. That index (a size_t value) is stored in the tp_subclasses + field. For static builtin types, we re-purposed the now-unused + tp_subclasses to avoid adding another field to PyTypeObject. + In all other cases tp_subclasses holds a dict like before. + (The field was previously defined as PyObject*, but is now void* + to reflect its dual use.) + + The index for each static builtin type isn't statically assigned. + Instead it is calculated the first time a type is initialized + (by the main interpreter). The index matches the order in which + the type was initialized relative to the others. The actual + value comes from the current value of num_builtins_initialized, + as each type is initialized for the main interpreter. + + num_builtins_initialized is incremented once for each static + builtin type. Once initialization is over for a subinterpreter, + the value will be the same as for all other interpreters. */ size_t num_builtins_initialized; static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; PyMutex mutex; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a3c137536a4..1cb53516a9a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -162,9 +162,14 @@ _PyStaticType_GetState(PyInterpreterState *interp, PyTypeObject *self) static void static_builtin_state_init(PyInterpreterState *interp, PyTypeObject *self) { - if (!static_builtin_index_is_set(self)) { + if (_Py_IsMainInterpreter(interp)) { + assert(!static_builtin_index_is_set(self)); static_builtin_index_set(self, interp->types.num_builtins_initialized); } + else { + assert(static_builtin_index_get(self) == + interp->types.num_builtins_initialized); + } static_builtin_state *state = static_builtin_state_get(interp, self); /* It should only be called once for each builtin type. */