summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDino Viehland <dinoviehland@meta.com>2024-04-22 05:57:05 (GMT)
committerGitHub <noreply@github.com>2024-04-22 05:57:05 (GMT)
commit8b541c017ea92040add608b3e0ef8dc85e9e6060 (patch)
tree880ff7c97041f374d21197b1b486686f2d64c998
parent1446024124fb98c3051199760380685f8a2fd127 (diff)
downloadcpython-8b541c017ea92040add608b3e0ef8dc85e9e6060.zip
cpython-8b541c017ea92040add608b3e0ef8dc85e9e6060.tar.gz
cpython-8b541c017ea92040add608b3e0ef8dc85e9e6060.tar.bz2
gh-112075: Make instance attributes stored in inline "dict" thread safe (#114742)
Make instance attributes stored in inline "dict" thread safe on free-threaded builds
-rw-r--r--Include/cpython/object.h1
-rw-r--r--Include/internal/pycore_dict.h19
-rw-r--r--Include/internal/pycore_object.h16
-rw-r--r--Include/internal/pycore_pyatomic_ft_wrappers.h14
-rw-r--r--Lib/test/test_class.py9
-rw-r--r--Objects/dictobject.c385
-rw-r--r--Objects/object.c45
-rw-r--r--Objects/typeobject.c30
-rw-r--r--Python/bytecodes.c15
-rw-r--r--Python/executor_cases.c.h10
-rw-r--r--Python/generated_cases.c.h13
-rw-r--r--Python/specialize.c3
-rw-r--r--Tools/cases_generator/analyzer.py1
13 files changed, 419 insertions, 142 deletions
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index 2797051..a8f5782 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -493,6 +493,7 @@ do { \
PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);
PyAPI_FUNC(int) PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg);
+PyAPI_FUNC(void) _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict);
PyAPI_FUNC(void) PyObject_ClearManagedDict(PyObject *obj);
#define TYPE_MAX_WATCHERS 8
diff --git a/Include/internal/pycore_dict.h b/Include/internal/pycore_dict.h
index fba0dfc..f33026d 100644
--- a/Include/internal/pycore_dict.h
+++ b/Include/internal/pycore_dict.h
@@ -1,4 +1,3 @@
-
#ifndef Py_INTERNAL_DICT_H
#define Py_INTERNAL_DICT_H
#ifdef __cplusplus
@@ -9,9 +8,10 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif
-#include "pycore_freelist.h" // _PyFreeListState
-#include "pycore_identifier.h" // _Py_Identifier
-#include "pycore_object.h" // PyManagedDictPointer
+#include "pycore_freelist.h" // _PyFreeListState
+#include "pycore_identifier.h" // _Py_Identifier
+#include "pycore_object.h" // PyManagedDictPointer
+#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_ACQUIRE
// Unsafe flavor of PyDict_GetItemWithError(): no error checking
extern PyObject* _PyDict_GetItemWithError(PyObject *dp, PyObject *key);
@@ -249,7 +249,7 @@ _PyDict_NotifyEvent(PyInterpreterState *interp,
return DICT_NEXT_VERSION(interp) | (mp->ma_version_tag & DICT_WATCHER_AND_MODIFICATION_MASK);
}
-extern PyDictObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj);
+extern PyDictObject *_PyObject_MaterializeManagedDict(PyObject *obj);
PyAPI_FUNC(PyObject *)_PyDict_FromItems(
PyObject *const *keys, Py_ssize_t keys_offset,
@@ -277,7 +277,6 @@ _PyDictValues_AddToInsertionOrder(PyDictValues *values, Py_ssize_t ix)
static inline size_t
shared_keys_usable_size(PyDictKeysObject *keys)
{
-#ifdef Py_GIL_DISABLED
// dk_usable will decrease for each instance that is created and each
// value that is added. dk_nentries will increase for each value that
// is added. We want to always return the right value or larger.
@@ -285,11 +284,9 @@ shared_keys_usable_size(PyDictKeysObject *keys)
// second, and conversely here we read dk_usable first and dk_entries
// second (to avoid the case where we read entries before the increment
// and read usable after the decrement)
- return (size_t)(_Py_atomic_load_ssize_acquire(&keys->dk_usable) +
- _Py_atomic_load_ssize_acquire(&keys->dk_nentries));
-#else
- return (size_t)keys->dk_nentries + (size_t)keys->dk_usable;
-#endif
+ Py_ssize_t dk_usable = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_usable);
+ Py_ssize_t dk_nentries = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_nentries);
+ return dk_nentries + dk_usable;
}
static inline size_t
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index 512f7a3..88b052f 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -12,6 +12,7 @@ extern "C" {
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
#include "pycore_emscripten_trampoline.h" // _PyCFunction_TrampolineCall()
#include "pycore_interp.h" // PyInterpreterState.gc
+#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_STORE_PTR_RELAXED
#include "pycore_pystate.h" // _PyInterpreterState_GET()
/* Check if an object is consistent. For example, ensure that the reference
@@ -659,10 +660,10 @@ extern PyObject* _PyType_GetDocFromInternalDoc(const char *, const char *);
extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *, int);
void _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp);
-extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
- PyObject *name, PyObject *value);
-PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
- PyObject *name);
+extern int _PyObject_StoreInstanceAttribute(PyObject *obj,
+ PyObject *name, PyObject *value);
+extern bool _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name,
+ PyObject **attr);
#ifdef Py_GIL_DISABLED
# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-1)
@@ -683,6 +684,13 @@ _PyObject_ManagedDictPointer(PyObject *obj)
return (PyManagedDictPointer *)((char *)obj + MANAGED_DICT_OFFSET);
}
+static inline PyDictObject *
+_PyObject_GetManagedDict(PyObject *obj)
+{
+ PyManagedDictPointer *dorv = _PyObject_ManagedDictPointer(obj);
+ return (PyDictObject *)FT_ATOMIC_LOAD_PTR_RELAXED(dorv->dict);
+}
+
static inline PyDictValues *
_PyObject_InlineValues(PyObject *obj)
{
diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h
index fed5d6e..bbfc462 100644
--- a/Include/internal/pycore_pyatomic_ft_wrappers.h
+++ b/Include/internal/pycore_pyatomic_ft_wrappers.h
@@ -21,7 +21,10 @@ extern "C" {
#ifdef Py_GIL_DISABLED
#define FT_ATOMIC_LOAD_PTR(value) _Py_atomic_load_ptr(&value)
+#define FT_ATOMIC_STORE_PTR(value, new_value) _Py_atomic_store_ptr(&value, new_value)
#define FT_ATOMIC_LOAD_SSIZE(value) _Py_atomic_load_ssize(&value)
+#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) \
+ _Py_atomic_load_ssize_acquire(&value)
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) \
_Py_atomic_load_ssize_relaxed(&value)
#define FT_ATOMIC_STORE_PTR(value, new_value) \
@@ -30,6 +33,12 @@ extern "C" {
_Py_atomic_load_ptr_acquire(&value)
#define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) \
_Py_atomic_load_uintptr_acquire(&value)
+#define FT_ATOMIC_LOAD_PTR_RELAXED(value) \
+ _Py_atomic_load_ptr_relaxed(&value)
+#define FT_ATOMIC_LOAD_UINT8(value) \
+ _Py_atomic_load_uint8(&value)
+#define FT_ATOMIC_STORE_UINT8(value, new_value) \
+ _Py_atomic_store_uint8(&value, new_value)
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \
_Py_atomic_store_ptr_relaxed(&value, new_value)
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) \
@@ -43,11 +52,16 @@ extern "C" {
#else
#define FT_ATOMIC_LOAD_PTR(value) value
+#define FT_ATOMIC_STORE_PTR(value, new_value) value = new_value
#define FT_ATOMIC_LOAD_SSIZE(value) value
+#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) value
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value
#define FT_ATOMIC_STORE_PTR(value, new_value) value = new_value
#define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) value
#define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) value
+#define FT_ATOMIC_LOAD_PTR_RELAXED(value) value
+#define FT_ATOMIC_LOAD_UINT8(value) value
+#define FT_ATOMIC_STORE_UINT8(value, new_value) value = new_value
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
#define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) value = new_value
diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py
index a9cfd8d..5885db8 100644
--- a/Lib/test/test_class.py
+++ b/Lib/test/test_class.py
@@ -873,6 +873,15 @@ class TestInlineValues(unittest.TestCase):
obj.foo = None # Aborted here
self.assertEqual(obj.__dict__, {"foo":None})
+ def test_store_attr_deleted_dict(self):
+ class Foo:
+ pass
+
+ f = Foo()
+ del f.__dict__
+ f.a = 3
+ self.assertEqual(f.a, 3)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 4e69641..2644516 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -1752,7 +1752,7 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
uint64_t new_version = _PyDict_NotifyEvent(
interp, PyDict_EVENT_MODIFIED, mp, key, value);
if (_PyDict_HasSplitTable(mp)) {
- mp->ma_values->values[ix] = value;
+ STORE_SPLIT_VALUE(mp, ix, value);
if (old_value == NULL) {
_PyDictValues_AddToInsertionOrder(mp->ma_values, ix);
mp->ma_used++;
@@ -2514,7 +2514,7 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
mp->ma_version_tag = new_version;
if (_PyDict_HasSplitTable(mp)) {
assert(old_value == mp->ma_values->values[ix]);
- mp->ma_values->values[ix] = NULL;
+ STORE_SPLIT_VALUE(mp, ix, NULL);
assert(ix < SHARED_KEYS_MAX_SIZE);
/* Update order */
delete_index_from_values(mp->ma_values, ix);
@@ -4226,7 +4226,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
assert(_PyDict_HasSplitTable(mp));
assert(mp->ma_values->values[ix] == NULL);
MAINTAIN_TRACKING(mp, key, value);
- mp->ma_values->values[ix] = Py_NewRef(value);
+ STORE_SPLIT_VALUE(mp, ix, Py_NewRef(value));
_PyDictValues_AddToInsertionOrder(mp->ma_values, ix);
mp->ma_used++;
mp->ma_version_tag = new_version;
@@ -6616,28 +6616,79 @@ make_dict_from_instance_attributes(PyInterpreterState *interp,
return res;
}
-PyDictObject *
-_PyObject_MakeDictFromInstanceAttributes(PyObject *obj)
+static PyDictObject *
+materialize_managed_dict_lock_held(PyObject *obj)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
+
+ OBJECT_STAT_INC(dict_materialized_on_request);
+
PyDictValues *values = _PyObject_InlineValues(obj);
PyInterpreterState *interp = _PyInterpreterState_GET();
PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
OBJECT_STAT_INC(dict_materialized_on_request);
- return make_dict_from_instance_attributes(interp, keys, values);
+ PyDictObject *dict = make_dict_from_instance_attributes(interp, keys, values);
+ FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict,
+ (PyDictObject *)dict);
+ return dict;
}
+PyDictObject *
+_PyObject_MaterializeManagedDict(PyObject *obj)
+{
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ if (dict != NULL) {
+ return dict;
+ }
+ Py_BEGIN_CRITICAL_SECTION(obj);
-int
-_PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
+#ifdef Py_GIL_DISABLED
+ dict = _PyObject_GetManagedDict(obj);
+ if (dict != NULL) {
+ // We raced with another thread creating the dict
+ goto exit;
+ }
+#endif
+ dict = materialize_managed_dict_lock_held(obj);
+
+#ifdef Py_GIL_DISABLED
+exit:
+#endif
+ Py_END_CRITICAL_SECTION();
+ return dict;
+}
+
+static int
+set_or_del_lock_held(PyDictObject *dict, PyObject *name, PyObject *value)
+{
+ if (value == NULL) {
+ Py_hash_t hash;
+ if (!PyUnicode_CheckExact(name) || (hash = unicode_get_hash(name)) == -1) {
+ hash = PyObject_Hash(name);
+ if (hash == -1)
+ return -1;
+ }
+ return delitem_knownhash_lock_held((PyObject *)dict, name, hash);
+ } else {
+ return setitem_lock_held(dict, name, value);
+ }
+}
+
+// Called with either the object's lock or the dict's lock held
+// depending on whether or not a dict has been materialized for
+// the object.
+static int
+store_instance_attr_lock_held(PyObject *obj, PyDictValues *values,
PyObject *name, PyObject *value)
{
- PyInterpreterState *interp = _PyInterpreterState_GET();
PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
assert(keys != NULL);
assert(values != NULL);
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
Py_ssize_t ix = DKIX_EMPTY;
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ assert(dict == NULL || ((PyDictObject *)dict)->ma_values == values);
if (PyUnicode_CheckExact(name)) {
Py_hash_t hash = unicode_get_hash(name);
if (hash == -1) {
@@ -6674,25 +6725,33 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
}
#endif
}
- PyDictObject *dict = _PyObject_ManagedDictPointer(obj)->dict;
+
if (ix == DKIX_EMPTY) {
+ int res;
if (dict == NULL) {
- dict = make_dict_from_instance_attributes(
- interp, keys, values);
- if (dict == NULL) {
+ // Make the dict but don't publish it in the object
+ // so that no one else will see it.
+ dict = make_dict_from_instance_attributes(PyInterpreterState_Get(), keys, values);
+ if (dict == NULL ||
+ set_or_del_lock_held(dict, name, value) < 0) {
+ Py_XDECREF(dict);
return -1;
}
- _PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)dict;
- }
- if (value == NULL) {
- return PyDict_DelItem((PyObject *)dict, name);
- }
- else {
- return PyDict_SetItem((PyObject *)dict, name, value);
+
+ FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict,
+ (PyDictObject *)dict);
+ return 0;
}
+
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(dict);
+
+ res = set_or_del_lock_held (dict, name, value);
+ return res;
}
+
PyObject *old_value = values->values[ix];
- values->values[ix] = Py_XNewRef(value);
+ FT_ATOMIC_STORE_PTR_RELEASE(values->values[ix], Py_XNewRef(value));
+
if (old_value == NULL) {
if (value == NULL) {
PyErr_Format(PyExc_AttributeError,
@@ -6719,6 +6778,72 @@ _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
return 0;
}
+static inline int
+store_instance_attr_dict(PyObject *obj, PyDictObject *dict, PyObject *name, PyObject *value)
+{
+ PyDictValues *values = _PyObject_InlineValues(obj);
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(dict);
+ if (dict->ma_values == values) {
+ res = store_instance_attr_lock_held(obj, values, name, value);
+ }
+ else {
+ res = set_or_del_lock_held(dict, name, value);
+ }
+ Py_END_CRITICAL_SECTION();
+ return res;
+}
+
+int
+_PyObject_StoreInstanceAttribute(PyObject *obj, PyObject *name, PyObject *value)
+{
+ PyDictValues *values = _PyObject_InlineValues(obj);
+ if (!FT_ATOMIC_LOAD_UINT8(values->valid)) {
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL) {
+ dict = (PyDictObject *)PyObject_GenericGetDict(obj, NULL);
+ if (dict == NULL) {
+ return -1;
+ }
+ int res = store_instance_attr_dict(obj, dict, name, value);
+ Py_DECREF(dict);
+ return res;
+ }
+ return store_instance_attr_dict(obj, dict, name, value);
+ }
+
+#ifdef Py_GIL_DISABLED
+ // We have a valid inline values, at least for now... There are two potential
+ // races with having the values become invalid. One is the dictionary
+ // being detached from the object. The other is if someone is inserting
+ // into the dictionary directly and therefore causing it to resize.
+ //
+ // If we haven't materialized the dictionary yet we lock on the object, which
+ // will also be used to prevent the dictionary from being materialized while
+ // we're doing the insertion. If we race and the dictionary gets created
+ // then we'll need to release the object lock and lock the dictionary to
+ // prevent resizing.
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL) {
+ int res;
+ Py_BEGIN_CRITICAL_SECTION(obj);
+ dict = _PyObject_GetManagedDict(obj);
+
+ if (dict == NULL) {
+ res = store_instance_attr_lock_held(obj, values, name, value);
+ }
+ Py_END_CRITICAL_SECTION();
+
+ if (dict == NULL) {
+ return res;
+ }
+ }
+ return store_instance_attr_dict(obj, dict, name, value);
+#else
+ return store_instance_attr_lock_held(obj, values, name, value);
+#endif
+}
+
/* Sanity check for managed dicts */
#if 0
#define CHECK(val) assert(val); if (!(val)) { return 0; }
@@ -6750,19 +6875,79 @@ _PyObject_ManagedDictValidityCheck(PyObject *obj)
}
#endif
-PyObject *
-_PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
- PyObject *name)
+// Attempts to get an instance attribute from the inline values. Returns true
+// if successful, or false if the caller needs to lookup in the dictionary.
+bool
+_PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr)
{
assert(PyUnicode_CheckExact(name));
+ PyDictValues *values = _PyObject_InlineValues(obj);
+ if (!FT_ATOMIC_LOAD_UINT8(values->valid)) {
+ return false;
+ }
+
PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
assert(keys != NULL);
Py_ssize_t ix = _PyDictKeys_StringLookup(keys, name);
if (ix == DKIX_EMPTY) {
- return NULL;
+ *attr = NULL;
+ return true;
+ }
+
+#ifdef Py_GIL_DISABLED
+ PyObject *value = _Py_atomic_load_ptr_relaxed(&values->values[ix]);
+ if (value == NULL || _Py_TryIncrefCompare(&values->values[ix], value)) {
+ *attr = value;
+ return true;
+ }
+
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL) {
+ // No dict, lock the object to prevent one from being
+ // materialized...
+ bool success = false;
+ Py_BEGIN_CRITICAL_SECTION(obj);
+
+ dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL) {
+ // Still no dict, we can read from the values
+ assert(values->valid);
+ value = values->values[ix];
+ *attr = Py_XNewRef(value);
+ success = true;
+ }
+
+ Py_END_CRITICAL_SECTION();
+
+ if (success) {
+ return true;
+ }
}
+
+ // We have a dictionary, we'll need to lock it to prevent
+ // the values from being resized.
+ assert(dict != NULL);
+
+ bool success;
+ Py_BEGIN_CRITICAL_SECTION(dict);
+
+ if (dict->ma_values == values && FT_ATOMIC_LOAD_UINT8(values->valid)) {
+ value = _Py_atomic_load_ptr_relaxed(&values->values[ix]);
+ *attr = Py_XNewRef(value);
+ success = true;
+ } else {
+ // Caller needs to lookup from the dictionary
+ success = false;
+ }
+
+ Py_END_CRITICAL_SECTION();
+
+ return success;
+#else
PyObject *value = values->values[ix];
- return Py_XNewRef(value);
+ *attr = Py_XNewRef(value);
+ return true;
+#endif
}
int
@@ -6775,20 +6960,19 @@ _PyObject_IsInstanceDictEmpty(PyObject *obj)
PyDictObject *dict;
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
PyDictValues *values = _PyObject_InlineValues(obj);
- if (values->valid) {
+ if (FT_ATOMIC_LOAD_UINT8(values->valid)) {
PyDictKeysObject *keys = CACHED_KEYS(tp);
for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
- if (values->values[i] != NULL) {
+ if (FT_ATOMIC_LOAD_PTR_RELAXED(values->values[i]) != NULL) {
return 0;
}
}
return 1;
}
- dict = _PyObject_ManagedDictPointer(obj)->dict;
+ dict = _PyObject_GetManagedDict(obj);
}
else if (tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
- PyManagedDictPointer* managed_dict = _PyObject_ManagedDictPointer(obj);
- dict = managed_dict->dict;
+ dict = _PyObject_GetManagedDict(obj);
}
else {
PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
@@ -6820,53 +7004,115 @@ PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
return 0;
}
+static void
+set_dict_inline_values(PyObject *obj, PyDictObject *new_dict)
+{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
+
+ PyDictValues *values = _PyObject_InlineValues(obj);
+
+ 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]);
+ }
+ }
+}
+
void
-PyObject_ClearManagedDict(PyObject *obj)
+_PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict)
{
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
assert(_PyObject_InlineValuesConsistencyCheck(obj));
PyTypeObject *tp = Py_TYPE(obj);
if (tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
- PyDictObject *dict = _PyObject_ManagedDictPointer(obj)->dict;
- if (dict) {
- _PyDict_DetachFromObject(dict, obj);
- _PyObject_ManagedDictPointer(obj)->dict = NULL;
- Py_DECREF(dict);
- }
- else {
- PyDictValues *values = _PyObject_InlineValues(obj);
- if (values->valid) {
- for (Py_ssize_t i = 0; i < values->capacity; i++) {
- Py_CLEAR(values->values[i]);
- }
- values->valid = 0;
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL) {
+#ifdef Py_GIL_DISABLED
+ Py_BEGIN_CRITICAL_SECTION(obj);
+
+ dict = _PyObject_ManagedDictPointer(obj)->dict;
+ if (dict == NULL) {
+ set_dict_inline_values(obj, (PyDictObject *)new_dict);
+ }
+
+ Py_END_CRITICAL_SECTION();
+
+ if (dict == NULL) {
+ return;
}
+#else
+ set_dict_inline_values(obj, (PyDictObject *)new_dict);
+ return;
+#endif
}
+
+ Py_BEGIN_CRITICAL_SECTION2(dict, obj);
+
+ // We've locked dict, but the actual dict could have changed
+ // since we locked it.
+ dict = _PyObject_ManagedDictPointer(obj)->dict;
+
+ FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
+ (PyDictObject *)Py_XNewRef(new_dict));
+
+ _PyDict_DetachFromObject(dict, obj);
+
+ Py_END_CRITICAL_SECTION2();
+
+ Py_XDECREF(dict);
}
else {
- Py_CLEAR(_PyObject_ManagedDictPointer(obj)->dict);
+ PyDictObject *dict;
+
+ Py_BEGIN_CRITICAL_SECTION(obj);
+
+ dict = _PyObject_ManagedDictPointer(obj)->dict;
+
+ FT_ATOMIC_STORE_PTR(_PyObject_ManagedDictPointer(obj)->dict,
+ (PyDictObject *)Py_XNewRef(new_dict));
+
+ Py_END_CRITICAL_SECTION();
+
+ Py_XDECREF(dict);
}
assert(_PyObject_InlineValuesConsistencyCheck(obj));
}
+void
+PyObject_ClearManagedDict(PyObject *obj)
+{
+ _PyObject_SetManagedDict(obj, NULL);
+}
+
int
_PyDict_DetachFromObject(PyDictObject *mp, PyObject *obj)
{
- assert(_PyObject_ManagedDictPointer(obj)->dict == mp);
- assert(_PyObject_InlineValuesConsistencyCheck(obj));
- if (mp->ma_values == NULL || mp->ma_values != _PyObject_InlineValues(obj)) {
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(obj);
+
+ if (FT_ATOMIC_LOAD_PTR_RELAXED(mp->ma_values) != _PyObject_InlineValues(obj)) {
return 0;
}
+
+ // We could be called with an unlocked dict when the caller knows the
+ // values are already detached, so we assert after inline values check.
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(mp);
assert(mp->ma_values->embedded == 1);
assert(mp->ma_values->valid == 1);
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
- Py_BEGIN_CRITICAL_SECTION(mp);
- mp->ma_values = copy_values(mp->ma_values);
- _PyObject_InlineValues(obj)->valid = 0;
- Py_END_CRITICAL_SECTION();
- if (mp->ma_values == NULL) {
+
+ PyDictValues *values = copy_values(mp->ma_values);
+
+ if (values == NULL) {
return -1;
}
+ mp->ma_values = values;
+
+ FT_ATOMIC_STORE_UINT8(_PyObject_InlineValues(obj)->valid, 0);
+
assert(_PyObject_InlineValuesConsistencyCheck(obj));
ASSERT_CONSISTENT(mp);
return 0;
@@ -6877,29 +7123,28 @@ PyObject_GenericGetDict(PyObject *obj, void *context)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
PyTypeObject *tp = Py_TYPE(obj);
+ PyDictObject *dict;
if (_PyType_HasFeature(tp, Py_TPFLAGS_MANAGED_DICT)) {
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(obj);
- PyDictObject *dict = managed_dict->dict;
+ dict = _PyObject_GetManagedDict(obj);
if (dict == NULL &&
(tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
- _PyObject_InlineValues(obj)->valid
- ) {
- PyDictValues *values = _PyObject_InlineValues(obj);
- OBJECT_STAT_INC(dict_materialized_on_request);
- dict = make_dict_from_instance_attributes(
- interp, CACHED_KEYS(tp), values);
- if (dict != NULL) {
- managed_dict->dict = (PyDictObject *)dict;
- }
+ FT_ATOMIC_LOAD_UINT8(_PyObject_InlineValues(obj)->valid)) {
+ dict = _PyObject_MaterializeManagedDict(obj);
}
- else {
- dict = managed_dict->dict;
+ else if (dict == NULL) {
+ Py_BEGIN_CRITICAL_SECTION(obj);
+
+ // Check again that we're not racing with someone else creating the dict
+ dict = _PyObject_GetManagedDict(obj);
if (dict == NULL) {
- dictkeys_incref(CACHED_KEYS(tp));
OBJECT_STAT_INC(dict_materialized_on_request);
+ dictkeys_incref(CACHED_KEYS(tp));
dict = (PyDictObject *)new_dict_with_shared_keys(interp, CACHED_KEYS(tp));
- managed_dict->dict = (PyDictObject *)dict;
+ FT_ATOMIC_STORE_PTR_RELEASE(_PyObject_ManagedDictPointer(obj)->dict,
+ (PyDictObject *)dict);
}
+
+ Py_END_CRITICAL_SECTION();
}
return Py_XNewRef((PyObject *)dict);
}
@@ -7109,7 +7354,7 @@ _PyObject_InlineValuesConsistencyCheck(PyObject *obj)
return 1;
}
assert(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
- PyDictObject *dict = (PyDictObject *)_PyObject_ManagedDictPointer(obj)->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
if (dict == NULL) {
return 1;
}
diff --git a/Objects/object.c b/Objects/object.c
index 73a1927..91bb011 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -6,6 +6,7 @@
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _Py_EnterRecursiveCallTstate()
#include "pycore_context.h" // _PyContextTokenMissing_Type
+#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION
#include "pycore_descrobject.h" // _PyMethodWrapper_Type
#include "pycore_dict.h" // _PyObject_MakeDictFromInstanceAttributes()
#include "pycore_floatobject.h" // _PyFloat_DebugMallocStats()
@@ -25,6 +26,7 @@
#include "pycore_typevarobject.h" // _PyTypeAlias_Type, _Py_initialize_generic
#include "pycore_unionobject.h" // _PyUnion_Type
+
#ifdef Py_LIMITED_API
// Prevent recursive call _Py_IncRef() <=> Py_INCREF()
# error "Py_LIMITED_API macro must not be defined"
@@ -1403,16 +1405,15 @@ _PyObject_GetDictPtr(PyObject *obj)
if ((Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
return _PyObject_ComputedDictPointer(obj);
}
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(obj);
- if (managed_dict->dict == NULL && Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
- PyDictObject *dict = (PyDictObject *)_PyObject_MakeDictFromInstanceAttributes(obj);
+ PyDictObject *dict = _PyObject_GetManagedDict(obj);
+ if (dict == NULL && Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
+ dict = _PyObject_MaterializeManagedDict(obj);
if (dict == NULL) {
PyErr_Clear();
return NULL;
}
- managed_dict->dict = dict;
}
- return (PyObject **)&managed_dict->dict;
+ return (PyObject **)&_PyObject_ManagedDictPointer(obj)->dict;
}
PyObject *
@@ -1480,10 +1481,9 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
}
}
}
- PyObject *dict;
- if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) && _PyObject_InlineValues(obj)->valid) {
- PyDictValues *values = _PyObject_InlineValues(obj);
- PyObject *attr = _PyObject_GetInstanceAttribute(obj, values, name);
+ PyObject *dict, *attr;
+ if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) &&
+ _PyObject_TryGetInstanceAttribute(obj, name, &attr)) {
if (attr != NULL) {
*method = attr;
Py_XDECREF(descr);
@@ -1492,8 +1492,7 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
dict = NULL;
}
else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
- PyManagedDictPointer* managed_dict = _PyObject_ManagedDictPointer(obj);
- dict = (PyObject *)managed_dict->dict;
+ dict = (PyObject *)_PyObject_GetManagedDict(obj);
}
else {
PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
@@ -1586,26 +1585,23 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
}
}
if (dict == NULL) {
- if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) && _PyObject_InlineValues(obj)->valid) {
- PyDictValues *values = _PyObject_InlineValues(obj);
- if (PyUnicode_CheckExact(name)) {
- res = _PyObject_GetInstanceAttribute(obj, values, name);
+ if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES)) {
+ if (PyUnicode_CheckExact(name) &&
+ _PyObject_TryGetInstanceAttribute(obj, name, &res)) {
if (res != NULL) {
goto done;
}
}
else {
- dict = (PyObject *)_PyObject_MakeDictFromInstanceAttributes(obj);
+ dict = (PyObject *)_PyObject_MaterializeManagedDict(obj);
if (dict == NULL) {
res = NULL;
goto done;
}
- _PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)dict;
}
}
else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
- PyManagedDictPointer* managed_dict = _PyObject_ManagedDictPointer(obj);
- dict = (PyObject *)managed_dict->dict;
+ dict = (PyObject *)_PyObject_GetManagedDict(obj);
}
else {
PyObject **dictptr = _PyObject_ComputedDictPointer(obj);
@@ -1700,12 +1696,13 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
if (dict == NULL) {
PyObject **dictptr;
- if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES) && _PyObject_InlineValues(obj)->valid) {
- res = _PyObject_StoreInstanceAttribute(
- obj, _PyObject_InlineValues(obj), name, value);
+
+ if ((tp->tp_flags & Py_TPFLAGS_INLINE_VALUES)) {
+ res = _PyObject_StoreInstanceAttribute(obj, name, value);
goto error_check;
}
- else if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
+
+ if ((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(obj);
dictptr = (PyObject **)&managed_dict->dict;
}
@@ -1779,7 +1776,7 @@ PyObject_GenericSetDict(PyObject *obj, PyObject *value, void *context)
PyObject **dictptr = _PyObject_GetDictPtr(obj);
if (dictptr == NULL) {
if (_PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_INLINE_VALUES) &&
- _PyObject_ManagedDictPointer(obj)->dict == NULL
+ _PyObject_GetManagedDict(obj) == NULL
) {
/* Was unable to convert to dict */
PyErr_NoMemory();
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 970c82d..808e11f 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3165,9 +3165,9 @@ subtype_setdict(PyObject *obj, PyObject *value, void *context)
"not a '%.200s'", Py_TYPE(value)->tp_name);
return -1;
}
+
if (Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
- PyObject_ClearManagedDict(obj);
- _PyObject_ManagedDictPointer(obj)->dict = (PyDictObject *)Py_XNewRef(value);
+ _PyObject_SetManagedDict(obj, value);
}
else {
dictptr = _PyObject_ComputedDictPointer(obj);
@@ -6194,15 +6194,27 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
/* Changing the class will change the implicit dict keys,
* so we must materialize the dictionary first. */
if (oldto->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
- PyDictObject *dict = _PyObject_ManagedDictPointer(self)->dict;
+ PyDictObject *dict = _PyObject_MaterializeManagedDict(self);
if (dict == NULL) {
- dict = (PyDictObject *)_PyObject_MakeDictFromInstanceAttributes(self);
- if (dict == NULL) {
- return -1;
- }
- _PyObject_ManagedDictPointer(self)->dict = dict;
+ return -1;
+ }
+
+ bool error = false;
+
+ Py_BEGIN_CRITICAL_SECTION2(self, dict);
+
+ // If we raced after materialization and replaced the dict
+ // then the materialized dict should no longer have the
+ // inline values in which case detach is a nop.
+ assert(_PyObject_GetManagedDict(self) == dict ||
+ dict->ma_values != _PyObject_InlineValues(self));
+
+ if (_PyDict_DetachFromObject(dict, self) < 0) {
+ error = true;
}
- if (_PyDict_DetachFromObject(dict, self)) {
+
+ Py_END_CRITICAL_SECTION2();
+ if (error) {
return -1;
}
}
diff --git a/Python/bytecodes.c b/Python/bytecodes.c
index c1fbd3c..b7511b9 100644
--- a/Python/bytecodes.c
+++ b/Python/bytecodes.c
@@ -1947,15 +1947,13 @@ dummy_func(
op(_CHECK_ATTR_WITH_HINT, (owner -- owner)) {
assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
DEOPT_IF(dict == NULL);
assert(PyDict_CheckExact((PyObject *)dict));
}
op(_LOAD_ATTR_WITH_HINT, (hint/1, owner -- attr, null if (oparg & 1))) {
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries);
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1);
if (DK_IS_UNICODE(dict->ma_keys)) {
@@ -2072,14 +2070,15 @@ dummy_func(
op(_GUARD_DORV_NO_DICT, (owner -- owner)) {
assert(Py_TYPE(owner)->tp_dictoffset < 0);
assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
- DEOPT_IF(_PyObject_ManagedDictPointer(owner)->dict);
+ DEOPT_IF(_PyObject_GetManagedDict(owner));
DEOPT_IF(_PyObject_InlineValues(owner)->valid == 0);
}
op(_STORE_ATTR_INSTANCE_VALUE, (index/1, value, owner --)) {
STAT_INC(STORE_ATTR, hit);
- assert(_PyObject_ManagedDictPointer(owner)->dict == NULL);
+ assert(_PyObject_GetManagedDict(owner) == NULL);
PyDictValues *values = _PyObject_InlineValues(owner);
+
PyObject *old_value = values->values[index];
values->values[index] = value;
if (old_value == NULL) {
@@ -2088,6 +2087,7 @@ dummy_func(
else {
Py_DECREF(old_value);
}
+
Py_DECREF(owner);
}
@@ -2102,8 +2102,7 @@ dummy_func(
assert(type_version != 0);
DEOPT_IF(tp->tp_version_tag != type_version);
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
DEOPT_IF(dict == NULL);
assert(PyDict_CheckExact((PyObject *)dict));
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h
index df87f91..841ce8c 100644
--- a/Python/executor_cases.c.h
+++ b/Python/executor_cases.c.h
@@ -1998,8 +1998,7 @@
PyObject *owner;
owner = stack_pointer[-1];
assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
if (dict == NULL) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@@ -2015,8 +2014,7 @@
oparg = CURRENT_OPARG();
owner = stack_pointer[-1];
uint16_t hint = (uint16_t)CURRENT_OPERAND();
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
if (hint >= (size_t)dict->ma_keys->dk_nentries) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
@@ -2159,7 +2157,7 @@
owner = stack_pointer[-1];
assert(Py_TYPE(owner)->tp_dictoffset < 0);
assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
- if (_PyObject_ManagedDictPointer(owner)->dict) {
+ if (_PyObject_GetManagedDict(owner)) {
UOP_STAT_INC(uopcode, miss);
JUMP_TO_JUMP_TARGET();
}
@@ -2177,7 +2175,7 @@
value = stack_pointer[-2];
uint16_t index = (uint16_t)CURRENT_OPERAND();
STAT_INC(STORE_ATTR, hit);
- assert(_PyObject_ManagedDictPointer(owner)->dict == NULL);
+ assert(_PyObject_GetManagedDict(owner) == NULL);
PyDictValues *values = _PyObject_InlineValues(owner);
PyObject *old_value = values->values[index];
values->values[index] = value;
diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h
index a426d9e..058cac8 100644
--- a/Python/generated_cases.c.h
+++ b/Python/generated_cases.c.h
@@ -4017,16 +4017,14 @@
// _CHECK_ATTR_WITH_HINT
{
assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_MANAGED_DICT);
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
DEOPT_IF(dict == NULL, LOAD_ATTR);
assert(PyDict_CheckExact((PyObject *)dict));
}
// _LOAD_ATTR_WITH_HINT
{
uint16_t hint = read_u16(&this_instr[4].cache);
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries, LOAD_ATTR);
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg>>1);
if (DK_IS_UNICODE(dict->ma_keys)) {
@@ -5309,7 +5307,7 @@
{
assert(Py_TYPE(owner)->tp_dictoffset < 0);
assert(Py_TYPE(owner)->tp_flags & Py_TPFLAGS_INLINE_VALUES);
- DEOPT_IF(_PyObject_ManagedDictPointer(owner)->dict, STORE_ATTR);
+ DEOPT_IF(_PyObject_GetManagedDict(owner), STORE_ATTR);
DEOPT_IF(_PyObject_InlineValues(owner)->valid == 0, STORE_ATTR);
}
// _STORE_ATTR_INSTANCE_VALUE
@@ -5317,7 +5315,7 @@
{
uint16_t index = read_u16(&this_instr[4].cache);
STAT_INC(STORE_ATTR, hit);
- assert(_PyObject_ManagedDictPointer(owner)->dict == NULL);
+ assert(_PyObject_GetManagedDict(owner) == NULL);
PyDictValues *values = _PyObject_InlineValues(owner);
PyObject *old_value = values->values[index];
values->values[index] = value;
@@ -5380,8 +5378,7 @@
assert(type_version != 0);
DEOPT_IF(tp->tp_version_tag != type_version, STORE_ATTR);
assert(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT);
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
DEOPT_IF(dict == NULL, STORE_ATTR);
assert(PyDict_CheckExact((PyObject *)dict));
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
diff --git a/Python/specialize.c b/Python/specialize.c
index 5e14bb5..ee51781 100644
--- a/Python/specialize.c
+++ b/Python/specialize.c
@@ -852,8 +852,7 @@ specialize_dict_access(
instr->op.code = values_op;
}
else {
- PyManagedDictPointer *managed_dict = _PyObject_ManagedDictPointer(owner);
- PyDictObject *dict = managed_dict->dict;
+ PyDictObject *dict = _PyObject_GetManagedDict(owner);
if (dict == NULL || !PyDict_CheckExact(dict)) {
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_NO_DICT);
return 0;
diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py
index d17b2b9..18cefa0 100644
--- a/Tools/cases_generator/analyzer.py
+++ b/Tools/cases_generator/analyzer.py
@@ -354,6 +354,7 @@ def has_error_without_pop(op: parser.InstDef) -> bool:
NON_ESCAPING_FUNCTIONS = (
"Py_INCREF",
"_PyManagedDictPointer_IsValues",
+ "_PyObject_GetManagedDict",
"_PyObject_ManagedDictPointer",
"_PyObject_InlineValues",
"_PyDictValues_AddToInsertionOrder",