]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-124296: Remove private dictionary version tag (PEP 699) (#124472)
authorSam Gross <colesbury@gmail.com>
Tue, 1 Oct 2024 16:39:56 +0000 (12:39 -0400)
committerGitHub <noreply@github.com>
Tue, 1 Oct 2024 16:39:56 +0000 (12:39 -0400)
13 files changed:
Include/cpython/dictobject.h
Include/internal/pycore_dict.h
Include/internal/pycore_dict_state.h
Lib/test/test_dict_version.py [deleted file]
Lib/test/test_free_threading/test_dict.py
Misc/NEWS.d/next/C_API/2024-09-24-20-34-21.gh-issue-124296.S4QoS1.rst [new file with mode: 0644]
Modules/_testcapi/dict.c
Modules/_testcapimodule.c
Objects/dictobject.c
Python/bytecodes.c
Python/executor_cases.c.h
Python/generated_cases.c.h
Python/optimizer_analysis.c

index e2861c963266ea8fe1d31bb9e02852c89533fd15..b113c7fdcf6515d0f64771571b71a502529c1d70 100644 (file)
@@ -14,16 +14,11 @@ typedef struct {
     /* Number of items in the dictionary */
     Py_ssize_t ma_used;
 
-    /* Dictionary version: globally unique, value change each time
-       the dictionary is modified */
-#ifdef Py_BUILD_CORE
-    /* Bits 0-7 are for dict watchers.
+    /* This is a private field for CPython's internal use.
+     * Bits 0-7 are for dict watchers.
      * Bits 8-11 are for the watched mutation counter (used by tier2 optimization)
-     * The remaining bits (12-63) are the actual version tag. */
-    uint64_t ma_version_tag;
-#else
-    Py_DEPRECATED(3.12) uint64_t ma_version_tag;
-#endif
+     * The remaining bits are not currently used. */
+    uint64_t _ma_watcher_tag;
 
     PyDictKeysObject *ma_keys;
 
index f9a043b0208c8f642db52b977f1d566a5654f5b6..1920724c1d4f575c849a1f603bdc64cd4d591600 100644 (file)
@@ -230,31 +230,6 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) {
 #define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1)
 #define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1)
 
-#ifdef Py_GIL_DISABLED
-
-#define THREAD_LOCAL_DICT_VERSION_COUNT 256
-#define THREAD_LOCAL_DICT_VERSION_BATCH THREAD_LOCAL_DICT_VERSION_COUNT * DICT_VERSION_INCREMENT
-
-static inline uint64_t
-dict_next_version(PyInterpreterState *interp)
-{
-    PyThreadState *tstate = PyThreadState_GET();
-    uint64_t cur_progress = (tstate->dict_global_version &
-                            (THREAD_LOCAL_DICT_VERSION_BATCH - 1));
-    if (cur_progress == 0) {
-        uint64_t next = _Py_atomic_add_uint64(&interp->dict_state.global_version,
-                                              THREAD_LOCAL_DICT_VERSION_BATCH);
-        tstate->dict_global_version = next;
-    }
-    return tstate->dict_global_version += DICT_VERSION_INCREMENT;
-}
-
-#define DICT_NEXT_VERSION(INTERP) dict_next_version(INTERP)
-
-#else
-#define DICT_NEXT_VERSION(INTERP) \
-    ((INTERP)->dict_state.global_version += DICT_VERSION_INCREMENT)
-#endif
 
 PyAPI_FUNC(void)
 _PyDict_SendEvent(int watcher_bits,
@@ -263,7 +238,7 @@ _PyDict_SendEvent(int watcher_bits,
                   PyObject *key,
                   PyObject *value);
 
-static inline uint64_t
+static inline void
 _PyDict_NotifyEvent(PyInterpreterState *interp,
                     PyDict_WatchEvent event,
                     PyDictObject *mp,
@@ -271,12 +246,11 @@ _PyDict_NotifyEvent(PyInterpreterState *interp,
                     PyObject *value)
 {
     assert(Py_REFCNT((PyObject*)mp) > 0);
-    int watcher_bits = mp->ma_version_tag & DICT_WATCHER_MASK;
+    int watcher_bits = mp->_ma_watcher_tag & DICT_WATCHER_MASK;
     if (watcher_bits) {
         RARE_EVENT_STAT_INC(watched_dict_modification);
         _PyDict_SendEvent(watcher_bits, event, mp, key, value);
     }
-    return DICT_NEXT_VERSION(interp) | (mp->ma_version_tag & DICT_WATCHER_AND_MODIFICATION_MASK);
 }
 
 extern PyDictObject *_PyObject_MaterializeManagedDict(PyObject *obj);
index 1a44755c7a01a3a34f908220ef165173ecf18570..11932b8d1e1ab60d49d69693d00dc9cd80ae3fc4 100644 (file)
@@ -12,10 +12,6 @@ extern "C" {
 #define DICT_WATCHED_MUTATION_BITS 4
 
 struct _Py_dict_state {
-    /*Global counter used to set ma_version_tag field of dictionary.
-     * It is incremented each time that a dictionary is created and each
-     * time that a dictionary is modified. */
-    uint64_t global_version;
     uint32_t next_keys_version;
     PyDict_WatchCallback watchers[DICT_MAX_WATCHERS];
 };
diff --git a/Lib/test/test_dict_version.py b/Lib/test/test_dict_version.py
deleted file mode 100644 (file)
index 243084c..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-"""
-Test implementation of the PEP 509: dictionary versioning.
-"""
-import unittest
-from test.support import import_helper
-
-# PEP 509 is implemented in CPython but other Python implementations
-# don't require to implement it
-_testcapi = import_helper.import_module('_testcapi')
-
-
-class DictVersionTests(unittest.TestCase):
-    type2test = dict
-
-    def setUp(self):
-        self.seen_versions = set()
-        self.dict = None
-
-    def check_version_unique(self, mydict):
-        version = _testcapi.dict_get_version(mydict)
-        self.assertNotIn(version, self.seen_versions)
-        self.seen_versions.add(version)
-
-    def check_version_changed(self, mydict, method, *args, **kw):
-        result = method(*args, **kw)
-        self.check_version_unique(mydict)
-        return result
-
-    def check_version_dont_change(self, mydict, method, *args, **kw):
-        version1 = _testcapi.dict_get_version(mydict)
-        self.seen_versions.add(version1)
-
-        result = method(*args, **kw)
-
-        version2 = _testcapi.dict_get_version(mydict)
-        self.assertEqual(version2, version1, "version changed")
-
-        return  result
-
-    def new_dict(self, *args, **kw):
-        d = self.type2test(*args, **kw)
-        self.check_version_unique(d)
-        return d
-
-    def test_constructor(self):
-        # new empty dictionaries must all have an unique version
-        empty1 = self.new_dict()
-        empty2 = self.new_dict()
-        empty3 = self.new_dict()
-
-        # non-empty dictionaries must also have an unique version
-        nonempty1 = self.new_dict(x='x')
-        nonempty2 = self.new_dict(x='x', y='y')
-
-    def test_copy(self):
-        d = self.new_dict(a=1, b=2)
-
-        d2 = self.check_version_dont_change(d, d.copy)
-
-        # dict.copy() must create a dictionary with a new unique version
-        self.check_version_unique(d2)
-
-    def test_setitem(self):
-        d = self.new_dict()
-
-        # creating new keys must change the version
-        self.check_version_changed(d, d.__setitem__, 'x', 'x')
-        self.check_version_changed(d, d.__setitem__, 'y', 'y')
-
-        # changing values must change the version
-        self.check_version_changed(d, d.__setitem__, 'x', 1)
-        self.check_version_changed(d, d.__setitem__, 'y', 2)
-
-    def test_setitem_same_value(self):
-        value = object()
-        d = self.new_dict()
-
-        # setting a key must change the version
-        self.check_version_changed(d, d.__setitem__, 'key', value)
-
-        # setting a key to the same value with dict.__setitem__
-        # must change the version
-        self.check_version_dont_change(d, d.__setitem__, 'key', value)
-
-        # setting a key to the same value with dict.update
-        # must change the version
-        self.check_version_dont_change(d, d.update, key=value)
-
-        d2 = self.new_dict(key=value)
-        self.check_version_dont_change(d, d.update, d2)
-
-    def test_setitem_equal(self):
-        class AlwaysEqual:
-            def __eq__(self, other):
-                return True
-
-        value1 = AlwaysEqual()
-        value2 = AlwaysEqual()
-        self.assertTrue(value1 == value2)
-        self.assertFalse(value1 != value2)
-        self.assertIsNot(value1, value2)
-
-        d = self.new_dict()
-        self.check_version_changed(d, d.__setitem__, 'key', value1)
-        self.assertIs(d['key'], value1)
-
-        # setting a key to a value equal to the current value
-        # with dict.__setitem__() must change the version
-        self.check_version_changed(d, d.__setitem__, 'key', value2)
-        self.assertIs(d['key'], value2)
-
-        # setting a key to a value equal to the current value
-        # with dict.update() must change the version
-        self.check_version_changed(d, d.update, key=value1)
-        self.assertIs(d['key'], value1)
-
-        d2 = self.new_dict(key=value2)
-        self.check_version_changed(d, d.update, d2)
-        self.assertIs(d['key'], value2)
-
-    def test_setdefault(self):
-        d = self.new_dict()
-
-        # setting a key with dict.setdefault() must change the version
-        self.check_version_changed(d, d.setdefault, 'key', 'value1')
-
-        # don't change the version if the key already exists
-        self.check_version_dont_change(d, d.setdefault, 'key', 'value2')
-
-    def test_delitem(self):
-        d = self.new_dict(key='value')
-
-        # deleting a key with dict.__delitem__() must change the version
-        self.check_version_changed(d, d.__delitem__, 'key')
-
-        # don't change the version if the key doesn't exist
-        self.check_version_dont_change(d, self.assertRaises, KeyError,
-                                       d.__delitem__, 'key')
-
-    def test_pop(self):
-        d = self.new_dict(key='value')
-
-        # pop() must change the version if the key exists
-        self.check_version_changed(d, d.pop, 'key')
-
-        # pop() must not change the version if the key does not exist
-        self.check_version_dont_change(d, self.assertRaises, KeyError,
-                                       d.pop, 'key')
-
-    def test_popitem(self):
-        d = self.new_dict(key='value')
-
-        # popitem() must change the version if the dict is not empty
-        self.check_version_changed(d, d.popitem)
-
-        # popitem() must not change the version if the dict is empty
-        self.check_version_dont_change(d, self.assertRaises, KeyError,
-                                       d.popitem)
-
-    def test_update(self):
-        d = self.new_dict(key='value')
-
-        # update() calling with no argument must not change the version
-        self.check_version_dont_change(d, d.update)
-
-        # update() must change the version
-        self.check_version_changed(d, d.update, key='new value')
-
-        d2 = self.new_dict(key='value 3')
-        self.check_version_changed(d, d.update, d2)
-
-    def test_clear(self):
-        d = self.new_dict(key='value')
-
-        # clear() must change the version if the dict is not empty
-        self.check_version_changed(d, d.clear)
-
-        # clear() must not change the version if the dict is empty
-        self.check_version_dont_change(d, d.clear)
-
-
-class Dict(dict):
-    pass
-
-
-class DictSubtypeVersionTests(DictVersionTests):
-    type2test = Dict
-
-
-if __name__ == "__main__":
-    unittest.main()
index 3126458e08e50abe927b052e47baa7f9140a47e7..80daf0d9cae9e0f1ecd45d3831a329dd95ef86a5 100644 (file)
@@ -142,41 +142,6 @@ class TestDict(TestCase):
             for ref in thread_list:
                 self.assertIsNone(ref())
 
-    @unittest.skipIf(_testcapi is None, 'need _testcapi module')
-    def test_dict_version(self):
-        dict_version = _testcapi.dict_version
-        THREAD_COUNT = 10
-        DICT_COUNT = 10000
-        lists = []
-        writers = []
-
-        def writer_func(thread_list):
-            for i in range(DICT_COUNT):
-                thread_list.append(dict_version({}))
-
-        for x in range(THREAD_COUNT):
-            thread_list = []
-            lists.append(thread_list)
-            writer = Thread(target=partial(writer_func, thread_list))
-            writers.append(writer)
-
-        for writer in writers:
-            writer.start()
-
-        for writer in writers:
-            writer.join()
-
-        total_len = 0
-        values = set()
-        for thread_list in lists:
-            for v in thread_list:
-                if v in values:
-                    print('dup', v, (v/4096)%256)
-                values.add(v)
-            total_len += len(thread_list)
-        versions = set(dict_version for thread_list in lists for dict_version in thread_list)
-        self.assertEqual(len(versions), THREAD_COUNT*DICT_COUNT)
-
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/C_API/2024-09-24-20-34-21.gh-issue-124296.S4QoS1.rst b/Misc/NEWS.d/next/C_API/2024-09-24-20-34-21.gh-issue-124296.S4QoS1.rst
new file mode 100644 (file)
index 0000000..e7b9187
--- /dev/null
@@ -0,0 +1,3 @@
+:c:type:`PyDictObject` no longer maintains a private version tag field
+``ma_version_tag`` per :pep:`699`.  This field was originally added in
+Python 3.6 (:pep:`509`) and deprecated in Python 3.12.
index e80d898118daa512395b847434e9ff483e319ceb..307797f98f12ae3a7fa58685aeb5b9e3b1275c92 100644 (file)
@@ -181,19 +181,6 @@ dict_popstring_null(PyObject *self, PyObject *args)
     RETURN_INT(PyDict_PopString(dict, key,  NULL));
 }
 
-static PyObject *
-dict_version(PyObject *self, PyObject *dict)
-{
-    if (!PyDict_Check(dict)) {
-        PyErr_SetString(PyExc_TypeError, "expected dict");
-        return NULL;
-    }
-_Py_COMP_DIAG_PUSH
-_Py_COMP_DIAG_IGNORE_DEPR_DECLS
-    return PyLong_FromUnsignedLongLong(((PyDictObject *)dict)->ma_version_tag);
-_Py_COMP_DIAG_POP
-}
-
 static PyMethodDef test_methods[] = {
     {"dict_containsstring", dict_containsstring, METH_VARARGS},
     {"dict_getitemref", dict_getitemref, METH_VARARGS},
@@ -204,7 +191,6 @@ static PyMethodDef test_methods[] = {
     {"dict_pop_null", dict_pop_null, METH_VARARGS},
     {"dict_popstring", dict_popstring, METH_VARARGS},
     {"dict_popstring_null", dict_popstring_null, METH_VARARGS},
-    {"dict_version", dict_version, METH_O},
     {NULL},
 };
 
index 5966eb674cf4e58fb742479d16be9f566696efcd..72b9792d7005ffa29221cba0d6cd3c6fc109e36e 100644 (file)
@@ -1909,25 +1909,6 @@ getitem_with_error(PyObject *self, PyObject *args)
     return PyObject_GetItem(map, key);
 }
 
-static PyObject *
-dict_get_version(PyObject *self, PyObject *args)
-{
-    PyDictObject *dict;
-    uint64_t version;
-
-    if (!PyArg_ParseTuple(args, "O!", &PyDict_Type, &dict))
-        return NULL;
-
-    _Py_COMP_DIAG_PUSH
-    _Py_COMP_DIAG_IGNORE_DEPR_DECLS
-    version = dict->ma_version_tag;
-    _Py_COMP_DIAG_POP
-
-    static_assert(sizeof(unsigned long long) >= sizeof(version),
-                  "version is larger than unsigned long long");
-    return PyLong_FromUnsignedLongLong((unsigned long long)version);
-}
-
 
 static PyObject *
 raise_SIGINT_then_send_None(PyObject *self, PyObject *args)
@@ -3407,7 +3388,6 @@ static PyMethodDef TestMethods[] = {
     {"return_result_with_error", return_result_with_error, METH_NOARGS},
     {"getitem_with_error", getitem_with_error, METH_VARARGS},
     {"Py_CompileString",     pycompilestring, METH_O},
-    {"dict_get_version", dict_get_version, METH_VARARGS},
     {"raise_SIGINT_then_send_None", raise_SIGINT_then_send_None, METH_VARARGS},
     {"stack_pointer", stack_pointer, METH_NOARGS},
 #ifdef W_STOPCODE
index ef9d23e62f95de3420f6350dfc9969e5ee139fe9..adfd91d1e4d63b488c003d6e25ab4cc43d9d4abe 100644 (file)
@@ -877,7 +877,7 @@ new_dict(PyInterpreterState *interp,
     mp->ma_keys = keys;
     mp->ma_values = values;
     mp->ma_used = used;
-    mp->ma_version_tag = DICT_NEXT_VERSION(interp);
+    mp->_ma_watcher_tag = 0;
     ASSERT_CONSISTENT(mp);
     return (PyObject *)mp;
 }
@@ -1678,8 +1678,7 @@ insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
         }
     }
 
-    uint64_t new_version = _PyDict_NotifyEvent(
-        interp, PyDict_EVENT_ADDED, mp, key, value);
+    _PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
     mp->ma_keys->dk_version = 0;
 
     Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);
@@ -1698,7 +1697,6 @@ insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
         STORE_VALUE(ep, value);
         STORE_HASH(ep, hash);
     }
-    mp->ma_version_tag = new_version;
     STORE_KEYS_USABLE(mp->ma_keys, mp->ma_keys->dk_usable - 1);
     STORE_KEYS_NENTRIES(mp->ma_keys, mp->ma_keys->dk_nentries + 1);
     assert(mp->ma_keys->dk_usable >= 0);
@@ -1744,16 +1742,14 @@ insert_split_value(PyInterpreterState *interp, PyDictObject *mp, PyObject *key,
     MAINTAIN_TRACKING(mp, key, value);
     PyObject *old_value = mp->ma_values->values[ix];
     if (old_value == NULL) {
-        uint64_t new_version = _PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
+        _PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
         STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value));
         _PyDictValues_AddToInsertionOrder(mp->ma_values, ix);
         STORE_USED(mp, mp->ma_used + 1);
-        mp->ma_version_tag = new_version;
     }
     else {
-        uint64_t new_version = _PyDict_NotifyEvent(interp, PyDict_EVENT_MODIFIED, mp, key, value);
+        _PyDict_NotifyEvent(interp, PyDict_EVENT_MODIFIED, mp, key, value);
         STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value));
