From 47e75a0025529fa538bc24d55b27ae4823d3df9d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 26 Jul 2022 17:26:43 -0600 Subject: [PATCH] gh-94673: Add Per-Interpreter Storage for Static Builtin Types (#95255) This is the last precursor to storing tp_subclasses (and tp_weaklist) on the interpreter state for static builtin types. Here we add per-type storage on PyInterpreterState, but only for the static builtin types. This involves the following: * add PyInterpreterState.types * move PyInterpreterState.type_cache to it * add a "num_builtins_initialized" field * add a "builtins" field (a static array big enough for all the static builtin types) * add _PyStaticType_GetState() to look up a static builtin type's state * (temporarily) add PyTypeObject.tp_static_builtin_index (to hold the type's index into PyInterpreterState.types.builtins) We will be eliminating tp_static_builtin_index in a later change. --- Include/cpython/object.h | 1 + Include/internal/pycore_interp.h | 2 +- Include/internal/pycore_typeobject.h | 16 ++++ Lib/test/test_sys.py | 2 +- Objects/typeobject.c | 111 +++++++++++++++++++++++++-- 5 files changed, 125 insertions(+), 7 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 614d6c18ee0..1ca1a576fb2 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -227,6 +227,7 @@ struct _typeobject { destructor tp_finalize; vectorcallfunc tp_vectorcall; + size_t tp_static_builtin_index; /* 0 means "not initialized" */ }; /* This struct is used by the specializer diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 6ce2945cd9f..d71386953a0 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 46f3bf78b13..dc1c02ba412 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -39,9 +39,25 @@ struct type_cache { #endif }; +/* 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 + +typedef struct { + PyTypeObject *type; +} static_builtin_state; + +struct types_state { + struct type_cache type_cache; + size_t num_builtins_initialized; + static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; +}; + + extern PyStatus _PyTypes_InitSlotDefs(void); extern int _PyStaticType_InitBuiltin(PyTypeObject *type); +extern static_builtin_state * _PyStaticType_GetState(PyTypeObject *); extern void _PyStaticType_Dealloc(PyTypeObject *type); diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 1dc10d8b0a3..f629dd5f034 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1507,7 +1507,7 @@ class SizeofTest(unittest.TestCase): check((1,2,3), vsize('') + 3*self.P) # type # static type: PyTypeObject - fmt = 'P2nPI13Pl4Pn9Pn12PIP' + fmt = 'P2nPI13Pl4Pn9Pn12PIPI' s = vsize('2P' + fmt) check(int, s) # class diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f9e6f6372ab..9eb2390118d 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -67,6 +67,88 @@ slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value); static inline PyTypeObject * subclass_from_ref(PyObject *ref); + +/* helpers for for static builtin types */ + +static inline int +static_builtin_index_is_set(PyTypeObject *self) +{ + return self->tp_static_builtin_index > 0; +} + +static inline size_t +static_builtin_index_get(PyTypeObject *self) +{ + assert(static_builtin_index_is_set(self)); + /* We store a 1-based index so 0 can mean "not initialized". */ + return self->tp_static_builtin_index - 1; +} + +static inline void +static_builtin_index_set(PyTypeObject *self, size_t index) +{ + assert(index < _Py_MAX_STATIC_BUILTIN_TYPES); + /* We store a 1-based index so 0 can mean "not initialized". */ + self->tp_static_builtin_index = index + 1; +} + +static inline void +static_builtin_index_clear(PyTypeObject *self) +{ + self->tp_static_builtin_index = 0; +} + +static inline static_builtin_state * +static_builtin_state_get(PyInterpreterState *interp, PyTypeObject *self) +{ + return &(interp->types.builtins[static_builtin_index_get(self)]); +} + +/* For static types we store some state in an array on each interpreter. */ +static_builtin_state * +_PyStaticType_GetState(PyTypeObject *self) +{ + assert(self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN); + PyInterpreterState *interp = _PyInterpreterState_GET(); + return static_builtin_state_get(interp, self); +} + +static void +static_builtin_state_init(PyTypeObject *self) +{ + /* Set the type's per-interpreter state. */ + PyInterpreterState *interp = _PyInterpreterState_GET(); + + /* It should only be called once for each builtin type. */ + assert(!static_builtin_index_is_set(self)); + + static_builtin_index_set(self, interp->types.num_builtins_initialized); + interp->types.num_builtins_initialized++; + + static_builtin_state *state = static_builtin_state_get(interp, self); + state->type = self; +} + +static void +static_builtin_state_clear(PyTypeObject *self) +{ + /* Reset the type's per-interpreter state. + This basically undoes what static_builtin_state_init() did. */ + PyInterpreterState *interp = _PyInterpreterState_GET(); + + static_builtin_state *state = static_builtin_state_get(interp, self); + state->type = NULL; + static_builtin_index_clear(self); + + assert(interp->types.num_builtins_initialized > 0); + interp->types.num_builtins_initialized--; +} + +// Also see _PyStaticType_InitBuiltin() and _PyStaticType_Dealloc(). + +/* end static builtin helpers */ + + /* * finds the beginning of the docstring's introspection signature. * if present, returns a pointer pointing to the first '('. @@ -206,7 +288,7 @@ static struct type_cache* get_type_cache(void) { PyInterpreterState *interp = _PyInterpreterState_GET(); - return &interp->type_cache; + return &interp->types.type_cache; } @@ -225,7 +307,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); @@ -242,7 +324,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", @@ -274,11 +356,17 @@ 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(); } + + 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); + } } @@ -4247,6 +4335,8 @@ clear_static_tp_subclasses(PyTypeObject *type) void _PyStaticType_Dealloc(PyTypeObject *type) { + assert(!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)); + type_dealloc_common(type); Py_CLEAR(type->tp_dict); @@ -4261,6 +4351,11 @@ _PyStaticType_Dealloc(PyTypeObject *type) } type->tp_flags &= ~Py_TPFLAGS_READY; + + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { + static_builtin_state_clear(type); + /* We leave _Py_TPFLAGS_STATIC_BUILTIN set on tp_flags. */ + } } @@ -6679,7 +6774,13 @@ _PyStaticType_InitBuiltin(PyTypeObject *self) { self->tp_flags = self->tp_flags | _Py_TPFLAGS_STATIC_BUILTIN; - return PyType_Ready(self); + static_builtin_state_init(self); + + int res = PyType_Ready(self); + if (res < 0) { + static_builtin_state_clear(self); + } + return res; }