bpo-44914: Maintain invariants of type version tags. (GH-27773)

* Do not invalidate type versions unnecessarily.
This commit is contained in:
Mark Shannon 2021-08-16 12:21:34 +01:00 committed by GitHub
parent 62bc716fde
commit 1a511dc92d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 18 additions and 27 deletions

View File

@ -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.

View File

@ -45,7 +45,7 @@ class object "PyObject *" "&PyBaseObject_Type"
// bpo-42745: next_version_tag remains shared by all interpreters because of static types // bpo-42745: next_version_tag remains shared by all interpreters because of static types
// Used to set PyTypeObject.tp_version_tag // 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 { typedef struct PySlot_Offset {
short subslot_offset; short subslot_offset;
@ -233,24 +233,14 @@ get_type_cache(void)
static 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++) { for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
struct type_cache_entry *entry = &cache->hashtable[i]; struct type_cache_entry *entry = &cache->hashtable[i];
entry->version = 0; entry->version = 0;
if (use_none) { Py_XSETREF(entry->name, _Py_XNewRef(value));
// 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);
}
entry->value = NULL; 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); sizeof(cache->hashtable) / 1024);
#endif #endif
unsigned int cur_version_tag = next_version_tag - 1; // Set to None, rather than NULL, so _PyType_Lookup() can
if (_Py_IsMainInterpreter(interp)) { // use Py_SETREF() rather than using slower Py_XSETREF().
next_version_tag = 0; type_cache_clear(cache, Py_None);
}
type_cache_clear(cache, 0); return next_version_tag - 1;
return cur_version_tag;
} }
@ -309,7 +296,8 @@ PyType_ClearCache(void)
void void
_PyType_Fini(PyInterpreterState *interp) _PyType_Fini(PyInterpreterState *interp)
{ {
_PyType_ClearCache(interp); struct type_cache *cache = &interp->type_cache;
type_cache_clear(cache, NULL);
if (_Py_IsMainInterpreter(interp)) { if (_Py_IsMainInterpreter(interp)) {
clear_slotdefs(); clear_slotdefs();
} }
@ -426,14 +414,12 @@ assign_version_tag(struct type_cache *cache, PyTypeObject *type)
if (!_PyType_HasFeature(type, Py_TPFLAGS_READY)) if (!_PyType_HasFeature(type, Py_TPFLAGS_READY))
return 0; return 0;
type->tp_version_tag = next_version_tag++; if (next_version_tag == 0) {
/* for stress-testing: next_version_tag &= 0xFF; */ /* We have run out of version numbers */
if (type->tp_version_tag == 0) {
// Wrap-around or just starting Python - clear the whole cache
type_cache_clear(cache, 1);
return 0; return 0;
} }
type->tp_version_tag = next_version_tag++;
assert (type->tp_version_tag != 0);
bases = type->tp_bases; bases = type->tp_bases;
n = PyTuple_GET_SIZE(bases); n = PyTuple_GET_SIZE(bases);