Skip to content

Commit fcd05a0

Browse files
committed
Pass shared keys version to specialization
Reading the shared keys version and looking up the key need to be performed atomically. Otherwise, a key inserted after the lookup could race with reading the version, causing us to incorrectly specialize that no key shadows the descriptor.
1 parent 8eeb4fe commit fcd05a0

File tree

1 file changed

+25
-20
lines changed

1 file changed

+25
-20
lines changed

Python/specialize.c

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,9 +1007,11 @@ specialize_dict_access(
10071007
return 1;
10081008
}
10091009

1010-
#ifndef Py_GIL_DISABLED
1011-
static int specialize_attr_loadclassattr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name,
1012-
PyObject* descr, DescriptorClassification kind, bool is_method);
1010+
static int
1011+
specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr,
1012+
PyObject *name, PyObject *descr,
1013+
DescriptorClassification kind, bool is_method,
1014+
uint32_t shared_keys_version);
10131015
static int specialize_class_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name);
10141016

10151017
/* Returns true if instances of obj's class are
@@ -1018,15 +1020,16 @@ static int specialize_class_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyOb
10181020
* For other objects, we check their actual dictionary.
10191021
*/
10201022
static bool
1021-
instance_has_key(PyObject *obj, PyObject* name)
1023+
instance_has_key(PyObject *obj, PyObject *name, uint32_t *shared_keys_version)
10221024
{
10231025
PyTypeObject *cls = Py_TYPE(obj);
10241026
if ((cls->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
10251027
return false;
10261028
}
10271029
if (cls->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
10281030
PyDictKeysObject *keys = ((PyHeapTypeObject *)cls)->ht_cached_keys;
1029-
Py_ssize_t index = _PyDictKeys_StringLookup(keys, name);
1031+
Py_ssize_t index =
1032+
_PyDictKeys_StringLookupAndVersion(keys, name, shared_keys_version);
10301033
return index >= 0;
10311034
}
10321035
PyDictObject *dict = _PyObject_GetManagedDict(obj);
@@ -1048,7 +1051,9 @@ specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* na
10481051
{
10491052
_PyAttrCache *cache = (_PyAttrCache *)(instr + 1);
10501053
PyTypeObject *type = Py_TYPE(owner);
1051-
bool shadow = instance_has_key(owner, name);
1054+
// 0 is not a valid version
1055+
uint32_t shared_keys_version = 0;
1056+
bool shadow = instance_has_key(owner, name, &shared_keys_version);
10521057
PyObject *descr = NULL;
10531058
DescriptorClassification kind = analyze_descriptor(type, name, &descr, 0);
10541059
assert(descr != NULL || kind == ABSENT || kind == GETSET_OVERRIDDEN);
@@ -1066,7 +1071,9 @@ specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* na
10661071
}
10671072
int oparg = instr->op.arg;
10681073
if (oparg & 1) {
1069-
if (specialize_attr_loadclassattr(owner, instr, name, descr, kind, true)) {
1074+
if (specialize_attr_loadclassattr(owner, instr, name, descr,
1075+
kind, true,
1076+
shared_keys_version)) {
10701077
return 0;
10711078
}
10721079
else {
@@ -1188,7 +1195,9 @@ specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* na
11881195
goto try_instance;
11891196
}
11901197
if ((instr->op.arg & 1) == 0) {
1191-
if (specialize_attr_loadclassattr(owner, instr, name, descr, kind, false)) {
1198+
if (specialize_attr_loadclassattr(owner, instr, name, descr,
1199+
kind, false,
1200+
shared_keys_version)) {
11921201
return 0;
11931202
}
11941203
}
@@ -1209,7 +1218,6 @@ specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* na
12091218
}
12101219
return -1;
12111220
}
1212-
#endif // Py_GIL_DISABLED
12131221

12141222
void
12151223
_Py_Specialize_LoadAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *name)
@@ -1239,12 +1247,7 @@ _Py_Specialize_LoadAttr(_PyStackRef owner_st, _Py_CODEUNIT *instr, PyObject *nam
12391247
#endif
12401248
}
12411249
else {
1242-
#ifdef Py_GIL_DISABLED
1243-
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_EXPECTED_ERROR);
1244-
fail = true;
1245-
#else
12461250
fail = specialize_instance_load_attr(owner, instr, name);
1247-
#endif
12481251
}
12491252

12501253
if (fail) {
@@ -1458,24 +1461,26 @@ specialize_class_load_attr(PyObject *owner, _Py_CODEUNIT *instr,
14581461
// can cause a significant drop in cache hits. A possible test is
14591462
// python.exe -m test_typing test_re test_dis test_zlib.
14601463
static int
1461-
specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
1462-
PyObject *descr, DescriptorClassification kind, bool is_method)
1464+
specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr,
1465+
PyObject *name, PyObject *descr,
1466+
DescriptorClassification kind, bool is_method,
1467+
uint32_t shared_keys_version)
14631468
{
14641469
_PyLoadMethodCache *cache = (_PyLoadMethodCache *)(instr + 1);
14651470
PyTypeObject *owner_cls = Py_TYPE(owner);
14661471

14671472
assert(descr != NULL);
14681473
assert((is_method && kind == METHOD) || (!is_method && kind == NON_DESCRIPTOR));
14691474
if (owner_cls->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
1475+
#ifndef Py_GIL_DISABLED
14701476
PyDictKeysObject *keys = ((PyHeapTypeObject *)owner_cls)->ht_cached_keys;
14711477
assert(_PyDictKeys_StringLookup(keys, name) < 0);
1472-
uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(
1473-
_PyInterpreterState_GET(), keys);
1474-
if (keys_version == 0) {
1478+
#endif
1479+
if (shared_keys_version == 0) {
14751480
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
14761481
return 0;
14771482
}
1478-
write_u32(cache->keys_version, keys_version);
1483+
write_u32(cache->keys_version, shared_keys_version);
14791484
specialize(instr, is_method ? LOAD_ATTR_METHOD_WITH_VALUES : LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES);
14801485
}
14811486
else {

0 commit comments

Comments
 (0)