Skip to content

Commit c424950

Browse files
jbower-fbmeta-codesync[bot]
authored andcommitted
Update handling of attribute dictionaries in inline_cache.cpp
Summary: 3.14 introduces "inline values" for object attributes which we need to support in our inline caches. For the most part I've completely reimplemented the `SplitMutator` kind of `AttributeMutator`. Primarily I did this so I could easily write with reference to the specialized instructions `[LOAD|STORE]_ATTR_WITH_HINT` and `[LOAD|STORE]_ATTR_INSTANCE_VALUE`. This made it much easier to convince myself they are probably correct. I'd also argue the API is sufficiently different that fitting everything between `#if-#defines` would be really hard to follow. Reviewed By: DinoV Differential Revision: D84294736 fbshipit-source-id: 4251fd0db6e0c027b0bd835f0f535aa2eca0f393
1 parent 1f76b1e commit c424950

5 files changed

Lines changed: 169 additions & 27 deletions

File tree

cinderx/Common/dict.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ static inline bool hasOnlyUnicodeKeys(PyObject* dict) {
6161
static inline Py_ssize_t getDictKeysIndex(
6262
PyDictKeysObject* keys,
6363
PyObject* name) {
64+
#if PY_VERSION_HEX >= 0x030E0000
65+
return _PyDictKeys_StringLookupSplit(keys, name);
66+
#endif
6467
#if PY_VERSION_HEX >= 0x030C0000
6568
for (Py_ssize_t i = 0; i < keys->dk_nentries; i++) {
6669
PyDictUnicodeEntry* ep = &DK_UNICODE_ENTRIES(keys)[i];

cinderx/Jit/inline_cache.cpp

Lines changed: 128 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,105 @@ bool SplitMutator::ensureValueOffset(BorrowedRef<> name) {
182182
}
183183
}
184184
#else
185-
assert(val_offset != -1);
185+
JIT_DCHECK(
186+
val_offset != -1,
187+
"Value offset not set for {} on split dict instance",
188+
repr(name));
186189
#endif
187190
return true;
188191
}
189192

193+
#if PY_VERSION_HEX >= 0x030E0000
194+
PyObject* SplitMutator::getAttrInline(PyObject* obj, PyObject* name) {
195+
if (!ensureValueOffset(name)) {
196+
return PyObject_GetAttr(obj, name);
197+
}
198+
PyDictValues* values = _PyObject_InlineValues(obj);
199+
if (!values->valid) {
200+
return getAttr(obj, name);
201+
}
202+
PyObject* result = values->values[val_offset];
203+
if (result == nullptr) {
204+
return raise_attribute_error(obj, name);
205+
}
206+
return Py_NewRef(result);
207+
}
208+
209+
PyObject* SplitMutator::getAttr(PyObject* obj, PyObject* name) {
210+
BorrowedRef<PyDictObject> dict = _PyObject_GetManagedDict(obj);
211+
212+
JIT_DCHECK(
213+
PyDict_Check(dict), "Expected dict, got {}", Py_TYPE(dict)->tp_name);
214+
215+
if (dict == nullptr) {
216+
return PyObject_GetAttr(obj, name);
217+
}
218+
if (!ensureValueOffset(name) || dict->ma_keys != keys) {
219+
// Slow path
220+
PyObject* attr_o;
221+
int res = [&] {
222+
auto strong_ref = Ref<>::create(dict);
223+
return PyDict_GetItemRef(dict, name, &attr_o);
224+
}();
225+
if (res == 0) {
226+
return raise_attribute_error(obj, name);
227+
}
228+
if (res == -1) {
229+
return nullptr;
230+
}
231+
return attr_o;
232+
}
233+
JIT_DCHECK(
234+
DK_IS_UNICODE(keys) && val_offset < keys->dk_nentries,
235+
"Expected dictionary keys object to change");
236+
PyObject* attr_o = dict->ma_values->values[val_offset];
237+
if (attr_o == nullptr) {
238+
return raise_attribute_error(obj, name);
239+
}
240+
return Py_NewRef(attr_o);
241+
}
242+
243+
int SplitMutator::setAttrInline(
244+
PyObject* obj,
245+
PyObject* name,
246+
PyObject* value) {
247+
if (!ensureValueOffset(name)) {
248+
return PyObject_SetAttr(obj, name, value);
249+
}
250+
PyDictValues* values = _PyObject_InlineValues(obj);
251+
PyDictObject* dict = _PyObject_GetManagedDict(obj);
252+
if (!values->valid || dict) {
253+
return setAttr(obj, name, value);
254+
}
255+
auto old_value = Ref<>::steal(values->values[val_offset]);
256+
values->values[val_offset] = Py_NewRef(value);
257+
if (!old_value) {
258+
_PyDictValues_AddToInsertionOrder(values, val_offset);
259+
}
260+
return 0;
261+
}
262+
263+
int SplitMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) {
264+
if (!ensureValueOffset(name)) {
265+
return PyObject_SetAttr(obj, name, value);
266+
}
267+
BorrowedRef<PyDictObject> dict = _PyObject_GetManagedDict(obj);
268+
if (dict == nullptr) {
269+
return PyObject_SetAttr(obj, name, value);
270+
}
271+
if (keys != dict->ma_keys) {
272+
// Slow path
273+
auto strong_ref = Ref<>::create(dict);
274+
return PyDict_SetItem(dict, name, value);
275+
}
276+
Cix_dict_insert_split_value(
277+
_PyInterpreterState_GET(), dict, name, value, val_offset);
278+
return 0;
279+
}
280+
281+
#else
282+
190283
int SplitMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) {
191-
#if PY_VERSION_HEX < 0x030E0000 // TASK(T229234686)
192284
#if PY_VERSION_HEX >= 0x030C0000
193285
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(obj);
194286
if (_PyDictOrValues_IsValues(dorv)) {
@@ -212,7 +304,8 @@ int SplitMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) {
212304
// Dictionary has been materialized but may still be using shared keys.
213305
BorrowedRef<PyDictObject> dict = (PyDictObject*)_PyDictOrValues_GetDict(dorv);
214306
if (dict == nullptr) {
215-
dict = (PyDictObject*)PyObject_GenericGetDict(obj, nullptr);
307+
dict =
308+
reinterpret_cast<PyDictObject*>(PyObject_GenericGetDict(obj, nullptr));
216309
if (dict == nullptr) {
217310
return -1;
218311
}
@@ -255,16 +348,11 @@ int SplitMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) {
255348

256349
return 0;
257350
}
258-
259351
auto strong_ref = Ref<>::create(dict);
260352
return PyDict_SetItem(dict, name, value);
261-
#else
262-
return 0;
263-
#endif
264353
}
265354

266355
PyObject* SplitMutator::getAttr(PyObject* obj, PyObject* name) {
267-
#if PY_VERSION_HEX < 0x030E0000
268356
#if PY_VERSION_HEX >= 0x030C0000
269357
PyDictOrValues dorv = *_PyObject_DictOrValuesPointer(obj);
270358
if (_PyDictOrValues_IsValues(dorv)) {
@@ -306,10 +394,8 @@ PyObject* SplitMutator::getAttr(PyObject* obj, PyObject* name) {
306394
}
307395
Py_INCREF(result);
308396
return result;
309-
#else
310-
return nullptr;
311-
#endif
312397
}
398+
#endif // PY_VERSION_HEX < 0x030E0000
313399

314400
int CombinedMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) {
315401
BorrowedRef<PyDictObject> dict = get_or_allocate_dict(obj, dict_offset);
@@ -470,17 +556,22 @@ void AttributeMutator::set_descr_or_classvar(
470556
void AttributeMutator::set_split(
471557
PyTypeObject* type,
472558
Py_ssize_t val_offset,
473-
PyDictKeysObject* keys) {
474-
set_type(type, Kind::kSplit);
559+
[[maybe_unused]] PyDictKeysObject* keys,
560+
bool inline_values) {
561+
set_type(type, inline_values ? Kind::kSplitInline : Kind::kSplit);
562+
#if PY_VERSION_HEX >= 0x030C0000
563+
split_.val_offset = val_offset;
564+
split_.keys = keys;
565+
#else
475566
JIT_CHECK(
476567
type->tp_dictoffset <= std::numeric_limits<uint32_t>::max(),
477568
"Dict offset does not fit into a 32-bit int");
569+
split_.dict_offset = static_cast<uint32_t>(type->tp_dictoffset);
478570
JIT_CHECK(
479-
val_offset <= std::numeric_limits<uint32_t>::max(),
571+
val_offset <= std::numeric_limits<int32_t>::max(),
480572
"Val offset does not fit into a 32-bit int");
481-
split_.dict_offset = static_cast<uint32_t>(type->tp_dictoffset);
482-
split_.val_offset = static_cast<uint32_t>(val_offset);
483-
split_.keys = keys;
573+
split_.val_offset = static_cast<int32_t>(val_offset);
574+
#endif
484575
}
485576

486577
inline int
@@ -489,6 +580,10 @@ AttributeMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) {
489580
switch (kind) {
490581
case AttributeMutator::Kind::kSplit:
491582
return split_.setAttr(obj, name, value);
583+
#if PY_VERSION_HEX >= 0x030E0000
584+
case AttributeMutator::Kind::kSplitInline:
585+
return split_.setAttrInline(obj, name, value);
586+
#endif
492587
case AttributeMutator::Kind::kCombined:
493588
return combined_.setAttr(obj, name, value);
494589
case AttributeMutator::Kind::kDataDescr:
@@ -508,6 +603,10 @@ inline PyObject* AttributeMutator::getAttr(PyObject* obj, PyObject* name) {
508603
switch (kind) {
509604
case AttributeMutator::Kind::kSplit:
510605
return split_.getAttr(obj, name);
606+
#if PY_VERSION_HEX >= 0x030E0000
607+
case AttributeMutator::Kind::kSplitInline:
608+
return split_.getAttrInline(obj, name);
609+
#endif
511610
case AttributeMutator::Kind::kCombined:
512611
return combined_.getAttr(obj, name);
513612
case AttributeMutator::Kind::kDataDescr:
@@ -636,6 +735,10 @@ void AttributeCache::fill(
636735
}
637736

638737
if (descr != nullptr) {
738+
// Not yet working.
739+
if (PY_VERSION_HEX >= 0x030E0000) {
740+
return;
741+
}
639742
BorrowedRef<PyTypeObject> descr_type(Py_TYPE(descr));
640743
if (descr_type->tp_descr_get != nullptr &&
641744
descr_type->tp_descr_set != nullptr) {
@@ -671,20 +774,21 @@ void AttributeCache::fill(
671774

672775
// Instance attribute with no shadowing. Specialize the lookup based on
673776
// whether or not the type is using split dictionaries.
674-
#if PY_VERSION_HEX < 0x030E0000 // TASK(T229234686)
675777
PyDictKeysObject* keys = getSplitKeys(type);
676778
#if PY_VERSION_HEX >= 0x030C0000
677779
if (PyType_HasFeature(type, Py_TPFLAGS_MANAGED_DICT)) {
678-
assert(keys != nullptr);
679-
mut->set_split(type, getDictKeysIndex(keys, name), keys);
780+
JIT_DCHECK(keys != nullptr, "Managed dict should have a split dict");
781+
bool inline_values = false;
782+
#if PY_VERSION_HEX >= 0x030E0000
783+
inline_values = type->tp_flags & Py_TPFLAGS_INLINE_VALUES;
784+
#endif
785+
mut->set_split(type, getDictKeysIndex(keys, name), keys, inline_values);
680786
#else
681787
Py_ssize_t val_offset;
682788
if (keys != nullptr && (val_offset = getDictKeysIndex(keys, name)) != -1) {
683-
mut->set_split(type, val_offset, keys);
789+
mut->set_split(type, val_offset, keys, false);
684790
#endif
685-
} else
686-
#endif
687-
{
791+
} else {
688792
mut->set_combined(type);
689793
}
690794
ac_watcher.watch(type, this);

cinderx/Jit/inline_cache.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ namespace jit {
2222
struct SplitMutator {
2323
PyObject* getAttr(PyObject* obj, PyObject* name);
2424
int setAttr(PyObject* obj, PyObject* name, PyObject* value);
25+
#if PY_VERSION_HEX >= 0x030E0000
26+
PyObject* getAttrInline(PyObject* obj, PyObject* name);
27+
int setAttrInline(PyObject* obj, PyObject* name, PyObject* value);
28+
#endif
2529
bool canInsertToSplitDict(BorrowedRef<PyDictObject> dict, BorrowedRef<> name);
2630
bool ensureValueOffset(BorrowedRef<> name);
2731

32+
#if PY_VERSION_HEX < 0x030C0000
2833
uint32_t dict_offset;
29-
uint32_t val_offset;
34+
int32_t val_offset;
35+
#else
36+
Py_ssize_t val_offset;
37+
#endif
3038
PyDictKeysObject* keys; // Borrowed
3139
};
3240

@@ -72,6 +80,7 @@ class AttributeMutator {
7280
enum class Kind : uint8_t {
7381
kEmpty,
7482
kSplit,
83+
kSplitInline,
7584
kCombined,
7685
kDataDescr,
7786
kMemberDescr,
@@ -91,8 +100,11 @@ class AttributeMutator {
91100
void set_member_descr(PyTypeObject* type, PyObject* descr);
92101
void
93102
set_descr_or_classvar(PyTypeObject* type, PyObject* descr, uint keys_version);
94-
void
95-
set_split(PyTypeObject* type, Py_ssize_t val_offset, PyDictKeysObject* keys);
103+
void set_split(
104+
PyTypeObject* type,
105+
Py_ssize_t val_offset,
106+
PyDictKeysObject* keys,
107+
bool values_inline);
96108

97109
PyObject* getAttr(PyObject* obj, PyObject* name);
98110
int setAttr(PyObject* obj, PyObject* name, PyObject* value);

cinderx/UpstreamBorrow/borrowed-3.14.c.template

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,20 @@ PyDictKeysObject* ci_dict_empty_keys;
174174
// @Borrow function ensure_shared_on_keys_version_assignment from Objects/dictobject.c [3.14]
175175
// @Borrow function _PyDict_GetKeysVersionForCurrentState from Objects/dictobject.c [3.14]
176176

177+
void Cix_dict_insert_split_value(
178+
PyInterpreterState *interp,
179+
PyDictObject *mp,
180+
PyObject *key,
181+
PyObject *value,
182+
Py_ssize_t ix) {
183+
#if defined(__clang__)
184+
[[clang::always_inline]]
185+
#elif defined(__GNUC__)
186+
[[gnu::always_inline]]
187+
#endif
188+
insert_split_value(interp, mp, key, value, ix);
189+
}
190+
177191
#define _PyObject_SetAttributeErrorContext _CiPyObject_SetAttributeErrorContext
178192
// @Borrow function _PyObject_SetAttributeErrorContext from Objects/object.c
179193

cinderx/UpstreamBorrow/borrowed.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ extern PyObject *Cix_monitoring_disable, *Cix_monitoring_missing;
124124
int _Ci_Instrument(PyCodeObject* co, PyInterpreterState* interp);
125125

126126
Py_ssize_t _PyDict_LookupIndex(PyDictObject*, PyObject*);
127+
128+
Py_ssize_t _PyDictKeys_StringLookupSplit(PyDictKeysObject* dk, PyObject* key);
127129
#endif
128130

129131
#if PY_VERSION_HEX >= 0x030C0000
@@ -179,6 +181,13 @@ void Cix_PyDict_SendEvent(
179181

180182
int Cix_set_attribute_error_context(PyObject* v, PyObject* name);
181183

184+
void Cix_dict_insert_split_value(
185+
PyInterpreterState* interp,
186+
PyDictObject* mp,
187+
PyObject* key,
188+
PyObject* value,
189+
Py_ssize_t ix);
190+
182191
// TODO: Get rid of this
183192
#include "internal/pycore_tuple.h"
184193
#define Cix_PyTuple_FromArray _PyTuple_FromArray

0 commit comments

Comments
 (0)