-        mp->ma_version_tag = new_version;
         // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
         // when dict only holds the strong reference to value in ep->me_value.
         Py_DECREF(old_value);
@@ -1815,8 +1811,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
     }
 
     if (old_value != value) {
-        uint64_t new_version = _PyDict_NotifyEvent(
-                interp, PyDict_EVENT_MODIFIED, mp, key, value);
+        _PyDict_NotifyEvent(interp, PyDict_EVENT_MODIFIED, mp, key, value);
         assert(old_value != NULL);
         assert(!_PyDict_HasSplitTable(mp));
         if (DK_IS_UNICODE(mp->ma_keys)) {
@@ -1827,7 +1822,6 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
             PyDictKeyEntry *ep = &DK_ENTRIES(mp->ma_keys)[ix];
             STORE_VALUE(ep, value);
         }
-        mp->ma_version_tag = new_version;
     }
     Py_XDECREF(old_value); /* which **CAN** re-enter (see issue #22653) */
     ASSERT_CONSISTENT(mp);
@@ -1857,8 +1851,7 @@ insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp,
         Py_DECREF(value);
         return -1;
     }
-    uint64_t new_version = _PyDict_NotifyEvent(
-            interp, PyDict_EVENT_ADDED, mp, key, value);
+    _PyDict_NotifyEvent(interp, PyDict_EVENT_ADDED, mp, key, value);
 
     /* We don't decref Py_EMPTY_KEYS here because it is immortal. */
     assert(mp->ma_values == NULL);
