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.
This commit is contained in:
Mark Shannon 2021-10-13 14:19:34 +01:00 committed by GitHub
parent 97308dfcdc
commit a8b9350964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 721 additions and 400 deletions

View File

@ -270,6 +270,7 @@ struct _typeobject {
destructor tp_finalize;
vectorcallfunc tp_vectorcall;
Py_ssize_t tp_inline_values_offset;
};
/* The *real* layout of a type object when allocated on the heap */

View File

@ -101,6 +101,8 @@ extern uint64_t _pydict_global_version;
#define DICT_NEXT_VERSION() (++_pydict_global_version)
PyObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values);
#ifdef __cplusplus
}
#endif

View File

@ -181,6 +181,16 @@ extern int _Py_CheckSlotResult(
extern PyObject* _PyType_AllocNoTrack(PyTypeObject *type, Py_ssize_t nitems);
extern int _PyObject_InitializeDict(PyObject *obj);
extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
PyObject *name, PyObject *value);
PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
PyObject *name);
PyDictValues ** _PyObject_ValuesPointer(PyObject *);
PyObject ** _PyObject_DictPointer(PyObject *);
int _PyObject_VisitInstanceAttributes(PyObject *self, visitproc visit, void *arg);
void _PyObject_ClearInstanceAttributes(PyObject *self);
void _PyObject_FreeInstanceAttributes(PyObject *self);
int _PyObject_IsInstanceDictEmpty(PyObject *);
#ifdef __cplusplus
}

View File

@ -333,6 +333,7 @@ given type object has a specified feature.
*/
#ifndef Py_LIMITED_API
/* Set if instances of the type object are treated as sequences for pattern matching */
#define Py_TPFLAGS_SEQUENCE (1 << 5)
/* Set if instances of the type object are treated as mappings for pattern matching */

21
Include/opcode.h generated
View File

