From 3c3537e47b8ebaaaee9cf0e4587296922b8be4b0 Mon Sep 17 00:00:00 2001 From: Dino Viehland Date: Fri, 16 Feb 2024 11:00:53 -0800 Subject: [PATCH] Make PyDictValues thread safe --- Include/internal/pycore_object.h | 50 ++++++++++++++++++++++ Include/internal/pycore_opcode_metadata.h | 2 +- Include/internal/pycore_uop_metadata.h | 4 +- Lib/test/test_opcache.py | 7 ++- Objects/dictobject.c | 46 ++++++++++++++------ Objects/object.c | 52 +++++++++++++++++------ Python/bytecodes.c | 13 ++++-- Python/executor_cases.c.h | 13 ++++-- Python/generated_cases.c.h | 13 ++++-- 9 files changed, 159 insertions(+), 41 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 34a83ea228e8b10..8f4773c707e1b7b 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -13,6 +13,7 @@ extern "C" { #include "pycore_emscripten_trampoline.h" // _PyCFunction_TrampolineCall() #include "pycore_interp.h" // PyInterpreterState.gc #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION /* Check if an object is consistent. For example, ensure that the reference counter is greater than or equal to 1, and ensure that ob_type is not NULL. @@ -655,6 +656,8 @@ _PyDictOrValues_IsValues(PyDictOrValues dorv) return ((uintptr_t)dorv.values) & 1; } +// Should only be used when we know the object isn't racing with other +// threads (e.g. finalization or GC). static inline PyDictValues * _PyDictOrValues_GetValues(PyDictOrValues dorv) { @@ -662,17 +665,64 @@ _PyDictOrValues_GetValues(PyDictOrValues dorv) return (PyDictValues *)(dorv.values + 1); } +static inline PyDictValues * +_PyDictOrValues_TryGetValues(PyObject *obj) +{ +#ifdef Py_GIL_DISABLED + if (_Py_IsOwnedByCurrentThread((PyObject *)obj) || _PyObject_GC_IS_SHARED(obj)) { + PyDictOrValues *dorv = _PyObject_DictOrValuesPointer(obj); + char *values = _Py_atomic_load_ptr_relaxed(&dorv->values); + if (((uintptr_t)values) & 1) { + return (PyDictValues *)(values + 1); + } + return NULL; + } + + // Make sure we don't race with materialization of the dict which will + // propagate the shared bit down to the dict + Py_BEGIN_CRITICAL_SECTION(obj); + _PyObject_GC_SET_SHARED(obj); + Py_END_CRITICAL_SECTION(); + + return _PyDictOrValues_TryGetValues(obj); +#else + char *values = _PyObject_DictOrValuesPointer(obj)->values; + if (((uintptr_t)values) & 1) { + return (PyDictValues *)(values + 1); + } + return NULL; +#endif +} + static inline PyObject * _PyDictOrValues_GetDict(PyDictOrValues dorv) { assert(!_PyDictOrValues_IsValues(dorv)); +#ifdef Py_GIL_DISABLED + return _Py_atomic_load_ptr_relaxed(&dorv.dict); +#else return dorv.dict; +#endif } static inline void _PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values) { +#ifdef Py_GIL_DISABLED + _Py_atomic_store_ptr_relaxed(&ptr->values, (char *)values - 1); +#else ptr->values = ((char *)values) - 1; +#endif +} + +static inline void +_PyDictOrValues_SetDict(PyDictOrValues *ptr, PyObject *dict) +{ +#ifdef Py_GIL_DISABLED + _Py_atomic_store_ptr_relaxed(&ptr->dict, dict); +#else + ptr->dict = dict; +#endif } extern PyObject ** _PyObject_ComputedDictPointer(PyObject *); diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 6b60a6fbffdc5e4..aa2890290128ccb 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1060,7 +1060,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[268] = { [LOAD_ATTR] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_CLASS] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, [LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG }, + [LOAD_ATTR_INSTANCE_VALUE] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_METHOD_LAZY_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_METHOD_NO_DICT] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [LOAD_ATTR_METHOD_WITH_VALUES] = { true, INSTR_FMT_IBC00000000, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 163a0320aa22985..e1fc35bfd1515d4 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -114,7 +114,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_TYPE_VERSION] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_CHECK_MANAGED_OBJECT_HAS_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, + [_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, [_CHECK_ATTR_MODULE] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_LOAD_ATTR_MODULE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, [_CHECK_ATTR_WITH_HINT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG | HAS_PASSTHROUGH_FLAG, @@ -123,7 +123,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_ATTR_CLASS] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, [_LOAD_ATTR_CLASS] = HAS_ARG_FLAG, [_GUARD_DORV_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG, - [_STORE_ATTR_INSTANCE_VALUE] = HAS_ESCAPES_FLAG, + [_STORE_ATTR_INSTANCE_VALUE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, [_STORE_ATTR_SLOT] = HAS_ESCAPES_FLAG, [_COMPARE_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_COMPARE_OP_FLOAT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 2b2783d57be8f41..48f0583c7888dd8 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -4,7 +4,7 @@ import threading import types import unittest -from test.support import threading_helper, check_impl_detail +from test.support import threading_helper, check_impl_detail, Py_GIL_DISABLED # Skip this module on other interpreters, it is cpython specific: if check_impl_detail(cpython=False): @@ -1047,6 +1047,7 @@ def test_dict_materialization(self): None ) + @unittest.skipIf(Py_GIL_DISABLED, "dematerialization disabled without the GIL") def test_dict_dematerialization(self): c = C() c.a = 1 @@ -1063,6 +1064,7 @@ def test_dict_dematerialization(self): (1, 2, '') ) + @unittest.skipIf(Py_GIL_DISABLED, "dematerialization disabled without the GIL") def test_dict_dematerialization_multiple_refs(self): c = C() c.a = 1 @@ -1076,6 +1078,7 @@ def test_dict_dematerialization_multiple_refs(self): ) self.assertIs(c.__dict__, d) + @unittest.skipIf(Py_GIL_DISABLED, "dematerialization disabled without the GIL") def test_dict_dematerialization_copy(self): c = C() c.a = 1 @@ -1102,6 +1105,7 @@ def test_dict_dematerialization_copy(self): ) #NOTE -- c3.__dict__ does not de-materialize + @unittest.skipIf(Py_GIL_DISABLED, "dematerialization disabled without the GIL") def test_dict_dematerialization_pickle(self): c = C() c.a = 1 @@ -1119,6 +1123,7 @@ def test_dict_dematerialization_pickle(self): (1, 2, '') ) + @unittest.skipIf(Py_GIL_DISABLED, "dematerialization disabled without the GIL") def test_dict_dematerialization_subclass(self): class D(dict): pass c = C() diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 25ab21881f8f746..8d0616c91e230f4 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -6005,6 +6005,10 @@ has_unique_reference(PyObject *op) bool _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) { +#ifdef Py_GIL_DISABLED + // We don't yet support dematerialization in no-GIL builds + return false; +#else assert(_PyObject_DictOrValuesPointer(obj) == dorv); assert(!_PyDictOrValues_IsValues(*dorv)); PyDictObject *dict = (PyDictObject *)_PyDictOrValues_GetDict(*dorv); @@ -6032,6 +6036,7 @@ _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv) dict->ma_values = NULL; Py_DECREF(dict); return true; +#endif } int @@ -6066,7 +6071,7 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values, if (dict == NULL) { return -1; } - _PyObject_DictOrValuesPointer(obj)->dict = dict; + _PyDictOrValues_SetDict(_PyObject_DictOrValuesPointer(obj), dict); if (value == NULL) { return PyDict_DelItem(dict, name); } @@ -6150,10 +6155,11 @@ _PyObject_IsInstanceDictEmpty(PyObject *obj) PyObject *dict; if (tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) { PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(obj); - if (_PyDictOrValues_IsValues(dorv)) { + PyDictValues *values; + if ((values = _PyDictOrValues_TryGetValues(obj)) != NULL) { PyDictKeysObject *keys = CACHED_KEYS(tp); for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) { - if (_PyDictOrValues_GetValues(dorv)->values[i] != NULL) { + if (values->values[i] != NULL) { return 0; } } @@ -6176,15 +6182,15 @@ _PyObject_FreeInstanceAttributes(PyObject *self) { PyTypeObject *tp = Py_TYPE(self); assert(Py_TYPE(self)->tp_flags & Py_TPFLAGS_MANAGED_DICT); - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(self); - if (!_PyDictOrValues_IsValues(dorv)) { + PyDictValues *values = _PyDictOrValues_TryGetValues(self); + if (values == NULL) { return; } - PyDictValues *values = _PyDictOrValues_GetValues(dorv); PyDictKeysObject *keys = CACHED_KEYS(tp); for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) { Py_XDECREF(values->values[i]); } + // TODO: Free with qsbr if we're shared free_values(values); } @@ -6195,6 +6201,9 @@ PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) { return 0; } +#ifdef Py_GIL_DISABLED + assert(_PyInterpreterState_GET()->stoptheworld.world_stopped); +#endif assert(tp->tp_dictoffset); PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(obj); if (_PyDictOrValues_IsValues(dorv)) { @@ -6225,13 +6234,14 @@ PyObject_ClearManagedDict(PyObject *obj) for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) { Py_CLEAR(values->values[i]); } - dorv_ptr->dict = NULL; + _PyDictOrValues_SetDict(dorv_ptr, NULL); + // TODO: Free with qsbr free_values(values); } else { - PyObject *dict = dorv_ptr->dict; + PyObject *dict = _PyDictOrValues_GetDict(*dorv_ptr); if (dict) { - dorv_ptr->dict = NULL; + _PyDictOrValues_SetDict(dorv_ptr, NULL); Py_DECREF(dict); } } @@ -6245,14 +6255,24 @@ PyObject_GenericGetDict(PyObject *obj, void *context) PyTypeObject *tp = Py_TYPE(obj); if (_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT)) { PyDictOrValues *dorv_ptr = _PyObject_DictOrValuesPointer(obj); - if (_PyDictOrValues_IsValues(*dorv_ptr)) { - PyDictValues *values = _PyDictOrValues_GetValues(*dorv_ptr); + PyDictValues *values; + if ((values = _PyDictOrValues_TryGetValues(obj)) != NULL) { OBJECT_STAT_INC(dict_materialized_on_request); + Py_BEGIN_CRITICAL_SECTION(obj); dict = make_dict_from_instance_attributes( interp, CACHED_KEYS(tp), values); if (dict != NULL) { - dorv_ptr->dict = dict; +#ifdef Py_GIL_DISABLED + if (_PyObject_GC_IS_SHARED(obj)) { + // The values were accessed from multiple threads and need to be + // freed via QSBR, now the dict needs to be shared as it owns the + // values. + _PyObject_GC_SET_SHARED(dict); + } +#endif + _PyDictOrValues_SetDict(dorv_ptr, dict); } + Py_END_CRITICAL_SECTION(); } else { dict = _PyDictOrValues_GetDict(*dorv_ptr); @@ -6260,7 +6280,7 @@ PyObject_GenericGetDict(PyObject *obj, void *context) dictkeys_incref(CACHED_KEYS(tp)); OBJECT_STAT_INC(dict_materialized_on_request); dict = new_dict_with_shared_keys(interp, CACHED_KEYS(tp)); - dorv_ptr->dict = dict; + _PyDictOrValues_SetDict(dorv_ptr, dict); } } } diff --git a/Objects/object.c b/Objects/object.c index 23eab8288a41e8b..6fecf481a6ccb11 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1398,13 +1398,38 @@ _PyObject_GetDictPtr(PyObject *obj) return _PyObject_ComputedDictPointer(obj); } PyDictOrValues *dorv_ptr = _PyObject_DictOrValuesPointer(obj); - if (_PyDictOrValues_IsValues(*dorv_ptr)) { - PyObject *dict = _PyObject_MakeDictFromInstanceAttributes(obj, _PyDictOrValues_GetValues(*dorv_ptr)); + PyDictValues *values; + if ((values = _PyDictOrValues_TryGetValues(obj)) != NULL) { + PyObject *dict; + Py_BEGIN_CRITICAL_SECTION(obj); +#ifdef Py_GIL_DISABLED + if ((values = _PyDictOrValues_TryGetValues(obj)) == NULL) { + dict = _PyDictOrValues_GetDict(*dorv_ptr); + goto done; + } +#endif + + dict = _PyObject_MakeDictFromInstanceAttributes(obj, values); + if (dict == NULL) { + goto done; + } + +#ifdef Py_GIL_DISABLED + if (_PyObject_GC_IS_SHARED(obj)) { + // The values were accessed from multiple threads and need to be + // freed via QSBR, now the dict needs to be shared as it owns the + // values. + _PyObject_GC_SET_SHARED(dict); + } +#endif + dorv_ptr->dict = dict; + +done: + Py_END_CRITICAL_SECTION(); if (dict == NULL) { PyErr_Clear(); return NULL; } - dorv_ptr->dict = dict; } return &dorv_ptr->dict; } @@ -1477,8 +1502,8 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) PyObject *dict; if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) { PyDictOrValues* dorv_ptr = _PyObject_DictOrValuesPointer(obj); - if (_PyDictOrValues_IsValues(*dorv_ptr)) { - PyDictValues *values = _PyDictOrValues_GetValues(*dorv_ptr); + PyDictValues *values; + if ((values = _PyDictOrValues_TryGetValues(obj)) != NULL) { PyObject *attr = _PyObject_GetInstanceAttribute(obj, values, name); if (attr != NULL) { *method = attr; @@ -1584,8 +1609,8 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, if (dict == NULL) { if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) { PyDictOrValues* dorv_ptr = _PyObject_DictOrValuesPointer(obj); - if (_PyDictOrValues_IsValues(*dorv_ptr)) { - PyDictValues *values = _PyDictOrValues_GetValues(*dorv_ptr); + PyDictValues *values; + if ((values = _PyDictOrValues_TryGetValues(obj)) != NULL) { if (PyUnicode_CheckExact(name)) { res = _PyObject_GetInstanceAttribute(obj, values, name); if (res != NULL) { @@ -1700,9 +1725,10 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, PyObject **dictptr; if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) { PyDictOrValues *dorv_ptr = _PyObject_DictOrValuesPointer(obj); - if (_PyDictOrValues_IsValues(*dorv_ptr)) { + PyDictValues *values; + if ((values = _PyDictOrValues_TryGetValues(obj)) != NULL) { res = _PyObject_StoreInstanceAttribute( - obj, _PyDictOrValues_GetValues(*dorv_ptr), name, value); + obj, values, name, value); goto error_check; } dictptr = &dorv_ptr->dict; @@ -1710,9 +1736,11 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, if (_PyObject_InitInlineValues(obj, tp) < 0) { goto done; } - res = _PyObject_StoreInstanceAttribute( - obj, _PyDictOrValues_GetValues(*dorv_ptr), name, value); - goto error_check; + if ((values = _PyDictOrValues_TryGetValues(obj)) != NULL) { + res = _PyObject_StoreInstanceAttribute( + obj, values, name, value); + goto error_check; + } } } else { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 6822e772e913e8d..8e3d246bb5c7204 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1915,8 +1915,11 @@ dummy_func( } op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) { - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); - attr = _PyDictOrValues_GetValues(dorv)->values[index]; + PyDictValues *values = _PyDictOrValues_TryGetValues(owner); +#ifdef Py_GIL_DISABLED + DEOPT_IF(values == NULL); +#endif + attr = values->values[index]; DEOPT_IF(attr == NULL); STAT_INC(LOAD_ATTR, hit); Py_INCREF(attr); @@ -2089,9 +2092,11 @@ dummy_func( } op(_STORE_ATTR_INSTANCE_VALUE, (index/1, value, owner --)) { - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); STAT_INC(STORE_ATTR, hit); - PyDictValues *values = _PyDictOrValues_GetValues(dorv); + PyDictValues *values = _PyDictOrValues_TryGetValues(owner); +#ifdef Py_GIL_DISABLED + DEOPT_IF(values == NULL); +#endif PyObject *old_value = values->values[index]; values->values[index] = value; if (old_value == NULL) { diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 11e2a1fe85d51dd..47ba67225e2e140 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1644,8 +1644,11 @@ oparg = CURRENT_OPARG(); owner = stack_pointer[-1]; uint16_t index = (uint16_t)CURRENT_OPERAND(); - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); - attr = _PyDictOrValues_GetValues(dorv)->values[index]; + PyDictValues *values = _PyDictOrValues_TryGetValues(owner); + #ifdef Py_GIL_DISABLED + if (values == NULL) goto deoptimize; + #endif + attr = values->values[index]; if (attr == NULL) goto deoptimize; STAT_INC(LOAD_ATTR, hit); Py_INCREF(attr); @@ -1802,9 +1805,11 @@ owner = stack_pointer[-1]; value = stack_pointer[-2]; uint16_t index = (uint16_t)CURRENT_OPERAND(); - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); STAT_INC(STORE_ATTR, hit); - PyDictValues *values = _PyDictOrValues_GetValues(dorv); + PyDictValues *values = _PyDictOrValues_TryGetValues(owner); + #ifdef Py_GIL_DISABLED + if (values == NULL) goto deoptimize; + #endif PyObject *old_value = values->values[index]; values->values[index] = value; if (old_value == NULL) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 6c19adc60c690f3..add9bab0117f83b 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3541,8 +3541,11 @@ // _LOAD_ATTR_INSTANCE_VALUE { uint16_t index = read_u16(&this_instr[4].cache); - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); - attr = _PyDictOrValues_GetValues(dorv)->values[index]; + PyDictValues *values = _PyDictOrValues_TryGetValues(owner); + #ifdef Py_GIL_DISABLED + DEOPT_IF(values == NULL, LOAD_ATTR); + #endif + attr = values->values[index]; DEOPT_IF(attr == NULL, LOAD_ATTR); STAT_INC(LOAD_ATTR, hit); Py_INCREF(attr); @@ -5171,9 +5174,11 @@ value = stack_pointer[-2]; { uint16_t index = read_u16(&this_instr[4].cache); - PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(owner); STAT_INC(STORE_ATTR, hit); - PyDictValues *values = _PyDictOrValues_GetValues(dorv); + PyDictValues *values = _PyDictOrValues_TryGetValues(owner); + #ifdef Py_GIL_DISABLED + DEOPT_IF(values == NULL, STORE_ATTR); + #endif PyObject *old_value = values->values[index]; values->values[index] = value; if (old_value == NULL) {