From 1a511dc92dd10ee8fc2e5da9f52f795924bdc89a Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Mon, 16 Aug 2021 12:21:34 +0100 Subject: [PATCH] bpo-44914: Maintain invariants of type version tags. (GH-27773) * Do not invalidate type versions unnecessarily. --- .../2021-08-16-11-36-02.bpo-44914.6Lgrx3.rst | 5 +++ Objects/typeobject.c | 40 ++++++------------- 2 files changed, 18 insertions(+), 27 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-08-16-11-36-02.bpo-44914.6Lgrx3.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-08-16-11-36-02.bpo-44914.6Lgrx3.rst b/Misc/NEWS.d/next/Core and Builtins/2021-08-16-11-36-02.bpo-44914.6Lgrx3.rst new file mode 100644 index 00000000000..5e306ffaf5d --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-08-16-11-36-02.bpo-44914.6Lgrx3.rst @@ -0,0 +1,5 @@ +Class version tags are no longer recycled. + +This means that a version tag serves as a unique identifier for the state of +a class. We rely on this for effective specialization of the LOAD_ATTR and +other instructions. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 7ae50c453ed..1efb9fc5944 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -45,7 +45,7 @@ class object "PyObject *" "&PyBaseObject_Type" // bpo-42745: next_version_tag remains shared by all interpreters because of static types // Used to set PyTypeObject.tp_version_tag -static unsigned int next_version_tag = 0; +static unsigned int next_version_tag = 1; typedef struct PySlot_Offset { short subslot_offset; @@ -233,24 +233,14 @@ get_type_cache(void) static void -type_cache_clear(struct type_cache *cache, int use_none) +type_cache_clear(struct type_cache *cache, PyObject *value) { for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) { struct type_cache_entry *entry = &cache->hashtable[i]; entry->version = 0; - if (use_none) { - // Set to None so _PyType_Lookup() can use Py_SETREF(), - // rather than using slower Py_XSETREF(). - Py_XSETREF(entry->name, Py_NewRef(Py_None)); - } - else { - Py_CLEAR(entry->name); - } + Py_XSETREF(entry->name, _Py_XNewRef(value)); entry->value = NULL; } - - // Mark all version tags as invalid - PyType_Modified(&PyBaseObject_Type); } @@ -287,14 +277,11 @@ _PyType_ClearCache(PyInterpreterState *interp) sizeof(cache->hashtable) / 1024); #endif - unsigned int cur_version_tag = next_version_tag - 1; - if (_Py_IsMainInterpreter(interp)) { - next_version_tag = 0; - } + // Set to None, rather than NULL, so _PyType_Lookup() can + // use Py_SETREF() rather than using slower Py_XSETREF(). + type_cache_clear(cache, Py_None); - type_cache_clear(cache, 0); - - return cur_version_tag; + return next_version_tag - 1; } @@ -309,7 +296,8 @@ PyType_ClearCache(void) void _PyType_Fini(PyInterpreterState *interp) { - _PyType_ClearCache(interp); + struct type_cache *cache = &interp->type_cache; + type_cache_clear(cache, NULL); if (_Py_IsMainInterpreter(interp)) { clear_slotdefs(); } @@ -426,14 +414,12 @@ assign_version_tag(struct type_cache *cache, PyTypeObject *type) if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) return 0; - type->tp_version_tag = next_version_tag++; - /* for stress-testing: next_version_tag &= 0xFF; */ - - if (type->tp_version_tag == 0) { - // Wrap-around or just starting Python - clear the whole cache - type_cache_clear(cache, 1); + if (next_version_tag == 0) { + /* We have run out of version numbers */ return 0; } + type->tp_version_tag = next_version_tag++; + assert (type->tp_version_tag != 0); bases = type->tp_bases; n = PyTuple_GET_SIZE(bases);