@ -147,7 +147,7 @@ extern "C" {
#define BINARY_SUBSCR_DICT 39
#define JUMP_ABSOLUTE_QUICK 40
#define LOAD_ATTR_ADAPTIVE 41
#define LOAD_ATTR_SPLIT_KEYS 42
#define LOAD_ATTR_INSTANCE_VALUE 42
#define LOAD_ATTR_WITH_HINT 43
#define LOAD_ATTR_SLOT 44
#define LOAD_ATTR_MODULE 45
@ -158,15 +158,16 @@ extern "C" {
#define LOAD_METHOD_CACHED 80
#define LOAD_METHOD_CLASS 81
#define LOAD_METHOD_MODULE 87
#define STORE_ATTR_ADAPTIVE 88
#define STORE_ATTR_SPLIT_KEYS 120
#define STORE_ATTR_SLOT 122
#define STORE_ATTR_WITH_HINT 123
#define LOAD_FAST__LOAD_FAST 127
#define STORE_FAST__LOAD_FAST 128
#define LOAD_FAST__LOAD_CONST 134
#define LOAD_CONST__LOAD_FAST 140
#define STORE_FAST__STORE_FAST 143
#define LOAD_METHOD_NO_DICT 88
#define STORE_ATTR_ADAPTIVE 120
#define STORE_ATTR_INSTANCE_VALUE 122
#define STORE_ATTR_SLOT 123
#define STORE_ATTR_WITH_HINT 127
#define LOAD_FAST__LOAD_FAST 128
#define STORE_FAST__LOAD_FAST 134
#define LOAD_FAST__LOAD_CONST 140
#define LOAD_CONST__LOAD_FAST 143
#define STORE_FAST__STORE_FAST 149
#define DO_TRACING 255
#ifdef NEED_OPCODE_JUMP_TABLES
static uint32_t _PyOpcode_RelativeJump[8] = {

View File

@ -231,7 +231,7 @@ _specialized_instructions = [
"BINARY_SUBSCR_DICT",
"JUMP_ABSOLUTE_QUICK",
"LOAD_ATTR_ADAPTIVE",
"LOAD_ATTR_SPLIT_KEYS",
"LOAD_ATTR_INSTANCE_VALUE",
"LOAD_ATTR_WITH_HINT",
"LOAD_ATTR_SLOT",
"LOAD_ATTR_MODULE",
@ -242,8 +242,9 @@ _specialized_instructions = [
"LOAD_METHOD_CACHED",
"LOAD_METHOD_CLASS",
"LOAD_METHOD_MODULE",
"LOAD_METHOD_NO_DICT",
"STORE_ATTR_ADAPTIVE",
"STORE_ATTR_SPLIT_KEYS",
"STORE_ATTR_INSTANCE_VALUE",
"STORE_ATTR_SLOT",
"STORE_ATTR_WITH_HINT",
# Super instructions

View File

@ -5500,17 +5500,19 @@ class SharedKeyTests(unittest.TestCase):
class B(A):
pass
#Shrink keys by repeatedly creating instances
[(A(), B()) for _ in range(20)]
a, b = A(), B()
self.assertEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(b)))
self.assertLess(sys.getsizeof(vars(a)), sys.getsizeof({"a":1}))
# Initial hash table can contain at most 5 elements.
# Initial hash table can contain only one or two elements.
# Set 6 attributes to cause internal resizing.
a.x, a.y, a.z, a.w, a.v, a.u = range(6)
self.assertNotEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(b)))
a2 = A()
self.assertEqual(sys.getsizeof(vars(a)), sys.getsizeof(vars(a2)))
self.assertLess(sys.getsizeof(vars(a)), sys.getsizeof({"a":1}))
b.u, b.v, b.w, b.t, b.s, b.r = range(6)
self.assertGreater(sys.getsizeof(vars(a)), sys.getsizeof(vars(a2)))
self.assertLess(sys.getsizeof(vars(a2)), sys.getsizeof({"a":1}))
self.assertLess(sys.getsizeof(vars(b)), sys.getsizeof({"a":1}))

View File

@ -994,8 +994,8 @@ class DictTest(unittest.TestCase):
@support.cpython_only
def test_splittable_setdefault(self):
"""split table must be combined when setdefault()
breaks insertion order"""
"""split table must keep correct insertion
order when attributes are adding using setdefault()"""
a, b = self.make_shared_key_dict(2)
a['a'] = 1
@ -1005,7 +1005,6 @@ class DictTest(unittest.TestCase):
size_b = sys.getsizeof(b)
b['a'] = 1
self.assertGreater(size_b, size_a)
self.assertEqual(list(a), ['x', 'y', 'z', 'a', 'b'])
self.assertEqual(list(b), ['x', 'y', 'z', 'b', 'a'])

View File

@ -444,7 +444,7 @@ class GCTests(unittest.TestCase):
# 0, thus mutating the trash graph as a side effect of merely asking
# whether __del__ exists. This used to (before 2.3b1) crash Python.
# Now __getattr__ isn't called.
self.assertEqual(gc.collect(), 4)
self.assertEqual(gc.collect(), 2)
self.assertEqual(len(gc.garbage), garbagelen)
def test_boom2(self):
@ -471,7 +471,7 @@ class GCTests(unittest.TestCase):
# there isn't a second time, so this simply cleans up the trash cycle.
# We expect a, b, a.__dict__ and b.__dict__ (4 objects) to get
# reclaimed this way.
self.assertEqual(gc.collect(), 4)
self.assertEqual(gc.collect(), 2)
self.assertEqual(len(gc.garbage), garbagelen)
def test_boom_new(self):
@ -491,7 +491,7 @@ class GCTests(unittest.TestCase):
gc.collect()
garbagelen = len(gc.garbage)
del a, b
self.assertEqual(gc.collect(), 4)
self.assertEqual(gc.collect(), 2)
self.assertEqual(len(gc.garbage), garbagelen)
def test_boom2_new(self):
@ -513,7 +513,7 @@ class GCTests(unittest.TestCase):
gc.collect()
garbagelen = len(gc.garbage)
del a, b
self.assertEqual(gc.collect(), 4)
self.assertEqual(gc.collect(), 2)
self.assertEqual(len(gc.garbage), garbagelen)
def test_get_referents(self):
@ -943,8 +943,8 @@ class GCTests(unittest.TestCase):
A()
t = gc.collect()
c, nc = getstats()
self.assertEqual(t, 2*N) # instance object & its dict
self.assertEqual(c - oldc, 2*N)
self.assertEqual(t, N) # instance objects
self.assertEqual(c - oldc, N)
self.assertEqual(nc - oldnc, 0)
# But Z() is not actually collected.
@ -964,8 +964,8 @@ class GCTests(unittest.TestCase):
Z()
t = gc.collect()
c, nc = getstats()
self.assertEqual(t, 2*N)
self.assertEqual(c - oldc, 2*N)
self.assertEqual(t, N)
self.assertEqual(c - oldc, N)
self.assertEqual(nc - oldnc, 0)
# The A() trash should have been reclaimed already but the
@ -974,8 +974,8 @@ class GCTests(unittest.TestCase):
zs.clear()
t = gc.collect()
c, nc = getstats()
self.assertEqual(t, 4)
self.assertEqual(c - oldc, 4)
self.assertEqual(t, 2)
self.assertEqual(c - oldc, 2)
self.assertEqual(nc - oldnc, 0)
gc.enable()
@ -1128,8 +1128,7 @@ class GCCallbackTests(unittest.TestCase):
@cpython_only
def test_collect_garbage(self):
self.preclean()
# Each of these cause four objects to be garbage: Two
# Uncollectables and their instance dicts.
# Each of these cause two objects to be garbage:
Uncollectable()
Uncollectable()
C1055820(666)
@ -1138,8 +1137,8 @@ class GCCallbackTests(unittest.TestCase):
if v[1] != "stop":
continue
info = v[2]
self.assertEqual(info["collected"], 2)
self.assertEqual(info["uncollectable"], 8)
self.assertEqual(info["collected"], 1)
self.assertEqual(info["uncollectable"], 4)
# We should now have the Uncollectables in gc.garbage
self.assertEqual(len(gc.garbage), 4)
@ -1156,7 +1155,7 @@ class GCCallbackTests(unittest.TestCase):
continue
info = v[2]
self.assertEqual(info["collected"], 0)
self.assertEqual(info["uncollectable"], 4)
self.assertEqual(info["uncollectable"], 2)
# Uncollectables should be gone
self.assertEqual(len(gc.garbage), 0)

View File

@ -1409,7 +1409,7 @@ class SizeofTest(unittest.TestCase):
check((1,2,3), vsize('') + 3*self.P)
# type
# static type: PyTypeObject
fmt = 'P2nPI13Pl4Pn9Pn11PIPP'
fmt = 'P2nPI13Pl4Pn9Pn12PIPP'
s = vsize(fmt)
check(int, s)
# class
@ -1422,15 +1422,15 @@ class SizeofTest(unittest.TestCase):
'5P')
class newstyleclass(object): pass
# Separate block for PyDictKeysObject with 8 keys and 5 entries
check(newstyleclass, s + calcsize(DICT_KEY_STRUCT_FORMAT) + 8 + 5*calcsize("n2P"))
check(newstyleclass, s + calcsize(DICT_KEY_STRUCT_FORMAT) + 32 + 21*calcsize("n2P"))
# dict with shared keys
check(newstyleclass().__dict__, size('nQ2P') + 5*self.P)
check(newstyleclass().__dict__, size('nQ2P') + 15*self.P)
o = newstyleclass()
o.a = o.b = o.c = o.d = o.e = o.f = o.g = o.h = 1
# Separate block for PyDictKeysObject with 16 keys and 10 entries
check(newstyleclass, s + calcsize(DICT_KEY_STRUCT_FORMAT) + 16 + 10*calcsize("n2P"))
check(newstyleclass, s + calcsize(DICT_KEY_STRUCT_FORMAT) + 32 + 21*calcsize("n2P"))
# dict with shared keys
check(newstyleclass().__dict__, size('nQ2P') + 10*self.P)
check(newstyleclass().__dict__, size('nQ2P') + 13*self.P)
# unicode
# each tuple contains a string and its expected character size
# don't put any static strings here, as they may contain

View File

@ -0,0 +1,3 @@
Object attributes are held in an array instead of a dictionary. An object's
dictionary are created lazily, only when needed. Reduces the memory
consumption of a typical Python object by about 30%. Patch by Mark Shannon.

View File

@ -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;

View File

