Skip to content

Commit 7b49890

Browse files
committed
Combine the slot name and slot value check
1 parent d66bf6e commit 7b49890

1 file changed

Lines changed: 37 additions & 60 deletions

File tree

‎Python/specialize.c‎

Lines changed: 37 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -748,13 +748,12 @@ specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr,
748748
PyObject *name, PyObject *descr,
749749
unsigned int tp_version,
750750
DescriptorClassification kind, bool is_method,
751-
uint32_t shared_keys_version,
752-
uint16_t slot_offset);
751+
uint32_t shared_keys_version);
753752
static int specialize_class_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name);
754753

755-
/* Returns true if instances of obj's class are
756-
* likely to have `name` in their __dict__.
757-
* For objects with inline values, we check in the shared keys.
754+
/* Returns true if obj may have its own value for `name`.
755+
* For objects with inline values, we check obj's slot for `name` in the
756+
* shared keys: it may be empty even though the name has a slot.
758757
* For other objects, we check their actual dictionary.
759758
*/
760759
static bool
@@ -768,7 +767,17 @@ instance_has_key(PyObject *obj, PyObject *name, uint32_t *shared_keys_version)
768767
PyDictKeysObject *keys = ((PyHeapTypeObject *)cls)->ht_cached_keys;
769768
Py_ssize_t index =
770769
_PyDictKeys_StringLookupAndVersion(keys, name, shared_keys_version);
771-
return index >= 0;
770+
if (index < 0) {
771+
return false;
772+
}
773+
bool result;
774+
Py_BEGIN_CRITICAL_SECTION(obj);
775+
PyDictValues *values = _PyObject_InlineValues(obj);
776+
result = !FT_ATOMIC_LOAD_UINT8(values->valid) ||
777+
index >= values->capacity ||
778+
FT_ATOMIC_LOAD_PTR_RELAXED(values->values[index]) != NULL;
779+
Py_END_CRITICAL_SECTION();
780+
return result;
772781
}
773782
PyDictObject *dict = _PyObject_GetManagedDict(obj);
774783
if (dict == NULL || !PyDict_CheckExact(dict)) {
@@ -786,45 +795,9 @@ instance_has_key(PyObject *obj, PyObject *name, uint32_t *shared_keys_version)
786795
return result;
787796
}
788797