@@ -1879,7 +1872,6 @@ insert_to_emptydict(PyInterpreterState *interp, PyDictObject *mp,
         STORE_VALUE(ep, value);
     }
     STORE_USED(mp, mp->ma_used + 1);
-    mp->ma_version_tag = new_version;
     newkeys->dk_usable--;
     newkeys->dk_nentries++;
     // We store the keys last so no one can see them in a partially inconsistent
@@ -2612,7 +2604,7 @@ delete_index_from_values(PyDictValues *values, Py_ssize_t ix)
 
 static void
 delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
-               PyObject *old_value, uint64_t new_version)
+               PyObject *old_value)
 {
     PyObject *old_key;
 
@@ -2622,7 +2614,6 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
     assert(hashpos >= 0);
 
     STORE_USED(mp, mp->ma_used - 1);
-    mp->ma_version_tag = new_version;
     if (_PyDict_HasSplitTable(mp)) {
         assert(old_value == mp->ma_values->values[ix]);
         STORE_SPLIT_VALUE(mp, ix, NULL);
@@ -2692,9 +2683,8 @@ delitem_knownhash_lock_held(PyObject *op, PyObject *key, Py_hash_t hash)
     }
 
     PyInterpreterState *interp = _PyInterpreterState_GET();
-    uint64_t new_version = _PyDict_NotifyEvent(
-            interp, PyDict_EVENT_DELETED, mp, key, NULL);
-    delitem_common(mp, hash, ix, old_value, new_version);
+    _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
+    delitem_common(mp, hash, ix, old_value);
     return 0;
 }
 