@ -5,6 +5,7 @@
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
#include "pycore_context.h"
#include "pycore_dict.h"
#include "pycore_initconfig.h" // _PyStatus_EXCEPTION()
#include "pycore_object.h" // _PyType_CheckConsistency()
#include "pycore_pyerrors.h" // _PyErr_Occurred()
@ -1065,10 +1066,8 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
return -1;
}
/* Helper to get a pointer to an object's __dict__ slot, if any */
PyObject **
_PyObject_GetDictPtr(PyObject *obj)
_PyObject_DictPointer(PyObject *obj)
{
Py_ssize_t dictoffset;
PyTypeObject *tp = Py_TYPE(obj);
@ -1090,6 +1089,35 @@ _PyObject_GetDictPtr(PyObject *obj)
return (PyObject **) ((char *)obj + dictoffset);
}
/* Helper to get a pointer to an object's __dict__ slot, if any.
* Creates the dict from inline attributes if necessary.
* Does not set an exception. */
PyObject **
_PyObject_GetDictPtr(PyObject *obj)
{
PyObject **dict_ptr = _PyObject_DictPointer(obj);
if (dict_ptr == NULL) {
return NULL;
}
if (*dict_ptr != NULL) {
return dict_ptr;
}
PyDictValues **values_ptr = _PyObject_ValuesPointer(obj);
if (values_ptr == NULL || *values_ptr == NULL) {
return dict_ptr;
}
PyObject *dict = _PyObject_MakeDictFromInstanceAttributes(obj, *values_ptr);
if (dict == NULL) {
PyErr_Clear();
return NULL;
}
assert(*dict_ptr == NULL);
assert(*values_ptr != NULL);
*values_ptr = NULL;
*dict_ptr = dict;
return dict_ptr;
}
PyObject *
PyObject_SelfIter(PyObject *obj)
{
@ -1136,7 +1164,7 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
}
}
if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_Check(name)) {
if (tp->tp_getattro != PyObject_GenericGetAttr || !PyUnicode_CheckExact(name)) {
*method = PyObject_GetAttr(obj, name);
return 0;
}
@ -1156,23 +1184,34 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
}
}
}
PyObject **dictptr = _PyObject_GetDictPtr(obj);
PyObject *dict;
if (dictptr != NULL && (dict = *dictptr) != NULL) {
Py_INCREF(dict);
PyObject *attr = PyDict_GetItemWithError(dict, name);
PyDictValues **values_ptr = _PyObject_ValuesPointer(obj);
if (values_ptr && *values_ptr) {
assert(*_PyObject_DictPointer(obj) == NULL);
PyObject *attr = _PyObject_GetInstanceAttribute(obj, *values_ptr, name);
if (attr != NULL) {
*method = Py_NewRef(attr);
Py_DECREF(dict);
*method = attr;
Py_XDECREF(descr);
return 0;
}
Py_DECREF(dict);
}
else {
PyObject **dictptr = _PyObject_DictPointer(obj);
PyObject *dict;
if (dictptr != NULL && (dict = *dictptr) != NULL) {
Py_INCREF(dict);
PyObject *attr = PyDict_GetItemWithError(dict, name);
if (attr != NULL) {
*method = Py_NewRef(attr);
Py_DECREF(dict);
Py_XDECREF(descr);
return 0;
}
Py_DECREF(dict);
if (PyErr_Occurred()) {
Py_XDECREF(descr);
return 0;
if (PyErr_Occurred()) {
Py_XDECREF(descr);
return 0;
}
}
}
@ -1200,6 +1239,17 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
return 0;
}
PyDictValues **
_PyObject_ValuesPointer(PyObject *obj)
{
PyTypeObject *tp = Py_TYPE(obj);
Py_ssize_t offset = tp->tp_inline_values_offset;
if (offset == 0) {
return NULL;
}
return (PyDictValues **) ((char *)obj + offset);
}
/* Generic GetAttr functions - put these in your tp_[gs]etattro slot. */
PyObject *
@ -1247,25 +1297,46 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
goto done;
}
}
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
dictoffset = tp->tp_dictoffset;
if (dictoffset != 0) {
if (dictoffset < 0) {
Py_ssize_t tsize = Py_SIZE(obj);
if (tsize < 0) {
tsize = -tsize;
PyDictValues **values_ptr = _PyObject_ValuesPointer(obj);
if (values_ptr && *values_ptr) {
if (PyUnicode_CheckExact(name)) {
assert(*_PyObject_DictPointer(obj) == NULL);
res = _PyObject_GetInstanceAttribute(obj, *values_ptr, name);
if (res != NULL) {
goto done;
}
size_t size = _PyObject_VAR_SIZE(tp, tsize);
_PyObject_ASSERT(obj, size <= PY_SSIZE_T_MAX);
dictoffset += (Py_ssize_t)size;
_PyObject_ASSERT(obj, dictoffset > 0);
_PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
}
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
else {
dictptr = _PyObject_DictPointer(obj);
assert(dictptr != NULL && *dictptr == NULL);
*dictptr = dict = _PyObject_MakeDictFromInstanceAttributes(obj, *values_ptr);
if (dict == NULL) {
res = NULL;
goto done;
}
*values_ptr = NULL;
}
}
else {
/* Inline _PyObject_DictPointer */
dictoffset = tp->tp_dictoffset;
if (dictoffset != 0) {
if (dictoffset < 0) {
Py_ssize_t tsize = Py_SIZE(obj);
if (tsize < 0) {
tsize = -tsize;
}
size_t size = _PyObject_VAR_SIZE(tp, tsize);
_PyObject_ASSERT(obj, size <= PY_SSIZE_T_MAX);
dictoffset += (Py_ssize_t)size;
_PyObject_ASSERT(obj, dictoffset > 0);
_PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
}
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
}
}
}
if (dict != NULL) {
@ -1328,7 +1399,6 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
PyTypeObject *tp = Py_TYPE(obj);
PyObject *descr;
descrsetfunc f;
PyObject **dictptr;
int res = -1;
if (!PyUnicode_Check(name)){
@ -1354,30 +1424,30 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
}
}
/* XXX [Steve Dower] These are really noisy - worth it? */
/*if (PyType_Check(obj) || PyModule_Check(obj)) {
if (value && PySys_Audit("object.__setattr__", "OOO", obj, name, value) < 0)
return -1;
if (!value && PySys_Audit("object.__delattr__", "OO", obj, name) < 0)
return -1;
}*/
if (dict == NULL) {
dictptr = _PyObject_GetDictPtr(obj);
if (dictptr == NULL) {
if (descr == NULL) {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
tp->tp_name, name);
PyDictValues **values_ptr = _PyObject_ValuesPointer(obj);
if (values_ptr && *values_ptr) {
res = _PyObject_StoreInstanceAttribute(obj, *values_ptr, name, value);
}
else {
PyObject **dictptr = _PyObject_DictPointer(obj);
if (dictptr == NULL) {
if (descr == NULL) {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
tp->tp_name, name);
}
else {
PyErr_Format(PyExc_AttributeError,
"'%.50s' object attribute '%U' is read-only",
tp->tp_name, name);
}
goto done;
}
else {
PyErr_Format(PyExc_AttributeError,
"'%.50s' object attribute '%U' is read-only",
tp->tp_name, name);
res = _PyObjectDict_SetItem(tp, dictptr, name, value);
}
goto done;
}
res = _PyObjectDict_SetItem(tp, dictptr, name, value);
}
else {
Py_INCREF(dict);
@ -1407,8 +1477,15 @@ PyObject_GenericSetDict(PyObject *obj, PyObject *value, void *context)
{
PyObject **dictptr = _PyObject_GetDictPtr(obj);
if (dictptr == NULL) {
PyErr_SetString(PyExc_AttributeError,
"This object has no __dict__");
PyDictValues** values_ptr = _PyObject_ValuesPointer(obj);
if (values_ptr != NULL && *values_ptr != NULL) {
/* Was unable to convert to dict */
PyErr_NoMemory();
}
else {
PyErr_SetString(PyExc_AttributeError,
"This object has no __dict__");
}
return -1;
}
if (value == NULL) {

View File

@ -1232,8 +1232,16 @@ subtype_traverse(PyObject *self, visitproc visit, void *arg)
assert(base);
}
if (type->tp_inline_values_offset) {
assert(type->tp_dictoffset);
int err = _PyObject_VisitInstanceAttributes(self, visit, arg);
if (err) {
return err;
}
}
if (type->tp_dictoffset != base->tp_dictoffset) {
PyObject **dictptr = _PyObject_GetDictPtr(self);
PyObject **dictptr = _PyObject_DictPointer(self);
if (dictptr && *dictptr)
Py_VISIT(*dictptr);
}
@ -1293,8 +1301,11 @@ subtype_clear(PyObject *self)
/* Clear the instance dict (if any), to break cycles involving only
__dict__ slots (as in the case 'self.__dict__ is self'). */
if (type->tp_inline_values_offset) {
_PyObject_ClearInstanceAttributes(self);
}
if (type->tp_dictoffset != base->tp_dictoffset) {
PyObject **dictptr = _PyObject_GetDictPtr(self);
PyObject **dictptr = _PyObject_DictPointer(self);
if (dictptr && *dictptr)
Py_CLEAR(*dictptr);
}
@ -1433,9 +1444,12 @@ subtype_dealloc(PyObject *self)
assert(base);
}
/* If we added a dict, DECREF it */
/* If we added a dict, DECREF it, or free inline values. */
if (type->tp_inline_values_offset) {
_PyObject_FreeInstanceAttributes(self);
}
if (type->tp_dictoffset && !base->tp_dictoffset) {
PyObject **dictptr = _PyObject_GetDictPtr(self);
PyObject **dictptr = _PyObject_DictPointer(self);
if (dictptr != NULL) {
PyObject *dict = *dictptr;
if (dict != NULL) {
@ -2159,7 +2173,6 @@ mro_internal(PyTypeObject *type, PyObject **p_old_mro)
return 1;
}
/* Calculate the best base amongst multiple base classes.
This is the first one that's on the path to the "solid base". */
@ -2230,6 +2243,10 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base)
return t_size != b_size ||
type->tp_itemsize != base->tp_itemsize;
}
if (type->tp_inline_values_offset && base->tp_inline_values_offset == 0 &&
type->tp_inline_values_offset + sizeof(PyDictValues *) == t_size &&
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
t_size -= sizeof(PyDictValues *);
if (type->tp_weaklistoffset && base->tp_weaklistoffset == 0 &&
type->tp_weaklistoffset + sizeof(PyObject *) == t_size &&
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
@ -2238,7 +2255,6 @@ extra_ivars(PyTypeObject *type, PyTypeObject *base)
type->tp_dictoffset + sizeof(PyObject *) == t_size &&
type->tp_flags & Py_TPFLAGS_HEAPTYPE)
t_size -= sizeof(PyObject *);
return t_size != b_size;
}
@ -2258,6 +2274,7 @@ solid_base(PyTypeObject *type)
}
static void object_dealloc(PyObject *);
static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *);
static int object_init(PyObject *, PyObject *, PyObject *);
static int update_slot(PyTypeObject *, PyObject *);
static void fixup_slot_dispatchers(PyTypeObject *);
@ -2979,6 +2996,13 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type)
type->tp_weaklistoffset = slotoffset;
slotoffset += sizeof(PyObject *);
}
if (type->tp_dictoffset > 0) {
type->tp_inline_values_offset = slotoffset;
slotoffset += sizeof(PyDictValues *);
}
else {
type->tp_inline_values_offset = 0;
}
type->tp_basicsize = slotoffset;
type->tp_itemsize = ctx->base->tp_itemsize;
@ -3181,7 +3205,8 @@ type_new_impl(type_new_ctx *ctx)
// Put the proper slots in place
fixup_slot_dispatchers(type);
if (type->tp_dictoffset) {
if (type->tp_inline_values_offset) {
assert(type->tp_dictoffset > 0);
PyHeapTypeObject *et = (PyHeapTypeObject*)type;
et->ht_cached_keys = _PyDict_NewKeysForClass();
}
@ -3195,6 +3220,7 @@ type_new_impl(type_new_ctx *ctx)
}
assert(_PyType_CheckConsistency(type));
return (PyObject *)type;
error:
@ -3550,7 +3576,8 @@ PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
if (PyType_Ready(type) < 0)
goto fail;
if (type->tp_dictoffset) {
if (type->tp_inline_values_offset) {
assert(type->tp_dictoffset > 0);
res->ht_cached_keys = _PyDict_NewKeysForClass();
}
@ -4257,7 +4284,6 @@ type_traverse(PyTypeObject *type, visitproc visit, void *arg)
static int
type_clear(PyTypeObject *type)
{
PyDictKeysObject *cached_keys;
/* Because of type_is_gc(), the collector only calls this
for heaptypes. */
_PyObject_ASSERT((PyObject *)type, type->tp_flags & Py_TPFLAGS_HEAPTYPE);
@ -4292,11 +4318,6 @@ type_clear(PyTypeObject *type)
*/
PyType_Modified(type);
cached_keys = ((PyHeapTypeObject *)type)->ht_cached_keys;
if (cached_keys != NULL) {
((PyHeapTypeObject *)type)->ht_cached_keys = NULL;
_PyDictKeys_DecRef(cached_keys);
}
if (type->tp_dict) {
PyDict_Clear(type->tp_dict);
}
@ -4618,6 +4639,7 @@ compatible_with_tp_base(PyTypeObject *child)
child->tp_itemsize == parent->tp_itemsize &&
child->tp_dictoffset == parent->tp_dictoffset &&
child->tp_weaklistoffset == parent->tp_weaklistoffset &&
child->tp_inline_values_offset == parent->tp_inline_values_offset &&
((child->tp_flags & Py_TPFLAGS_HAVE_GC) ==
(parent->tp_flags & Py_TPFLAGS_HAVE_GC)) &&
(child->tp_dealloc == subtype_dealloc ||
@ -4637,6 +4659,8 @@ same_slots_added(PyTypeObject *a, PyTypeObject *b)
size += sizeof(PyObject *);
if (a->tp_weaklistoffset == size && b->tp_weaklistoffset == size)
size += sizeof(PyObject *);
if (a->tp_inline_values_offset == size && b->tp_inline_values_offset == size)
size += sizeof(PyObject *);
/* Check slots compliance */
if (!(a->tp_flags & Py_TPFLAGS_HEAPTYPE) ||
@ -4781,6 +4805,17 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
}
if (compatible_for_assignment(oldto, newto, "__class__")) {
/* Changing the class will change the implicit dict keys,
* so we must materialize the dictionary first. */
assert(oldto->tp_inline_values_offset == newto->tp_inline_values_offset);
_PyObject_GetDictPtr(self);
PyDictValues** values_ptr = _PyObject_ValuesPointer(self);
if (values_ptr != NULL && *values_ptr != NULL) {
/* Was unable to convert to dict */
PyErr_NoMemory();
return -1;
}
assert(_PyObject_ValuesPointer(self) == NULL || *_PyObject_ValuesPointer(self) == NULL);
if (newto->tp_flags & Py_TPFLAGS_HEAPTYPE) {
Py_INCREF(newto);
}
@ -4906,23 +4941,16 @@ _PyObject_GetState(PyObject *obj, int required)
Py_TYPE(obj)->tp_name);
return NULL;
}
{
PyObject **dict;
dict = _PyObject_GetDictPtr(obj);
/* It is possible that the object's dict is not initialized
yet. In this case, we will return None for the state.
We also return None if the dict is empty to make the behavior
consistent regardless whether the dict was initialized or not.
This make unit testing easier. */
if (dict != NULL && *dict != NULL && PyDict_GET_SIZE(*dict)) {
state = *dict;
}
else {
state = Py_None;
}
if (_PyObject_IsInstanceDictEmpty(obj)) {
state = Py_None;
Py_INCREF(state);
}
else {
state = PyObject_GenericGetDict(obj, NULL);
if (state == NULL) {
return NULL;
}
}
slotnames = _PyType_GetSlotNames(Py_TYPE(obj));
if (slotnames == NULL) {
@ -4933,12 +4961,18 @@ _PyObject_GetState(PyObject *obj, int required)
assert(slotnames == Py_None || PyList_Check(slotnames));
if (required) {
Py_ssize_t basicsize = PyBaseObject_Type.tp_basicsize;
if (Py_TYPE(obj)->tp_dictoffset)
if (Py_TYPE(obj)->tp_dictoffset) {
basicsize += sizeof(PyObject *);
if (Py_TYPE(obj)->tp_weaklistoffset)
}
if (Py_TYPE(obj)->tp_weaklistoffset) {
basicsize += sizeof(PyObject *);
if (slotnames != Py_None)
}
if (Py_TYPE(obj)->tp_inline_values_offset) {
basicsize += sizeof(PyDictValues *);
}
if (slotnames != Py_None) {
basicsize += sizeof(PyObject *) * PyList_GET_SIZE(slotnames);
}
if (Py_TYPE(obj)->tp_basicsize > basicsize) {
Py_DECREF(slotnames);
Py_DECREF(state);
@ -5708,6 +5742,7 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
COPYVAL(tp_itemsize);
COPYVAL(tp_weaklistoffset);
COPYVAL(tp_dictoffset);
COPYVAL(tp_inline_values_offset);
#undef COPYVAL
/* Setup fast subclass flags */

View File

@ -3618,7 +3618,7 @@ check_eval_breaker:
}
}
TARGET(LOAD_ATTR_SPLIT_KEYS) {
TARGET(LOAD_ATTR_INSTANCE_VALUE) {
assert(cframe.use_tracing == 0);
PyObject *owner = TOP();
PyObject *res;
@ -3629,11 +3629,10 @@ check_eval_breaker:
assert(cache1->tp_version != 0);
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, LOAD_ATTR);
assert(tp->tp_dictoffset > 0);
PyDictObject *dict = *(PyDictObject **)(((char *)owner) + tp->tp_dictoffset);
DEOPT_IF(dict == NULL, LOAD_ATTR);
assert(PyDict_CheckExact((PyObject *)dict));
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, LOAD_ATTR);
res = dict->ma_values->values[cache0->index];
assert(tp->tp_inline_values_offset > 0);
PyDictValues *values = *(PyDictValues **)(((char *)owner) + tp->tp_inline_values_offset);
DEOPT_IF(values == NULL, LOAD_ATTR);
res = values->values[cache0->index];
DEOPT_IF(res == NULL, LOAD_ATTR);
STAT_INC(LOAD_ATTR, hit);
record_cache_hit(cache0);
@ -3725,7 +3724,7 @@ check_eval_breaker:
}
}
TARGET(STORE_ATTR_SPLIT_KEYS) {
TARGET(STORE_ATTR_INSTANCE_VALUE) {
assert(cframe.use_tracing == 0);
PyObject *owner = TOP();
PyTypeObject *tp = Py_TYPE(owner);
@ -3735,31 +3734,23 @@ check_eval_breaker:
assert(cache1->tp_version != 0);
DEOPT_IF(tp->tp_version_tag != cache1->tp_version, STORE_ATTR);
assert(tp->tp_dictoffset > 0);
PyDictObject *dict = *(PyDictObject **)(((char *)owner) + tp->tp_dictoffset);
DEOPT_IF(dict == NULL, STORE_ATTR);
assert(PyDict_CheckExact((PyObject *)dict));
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, STORE_ATTR);
assert(tp->tp_inline_values_offset > 0);
PyDictValues *values = *(PyDictValues **)(((char *)owner) + tp->tp_inline_values_offset);
DEOPT_IF(values == NULL, STORE_ATTR);
STAT_INC(STORE_ATTR, hit);
record_cache_hit(cache0);
int index = cache0->index;
STACK_SHRINK(1);
PyObject *value = POP();
PyObject *old_value = dict->ma_values->values[index];
dict->ma_values->values[index] = value;
PyObject *old_value = values->values[index];
values->values[index] = value;
if (old_value == NULL) {
assert(index < 16);
dict->ma_values->mv_order = (dict->ma_values->mv_order << 4) | index;
dict->ma_used++;
values->mv_order = (values->mv_order << 4) | index;
}
else {
Py_DECREF(old_value);
}
/* Ensure dict is GC tracked if it needs to be */
if (!_PyObject_GC_IS_TRACKED(dict) && _PyObject_GC_MAY_BE_TRACKED(value)) {
_PyObject_GC_TRACK(dict);
}
/* PEP 509 */
dict->ma_version_tag = DICT_NEXT_VERSION();
Py_DECREF(owner);
DISPATCH();
}
@ -4474,21 +4465,31 @@ check_eval_breaker:
_PyObjectCache *cache2 = &caches[-2].obj;
DEOPT_IF(self_cls->tp_version_tag != cache1->tp_version, LOAD_METHOD);
assert(cache1->dk_version_or_hint != 0);
assert(cache1->tp_version != 0);
assert(self_cls->tp_dictoffset >= 0);
assert(Py_TYPE(self_cls)->tp_dictoffset > 0);
// inline version of _PyObject_GetDictPtr for offset >= 0
PyObject *dict = self_cls->tp_dictoffset != 0 ?
*(PyObject **) ((char *)self + self_cls->tp_dictoffset) : NULL;
// Ensure self.__dict__ didn't modify keys.
// Don't care if self has no dict, it could be builtin or __slots__.
DEOPT_IF(dict != NULL &&
((PyDictObject *)dict)->ma_keys->dk_version !=
cache1->dk_version_or_hint, LOAD_METHOD);
assert(self_cls->tp_dictoffset > 0);
assert(self_cls->tp_inline_values_offset > 0);
PyDictObject *dict = *(PyDictObject **)(((char *)self) + self_cls->tp_dictoffset);
DEOPT_IF(dict != NULL, LOAD_METHOD);
DEOPT_IF(((PyHeapTypeObject *)self_cls)->ht_cached_keys->dk_version != cache1->dk_version_or_hint, LOAD_METHOD);
STAT_INC(LOAD_METHOD, hit);
record_cache_hit(cache0);
PyObject *res = cache2->obj;
assert(res != NULL);
assert(_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR));
Py_INCREF(res);
SET_TOP(res);
PUSH(self);
DISPATCH();
}
TARGET(LOAD_METHOD_NO_DICT) {
PyObject *self = TOP();
PyTypeObject *self_cls = Py_TYPE(self);
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyAttrCache *cache1 = &caches[-1].attr;
_PyObjectCache *cache2 = &caches[-2].obj;
DEOPT_IF(self_cls->tp_version_tag != cache1->tp_version, LOAD_METHOD);
assert(self_cls->tp_dictoffset == 0);
STAT_INC(LOAD_METHOD, hit);
record_cache_hit(cache0);
PyObject *res = cache2->obj;
@ -4530,7 +4531,6 @@ check_eval_breaker:
record_cache_hit(cache0);
PyObject *res = cache2->obj;
assert(res != NULL);
assert(_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR));
Py_INCREF(res);
SET_TOP(NULL);
Py_DECREF(cls);

