Skip to content

Commit 209a0a7

Browse files
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.
1 parent 0dc8b50 commit 209a0a7

File tree

4 files changed

+94
-43
lines changed

4 files changed

+94
-43
lines changed

Include/internal/pycore_runtime.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ extern "C" {
2525
#include "pycore_signal.h" // struct _signals_runtime_state
2626
#include "pycore_time.h" // struct _time_runtime_state
2727
#include "pycore_tracemalloc.h" // struct _tracemalloc_runtime_state
28+
#include "pycore_typeobject.h" // struct types_runtime_state
2829
#include "pycore_unicodeobject.h" // struct _Py_unicode_runtime_ids
2930

3031
struct _getargs_runtime_state {
@@ -150,13 +151,7 @@ typedef struct pyruntimestate {
150151
struct _py_object_runtime_state object_state;
151152
struct _Py_float_runtime_state float_state;
152153
struct _Py_unicode_runtime_state unicode_state;
153-
154-
struct {
155-
/* Used to set PyTypeObject.tp_version_tag */
156-
// bpo-42745: next_version_tag remains shared by all interpreters
157-
// because of static types.
158-
unsigned int next_version_tag;
159-
} types;
154+
struct _types_runtime_state types;
160155

161156
/* All the objects that are shared by the runtime's interpreters. */
162157
struct _Py_static_objects static_objects;

Include/internal/pycore_runtime_init.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ extern PyTypeObject _PyExc_MemoryError;
112112
.func_state = { \
113113
.next_version = 1, \
114114
}, \
115+
.types = { \
116+
.next_version_tag = _Py_TYPE_BASE_VERSION_TAG, \
117+
}, \
115118
.static_objects = { \
116119
.singletons = { \
117120
._not_used = 1, \

Include/internal/pycore_typeobject.h

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,17 @@ extern "C" {
1111
#endif
1212

1313

14-
/* runtime lifecycle */
15-
16-
extern PyStatus _PyTypes_InitTypes(PyInterpreterState *);
17-
extern void _PyTypes_FiniTypes(PyInterpreterState *);
18-
extern void _PyTypes_Fini(PyInterpreterState *);
19-
14+
/* state */
2015

21-
/* other API */
22-
23-
/* Length of array of slotdef pointers used to store slots with the
24-
same __name__. There should be at most MAX_EQUIV-1 slotdef entries with
25-
the same __name__, for any __name__. Since that's a static property, it is
26-
appropriate to declare fixed-size arrays for this. */
27-
#define MAX_EQUIV 10
16+
#define _Py_TYPE_BASE_VERSION_TAG (2<<16)
17+
#define _Py_MAX_GLOBAL_TYPE_VERSION_TAG (_Py_TYPE_BASE_VERSION_TAG - 1)
2818

29-
typedef struct wrapperbase pytype_slotdef;
19+
struct _types_runtime_state {
20+
/* Used to set PyTypeObject.tp_version_tag for core static types. */
21+
// bpo-42745: next_version_tag remains shared by all interpreters
22+
// because of static types.
23+
unsigned int next_version_tag;
24+
};
3025

3126

3227
// Type attribute lookup cache: speed up attribute and method lookups,
@@ -57,6 +52,36 @@ typedef struct {
5752
PyObject *tp_weaklist;
5853
} static_builtin_state;
5954

55+
struct types_state {
56+
/* Used to set PyTypeObject.tp_version_tag.
57+
It starts at _Py_MAX_GLOBAL_TYPE_VERSION_TAG + 1,
58+
where all those lower numbers are used for core static types. */
59+
unsigned int next_version_tag;
60+
61+
struct type_cache type_cache;
62+
size_t num_builtins_initialized;
63+
static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES];
64+
};
65+
66+
67+
/* runtime lifecycle */
68+
69+
extern PyStatus _PyTypes_InitTypes(PyInterpreterState *);
70+
extern void _PyTypes_FiniTypes(PyInterpreterState *);
71+
extern void _PyTypes_Fini(PyInterpreterState *);
72+
73+
74+
/* other API */
75+
76+
/* Length of array of slotdef pointers used to store slots with the
77+
same __name__. There should be at most MAX_EQUIV-1 slotdef entries with
78+
the same __name__, for any __name__. Since that's a static property, it is
79+
appropriate to declare fixed-size arrays for this. */
80+
#define MAX_EQUIV 10
81+
82+
typedef struct wrapperbase pytype_slotdef;
83+
84+
6085
static inline PyObject **
6186
_PyStaticType_GET_WEAKREFS_LISTPTR(static_builtin_state *state)
6287
{
@@ -78,12 +103,6 @@ _PyType_GetModuleState(PyTypeObject *type)
78103
return mod->md_state;
79104
}
80105

81-
struct types_state {
82-
struct type_cache type_cache;
83-
size_t num_builtins_initialized;
84-
static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES];
85-
};
86-
87106

88107
extern int _PyStaticType_InitBuiltin(PyTypeObject *type);
89108
extern static_builtin_state * _PyStaticType_GetState(PyTypeObject *);

Objects/typeobject.c

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ class object "PyObject *" "&PyBaseObject_Type"
4545
PyUnicode_IS_READY(name) && \
4646
(PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE)
4747

48-
#define next_version_tag (_PyRuntime.types.next_version_tag)
48+
#define NEXT_GLOBAL_VERSION_TAG _PyRuntime.types.next_version_tag
49+
#define NEXT_VERSION_TAG(interp) \
50+
(interp)->types.next_version_tag
4951

5052
typedef struct PySlot_Offset {
5153
short subslot_offset;
@@ -332,7 +334,7 @@ _PyType_ClearCache(PyInterpreterState *interp)
332334
// use Py_SETREF() rather than using slower Py_XSETREF().
333335
type_cache_clear(cache, Py_None);
334336

335-
return next_version_tag - 1;
337+
return NEXT_VERSION_TAG(interp) - 1;
336338
}
337339

338340

@@ -401,7 +403,7 @@ PyType_ClearWatcher(int watcher_id)
401403
return 0;
402404
}
403405

404-
static int assign_version_tag(PyTypeObject *type);
406+
static int assign_version_tag(PyInterpreterState *interp, PyTypeObject *type);
405407

406408
int
407409
PyType_Watch(int watcher_id, PyObject* obj)
@@ -416,7 +418,7 @@ PyType_Watch(int watcher_id, PyObject* obj)
416418
return -1;
417419
}
418420
// ensure we will get a callback on the next modification
419-
assign_version_tag(type);
421+
assign_version_tag(interp, type);
420422
type->tp_watched |= (1 << watcher_id);
421423
return 0;
422424
}
@@ -549,7 +551,9 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
549551
}
550552
}
551553
return;
554+
552555
clear:
556+
assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
553557
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
554558
type->tp_version_tag = 0; /* 0 is not a valid version tag */
555559
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
@@ -560,7 +564,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
560564
}
561565