@@ -2740,9 +2730,8 @@ delitemif_lock_held(PyObject *op, PyObject *key,
 
     if (res > 0) {
         PyInterpreterState *interp = _PyInterpreterState_GET();
-        uint64_t new_version = _PyDict_NotifyEvent(
-                interp, PyDict_EVENT_DELETED, mp, key, NULL);
-        delitem_common(mp, hash, ix, old_value, new_version);
+        _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
+        delitem_common(mp, hash, ix, old_value);
         return 1;
     } else {
         return 0;
@@ -2786,11 +2775,9 @@ clear_lock_held(PyObject *op)
     }
     /* Empty the dict... */
     PyInterpreterState *interp = _PyInterpreterState_GET();
-    uint64_t new_version = _PyDict_NotifyEvent(
-            interp, PyDict_EVENT_CLEARED, mp, NULL, NULL);
+    _PyDict_NotifyEvent(interp, PyDict_EVENT_CLEARED, mp, NULL, NULL);
     // We don't inc ref empty keys because they're immortal
     ensure_shared_on_resize(mp);
-    mp->ma_version_tag = new_version;
     STORE_USED(mp, 0);
     if (oldvalues == NULL) {
         set_keys(mp, Py_EMPTY_KEYS);
@@ -2950,9 +2937,8 @@ _PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash,
 
     assert(old_value != NULL);
     PyInterpreterState *interp = _PyInterpreterState_GET();
-    uint64_t new_version = _PyDict_NotifyEvent(
-            interp, PyDict_EVENT_DELETED, mp, key, NULL);
-    delitem_common(mp, hash, ix, Py_NewRef(old_value), new_version);
+    _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, mp, key, NULL);
+    delitem_common(mp, hash, ix, Py_NewRef(old_value));
 
     ASSERT_CONSISTENT(mp);
     if (result) {
@@ -3717,8 +3703,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
             (DK_LOG_SIZE(okeys) == PyDict_LOG_MINSIZE ||
              USABLE_FRACTION(DK_SIZE(okeys)/2) < other->ma_used)
         ) {
-            uint64_t new_version = _PyDict_NotifyEvent(
-                    interp, PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL);
+            _PyDict_NotifyEvent(interp, PyDict_EVENT_CLONED, mp, (PyObject *)other, NULL);
             PyDictKeysObject *keys = clone_combined_dict_keys(other);
             if (keys == NULL)
                 return -1;
@@ -3727,7 +3712,6 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
             dictkeys_decref(interp, mp->ma_keys, IS_DICT_SHARED(mp));
             mp->ma_keys = keys;
             STORE_USED(mp, other->ma_used);
-            mp->ma_version_tag = new_version;
             ASSERT_CONSISTENT(mp);
 
             if (_PyObject_GC_IS_TRACKED(other) && !_PyObject_GC_IS_TRACKED(mp)) {
@@ -3982,7 +3966,7 @@ copy_lock_held(PyObject *o)
         split_copy->ma_values = newvalues;
         split_copy->ma_keys = mp->ma_keys;
         split_copy->ma_used = mp->ma_used;
-        split_copy->ma_version_tag = DICT_NEXT_VERSION(interp);
+        split_copy->_ma_watcher_tag = 0;
         dictkeys_incref(mp->ma_keys);
         if (_PyObject_GC_IS_TRACKED(mp))
             _PyObject_GC_TRACK(split_copy);
@@ -4430,7 +4414,6 @@ dict_popitem_impl(PyDictObject *self)
 {
     Py_ssize_t i, j;
     PyObject *res;
-    uint64_t new_version;
     PyInterpreterState *interp = _PyInterpreterState_GET();
 
     ASSERT_DICT_LOCKED(self);
@@ -4473,8 +4456,7 @@ dict_popitem_impl(PyDictObject *self)
         assert(i >= 0);
 
         key = ep0[i].me_key;
-        new_version = _PyDict_NotifyEvent(
-                interp, PyDict_EVENT_DELETED, self, key, NULL);
+        _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, self, key, NULL);
         hash = unicode_get_hash(key);
         value = ep0[i].me_value;
         ep0[i].me_key = NULL;
@@ -4489,8 +4471,7 @@ dict_popitem_impl(PyDictObject *self)
         assert(i >= 0);
 
         key = ep0[i].me_key;
-        new_version = _PyDict_NotifyEvent(
-                interp, PyDict_EVENT_DELETED, self, key, NULL);
+        _PyDict_NotifyEvent(interp, PyDict_EVENT_DELETED, self, key, NULL);
         hash = ep0[i].me_hash;
         value = ep0[i].me_value;
         ep0[i].me_key = NULL;
@@ -4508,7 +4489,6 @@ dict_popitem_impl(PyDictObject *self)
     /* We can't dk_usable++ since there is DKIX_DUMMY in indices */
     STORE_KEYS_NENTRIES(self->ma_keys, i);
     STORE_USED(self, self->ma_used - 1);
-    self->ma_version_tag = new_version;
     ASSERT_CONSISTENT(self);
     return res;
 }
@@ -4759,8 +4739,7 @@ dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     PyDictObject *d = (PyDictObject *)self;
 
     d->ma_used = 0;
-    d->ma_version_tag = DICT_NEXT_VERSION(
-            _PyInterpreterState_GET());
+    d->_ma_watcher_tag = 0;
     dictkeys_incref(Py_EMPTY_KEYS);
     d->ma_keys = Py_EMPTY_KEYS;
     d->ma_values = NULL;
@@ -7384,7 +7363,7 @@ PyDict_Watch(int watcher_id, PyObject* dict)
     if (validate_watcher_id(interp, watcher_id)) {
         return -1;
     }
-    ((PyDictObject*)dict)->ma_version_tag |= (1LL << watcher_id);
+    ((PyDictObject*)dict)->_ma_watcher_tag |= (1LL << watcher_id);
     return 0;
 }
 
@@ -7399,7 +7378,7 @@ PyDict_Unwatch(int watcher_id, PyObject* dict)
     if (validate_watcher_id(interp, watcher_id)) {
         return -1;
     }
-    ((PyDictObject*)dict)->ma_version_tag &= ~(1LL << watcher_id);
+    ((PyDictObject*)dict)->_ma_watcher_tag &= ~(1LL << watcher_id);
     return 0;
 }
 