View File

@ -41,7 +41,7 @@ static void *opcode_targets[256] = {
&&TARGET_BINARY_SUBSCR_DICT,
&&TARGET_JUMP_ABSOLUTE_QUICK,
&&TARGET_LOAD_ATTR_ADAPTIVE,
&&TARGET_LOAD_ATTR_SPLIT_KEYS,
&&TARGET_LOAD_ATTR_INSTANCE_VALUE,
&&TARGET_LOAD_ATTR_WITH_HINT,
&&TARGET_LOAD_ATTR_SLOT,
&&TARGET_LOAD_ATTR_MODULE,
@ -87,7 +87,7 @@ static void *opcode_targets[256] = {
&&TARGET_SETUP_ANNOTATIONS,
&&TARGET_YIELD_VALUE,
&&TARGET_LOAD_METHOD_MODULE,
&&TARGET_STORE_ATTR_ADAPTIVE,
&&TARGET_LOAD_METHOD_NO_DICT,
&&TARGET_POP_EXCEPT,
&&TARGET_STORE_NAME,
&&TARGET_DELETE_NAME,
@ -119,36 +119,36 @@ static void *opcode_targets[256] = {
&&TARGET_IS_OP,
&&TARGET_CONTAINS_OP,
&&TARGET_RERAISE,
&&TARGET_STORE_ATTR_SPLIT_KEYS,
&&TARGET_STORE_ATTR_ADAPTIVE,
&&TARGET_JUMP_IF_NOT_EXC_MATCH,
&&TARGET_STORE_ATTR_INSTANCE_VALUE,
&&TARGET_STORE_ATTR_SLOT,
&&TARGET_STORE_ATTR_WITH_HINT,
&&TARGET_LOAD_FAST,
&&TARGET_STORE_FAST,
&&TARGET_DELETE_FAST,
&&TARGET_STORE_ATTR_WITH_HINT,
&&TARGET_LOAD_FAST__LOAD_FAST,
&&TARGET_STORE_FAST__LOAD_FAST,
&&TARGET_GEN_START,
&&TARGET_RAISE_VARARGS,
&&TARGET_CALL_FUNCTION,
&&TARGET_MAKE_FUNCTION,
&&TARGET_BUILD_SLICE,
&&TARGET_LOAD_FAST__LOAD_CONST,
&&TARGET_STORE_FAST__LOAD_FAST,
&&TARGET_MAKE_CELL,
&&TARGET_LOAD_CLOSURE,
&&TARGET_LOAD_DEREF,
&&TARGET_STORE_DEREF,
&&TARGET_DELETE_DEREF,
&&TARGET_LOAD_CONST__LOAD_FAST,
&&TARGET_LOAD_FAST__LOAD_CONST,
&&TARGET_CALL_FUNCTION_KW,
&&TARGET_CALL_FUNCTION_EX,
&&TARGET_STORE_FAST__STORE_FAST,
&&TARGET_LOAD_CONST__LOAD_FAST,
&&TARGET_EXTENDED_ARG,
&&TARGET_LIST_APPEND,
&&TARGET_SET_ADD,
&&TARGET_MAP_ADD,
&&TARGET_LOAD_CLASSDEREF,
&&_unknown_opcode,
&&TARGET_STORE_FAST__STORE_FAST,
&&_unknown_opcode,
&&_unknown_opcode,
&&TARGET_MATCH_CLASS,

View File

@ -4,6 +4,7 @@
#include "pycore_dict.h"
#include "pycore_long.h"
#include "pycore_moduleobject.h"
#include "pycore_object.h"
#include "opcode.h"
#include "structmember.h" // struct PyMemberDef, T_OFFSET_EX
@ -462,6 +463,7 @@ specialize_module_load_attr(
PyObject *value = NULL;
PyObject *getattr;
_Py_IDENTIFIER(__getattr__);
assert(owner->ob_type->tp_inline_values_offset == 0);
PyDictObject *dict = (PyDictObject *)m->md_dict;
if (dict == NULL) {
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_NO_DICT);
@ -584,7 +586,7 @@ specialize_dict_access(
PyObject *owner, _Py_CODEUNIT *instr, PyTypeObject *type,
DesciptorClassification kind, PyObject *name,
_PyAdaptiveEntry *cache0, _PyAttrCache *cache1,
int base_op, int split_op, int hint_op)
int base_op, int values_op, int hint_op)
{
assert(kind == NON_OVERRIDING || kind == NON_DESCRIPTOR || kind == ABSENT ||
kind == BUILTIN_CLASSMETHOD || kind == PYTHON_CLASSMETHOD);
@ -595,17 +597,11 @@ specialize_dict_access(
}
if (type->tp_dictoffset > 0) {
PyObject **dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
if (*dictptr == NULL || !PyDict_CheckExact(*dictptr)) {
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_NO_DICT);
return 0;
}
// We found an instance with a __dict__.
PyDictObject *dict = (PyDictObject *)*dictptr;
PyDictKeysObject *keys = dict->ma_keys;
if ((type->tp_flags & Py_TPFLAGS_HEAPTYPE)
&& keys == ((PyHeapTypeObject*)type)->ht_cached_keys
) {
// Keys are shared
if (type->tp_inline_values_offset && dict == NULL) {
// Virtual dictionary
PyDictKeysObject *keys = ((PyHeapTypeObject *)type)->ht_cached_keys;
assert(type->tp_inline_values_offset > 0);
assert(PyUnicode_CheckExact(name));
Py_ssize_t index = _PyDictKeys_StringLookup(keys, name);
assert (index != DKIX_ERROR);
@ -613,18 +609,17 @@ specialize_dict_access(
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_OUT_OF_RANGE);
return 0;
}
uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(keys);
if (keys_version == 0) {
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_OUT_OF_VERSIONS);
return 0;
}
cache1->dk_version_or_hint = keys_version;
cache1->tp_version = type->tp_version_tag;
cache0->index = (uint16_t)index;
*instr = _Py_MAKECODEUNIT(split_op, _Py_OPARG(*instr));
*instr = _Py_MAKECODEUNIT(values_op, _Py_OPARG(*instr));
return 0;
}
else {
if (dict == NULL || !PyDict_CheckExact(dict)) {
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_NO_DICT);
return 0;
}
// We found an instance with a __dict__.
PyObject *value = NULL;
Py_ssize_t hint =
_PyDict_GetItemHint(dict, name, -1, &value);
@ -736,7 +731,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
}
int err = specialize_dict_access(
owner, instr, type, kind, name, cache0, cache1,
LOAD_ATTR, LOAD_ATTR_SPLIT_KEYS, LOAD_ATTR_WITH_HINT
LOAD_ATTR, LOAD_ATTR_INSTANCE_VALUE, LOAD_ATTR_WITH_HINT
);
if (err < 0) {
return -1;
@ -818,7 +813,7 @@ _Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, S
int err = specialize_dict_access(
owner, instr, type, kind, name, cache0, cache1,
STORE_ATTR, STORE_ATTR_SPLIT_KEYS, STORE_ATTR_WITH_HINT
STORE_ATTR, STORE_ATTR_INSTANCE_VALUE, STORE_ATTR_WITH_HINT
);
if (err < 0) {
return -1;
@ -875,6 +870,27 @@ load_method_fail_kind(DesciptorClassification kind)
}
#endif
static int
specialize_class_load_method(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
_PyAttrCache *cache1, _PyObjectCache *cache2)
{
PyObject *descr = NULL;
DesciptorClassification kind = 0;
kind = analyze_descriptor((PyTypeObject *)owner, name, &descr, 0);
switch (kind) {
case METHOD:
case NON_DESCRIPTOR:
cache1->tp_version = ((PyTypeObject *)owner)->tp_version_tag;
cache2->obj = descr;
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_CLASS, _Py_OPARG(*instr));
return 0;
default:
SPECIALIZATION_FAIL(LOAD_METHOD, load_method_fail_kind(kind));
return -1;
}
}
// Please collect stats carefully before and after modifying. A subtle change
// can cause a significant drop in cache hits. A possible test is
// python.exe -m test_typing test_re test_dis test_zlib.
@ -886,7 +902,6 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
_PyObjectCache *cache2 = &cache[-2].obj;
PyTypeObject *owner_cls = Py_TYPE(owner);
PyDictObject *owner_dict = NULL;
if (PyModule_CheckExact(owner)) {
int err = specialize_module_load_attr(owner, instr, name, cache0, cache1,
LOAD_METHOD, LOAD_METHOD_MODULE);
@ -900,9 +915,12 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
return -1;
}
}
if (Py_TYPE(owner_cls)->tp_dictoffset < 0) {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_OUT_OF_RANGE);
goto fail;
if (PyType_Check(owner)) {
int err = specialize_class_load_method(owner, instr, name, cache1, cache2);
if (err) {
goto fail;
}
goto success;
}
// Technically this is fine for bound method calls, but it's uncommon and
// slightly slower at runtime to get dict.
@ -910,66 +928,45 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_OUT_OF_RANGE);
goto fail;
}
PyObject **owner_dictptr = _PyObject_GetDictPtr(owner);
int owner_has_dict = (owner_dictptr != NULL && *owner_dictptr != NULL);
owner_dict = owner_has_dict ? (PyDictObject *)*owner_dictptr : NULL;
// Make sure dict doesn't get GC-ed halfway.
Py_XINCREF(owner_dict);
// Check for classmethods.
int owner_is_class = PyType_Check(owner);
owner_cls = owner_is_class ? (PyTypeObject *)owner : owner_cls;
if ((owner_cls->tp_flags & Py_TPFLAGS_VALID_VERSION_TAG) == 0 ||
owner_cls->tp_version_tag == 0) {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_OUT_OF_VERSIONS);
goto fail;
}
PyObject *descr = NULL;
DesciptorClassification kind = 0;
kind = analyze_descriptor(owner_cls, name, &descr, 0);
// Store the version right away, in case it's modified halfway through.
cache1->tp_version = owner_cls->tp_version_tag;
assert(descr != NULL || kind == ABSENT || kind == GETSET_OVERRIDDEN);
if (kind != METHOD) {
SPECIALIZATION_FAIL(LOAD_METHOD, load_method_fail_kind(kind));
goto fail;
}
// If o.__dict__ changes, the method might be found in o.__dict__
// instead of old type lookup. So record o.__dict__'s keys.
uint32_t keys_version = UINT32_MAX;
if (owner_has_dict) {
// _PyDictKeys_GetVersionForCurrentState isn't accurate for
// custom dict subclasses at the moment.
if (!PyDict_CheckExact(owner_dict)) {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_DICT_SUBCLASS);
if (owner_cls->tp_inline_values_offset) {
PyObject **owner_dictptr = _PyObject_DictPointer(owner);
assert(owner_dictptr);
if (*owner_dictptr) {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_IS_ATTR);
goto fail;
}
assert(PyUnicode_CheckExact(name));
Py_hash_t hash = PyObject_Hash(name);
if (hash == -1) {
return -1;
PyDictKeysObject *keys = ((PyHeapTypeObject *)owner_cls)->ht_cached_keys;
Py_ssize_t index = _PyDictKeys_StringLookup(keys, name);
if (index != DKIX_EMPTY) {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_IS_ATTR);
goto fail;
}
PyObject *value = NULL;
if (!owner_is_class) {
// Instance methods shouldn't be in o.__dict__. That makes
// it an attribute.
Py_ssize_t ix = _Py_dict_lookup(owner_dict, name, hash, &value);
assert(ix != DKIX_ERROR);
if (ix != DKIX_EMPTY) {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_IS_ATTR);
goto fail;
}
}
keys_version = _PyDictKeys_GetVersionForCurrentState(owner_dict->ma_keys);
uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(keys);
if (keys_version == 0) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_OUT_OF_VERSIONS);
goto fail;
}
// Fall through.
} // Else owner is maybe a builtin with no dict, or __slots__. Doesn't matter.
cache1->dk_version_or_hint = keys_version;
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_CACHED, _Py_OPARG(*instr));
}
else {
if (owner_cls->tp_dictoffset == 0) {
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_NO_DICT, _Py_OPARG(*instr));
}
else {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_IS_ATTR);
goto fail;
}
}
/* `descr` is borrowed. This is safe for methods (even inherited ones from
* super classes!) as long as tp_version_tag is validated for two main reasons:
*
@ -984,19 +981,15 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
* PyType_Modified usages in typeobject.c). The MCACHE has been
* working since Python 2.6 and it's battle-tested.
*/
cache1->tp_version = owner_cls->tp_version_tag;
cache2->obj = descr;
cache1->dk_version_or_hint = keys_version;
*instr = _Py_MAKECODEUNIT(owner_is_class ? LOAD_METHOD_CLASS :
LOAD_METHOD_CACHED, _Py_OPARG(*instr));
// Fall through.
success:
Py_XDECREF(owner_dict);
STAT_INC(LOAD_METHOD, specialization_success);
assert(!PyErr_Occurred());
cache0->counter = saturating_start();
return 0;
fail:
Py_XDECREF(owner_dict);
STAT_INC(LOAD_METHOD, specialization_failure);
assert(!PyErr_Occurred());
cache_backoff(cache0);

