Skip to content

gh-95795: Move types.next_version_tag to PyInterpreterState #102343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
9 changes: 2 additions & 7 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_runtime_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down
59 changes: 39 additions & 20 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,17 @@ extern "C" {
#endif


/* runtime lifecycle */

extern PyStatus _PyTypes_InitTypes(PyInterpreterState *);
extern void _PyTypes_FiniTypes(PyInterpreterState *);
extern void _PyTypes_Fini(PyInterpreterState *);

/* state */

/* 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
#define _Py_TYPE_BASE_VERSION_TAG (2<<16)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean (1<<16) here? This is currently the same as (1<<17)...

#define _Py_MAX_GLOBAL_TYPE_VERSION_TAG (_Py_TYPE_BASE_VERSION_TAG - 1)

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,
Expand Down Expand Up @@ -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)
{
Expand All @@ -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 *);
Expand Down
66 changes: 50 additions & 16 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}


Expand Down Expand Up @@ -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)
Expand All @@ -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;
}
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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);
}


Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand Down