Skip to content

Commit

Permalink
gh-95795: Move types.next_version_tag to PyInterpreterState (gh-102343)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ericsnowcurrently committed Apr 24, 2023
1 parent 0dc8b50 commit 209a0a7
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 43 deletions.
9 changes: 2 additions & 7 deletions Include/internal/pycore_runtime.h
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
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
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)
#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
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;

Check warning on line 6709 in Objects/typeobject.c

View workflow job for this annotation

GitHub Actions / Address sanitizer

unused variable ‘isbuiltin’ [-Wunused-variable]

This comment has been minimized.

Copy link
@sunmy2019

sunmy2019 Apr 25, 2023

Member

isbuiltin is only for assert usage, which caused an unused variable warning in release mode.

I will open a PR for this.

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

0 comments on commit 209a0a7

Please sign in to comment.