562566
static int
563-
assign_version_tag(PyTypeObject *type)
567+
assign_version_tag(PyInterpreterState *interp, PyTypeObject *type)
564568
{
565569
/* Ensure that the tp_version_tag is valid and set
566570
Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this
@@ -574,18 +578,30 @@ assign_version_tag(PyTypeObject *type)
574578
return 0;
575579
}
576580

577-
if (next_version_tag == 0) {
578-
/* We have run out of version numbers */
579-
return 0;
581+
if (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) {
582+
/* static types */
583+
if (NEXT_GLOBAL_VERSION_TAG > _Py_MAX_GLOBAL_TYPE_VERSION_TAG) {
584+
/* We have run out of version numbers */
585+
return 0;
586+
}
587+
type->tp_version_tag = NEXT_GLOBAL_VERSION_TAG++;
588+
assert (type->tp_version_tag <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG);
589+
}
590+
else {
591+
/* heap types */
592+
if (NEXT_VERSION_TAG(interp) == 0) {
593+
/* We have run out of version numbers */
594+
return 0;
595+
}
596+
type->tp_version_tag = NEXT_VERSION_TAG(interp)++;
597+
assert (type->tp_version_tag != 0);
580598
}
581-
type->tp_version_tag = next_version_tag++;
582-
assert (type->tp_version_tag != 0);
583599

584600
PyObject *bases = type->tp_bases;
585601
Py_ssize_t n = PyTuple_GET_SIZE(bases);
586602
for (Py_ssize_t i = 0; i < n; i++) {
587603
PyObject *b = PyTuple_GET_ITEM(bases, i);
588-
if (!assign_version_tag(_PyType_CAST(b)))
604+
if (!assign_version_tag(interp, _PyType_CAST(b)))
589605
return 0;
590606
}
591607
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
@@ -594,7 +610,8 @@ assign_version_tag(PyTypeObject *type)
594610

595611
int PyUnstable_Type_AssignVersionTag(PyTypeObject *type)
596612
{
597-
return assign_version_tag(type);
613+
PyInterpreterState *interp = _PyInterpreterState_GET();
614+
return assign_version_tag(interp, type);
598615
}
599616

600617

@@ -2346,7 +2363,15 @@ mro_internal(PyTypeObject *type, PyObject **p_old_mro)
23462363
from the custom MRO */
23472364
type_mro_modified(type, type->tp_bases);
23482365

2349-
PyType_Modified(type);
2366+
// XXX Expand this to Py_TPFLAGS_IMMUTABLETYPE?
2367+
if (!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN)) {
2368+
PyType_Modified(type);
2369+
}
2370+
else {
2371+
/* For static builtin types, this is only called during init
2372+
before the method cache has been populated. */
2373+
assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG));
2374+
}
23502375

