summaryrefslogtreecommitdiffstats
path: root/Objects/dictobject.c
diff options
context:
space:
mode:
authorMark Shannon <mark@hotpy.org>2021-10-13 13:19:34 (GMT)
committerGitHub <noreply@github.com>2021-10-13 13:19:34 (GMT)
commita8b9350964f43cb648c98c179c8037fbf3ff8a7d (patch)
tree13a539432c9d48ac278d34d040f17a7a12eac771 /Objects/dictobject.c
parent97308dfcdc0696e0b116c37386e2ff4d72e6c3f4 (diff)
downloadcpython-a8b9350964f43cb648c98c179c8037fbf3ff8a7d.zip
cpython-a8b9350964f43cb648c98c179c8037fbf3ff8a7d.tar.gz
cpython-a8b9350964f43cb648c98c179c8037fbf3ff8a7d.tar.bz2
bpo-45340: Don't create object dictionaries unless actually needed (GH-28802)
* Never change types' cached keys. It could invalidate inline attribute objects. * Lazily create object dictionaries. * Update specialization of LOAD/STORE_ATTR. * Don't update shared keys version for deletion of value. * Update gdb support to handle instance values. * Rename SPLIT_KEYS opcodes to INSTANCE_VALUE.
Diffstat (limited to 'Objects/dictobject.c')
-rw-r--r--Objects/dictobject.c434
1 files changed, 289 insertions, 145 deletions
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 60470bf..3d6e4c1 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -634,7 +634,7 @@ new_values(Py_ssize_t size)
/* Consumes a reference to the keys object */
static PyObject *
-new_dict(PyDictKeysObject *keys, PyDictValues *values)
+new_dict(PyDictKeysObject *keys, PyDictValues *values, Py_ssize_t used, int free_values_on_failure)
{
PyDictObject *mp;
assert(keys != NULL);
@@ -653,7 +653,7 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values)
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
if (mp == NULL) {
dictkeys_decref(keys);
- if (values != empty_values) {
+ if (free_values_on_failure) {
free_values(values);
}
return NULL;
@@ -661,12 +661,18 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values)
}
mp->ma_keys = keys;
mp->ma_values = values;
- mp->ma_used = 0;
+ mp->ma_used = used;
mp->ma_version_tag = DICT_NEXT_VERSION();
ASSERT_CONSISTENT(mp);
return (PyObject *)mp;
}
+static inline Py_ssize_t
+shared_keys_usable_size(PyDictKeysObject *keys)
+{
+ return keys->dk_nentries + keys->dk_usable;
+}
+
/* Consumes a reference to the keys object */
static PyObject *
new_dict_with_shared_keys(PyDictKeysObject *keys)
@@ -674,7 +680,7 @@ new_dict_with_shared_keys(PyDictKeysObject *keys)
PyDictValues *values;
Py_ssize_t i, size;
- size = USABLE_FRACTION(DK_SIZE(keys));
+ size = shared_keys_usable_size(keys);
values = new_values(size);
if (values == NULL) {
dictkeys_decref(keys);
@@ -684,7 +690,7 @@ new_dict_with_shared_keys(PyDictKeysObject *keys)
for (i = 0; i < size; i++) {
values->values[i] = NULL;
}
- return new_dict(keys, values);
+ return new_dict(keys, values, 0, 1);
}
@@ -733,7 +739,7 @@ PyObject *
PyDict_New(void)
{
dictkeys_incref(Py_EMPTY_KEYS);
- return new_dict(Py_EMPTY_KEYS, empty_values);
+ return new_dict(Py_EMPTY_KEYS, empty_values, 0, 0);
}
/* Search index of hash table from offset of entry table */
@@ -998,6 +1004,40 @@ insertion_resize(PyDictObject *mp)
return dictresize(mp, calculate_log2_keysize(GROWTH_RATE(mp)));
}
+static int
+insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
+{
+ assert(PyUnicode_CheckExact(name));
+ Py_hash_t hash = ((PyASCIIObject *)name)->hash;
+ if (hash == -1) {
+ hash = PyUnicode_Type.tp_hash(name);
+ if (hash == -1) {
+ PyErr_Clear();
+ return DKIX_EMPTY;
+ }
+ }
+ Py_ssize_t ix = dictkeys_stringlookup(keys, name, hash);
+ if (ix == DKIX_EMPTY) {
+ if (keys->dk_usable <= 0) {
+ return DKIX_EMPTY;
+ }
+ Py_INCREF(name);
+ /* Insert into new slot. */
+ keys->dk_version = 0;
+ Py_ssize_t hashpos = find_empty_slot(keys, hash);
+ ix = keys->dk_nentries;
+ PyDictKeyEntry *ep = &DK_ENTRIES(keys)[ix];
+ dictkeys_set_index(keys, hashpos, ix);
+ assert(ep->me_key == NULL);
+ ep->me_key = name;
+ ep->me_hash = hash;
+ keys->dk_usable--;
+ keys->dk_nentries++;
+ }
+ assert (ix < SHARED_KEYS_MAX_SIZE);
+ return (int)ix;
+}
+
/*
Internal routine to insert a new item into the table.
Used both by the internal resize routine and by the public insert routine.
@@ -1043,7 +1083,7 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
Py_ssize_t index = mp->ma_keys->dk_nentries;
assert(index < SHARED_KEYS_MAX_SIZE);
assert((mp->ma_values->mv_order >> 60) == 0);
- mp->ma_values->mv_order = (mp->ma_values->mv_order)<<4 | index;
+ mp->ma_values->mv_order = ((mp->ma_values->mv_order)<<4) | index;
assert (mp->ma_values->values[index] == NULL);
mp->ma_values->values[index] = value;
}
@@ -1144,8 +1184,7 @@ actually be smaller than the old one.
If a table is split (its keys and hashes are shared, its values are not),
then the values are temporarily copied into the table, it is resized as
a combined table, then the me_value slots in the old table are NULLed out.
-After resizing a table is always combined,
-but can be resplit by make_keys_shared().
+After resizing a table is always combined.
*/
static int
dictresize(PyDictObject *mp, uint8_t log2_newsize)
@@ -1186,19 +1225,16 @@ dictresize(PyDictObject *mp, uint8_t log2_newsize)
if (oldvalues != NULL) {
/* Convert split table into new combined table.
* We must incref keys; we can transfer values.
- * Note that values of split table is always dense.
*/
for (Py_ssize_t i = 0; i < numentries; i++) {
- int index = oldvalues->mv_order >> ((numentries-1-i)*4) & 15;
- assert(oldvalues->values[index] != NULL);
+ int index = get_index_from_order(mp, i);
PyDictKeyEntry *ep = &oldentries[index];
- PyObject *key = ep->me_key;
- Py_INCREF(key);
- newentries[i].me_key = key;
+ assert(oldvalues->values[index] != NULL);
+ Py_INCREF(ep->me_key);
+ newentries[i].me_key = ep->me_key;
newentries[i].me_hash = ep->me_hash;
newentries[i].me_value = oldvalues->values[index];
}
-
dictkeys_decref(oldkeys);
mp->ma_values = NULL;
if (oldvalues != empty_values) {
@@ -1241,69 +1277,8 @@ dictresize(PyDictObject *mp, uint8_t log2_newsize)
build_indices(mp->ma_keys, newentries, numentries);
mp->ma_keys->dk_usable -= numentries;
mp->ma_keys->dk_nentries = numentries;
- return 0;
-}
-
-/* Returns NULL if unable to split table.
- * A NULL return does not necessarily indicate an error */
-static PyDictKeysObject *
-make_keys_shared(PyObject *op)
-{
- Py_ssize_t i;
- Py_ssize_t size;
- PyDictObject *mp = (PyDictObject *)op;
-
- if (!PyDict_CheckExact(op))
- return NULL;
- if (mp->ma_used > SHARED_KEYS_MAX_SIZE) {
- return NULL;
- }
- if (!_PyDict_HasSplitTable(mp)) {
- PyDictKeyEntry *ep0;
- PyDictValues *values;
- assert(mp->ma_keys->dk_refcnt == 1);
- if (mp->ma_keys->dk_kind == DICT_KEYS_GENERAL) {
- return NULL;
- }
- else if (mp->ma_used > mp->ma_keys->dk_nentries) {
- /* Remove dummy keys */
- if (dictresize(mp, DK_LOG_SIZE(mp->ma_keys)))
- return NULL;
- }
- assert(mp->ma_used == mp->ma_keys->dk_nentries);
- /* Copy values into a new array */
- ep0 = DK_ENTRIES(mp->ma_keys);
- size = USABLE_FRACTION(DK_SIZE(mp->ma_keys));
- values = new_values(size);
- if (values == NULL) {
- PyErr_SetString(PyExc_MemoryError,
- "Not enough memory to allocate new values array");
- return NULL;
- }
- uint64_t order = 0;
- for (i = 0; i < mp->ma_used; i++) {
- order <<= 4;
- order |= i;
- assert(ep0[i].me_value != NULL);
- values->values[i] = ep0[i].me_value;
- ep0[i].me_value = NULL;
- }
- values->mv_order = order;
- for (; i < size; i++) {
- assert(ep0[i].me_value == NULL);
- values->values[i] = NULL;
- ep0[i].me_value = NULL;
- }
- if (mp->ma_keys->dk_nentries + mp->ma_keys->dk_usable > SHARED_KEYS_MAX_SIZE) {
- assert(mp->ma_keys->dk_nentries <= SHARED_KEYS_MAX_SIZE);
- mp->ma_keys->dk_usable = SHARED_KEYS_MAX_SIZE - mp->ma_keys->dk_nentries;
- }
- mp->ma_keys->dk_kind = DICT_KEYS_SPLIT;
- mp->ma_values = values;
- }
- dictkeys_incref(mp->ma_keys);
ASSERT_CONSISTENT(mp);
- return mp->ma_keys;
+ return 0;
}
PyObject *
@@ -1331,7 +1306,7 @@ _PyDict_NewPresized(Py_ssize_t minused)
new_keys = new_keys_object(log2_newsize);
if (new_keys == NULL)
return NULL;
- return new_dict(new_keys, NULL);
+ return new_dict(new_keys, NULL, 0, 0);
}
/* Note that, for historical reasons, PyDict_GetItem() suppresses all errors
@@ -1503,6 +1478,9 @@ _PyDict_GetItemStringWithError(PyObject *v, const char *key)
/* Fast version of global value lookup (LOAD_GLOBAL).
* Lookup in globals, then builtins.
*
+ *
+ *
+ *
* Raise an exception and return NULL if an error occurred (ex: computing the
* key hash failed, key comparison failed, ...). Return NULL if the key doesn't
* exist. Return the value if the key exists.
@@ -1590,6 +1568,21 @@ _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value,
return insertdict(mp, key, hash, value);
}
+static uint64_t
+delete_index_from_order(uint64_t order, Py_ssize_t ix)
+{ /* Update order */
+ for (int i = 0;; i+= 4) {
+ assert (i < 64);
+ if (((order >> i) & 15) == (uint64_t)ix) {
+ /* Remove 4 bits at ith position */
+ uint64_t high = ((order>>i)>>4)<<i;
+ uint64_t low = order & ((((uint64_t)1)<<i)-1);
+ return high | low;
+ }
+ }
+ Py_UNREACHABLE();
+}
+
static int
delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
PyObject *old_value)
@@ -1601,7 +1594,6 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
assert(hashpos >= 0);
mp->ma_used--;
- mp->ma_keys->dk_version = 0;
mp->ma_version_tag = DICT_NEXT_VERSION();
ep = &DK_ENTRIES(mp->ma_keys)[ix];
if (mp->ma_values) {
@@ -1609,19 +1601,12 @@ delitem_common(PyDictObject *mp, Py_hash_t hash, Py_ssize_t ix,
mp->ma_values->values[ix] = NULL;
assert(ix < SHARED_KEYS_MAX_SIZE);
/* Update order */
- for (int i = 0;; i+= 4) {
- assert (i < 64);
- if (((mp->ma_values->mv_order >> i) & 15) == (uint64_t)ix) {
- /* Remove 4 bits at ith position */
- uint64_t order = mp->ma_values->mv_order;
- uint64_t high = ((order>>i)>>4)<<i;
- uint64_t low = order & ((((uint64_t)1)<<i)-1);
- mp->ma_values->mv_order = high | low;
- break;
- }
- }
+ mp->ma_values->mv_order =
+ delete_index_from_order(mp->ma_values->mv_order, ix);
+ ASSERT_CONSISTENT(mp);
}
else {
+ mp->ma_keys->dk_version = 0;
dictkeys_set_index(mp->ma_keys, hashpos, DKIX_DUMMY);
old_key = ep->me_key;
ep->me_key = NULL;
@@ -2692,7 +2677,7 @@ PyDict_Copy(PyObject *o)
if (_PyDict_HasSplitTable(mp)) {
PyDictObject *split_copy;
- Py_ssize_t size = USABLE_FRACTION(DK_SIZE(mp->ma_keys));
+ Py_ssize_t size = shared_keys_usable_size(mp->ma_keys);
PyDictValues *newvalues;
newvalues = new_values(size);
if (newvalues == NULL)
@@ -2740,7 +2725,7 @@ PyDict_Copy(PyObject *o)
if (keys == NULL) {
return NULL;
}
- PyDictObject *new = (PyDictObject *)new_dict(keys, NULL);
+ PyDictObject *new = (PyDictObject *)new_dict(keys, NULL, 0, 0);
if (new == NULL) {
/* In case of an error, `new_dict()` takes care of
cleaning up `keys`. */
@@ -2979,15 +2964,6 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
if (ix == DKIX_ERROR)
return NULL;
- if (_PyDict_HasSplitTable(mp) &&
- ((ix >= 0 && value == NULL && mp->ma_used != ix) ||
- (ix == DKIX_EMPTY && mp->ma_used != mp->ma_keys->dk_nentries))) {
- if (insertion_resize(mp) < 0) {
- return NULL;
- }
- ix = DKIX_EMPTY;
- }
-
if (ix == DKIX_EMPTY) {
mp->ma_keys->dk_version = 0;
PyDictKeyEntry *ep, *ep0;
@@ -3028,7 +3004,7 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
else if (value == NULL) {
value = defaultobj;
assert(_PyDict_HasSplitTable(mp));
- assert(ix == mp->ma_used);
+ assert(mp->ma_values->values[ix] == NULL);
Py_INCREF(value);
MAINTAIN_TRACKING(mp, key, value);
mp->ma_values->values[ix] = value;
@@ -3204,20 +3180,22 @@ static PyObject *dictiter_new(PyDictObject *, PyTypeObject *);
Py_ssize_t
_PyDict_SizeOf(PyDictObject *mp)
{
- Py_ssize_t size, usable, res;
+ Py_ssize_t size, res;
size = DK_SIZE(mp->ma_keys);
- usable = USABLE_FRACTION(size);
res = _PyObject_SIZE(Py_TYPE(mp));
- if (mp->ma_values)
- res += usable * sizeof(PyObject*);
+ if (mp->ma_values) {
+ res += shared_keys_usable_size(mp->ma_keys) * sizeof(PyObject*);
+ }
/* If the dictionary is split, the keys portion is accounted-for
in the type object. */
- if (mp->ma_keys->dk_refcnt == 1)
+ if (mp->ma_keys->dk_refcnt == 1) {
+ Py_ssize_t usable = USABLE_FRACTION(size);
res += (sizeof(PyDictKeysObject)
+ DK_IXSIZE(mp->ma_keys) * size
+ sizeof(PyDictKeyEntry) * usable);
+ }
return res;
}
@@ -4919,11 +4897,14 @@ dictvalues_reversed(_PyDictViewObject *dv, PyObject *Py_UNUSED(ignored))
PyDictKeysObject *
_PyDict_NewKeysForClass(void)
{
- PyDictKeysObject *keys = new_keys_object(PyDict_LOG_MINSIZE);
+ PyDictKeysObject *keys = new_keys_object(5); /* log2(32) */
if (keys == NULL) {
PyErr_Clear();
}
else {
+ assert(keys->dk_nentries == 0);
+ /* Set to max size+1 as it will shrink by one before each new object */
+ keys->dk_usable = SHARED_KEYS_MAX_SIZE;
keys->dk_kind = DICT_KEYS_SPLIT;
}
return keys;
@@ -4931,15 +4912,42 @@ _PyDict_NewKeysForClass(void)
#define CACHED_KEYS(tp) (((PyHeapTypeObject*)tp)->ht_cached_keys)
+static int
+init_inline_values(PyObject *obj, PyTypeObject *tp)
+{
+ assert(tp->tp_flags & Py_TPFLAGS_HEAPTYPE);
+ assert(tp->tp_dictoffset > 0);
+ assert(tp->tp_inline_values_offset > 0);
+ PyDictKeysObject *keys = CACHED_KEYS(tp);
+ assert(keys != NULL);
+ if (keys->dk_usable > 1) {
+ keys->dk_usable--;
+ }
+ Py_ssize_t size = shared_keys_usable_size(keys);
+ assert(size > 0);
+ PyDictValues *values = new_values(size);
+ if (values == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ values->mv_order = 0;
+ for (int i = 0; i < size; i++) {
+ values->values[i] = NULL;
+ }
+ *((PyDictValues **)((char *)obj + tp->tp_inline_values_offset)) = values;
+ return 0;
+}
+
int
_PyObject_InitializeDict(PyObject *obj)
{
- PyObject **dictptr = _PyObject_GetDictPtr(obj);
- if (dictptr == NULL) {
+ PyTypeObject *tp = Py_TYPE(obj);
+ if (tp->tp_dictoffset == 0) {
return 0;
}
- assert(*dictptr == NULL);
- PyTypeObject *tp = Py_TYPE(obj);
+ if (tp->tp_inline_values_offset) {
+ return init_inline_values(obj, tp);
+ }
PyObject *dict;
if (_PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE) && CACHED_KEYS(tp)) {
dictkeys_incref(CACHED_KEYS(tp));
@@ -4951,15 +4959,174 @@ _PyObject_InitializeDict(PyObject *obj)
if (dict == NULL) {
return -1;
}
+ PyObject **dictptr = _PyObject_DictPointer(obj);
*dictptr = dict;
return 0;
}
+static PyObject *
+make_dict_from_instance_attributes(PyDictKeysObject *keys, PyDictValues *values)
+{
+ dictkeys_incref(keys);
+ Py_ssize_t used = 0;
+ Py_ssize_t track = 0;
+ for (Py_ssize_t i = 0; i < shared_keys_usable_size(keys); i++) {
+ PyObject *val = values->values[i];
+ if (val != NULL) {
+ used += 1;
+ track += _PyObject_GC_MAY_BE_TRACKED(val);
+ }
+ }
+ PyObject *res = new_dict(keys, values, used, 0);
+ if (track && res) {
+ _PyObject_GC_TRACK(res);
+ }
+ return res;
+}
+
+PyObject *
+_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values)
+{
+ assert(Py_TYPE(obj)->tp_inline_values_offset != 0);
+ PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
+ return make_dict_from_instance_attributes(keys, values);
+}
+
+int
+_PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
+ PyObject *name, PyObject *value)
+{
+ assert(PyUnicode_CheckExact(name));
+ PyTypeObject *tp = Py_TYPE(obj);
+ PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
+ assert(keys != NULL);
+ assert(values != NULL);
+ int ix = insert_into_dictkeys(keys, name);
+ if (ix == DKIX_EMPTY) {
+ if (value == NULL) {
+ PyErr_SetObject(PyExc_AttributeError, name);
+ return -1;
+ }
+ PyObject *dict = make_dict_from_instance_attributes(keys, values);
+ if (dict == NULL) {
+ return -1;
+ }
+ *((PyDictValues **)((char *)obj + tp->tp_inline_values_offset)) = NULL;
+ *((PyObject **) ((char *)obj + tp->tp_dictoffset)) = dict;
+ return PyDict_SetItem(dict, name, value);
+ }
+ PyObject *old_value = values->values[ix];
+ Py_XINCREF(value);
+ values->values[ix] = value;
+ if (old_value == NULL) {
+ if (value == NULL) {
+ PyErr_SetObject(PyExc_AttributeError, name);
+ return -1;
+ }
+ values->mv_order = (values->mv_order << 4) | ix;
+ }
+ else {
+ if (value == NULL) {
+ values->mv_order = delete_index_from_order(values->mv_order, ix);
+ }
+ Py_DECREF(old_value);
+ }
+ return 0;
+}
+
+PyObject *
+_PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
+ PyObject *name)
+{
+ assert(PyUnicode_CheckExact(name));
+ PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
+ assert(keys != NULL);
+ Py_ssize_t ix = _PyDictKeys_StringLookup(keys, name);
+ if (ix == DKIX_EMPTY) {
+ return NULL;
+ }
+ PyObject *value = values->values[ix];
+ Py_XINCREF(value);
+ return value;
+}
+
+int
+_PyObject_IsInstanceDictEmpty(PyObject *obj)
+{
+ PyTypeObject *tp = Py_TYPE(obj);
+ if (tp->tp_dictoffset == 0) {
+ return 1;
+ }
+ PyDictValues **values_ptr = _PyObject_ValuesPointer(obj);
+ if (values_ptr && *values_ptr) {
+ PyDictKeysObject *keys = CACHED_KEYS(tp);
+ for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
+ if ((*values_ptr)->values[i] != NULL) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ PyObject **dictptr = _PyObject_DictPointer(obj);
+ PyObject *dict = *dictptr;
+ if (dict == NULL) {
+ return 1;
+ }
+ return ((PyDictObject *)dict)->ma_used == 0;
+}
+
+
+int
+_PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg)
+{
+ PyTypeObject *tp = Py_TYPE(self);
+ assert(tp->tp_inline_values_offset);
+ PyDictValues **values_ptr = _PyObject_ValuesPointer(self);
+ if (*values_ptr == NULL) {
+ return 0;
+ }
+ PyDictKeysObject *keys = CACHED_KEYS(tp);
+ for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
+ Py_VISIT((*values_ptr)->values[i]);
+ }
+ return 0;
+}
+
+void
+_PyObject_ClearInstanceAttributes(PyObject *self)
+{
+ PyTypeObject *tp = Py_TYPE(self);
+ assert(tp->tp_inline_values_offset);
+ PyDictValues **values_ptr = _PyObject_ValuesPointer(self);
+ if (*values_ptr == NULL) {
+ return;
+ }
+ PyDictKeysObject *keys = CACHED_KEYS(tp);
+ for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
+ Py_CLEAR((*values_ptr)->values[i]);
+ }
+}
+
+void
+_PyObject_FreeInstanceAttributes(PyObject *self)
+{
+ PyTypeObject *tp = Py_TYPE(self);
+ assert(tp->tp_inline_values_offset);
+ PyDictValues **values_ptr = _PyObject_ValuesPointer(self);
+ if (*values_ptr == NULL) {
+ return;
+ }
+ PyDictKeysObject *keys = CACHED_KEYS(tp);
+ for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
+ Py_XDECREF((*values_ptr)->values[i]);
+ }
+ free_values(*values_ptr);
+}
PyObject *
PyObject_GenericGetDict(PyObject *obj, void *context)
{
- PyObject **dictptr = _PyObject_GetDictPtr(obj);
+ PyObject **dictptr = _PyObject_DictPointer(obj);
if (dictptr == NULL) {
PyErr_SetString(PyExc_AttributeError,
"This object has no __dict__");
@@ -4968,7 +5135,14 @@ PyObject_GenericGetDict(PyObject *obj, void *context)
PyObject *dict = *dictptr;
if (dict == NULL) {
PyTypeObject *tp = Py_TYPE(obj);
- if (_PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE) && CACHED_KEYS(tp)) {
+ PyDictValues **values_ptr = _PyObject_ValuesPointer(obj);
+ if (values_ptr && *values_ptr) {
+ *dictptr = dict = make_dict_from_instance_attributes(CACHED_KEYS(tp), *values_ptr);
+ if (dict != NULL) {
+ *values_ptr = NULL;
+ }
+ }
+ else if (_PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE) && CACHED_KEYS(tp)) {
dictkeys_incref(CACHED_KEYS(tp));
*dictptr = dict = new_dict_with_shared_keys(CACHED_KEYS(tp));
}
@@ -5003,37 +5177,7 @@ _PyObjectDict_SetItem(PyTypeObject *tp, PyObject **dictptr,
res = PyDict_DelItem(dict, key);
}
else {
- int was_shared = (cached == ((PyDictObject *)dict)->ma_keys);
res = PyDict_SetItem(dict, key, value);
- if (was_shared &&
- (cached = CACHED_KEYS(tp)) != NULL &&
- cached != ((PyDictObject *)dict)->ma_keys &&
- cached->dk_nentries <= SHARED_KEYS_MAX_SIZE) {
- /* PyDict_SetItem() may call dictresize and convert split table
- * into combined table. In such case, convert it to split
- * table again and update type's shared key only when this is
- * the only dict sharing key with the type.
- *
- * This is to allow using shared key in class like this:
- *
- * class C:
- * def __init__(self):
- * # one dict resize happens
- * self.a, self.b, self.c = 1, 2, 3
- * self.d, self.e, self.f = 4, 5, 6
- * a = C()
- */
- if (cached->dk_refcnt == 1) {
- PyDictKeysObject *new_cached = make_keys_shared(dict);
- if (new_cached != NULL) {
- CACHED_KEYS(tp) = new_cached;
- dictkeys_decref(cached);
- }
- else if (PyErr_Occurred()) {
- return -1;
- }
- }
- }
}
} else {
dict = *dictptr;