diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 900d001d4dd56a..e30d626439e7f8 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -7163,6 +7163,17 @@ PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) return 0; } +static void +clear_inline_values(PyDictValues *values) +{ + if (values->valid) { + FT_ATOMIC_STORE_UINT8(values->valid, 0); + for (Py_ssize_t i = 0; i < values->capacity; i++) { + Py_CLEAR(values->values[i]); + } + } +} + static void set_dict_inline_values(PyObject *obj, PyDictObject *new_dict) { @@ -7173,12 +7184,7 @@ set_dict_inline_values(PyObject *obj, PyDictObject *new_dict) Py_XINCREF(new_dict); FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict, new_dict); - if (values->valid) { - FT_ATOMIC_STORE_UINT8(values->valid, 0); - for (Py_ssize_t i = 0; i < values->capacity; i++) { - Py_CLEAR(values->values[i]); - } - } + clear_inline_values(values); } #ifdef Py_GIL_DISABLED @@ -7256,8 +7262,8 @@ decref_maybe_delay(PyObject *obj, bool delay) } } -static int -set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear) +int +_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict) { assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT); #ifndef NDEBUG @@ -7292,8 +7298,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear) // Decref for the dictionary we incref'd in try_set_dict_inline_only_or_other_dict // while the object was locked - decref_maybe_delay((PyObject *)prev_dict, - !clear && prev_dict != cur_dict); + decref_maybe_delay((PyObject *)prev_dict, prev_dict != cur_dict); if (err != 0) { return err; } @@ -7303,7 +7308,7 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear) if (prev_dict != NULL) { // decref for the dictionary that we replaced - decref_maybe_delay((PyObject *)prev_dict, !clear); + decref_maybe_delay((PyObject *)prev_dict, true); } return 0; @@ -7333,45 +7338,15 @@ set_or_clear_managed_dict(PyObject *obj, PyObject *new_dict, bool clear) (PyDictObject *)Py_XNewRef(new_dict)); Py_END_CRITICAL_SECTION(); - decref_maybe_delay((PyObject *)dict, !clear); + decref_maybe_delay((PyObject *)dict, true); } assert(_PyObject_InlineValuesConsistencyCheck(obj)); return err; } -int -_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict) -{ - return set_or_clear_managed_dict(obj, new_dict, false); -} - -void -PyObject_ClearManagedDict(PyObject *obj) -{ - if (set_or_clear_managed_dict(obj, NULL, true) < 0) { - /* Must be out of memory */ - assert(PyErr_Occurred() == PyExc_MemoryError); - PyErr_FormatUnraisable("Exception ignored while " - "clearing an object managed dict"); - /* Clear the dict */ - PyDictObject *dict = _PyObject_GetManagedDict(obj); - Py_BEGIN_CRITICAL_SECTION2(dict, obj); - dict = _PyObject_ManagedDictPointer(obj)->dict; - PyInterpreterState *interp = _PyInterpreterState_GET(); - PyDictKeysObject *oldkeys = dict->ma_keys; - set_keys(dict, Py_EMPTY_KEYS); - dict->ma_values = NULL; - dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(dict)); - STORE_USED(dict, 0); - set_dict_inline_values(obj, NULL); - Py_END_CRITICAL_SECTION2(); - } -} - -int -_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj) +static int +detach_dict_from_object(PyDictObject *mp, PyObject *obj) { - ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj); assert(_PyObject_ManagedDictPointer(obj)->dict == mp); assert(_PyObject_InlineValuesConsistencyCheck(obj)); @@ -7401,6 +7376,60 @@ _PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj) return 0; } + +void +PyObject_ClearManagedDict(PyObject *obj) +{ + // This is called when the object is being freed or cleared + // by the GC and therefore known to have no references. + if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) { + PyDictObject *dict = _PyObject_GetManagedDict(obj); + if (dict == NULL) { + // We have no materialized dictionary and inline values + // that just need to be cleared. + // No dict to clear, we're done + clear_inline_values(_PyObject_InlineValues(obj)); + return; + } + else if (FT_ATOMIC_LOAD_PTR_RELAXED(dict->ma_values) == + _PyObject_InlineValues(obj)) { + // We have a materialized object which points at the inline + // values. We need to materialize the keys. Nothing can modify + // this object, but we need to lock the dictionary. + int err; + Py_BEGIN_CRITICAL_SECTION(dict); + err = detach_dict_from_object(dict, obj); + Py_END_CRITICAL_SECTION(); + + if (err) { + /* Must be out of memory */ + assert(PyErr_Occurred() == PyExc_MemoryError); + PyErr_FormatUnraisable("Exception ignored while " + "clearing an object managed dict"); + /* Clear the dict */ + Py_BEGIN_CRITICAL_SECTION(dict); + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyDictKeysObject *oldkeys = dict->ma_keys; + set_keys(dict, Py_EMPTY_KEYS); + dict->ma_values = NULL; + dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(dict)); + STORE_USED(dict, 0); + clear_inline_values(_PyObject_InlineValues(obj)); + Py_END_CRITICAL_SECTION(); + } + } + } + Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict); +} + +int +_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj) +{ + ASSERT_WORLD_STOPPED_OR_OBJ_LOCKED(obj); + + return detach_dict_from_object(mp, obj); +} + static inline PyObject * ensure_managed_dict(PyObject *obj) {