Skip to content

Commit d2b707c

Browse files
sweeneyderemykarem
authored andcommitted
bpo-45609: Specialize STORE_SUBSCR (pythonGH-29242)
* Specialize STORE_SUBSCR for list[int], and dict[object] * Adds _PyDict_SetItem_Take2 which consumes references to the key and values.
1 parent 9a63968 commit d2b707c

File tree

10 files changed

+241
-71
lines changed

10 files changed

+241
-71
lines changed

Include/internal/pycore_code.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ int _Py_Specialize_StoreAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *nam
268268
int _Py_Specialize_LoadGlobal(PyObject *globals, PyObject *builtins, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
269269
int _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
270270
int _Py_Specialize_BinarySubscr(PyObject *sub, PyObject *container, _Py_CODEUNIT *instr, SpecializedCacheEntry *cache);
271+
int _Py_Specialize_StoreSubscr(PyObject *container, PyObject *sub, _Py_CODEUNIT *instr);
271272
int _Py_Specialize_CallFunction(PyObject *callable, _Py_CODEUNIT *instr, int nargs, SpecializedCacheEntry *cache, PyObject *builtins);
272273
void _Py_Specialize_BinaryOp(PyObject *lhs, PyObject *rhs, _Py_CODEUNIT *instr,
273274
SpecializedCacheEntry *cache);

Include/internal/pycore_dict.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ typedef struct {
2222
*/
2323
Py_ssize_t _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);
2424

25+
/* Consumes references to key and value */
26+
int _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value);
2527

2628
#define DKIX_EMPTY (-1)
2729
#define DKIX_DUMMY (-2) /* Used internally */

Include/opcode.h

Lines changed: 32 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/opcode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ def jabs_op(name, op):
238238
"BINARY_SUBSCR_LIST_INT",
239239
"BINARY_SUBSCR_TUPLE_INT",
240240
"BINARY_SUBSCR_DICT",
241+
"STORE_SUBSCR_ADAPTIVE",
242+
"STORE_SUBSCR_LIST_INT",
243+
"STORE_SUBSCR_DICT",
241244
"CALL_FUNCTION_ADAPTIVE",
242245
"CALL_FUNCTION_BUILTIN_O",
243246
"CALL_FUNCTION_BUILTIN_FAST",

Lib/test/test_dict.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,14 @@ def _tracked(self, t):
892892
gc.collect()
893893
self.assertTrue(gc.is_tracked(t), t)
894894

895+
def test_string_keys_can_track_values(self):
896+
# Test that this doesn't leak.
897+
for i in range(10):
898+
d = {}
899+
for j in range(10):
900+
d[str(j)] = j
901+
d["foo"] = d
902+
895903
@support.cpython_only
896904
def test_track_literals(self):
897905
# Test GC-optimization of dict literals
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Specialized the ``STORE_SUBSCR`` opcode using the PEP 659 machinery.

Objects/dictobject.c

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,15 +1055,14 @@ insert_into_dictkeys(PyDictKeysObject *keys, PyObject *name)
10551055
Internal routine to insert a new item into the table.
10561056
Used both by the internal resize routine and by the public insert routine.
10571057
Returns -1 if an error occurred, or 0 on success.
1058+
Consumes key and value references.
10581059
*/
10591060
static int
10601061
insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
10611062
{
10621063
PyObject *old_value;
10631064
PyDictKeyEntry *ep;
10641065

1065-
Py_INCREF(key);
1066-
Py_INCREF(value);
10671066
if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) {
10681067
if (insertion_resize(mp) < 0)
10691068
goto Fail;
@@ -1138,6 +1137,7 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
11381137
}
11391138