View File

@ -445,10 +445,11 @@ def _write_instance_repr(out, visited, name, pyop_attrdict, address):
out.write(name)
# Write dictionary of instance attributes:
if isinstance(pyop_attrdict, PyDictObjectPtr):
if isinstance(pyop_attrdict, (PyKeysValuesPair, PyDictObjectPtr)):
out.write('(')
first = True
for pyop_arg, pyop_val in pyop_attrdict.iteritems():
items = pyop_attrdict.iteritems()
for pyop_arg, pyop_val in items:
if not first:
out.write(', ')
first = False
@ -520,6 +521,25 @@ class HeapTypeObjectPtr(PyObjectPtr):
# Not found, or some kind of error:
return None
def get_keys_values(self):
typeobj = self.type()
values_offset = int_from_int(typeobj.field('tp_inline_values_offset'))
if values_offset == 0:
return None
charptr = self._gdbval.cast(_type_char_ptr()) + values_offset
PyDictValuesPtrPtr = gdb.lookup_type("PyDictValues").pointer().pointer()
valuesptr = charptr.cast(PyDictValuesPtrPtr)
values = valuesptr.dereference()
if long(values) == 0:
return None
values = values['values']
return PyKeysValuesPair(self.get_cached_keys(), values)
def get_cached_keys(self):
typeobj = self.type()
HeapTypePtr = gdb.lookup_type("PyHeapTypeObject").pointer()
return typeobj._gdbval.cast(HeapTypePtr)['ht_cached_keys']
def proxyval(self, visited):
'''
Support for classes.
@ -533,7 +553,10 @@ class HeapTypeObjectPtr(PyObjectPtr):
visited.add(self.as_address())
pyop_attr_dict = self.get_attr_dict()
if pyop_attr_dict:
keys_values = self.get_keys_values()
if keys_values:
attr_dict = keys_values.proxyval(visited)
elif pyop_attr_dict:
attr_dict = pyop_attr_dict.proxyval(visited)
else:
attr_dict = {}
@ -549,9 +572,11 @@ class HeapTypeObjectPtr(PyObjectPtr):
return
visited.add(self.as_address())
pyop_attrdict = self.get_attr_dict()
pyop_attrs = self.get_keys_values()
if not pyop_attrs:
pyop_attrs = self.get_attr_dict()
_write_instance_repr(out, visited,
self.safe_tp_name(), pyop_attrdict, self.as_address())
self.safe_tp_name(), pyop_attrs, self.as_address())
class ProxyException(Exception):
def __init__(self, tp_name, args):
@ -673,6 +698,32 @@ class PyCodeObjectPtr(PyObjectPtr):
assert False, "Unreachable"
def items_from_keys_and_values(keys, values):
entries, nentries = PyDictObjectPtr._get_entries(keys)
for i in safe_range(nentries):
ep = entries[i]
pyop_value = PyObjectPtr.from_pyobject_ptr(values[i])
if not pyop_value.is_null():
pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
yield (pyop_key, pyop_value)
class PyKeysValuesPair:
def __init__(self, keys, values):
self.keys = keys
self.values = values
def iteritems(self):
return items_from_keys_and_values(self.keys, self.values)
def proxyval(self, visited):
result = {}
for pyop_key, pyop_value in self.iteritems():
proxy_key = pyop_key.proxyval(visited)
proxy_value = pyop_value.proxyval(visited)
result[proxy_key] = proxy_value
return result
class PyDictObjectPtr(PyObjectPtr):
"""
Class wrapping a gdb.Value that's a PyDictObject* i.e. a dict instance
@ -690,13 +741,14 @@ class PyDictObjectPtr(PyObjectPtr):
has_values = long(values)
if has_values:
values = values['values']
if has_values:
for item in items_from_keys_and_values(keys, values):
yield item
return
entries, nentries = self._get_entries(keys)
for i in safe_range(nentries):
ep = entries[i]
if has_values:
pyop_value = PyObjectPtr.from_pyobject_ptr(values[i])
else:
pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
if not pyop_value.is_null():
pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
yield (pyop_key, pyop_value)
@ -732,7 +784,8 @@ class PyDictObjectPtr(PyObjectPtr):
pyop_value.write_repr(out, visited)
out.write('}')
def _get_entries(self, keys):
@staticmethod
def _get_entries(keys):
dk_nentries = int(keys['dk_nentries'])
dk_size = 1<<int(keys['dk_log2_size'])
try:
@ -1958,7 +2011,7 @@ def move_in_stack(move_up):
print('Unable to find an older python frame')
else:
print('Unable to find a newer python frame')
class PyUp(gdb.Command):
'Select and print all python stack frame in the same eval loop starting from the one that called this one (if any)'