789-
/* Returns true if `owner`'s inline slot for `name` (an entry in the class's
790-
* shared keys) holds a value, or if there is no inline slot a specialized
791-
* instruction could check. Returns false if the slot is empty, and stores
792-
* its byte offset from the start of the object in *empty_slot_offset. An
793-
* empty slot means the attribute is not set on this instance even though the
794-
* class's shared keys have an entry for it (e.g. from __static_attributes__),
795-
* so a load finds the class attribute.
796-
*/
797-
static bool
798-
inline_slot_is_set(PyObject *owner, PyObject *name, uint16_t *empty_slot_offset)
799-
{
800-
PyTypeObject *type = Py_TYPE(owner);
801-
if (!(type->tp_flags & Py_TPFLAGS_INLINE_VALUES)) {
802-
return true;
803-
}
804-
bool set = true;
805-
Py_BEGIN_CRITICAL_SECTION(owner);
806-
PyDictValues *values = _PyObject_InlineValues(owner);
807-
if (FT_ATOMIC_LOAD_UINT8(values->valid)) {
808-
PyDictKeysObject *keys = ((PyHeapTypeObject *)type)->ht_cached_keys;
809-
Py_ssize_t index = _PyDictKeys_StringLookupSplit(keys, name);
810-
if (index >= 0 && index < values->capacity &&
811-
FT_ATOMIC_LOAD_PTR_RELAXED(values->values[index]) == NULL)
812-
{
813-
Py_ssize_t slot = (char *)&values->values[index] - (char *)owner;
814-
if (slot == (uint16_t)slot) {
815-
*empty_slot_offset = (uint16_t)slot;
816-
set = false;
817-
}
818-
}
819-
}
820-
Py_END_CRITICAL_SECTION();
821-
return set;
822-
}
823-
824798
static int
825799
do_specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* name,
826800
bool shadow, uint32_t shared_keys_version,
827-
uint16_t empty_slot_offset,
828801
DescriptorClassification kind, PyObject *descr, unsigned int tp_version)
829802
{
830803
_PyAttrCache *cache = (_PyAttrCache *)(instr + 1);
@@ -846,8 +819,7 @@ do_specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject*
846819
if (oparg & 1) {
847820
if (specialize_attr_loadclassattr(owner, instr, name, descr,
848821
tp_version, kind, true,
849-
shared_keys_version,
850-
empty_slot_offset)) {
822+
shared_keys_version)) {
851823
return 0;
852824
}
853825
else {
@@ -993,8 +965,7 @@ do_specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject*
993965
if ((oparg & 1) == 0) {
994966
if (specialize_attr_loadclassattr(owner, instr, name, descr,
995967
tp_version, kind, false,
996-
shared_keys_version,
997-
empty_slot_offset)) {
968+
shared_keys_version)) {
998969
return 0;
999970
}
1000971
}
@@ -1028,14 +999,8 @@ specialize_instance_load_attr(PyObject* owner, _Py_CODEUNIT* instr, PyObject* na
1028999
// then the type version, this ensures we will still deopt if that happens.
10291000
DescriptorClassification kind = analyze_descriptor_load(type, name, &descr, &tp_version);
10301001
uint32_t shared_keys_version = 0;
1031-
uint16_t empty_slot_offset = 0;
1032-
/* True if this instance has its own value for the name. For inline
1033-
* values, the name being in the shared keys is not enough: the slot may
1034-
* be empty, and then the load finds the class attribute. */
1035-
bool shadow = instance_has_key(owner, name, &shared_keys_version) &&
1036-
inline_slot_is_set(owner, name, &empty_slot_offset);
1037-
int result = do_specialize_instance_load_attr(owner, instr, name, shadow, shared_keys_version,
1038-
empty_slot_offset, kind, descr, tp_version);
1002+
bool shadow = instance_has_key(owner, name, &shared_keys_version);
1003+
int result = do_specialize_instance_load_attr(owner, instr, name, shadow, shared_keys_version, kind, descr, tp_version);
10391004
Py_XDECREF(descr);
10401005
return result;
10411006
}
@@ -1310,8 +1275,7 @@ specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr,
13101275
PyObject *name, PyObject *descr,
13111276
unsigned int tp_version,
13121277
DescriptorClassification kind, bool is_method,
1313-
uint32_t shared_keys_version,
1314-
uint16_t slot_offset)
1278+
uint32_t shared_keys_version)
13151279
{
13161280
_PyLoadMethodCache *cache = (_PyLoadMethodCache *)(instr + 1);
13171281
PyTypeObject *owner_cls = Py_TYPE(owner);
@@ -1328,14 +1292,27 @@ specialize_attr_loadclassattr(PyObject *owner, _Py_CODEUNIT *instr,
13281292

13291293
unsigned long tp_flags = PyType_GetFlags(owner_cls);
13301294
if (tp_flags & Py_TPFLAGS_INLINE_VALUES) {
1331-
#ifndef Py_GIL_DISABLED
1332-
assert(slot_offset != 0 || _PyDictKeys_StringLookup(
1333-
((PyHeapTypeObject *)owner_cls)->ht_cached_keys, name) < 0);
1334-
#endif
13351295
if (shared_keys_version == 0) {
13361296
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
13371297
return 0;
13381298
}
1299+
/* The name can have a slot in the shared keys (e.g. from
1300+
* __static_attributes__) that is empty in this instance. Store the
1301+
* slot's offset so the instruction can check it stays empty; 0 means
1302+
* there is no slot to check. */
1303+
uint16_t slot_offset = 0;
1304+
Py_ssize_t index = _PyDictKeys_StringLookupSplit(
1305+
((PyHeapTypeObject *)owner_cls)->ht_cached_keys, name);
1306+
assert(index != DKIX_ERROR);
1307+
if (index >= 0) {
1308+
char *value_addr = (char *)&_PyObject_InlineValues(owner)->values[index];
1309+
Py_ssize_t offset = value_addr - (char *)owner;
1310+
if (offset != (uint16_t)offset) {
1311+
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_RANGE);
1312+
return 0;
1313+
}
1314+
slot_offset = (uint16_t)offset;
1315+
}
13391316
cache->keys_version[0] = slot_offset;
13401317
specialize(instr, is_method ? LOAD_ATTR_METHOD_WITH_VALUES : LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES);
13411318
}

0 commit comments

Comments
 (0)