Skip to content

Commit b3c2ef5

Browse files
[3.14] gh-134043: use stackrefs for dict lookup in _PyObject_GetMethodStackRef (GH-136412) (#146077)
(cherry picked from commit cbe6ebe) Co-authored-by: Kumar Aditya <kumaraditya@python.org>
1 parent a005f32 commit b3c2ef5

File tree

3 files changed

+91
-19
lines changed

3 files changed

+91
-19
lines changed

Include/internal/pycore_dict.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ extern Py_ssize_t _Py_dict_lookup(PyDictObject *mp, PyObject *key, Py_hash_t has
119119
extern Py_ssize_t _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr);
120120
extern Py_ssize_t _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr);
121121

122+
extern int _PyDict_GetMethodStackRef(PyDictObject *dict, PyObject *name, _PyStackRef *method);
123+
122124
extern Py_ssize_t _PyDict_LookupIndex(PyDictObject *, PyObject *);
123125
extern Py_ssize_t _PyDictKeys_StringLookup(PyDictKeysObject* dictkeys, PyObject *key);
124126

Include/internal/pycore_stackref.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,13 @@ _Py_TryXGetStackRef(PyObject **src, _PyStackRef *out)
782782

783783
#endif
784784

785+
#define PyStackRef_XSETREF(dst, src) \
786+
do { \
787+
_PyStackRef _tmp_dst_ref = (dst); \
788+
(dst) = (src); \
789+
PyStackRef_XCLOSE(_tmp_dst_ref); \
790+
} while(0)
791+
785792
// Like Py_VISIT but for _PyStackRef fields
786793
#define _Py_VISIT_STACKREF(ref) \
787794
do { \

Objects/dictobject.c

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,34 +1584,49 @@ _Py_dict_lookup_threadsafe(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyOb
15841584
return ix;
15851585
}
15861586

1587+
static Py_ssize_t
1588+
lookup_threadsafe_unicode(PyDictKeysObject *dk, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr)
1589+
{
1590+
assert(dk->dk_kind == DICT_KEYS_UNICODE);
1591+
assert(PyUnicode_CheckExact(key));
1592+
1593+
Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
1594+
if (ix == DKIX_EMPTY) {
1595+
*value_addr = PyStackRef_NULL;
1596+
return ix;
1597+
}
1598+
else if (ix >= 0) {
1599+
PyObject **addr_of_value = &DK_UNICODE_ENTRIES(dk)[ix].me_value;
1600+
PyObject *value = _Py_atomic_load_ptr(addr_of_value);
1601+
if (value == NULL) {
1602+
*value_addr = PyStackRef_NULL;
1603+
return DKIX_EMPTY;
1604+
}
1605+
if (_PyObject_HasDeferredRefcount(value)) {
1606+
*value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED };
1607+
return ix;
1608+
}
1609+
if (_Py_TryIncrefCompare(addr_of_value, value)) {
1610+
*value_addr = PyStackRef_FromPyObjectSteal(value);
1611+
return ix;
1612+
}
1613+
return DKIX_KEY_CHANGED;
1614+
}
1615+
assert(ix == DKIX_KEY_CHANGED);
1616+
return ix;
1617+
}
1618+
15871619
Py_ssize_t
15881620
_Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t hash, _PyStackRef *value_addr)
15891621
{
15901622
ensure_shared_on_read(mp);
15911623

15921624
PyDictKeysObject *dk = _Py_atomic_load_ptr_acquire(&mp->ma_keys);
15931625
if (dk->dk_kind == DICT_KEYS_UNICODE && PyUnicode_CheckExact(key)) {
1594-
Py_ssize_t ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
1595-
if (ix == DKIX_EMPTY) {
1596-
*value_addr = PyStackRef_NULL;
1626+
Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, value_addr);
1627+
if (ix != DKIX_KEY_CHANGED) {
15971628
return ix;
15981629
}
1599-
else if (ix >= 0) {
1600-
PyObject **addr_of_value = &DK_UNICODE_ENTRIES(dk)[ix].me_value;
1601-
PyObject *value = _Py_atomic_load_ptr(addr_of_value);
1602-
if (value == NULL) {
1603-
*value_addr = PyStackRef_NULL;
1604-
return DKIX_EMPTY;
1605-
}
1606-
if (_PyObject_HasDeferredRefcount(value)) {
1607-
*value_addr = (_PyStackRef){ .bits = (uintptr_t)value | Py_TAG_DEFERRED };
1608-
return ix;
1609-
}
1610-
if (_Py_TryIncrefCompare(addr_of_value, value)) {
1611-
*value_addr = PyStackRef_FromPyObjectSteal(value);
1612-
return ix;
1613-
}
1614-
}
16151630
}
16161631

16171632
PyObject *obj;
@@ -1651,6 +1666,54 @@ _Py_dict_lookup_threadsafe_stackref(PyDictObject *mp, PyObject *key, Py_hash_t h
16511666

16521667
#endif
16531668

1669+
// Looks up the unicode key `key` in the dictionary. Note that `*method` may
1670+
// already contain a valid value! See _PyObject_GetMethodStackRef().
1671+
int
1672+
_PyDict_GetMethodStackRef(PyDictObject *mp, PyObject *key, _PyStackRef *method)
1673+
{
1674+
assert(PyUnicode_CheckExact(key));
1675+
Py_hash_t hash = hash_unicode_key(key);
1676+
1677+
#ifdef Py_GIL_DISABLED
1678+
// NOTE: We can only do the fast-path lookup if we are on the owning
1679+
// thread or if the dict is already marked as shared so that the load
1680+
// of ma_keys is safe without a lock. We cannot call ensure_shared_on_read()
1681+
// in this code path without incref'ing the dict because the dict is a
1682+
// borrowed reference protected by QSBR, and acquiring the lock could lead
1683+
// to a quiescent state (allowing the dict to be freed).
1684+
if (_Py_IsOwnedByCurrentThread((PyObject *)mp) || IS_DICT_SHARED(mp)) {
1685+
PyDictKeysObject *dk = _Py_atomic_load_ptr_acquire(&mp->ma_keys);
1686+
if (dk->dk_kind == DICT_KEYS_UNICODE) {
1687+
_PyStackRef ref;
1688+
Py_ssize_t ix = lookup_threadsafe_unicode(dk, key, hash, &ref);
1689+
if (ix >= 0) {
1690+
assert(!PyStackRef_IsNull(ref));
1691+
PyStackRef_XSETREF(*method, ref);
1692+
return 1;
1693+
}
1694+
else if (ix == DKIX_EMPTY) {
1695+
return 0;
1696+
}
1697+
assert(ix == DKIX_KEY_CHANGED);
1698+
}
1699+
}
1700+
#endif
1701+
1702+
PyObject *obj;
1703+
Py_INCREF(mp);
1704+
Py_ssize_t ix = _Py_dict_lookup_threadsafe(mp, key, hash, &obj);
1705+
Py_DECREF(mp);
1706+
if (ix == DKIX_ERROR) {
1707+
PyStackRef_CLEAR(*method);
1708+
return -1;
1709+
}
1710+
else if (ix >= 0 && obj != NULL) {
1711+
PyStackRef_XSETREF(*method, PyStackRef_FromPyObjectSteal(obj));
1712+
return 1;
1713+
}
1714+
return 0; // not found
1715+
}
1716+
16541717
int
16551718
_PyDict_HasOnlyStringKeys(PyObject *dict)
16561719
{

0 commit comments

Comments
 (0)