index 8535306d9c7a037c5e041f011c81b9b2b835ae31..c712c772201e10f5641d9a24e0348a3aa822b730 100644 (file)
@@ -2254,7 +2254,6 @@ dummy_func(
             PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
             DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries);
             PyObject *old_value;
-            uint64_t new_version;
             DEOPT_IF(!DK_IS_UNICODE(dict->ma_keys));
             PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + hint;
             DEOPT_IF(ep->me_key != name);
@@ -2264,9 +2263,8 @@ dummy_func(
             }
             old_value = ep->me_value;
             PyDict_WatchEvent event = old_value == NULL ? PyDict_EVENT_ADDED : PyDict_EVENT_MODIFIED;
-            new_version = _PyDict_NotifyEvent(tstate->interp, event, dict, name, PyStackRef_AsPyObjectBorrow(value));
+            _PyDict_NotifyEvent(tstate->interp, event, dict, name, PyStackRef_AsPyObjectBorrow(value));
             ep->me_value = PyStackRef_AsPyObjectSteal(value);
-            dict->ma_version_tag = new_version; // PEP 509
             // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
             // when dict only holds the strong reference to value in ep->me_value.
             Py_XDECREF(old_value);
index 650bf4533a3a8686888508d20393b482a90bf0ee..fdfec66b73c730c107ad14bfdba54a844f3b5231 100644 (file)
                 JUMP_TO_JUMP_TARGET();
             }
             PyObject *old_value;
