From 209a0a76552c19088e74b0f27827b9214b6c2cf8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 24 Apr 2023 16:30:13 -0600 Subject: [PATCH] gh-95795: Move types.next_version_tag to PyInterpreterState (gh-102343) Core static types will continue to use the global value. All other types will use the per-interpreter value. They all share the same range, where the global types use values < 2^16 and each interpreter uses values higher than that. --- Include/internal/pycore_runtime.h | 9 +--- Include/internal/pycore_runtime_init.h | 3 ++ Include/internal/pycore_typeobject.h | 59 +++++++++++++++-------- Objects/typeobject.c | 66 +++++++++++++++++++------- 4 files changed, 94 insertions(+), 43 deletions(-) diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 2a3fd8ab281..789a1fbc928 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -25,6 +25,7 @@ extern "C" { #include "pycore_signal.h" // struct _signals_runtime_state #include "pycore_time.h" // struct _time_runtime_state #include "pycore_tracemalloc.h" // struct _tracemalloc_runtime_state +#include "pycore_typeobject.h" // struct types_runtime_state #include "pycore_unicodeobject.h" // struct _Py_unicode_runtime_ids struct _getargs_runtime_state { @@ -150,13 +151,7 @@ typedef struct pyruntimestate { struct _py_object_runtime_state object_state; struct _Py_float_runtime_state float_state; struct _Py_unicode_runtime_state unicode_state; - - struct { - /* Used to set PyTypeObject.tp_version_tag */ - // bpo-42745: next_version_tag remains shared by all interpreters - // because of static types. - unsigned int next_version_tag; - } types; + struct _types_runtime_state types; /* All the objects that are shared by the runtime's interpreters. */ struct _Py_static_objects static_objects; diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h index d8425b3199a..843f8a34410 100644 --- a/Include/internal/pycore_runtime_init.h +++ b/Include/internal/pycore_runtime_init.h @@ -112,6 +112,9 @@ extern PyTypeObject _PyExc_MemoryError; .func_state = { \ .next_version = 1, \ }, \ + .types = { \ + .next_version_tag = _Py_TYPE_BASE_VERSION_TAG, \ + }, \ .static_objects = { \ .singletons = { \ ._not_used = 1, \ diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 324e500eaab..fa59c533f9e 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -11,22 +11,17 @@ extern "C" { #endif -/* runtime lifecycle */ +/* state */ -extern PyStatus _PyTypes_InitTypes(PyInterpreterState *); -extern void _PyTypes_FiniTypes(PyInterpreterState *); -extern void _PyTypes_Fini(PyInterpreterState *); +#define _Py_TYPE_BASE_VERSION_TAG (2<<16) +#define _Py_MAX_GLOBAL_TYPE_VERSION_TAG (_Py_TYPE_BASE_VERSION_TAG - 1) - -/* other API */ - -/* Length of array of slotdef pointers used to store slots with the - same __name__. There should be at most MAX_EQUIV-1 slotdef entries with - the same __name__, for any __name__. Since that's a static property, it is - appropriate to declare fixed-size arrays for this. */ -#define MAX_EQUIV 10 - -typedef struct wrapperbase pytype_slotdef; +struct _types_runtime_state { + /* Used to set PyTypeObject.tp_version_tag for core static types. */ + // bpo-42745: next_version_tag remains shared by all interpreters + // because of static types. + unsigned int next_version_tag; +}; // Type attribute lookup cache: speed up attribute and method lookups, @@ -57,6 +52,36 @@ typedef struct { PyObject *tp_weaklist; } static_builtin_state; +struct types_state { + /* Used to set PyTypeObject.tp_version_tag. + It starts at _Py_MAX_GLOBAL_TYPE_VERSION_TAG + 1, + where all those lower numbers are used for core static types. */ + unsigned int next_version_tag; + + struct type_cache type_cache; + size_t num_builtins_initialized; + static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; +}; + + +/* runtime lifecycle */ + +extern PyStatus _PyTypes_InitTypes(PyInterpreterState *); +extern void _PyTypes_FiniTypes(PyInterpreterState *); +extern void _PyTypes_Fini(PyInterpreterState *); + + +/* other API */ + +/* Length of array of slotdef pointers used to store slots with the + same __name__. There should be at most MAX_EQUIV-1 slotdef entries with + the same __name__, for any __name__. Since that's a static property, it is + appropriate to declare fixed-size arrays for this. */ +#define MAX_EQUIV 10 + +typedef struct wrapperbase pytype_slotdef; + + static inline PyObject ** _PyStaticType_GET_WEAKREFS_LISTPTR(static_builtin_state *state) { @@ -78,12 +103,6 @@ _PyType_GetModuleState(PyTypeObject *type) return mod->md_state; } -struct types_state { - struct type_cache type_cache; - size_t num_builtins_initialized; - static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES]; -}; - extern int _PyStaticType_InitBuiltin(PyTypeObject *type); extern static_builtin_state * _PyStaticType_GetState(PyTypeObject *); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index e1497d69137..07c900932b4 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -45,7 +45,9 @@ class object "PyObject *" "&PyBaseObject_Type" PyUnicode_IS_READY(name) && \ (PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE) -#define next_version_tag (_PyRuntime.types.next_version_tag) +#define NEXT_GLOBAL_VERSION_TAG _PyRuntime.types.next_version_tag +#define NEXT_VERSION_TAG(interp) \ + (interp)->types.next_version_tag typedef struct PySlot_Offset { short subslot_offset; @@ -332,7 +334,7 @@ _PyType_ClearCache(PyInterpreterState *interp) // use Py_SETREF() rather than using slower Py_XSETREF(). type_cache_clear(cache, Py_None); - return next_version_tag - 1; + return NEXT_VERSION_TAG(interp) - 1; } @@ -401,7 +403,7 @@ PyType_ClearWatcher(int watcher_id) return 0; } -static int assign_version_tag(PyTypeObject *type); +static int assign_version_tag(PyInterpreterState *interp, PyTypeObject *type); int PyType_Watch(int watcher_id, PyObject* obj) @@ -416,7 +418,7 @@ PyType_Watch(int watcher_id, PyObject* obj) return -1; } // ensure we will get a callback on the next modification - assign_version_tag(type); + assign_version_tag(interp, type); type->tp_watched |= (1 << watcher_id); return 0; } @@ -549,7 +551,9 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { } } return; + clear: + assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG; type->tp_version_tag = 0; /* 0 is not a valid version tag */ if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { @@ -560,7 +564,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) { } static int -assign_version_tag(PyTypeObject *type) +assign_version_tag(PyInterpreterState *interp, PyTypeObject *type) { /* Ensure that the tp_version_tag is valid and set Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this @@ -574,18 +578,30 @@ assign_version_tag(PyTypeObject *type) return 0; } - if (next_version_tag == 0) { - /* We have run out of version numbers */ - return 0; + if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) { + /* static types */ + if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG) { + /* We have run out of version numbers */ + return 0; + } + type->tp_version_tag = NEXT_GLOBAL_VERSION_TAG++; + assert (type->tp_version_tag <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG); + } + else { + /* heap types */ + if (NEXT_VERSION_TAG(interp) == 0) { + /* We have run out of version numbers */ + return 0; + } + type->tp_version_tag = NEXT_VERSION_TAG(interp)++; + assert (type->tp_version_tag != 0); } - type->tp_version_tag = next_version_tag++; - assert (type->tp_version_tag != 0); PyObject *bases = type->tp_bases; Py_ssize_t n = PyTuple_GET_SIZE(bases); for (Py_ssize_t i = 0; i < n; i++) { PyObject *b = PyTuple_GET_ITEM(bases, i); - if (!assign_version_tag(_PyType_CAST(b))) + if (!assign_version_tag(interp, _PyType_CAST(b))) return 0; } type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG; @@ -594,7 +610,8 @@ assign_version_tag(PyTypeObject *type) int PyUnstable_Type_AssignVersionTag(PyTypeObject *type) { - return assign_version_tag(type); + PyInterpreterState *interp = _PyInterpreterState_GET(); + return assign_version_tag(interp, type); } @@ -2346,7 +2363,15 @@ mro_internal(PyTypeObject *type, PyObject **p_old_mro) from the custom MRO */ type_mro_modified(type, type->tp_bases); - PyType_Modified(type); + // XXX Expand this to Py_TPFLAGS_IMMUTABLETYPE? + if (!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)) { + PyType_Modified(type); + } + else { + /* For static builtin types, this is only called during init + before the method cache has been populated. */ + assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); + } if (p_old_mro != NULL) *p_old_mro = old_mro; /* transfer the ownership */ @@ -4181,6 +4206,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) { PyObject *res; int error; + PyInterpreterState *interp = _PyInterpreterState_GET(); unsigned int h = MCACHE_HASH_METHOD(type, name); struct type_cache *cache = get_type_cache(); @@ -4215,7 +4241,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) return NULL; } - if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) { + if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(interp, type)) { h = MCACHE_HASH_METHOD(type, name); struct type_cache_entry *entry = &cache->hashtable[h]; entry->version = type->tp_version_tag; @@ -6676,8 +6702,11 @@ type_ready_mro(PyTypeObject *type) assert(type->tp_mro != NULL); assert(PyTuple_Check(type->tp_mro)); - /* All bases of statically allocated type should be statically allocated */ + /* All bases of statically allocated type should be statically allocated, + and static builtin types must have static builtin bases. */ if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { + assert(type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE); + int isbuiltin = type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN; PyObject *mro = type->tp_mro; Py_ssize_t n = PyTuple_GET_SIZE(mro); for (Py_ssize_t i = 0; i < n; i++) { @@ -6689,6 +6718,7 @@ type_ready_mro(PyTypeObject *type) type->tp_name, base->tp_name); return -1; } + assert(!isbuiltin || (base->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)); } } return 0; @@ -7000,7 +7030,11 @@ PyType_Ready(PyTypeObject *type) int _PyStaticType_InitBuiltin(PyTypeObject *self) { - self->tp_flags = self->tp_flags | _Py_TPFLAGS_STATIC_BUILTIN; + self->tp_flags |= _Py_TPFLAGS_STATIC_BUILTIN; + + assert(NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG); + self->tp_version_tag = NEXT_GLOBAL_VERSION_TAG++; + self->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG; static_builtin_state_init(self);