11401139
// Same to insertdict but specialized for ma_keys = Py_EMPTY_KEYS.
1140+
// Consumes key and value references.
11411141
static int
11421142
insert_to_emptydict(PyDictObject *mp, PyObject *key, Py_hash_t hash,
11431143
PyObject *value)
@@ -1146,6 +1146,8 @@ insert_to_emptydict(PyDictObject *mp, PyObject *key, Py_hash_t hash,
11461146

11471147
PyDictKeysObject *newkeys = new_keys_object(PyDict_LOG_MINSIZE);
11481148
if (newkeys == NULL) {
1149+
Py_DECREF(key);
1150+
Py_DECREF(value);
11491151
return -1;
11501152
}
11511153
if (!PyUnicode_CheckExact(key)) {
@@ -1155,8 +1157,6 @@ insert_to_emptydict(PyDictObject *mp, PyObject *key, Py_hash_t hash,
11551157
mp->ma_keys = newkeys;
11561158
mp->ma_values = NULL;
11571159

1158-
Py_INCREF(key);
1159-
Py_INCREF(value);
11601160
MAINTAIN_TRACKING(mp, key, value);
11611161

11621162
size_t hashpos = (size_t)hash & (PyDict_MINSIZE-1);
@@ -1529,39 +1529,51 @@ _PyDict_LoadGlobal(PyDictObject *globals, PyDictObject *builtins, PyObject *key)
15291529
return value;
15301530
}
15311531

1532-
/* CAUTION: PyDict_SetItem() must guarantee that it won't resize the
1533-
* dictionary if it's merely replacing the value for an existing key.
1534-
* This means that it's safe to loop over a dictionary with PyDict_Next()
1535-
* and occasionally replace a value -- but you can't insert new keys or
1536-
* remove them.
1537-
*/
1532+
/* Consumes references to key and value */
15381533
int
1539-
PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value)
1534+
_PyDict_SetItem_Take2(PyDictObject *mp, PyObject *key, PyObject *value)
15401535
{
1541-
PyDictObject *mp;
1542-
Py_hash_t hash;
1543-
if (!PyDict_Check(op)) {
1544-
PyErr_BadInternalCall();
1545-
return -1;
1546-
}
15471536
assert(key);
15481537
assert(value);
1549-
mp = (PyDictObject *)op;
1538+
assert(PyDict_Check(mp));
1539+
Py_hash_t hash;
15501540
if (!PyUnicode_CheckExact(key) ||
15511541
(hash = ((PyASCIIObject *) key)->hash) == -1)
15521542
{
15531543
hash = PyObject_Hash(key);
1554-
if (hash == -1)
1544+
if (hash == -1) {
1545+
Py_DECREF(key);
1546+
Py_DECREF(value);
15551547
return -1;
1548+
}
15561549
}
1557-
15581550
if (mp->ma_keys == Py_EMPTY_KEYS) {
15591551
return insert_to_emptydict(mp, key, hash, value);
15601552
}
15611553
/* insertdict() handles any resizing that might be necessary */
15621554
return insertdict(mp, key, hash, value);
15631555
}
15641556

1557+
/* CAUTION: PyDict_SetItem() must guarantee that it won't resize the
1558+
* dictionary if it's merely replacing the value for an existing key.
1559+
* This means that it's safe to loop over a dictionary with PyDict_Next()
1560+
* and occasionally replace a value -- but you can't insert new keys or
1561+
* remove them.
1562+
*/
1563+
int
1564+
PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value)
1565+
{
1566+
if (!PyDict_Check(op)) {
1567+
PyErr_BadInternalCall();
1568+
return -1;
1569+
}
1570+
assert(key);
1571+
assert(value);
1572+
Py_INCREF(key);
1573+
Py_INCREF(value);
1574+
return _PyDict_SetItem_Take2((PyDictObject *)op, key, value);
1575+
}
1576+
15651577
int
15661578
_PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value,
15671579
Py_hash_t hash)
@@ -1577,6 +1589,8 @@ _PyDict_SetItem_KnownHash(PyObject *op, PyObject *key, PyObject *value,
15771589
assert(hash != -1);
15781590
mp = (PyDictObject *)op;
15791591

1592+
Py_INCREF(key);
1593+
Py_INCREF(value);
15801594
if (mp->ma_keys == Py_EMPTY_KEYS) {
15811595
return insert_to_emptydict(mp, key, hash, value);
15821596
}
@@ -1917,6 +1931,8 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
19171931
}
19181932

19191933
while (_PyDict_Next(iterable, &pos, &key, &oldvalue, &hash)) {
1934+
Py_INCREF(key);
1935+
Py_INCREF(value);
19201936
if (insertdict(mp, key, hash, value)) {
19211937
Py_DECREF(d);
19221938
return NULL;
@@ -1936,6 +1952,8 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
19361952
}
19371953

19381954
while (_PySet_NextEntry(iterable, &pos, &key, &hash)) {
1955+
Py_INCREF(key);
1956+
Py_INCREF(value);
19391957
if (insertdict(mp, key, hash, value)) {
19401958
Py_DECREF(d);
19411959
return NULL;
@@ -2562,11 +2580,16 @@ dict_merge(PyObject *a, PyObject *b, int override)
25622580
int err = 0;
25632581
Py_INCREF(key);
25642582
Py_INCREF(value);
2565-
if (override == 1)
2583+
if (override == 1) {
2584+
Py_INCREF(key);
2585+
Py_INCREF(value);
25662586
err = insertdict(mp, key, hash, value);
2587+
}
25672588
else {
25682589
err = _PyDict_Contains_KnownHash(a, key, hash);
25692590
if (err == 0) {
2591+
Py_INCREF(key);
2592+
Py_INCREF(value);
25702593
err = insertdict(mp, key, hash, value);
25712594
}
25722595
else if (err > 0) {
@@ -2967,7 +2990,10 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
29672990
if (hash == -1)
29682991
return NULL;
29692992
}
2993+
29702994
if (mp->ma_keys == Py_EMPTY_KEYS) {
2995+
Py_INCREF(key);
2996+
Py_INCREF(defaultobj);
29712997
if (insert_to_emptydict(mp, key, hash, defaultobj) < 0) {
29722998
return NULL;
29732999
}

0 commit comments

Comments
 (0)