-            uint64_t new_version;
             if (!DK_IS_UNICODE(dict->ma_keys)) {
                 UOP_STAT_INC(uopcode, miss);
                 JUMP_TO_JUMP_TARGET();
             }
             old_value = ep->me_value;
             PyDict_WatchEvent event = old_value == NULL ? PyDict_EVENT_ADDED : PyDict_EVENT_MODIFIED;
-            new_version = _PyDict_NotifyEvent(tstate->interp, event, dict, name, PyStackRef_AsPyObjectBorrow(value));
+            _PyDict_NotifyEvent(tstate->interp, event, dict, name, PyStackRef_AsPyObjectBorrow(value));
             ep->me_value = PyStackRef_AsPyObjectSteal(value);
-            dict->ma_version_tag = new_version; // PEP 509
             // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
             // when dict only holds the strong reference to value in ep->me_value.
             Py_XDECREF(old_value);
index 1201fe82efb91985f3c7d60943ea4ad98656bbb8..9de7554d4dfd559f8f541bd8c0c9f28da682dfe6 100644 (file)
                 PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
                 DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries, STORE_ATTR);
                 PyObject *old_value;
-                uint64_t new_version;
                 DEOPT_IF(!DK_IS_UNICODE(dict->ma_keys), STORE_ATTR);
                 PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + hint;
                 DEOPT_IF(ep->me_key != name, STORE_ATTR);
                 }
                 old_value = ep->me_value;
                 PyDict_WatchEvent event = old_value == NULL ? PyDict_EVENT_ADDED : PyDict_EVENT_MODIFIED;