23512376
if (p_old_mro != NULL)
23522377
*p_old_mro = old_mro; /* transfer the ownership */
@@ -4181,6 +4206,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
41814206
{
41824207
PyObject *res;
41834208
int error;
4209+
PyInterpreterState *interp = _PyInterpreterState_GET();
41844210

41854211
unsigned int h = MCACHE_HASH_METHOD(type, name);
41864212
struct type_cache *cache = get_type_cache();
@@ -4215,7 +4241,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
42154241
return NULL;
42164242
}
42174243

4218-
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(type)) {
4244+
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(interp, type)) {
42194245
h = MCACHE_HASH_METHOD(type, name);
42204246
struct type_cache_entry *entry = &cache->hashtable[h];
42214247
entry->version = type->tp_version_tag;
@@ -6676,8 +6702,11 @@ type_ready_mro(PyTypeObject *type)
66766702
assert(type->tp_mro != NULL);
66776703
assert(PyTuple_Check(type->tp_mro));
66786704

6679-
/* All bases of statically allocated type should be statically allocated */
6705+
/* All bases of statically allocated type should be statically allocated,
6706+
and static builtin types must have static builtin bases. */
66806707
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
6708+
assert(type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE);
6709+
int isbuiltin = type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN;
66816710
PyObject *mro = type->tp_mro;
66826711
Py_ssize_t n = PyTuple_GET_SIZE(mro);
66836712
for (Py_ssize_t i = 0; i < n; i++) {
@@ -6689,6 +6718,7 @@ type_ready_mro(PyTypeObject *type)
66896718
type->tp_name, base->tp_name);
66906719
return -1;
66916720
}
6721+
assert(!isbuiltin || (base->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
66926722
}
66936723
}
66946724
return 0;
@@ -7000,7 +7030,11 @@ PyType_Ready(PyTypeObject *type)
70007030
int
70017031
_PyStaticType_InitBuiltin(PyTypeObject *self)
70027032
{
7003-
self->tp_flags = self->tp_flags | _Py_TPFLAGS_STATIC_BUILTIN;
7033+
self->tp_flags |= _Py_TPFLAGS_STATIC_BUILTIN;
7034+
7035+
assert(NEXT_GLOBAL_VERSION_TAG <= _Py_MAX_GLOBAL_TYPE_VERSION_TAG);
7036+
self->tp_version_tag = NEXT_GLOBAL_VERSION_TAG++;
7037+
self->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
70047038

70057039
static_builtin_state_init(self);
70067040

0 commit comments

Comments
 (0)