-                new_version = _PyDict_NotifyEvent(tstate->interp, event, dict, name, PyStackRef_AsPyObjectBorrow(value));
+                _PyDict_NotifyEvent(tstate->interp, event, dict, name, PyStackRef_AsPyObjectBorrow(value));
                 ep->me_value = PyStackRef_AsPyObjectSteal(value);
-                dict->ma_version_tag = new_version; // PEP 509
                 // old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
                 // when dict only holds the strong reference to value in ep->me_value.
                 Py_XDECREF(old_value);
index b202b58a8b7214c8c0b2586435d5fe8b37534f69..f30e873605d8588630fab249e1aa5b8c2b3494fe 100644 (file)
@@ -56,14 +56,14 @@ static int
 get_mutations(PyObject* dict) {
     assert(PyDict_CheckExact(dict));
     PyDictObject *d = (PyDictObject *)dict;
-    return (d->ma_version_tag >> DICT_MAX_WATCHERS) & ((1 << DICT_WATCHED_MUTATION_BITS)-1);
+    return (d->_ma_watcher_tag >> DICT_MAX_WATCHERS) & ((1 << DICT_WATCHED_MUTATION_BITS)-1);
 }
 
 static void
 increment_mutations(PyObject* dict) {
     assert(PyDict_CheckExact(dict));
     PyDictObject *d = (PyDictObject *)dict;
-    d->ma_version_tag += (1 << DICT_MAX_WATCHERS);
+    d->_ma_watcher_tag += (1 << DICT_MAX_WATCHERS);
 }
 
 /* The first two dict watcher IDs are reserved for CPython,