Jit/inline_cache.cpp (600 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "Jit/inline_cache.h" #include "Objects/dict-common.h" #include "Python.h" #include "switchboard.h" #include "Jit/codegen/gen_asm.h" #include "Jit/dict_watch.h" #include <algorithm> // clang-format off #include "internal/pycore_pystate.h" #include "internal/pycore_object.h" #include "structmember.h" // clang-format on namespace jit { namespace { template <class T> struct TypeWatcher { std::unordered_map<BorrowedRef<PyTypeObject>, std::vector<T*>> caches; void watch(BorrowedRef<PyTypeObject> type, T* cache) { caches[type].emplace_back(cache); } void typeChanged(BorrowedRef<PyTypeObject> type) { auto it = caches.find(type); if (it == caches.end()) { return; } std::vector<T*> to_notify = std::move(it->second); caches.erase(it); for (T* cache : to_notify) { cache->typeChanged(type); } } }; TypeWatcher<AttributeCache> ac_watcher; TypeWatcher<LoadTypeAttrCache> ltac_watcher; } // namespace AttributeMutator::AttributeMutator() { reset(); } PyTypeObject* AttributeMutator::type() const { return type_; } void AttributeMutator::reset() { kind_ = Kind::kEmpty; type_ = nullptr; } bool AttributeMutator::isEmpty() const { return kind_ == Kind::kEmpty; } void AttributeMutator::set_combined(PyTypeObject* type) { kind_ = Kind::kCombined; type_ = type; combined_.dict_offset = type->tp_dictoffset; } void AttributeMutator::set_split( PyTypeObject* type, Py_ssize_t val_offset, PyDictKeysObject* keys) { kind_ = Kind::kSplit; type_ = type; split_.dict_offset = type->tp_dictoffset; split_.val_offset = val_offset; split_.keys = keys; } void AttributeMutator::set_data_descr(PyTypeObject* type, PyObject* descr) { kind_ = Kind::kDataDescr; type_ = type; data_descr_.descr = descr; } void AttributeMutator::set_member_descr(PyTypeObject* type, PyObject* descr) { kind_ = Kind::kMemberDescr; type_ = type; member_descr_.memberdef = ((PyMemberDescrObject*)descr)->d_member; } void AttributeMutator::set_descr_or_classvar( PyTypeObject* type, PyObject* descr) { kind_ = Kind::kDescrOrClassVar; type_ = type; descr_or_cvar_.descr = descr; descr_or_cvar_.dictoffset = type->tp_dictoffset; } void AttributeCache::typeChanged(PyTypeObject* type) { for (auto& entry : entries_) { if (entry.type() == type) { entry.reset(); } } } void AttributeCache::fill( BorrowedRef<PyTypeObject> type, BorrowedRef<> name, BorrowedRef<> descr) { if (!PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { // The type must have a valid version tag in order for us to be able to // invalidate the cache when the type is modified. See the comment at // the top of `PyType_Modified` for more details. return; } AttributeMutator* mut = findEmptyEntry(); if (mut == nullptr) { return; } if (descr != nullptr) { BorrowedRef<PyTypeObject> descr_type(Py_TYPE(descr)); if (descr_type->tp_descr_set != nullptr) { // Data descriptor if (descr_type == &PyMemberDescr_Type) { mut->set_member_descr(type, descr); } else { mut->set_data_descr(type, descr); } } else { // Non-data descriptor or class var mut->set_descr_or_classvar(type, descr); } ac_watcher.watch(type, this); return; } if (type->tp_dictoffset < 0 || !PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { // We only support the common case for objects - fixed-size instances // (tp_dictoffset >= 0) of heap types (Py_TPFLAGS_HEAPTYPE). return; } // Instance attribute with no shadowing. Specialize the lookup based on // whether or not the type is using split dictionaries. PyHeapTypeObject* ht = reinterpret_cast<PyHeapTypeObject*>(type.get()); PyDictKeysObject* keys = ht->ht_cached_keys; Py_ssize_t val_offset; if (keys != nullptr && (val_offset = _PyDictKeys_GetSplitIndex(keys, name)) != -1) { mut->set_split(type, val_offset, keys); } else { mut->set_combined(type); } ac_watcher.watch(type, this); } AttributeMutator* AttributeCache::findEmptyEntry() { auto it = std::find_if( entries_.begin(), entries_.end(), [](const AttributeMutator& e) { return e.isEmpty(); }); return it == entries_.end() ? nullptr : it; } inline PyObject* AttributeMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) { switch (kind_) { case AttributeMutator::Kind::kSplit: return split_.setAttr(obj, name, value); case AttributeMutator::Kind::kCombined: return combined_.setAttr(obj, name, value); case AttributeMutator::Kind::kDataDescr: return data_descr_.setAttr(obj, value); case AttributeMutator::Kind::kMemberDescr: return member_descr_.setAttr(obj, value); case AttributeMutator::Kind::kDescrOrClassVar: return descr_or_cvar_.setAttr(obj, name, value); default: JIT_CHECK( false, "cannot invoke setAttr for attr of kind %d", static_cast<int>(kind_)); } } inline PyObject* AttributeMutator::getAttr(PyObject* obj, PyObject* name) { switch (kind_) { case AttributeMutator::Kind::kSplit: return split_.getAttr(obj, name); case AttributeMutator::Kind::kCombined: return combined_.getAttr(obj, name); case AttributeMutator::Kind::kDataDescr: return data_descr_.getAttr(obj); case AttributeMutator::Kind::kMemberDescr: return member_descr_.getAttr(obj); case AttributeMutator::Kind::kDescrOrClassVar: return descr_or_cvar_.getAttr(obj, name); default: JIT_CHECK( false, "cannot invoke getAttr for attr of kind %d", static_cast<int>(kind_)); } } static inline PyDictObject* get_dict(PyObject* obj, Py_ssize_t dictoffset) { PyObject** dictptr = (PyObject**)((char*)obj + dictoffset); return (PyDictObject*)*dictptr; } static inline PyDictObject* get_or_allocate_dict( PyObject* obj, Py_ssize_t dict_offset) { PyDictObject* dict = get_dict(obj, dict_offset); if (dict == nullptr) { dict = reinterpret_cast<PyDictObject*>(PyObject_GenericGetDict(obj, nullptr)); if (dict == nullptr) { return nullptr; } Py_DECREF(dict); } return dict; } static PyObject* __attribute__((noinline)) raise_attribute_error(PyObject* obj, PyObject* name) { PyErr_Format( PyExc_AttributeError, "'%.50s' object has no attribute '%U'", Py_TYPE(obj)->tp_name, name); return nullptr; } PyObject* SplitMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) { PyDictObject* dict = get_or_allocate_dict(obj, dict_offset); if (dict == nullptr) { return nullptr; } PyObject* dictobj = reinterpret_cast<PyObject*>(dict); PyObject* result = Py_None; if ((dict->ma_keys == keys) && ((dict->ma_used == val_offset) || (dict->ma_values[val_offset] != nullptr))) { PyObject* old_value = dict->ma_values[val_offset]; if (!_PyObject_GC_IS_TRACKED(dictobj)) { if (_PyObject_GC_MAY_BE_TRACKED(value)) { _PyObject_GC_TRACK(dictobj); } } Py_INCREF(value); dict->ma_values[val_offset] = value; _PyDict_IncVersionForSet(dict, name, value); if (old_value == nullptr) { dict->ma_used++; } else { Py_DECREF(old_value); } } else { Py_INCREF(dictobj); if (PyDict_SetItem(dictobj, name, value) < 0) { result = nullptr; } Py_DECREF(dictobj); } return result; } PyObject* SplitMutator::getAttr(PyObject* obj, PyObject* name) { PyDictObject* dict = get_dict(obj, dict_offset); if (dict == nullptr) { return raise_attribute_error(obj, name); } PyObject* result = nullptr; if (dict->ma_keys == keys) { result = dict->ma_values[val_offset]; } else { auto dictobj = reinterpret_cast<PyObject*>(dict); Py_INCREF(dictobj); result = PyDict_GetItem(dictobj, name); Py_DECREF(dictobj); } if (result == nullptr) { return raise_attribute_error(obj, name); } Py_INCREF(result); return result; } PyObject* CombinedMutator::setAttr(PyObject* obj, PyObject* name, PyObject* value) { PyDictObject* dict = get_or_allocate_dict(obj, dict_offset); if (dict == nullptr) { return nullptr; } PyObject* result = Py_None; auto dictobj = reinterpret_cast<PyObject*>(dict); Py_INCREF(dictobj); if (PyDict_SetItem(dictobj, name, value) < 0) { result = nullptr; } Py_DECREF(dictobj); return result; } PyObject* CombinedMutator::getAttr(PyObject* obj, PyObject* name) { auto dict = reinterpret_cast<PyObject*>(get_dict(obj, dict_offset)); if (dict == nullptr) { return raise_attribute_error(obj, name); } Py_INCREF(dict); PyObject* result = PyDict_GetItem(dict, name); Py_DECREF(dict); if (result == nullptr) { return raise_attribute_error(obj, name); } Py_INCREF(result); return result; } PyObject* DataDescrMutator::setAttr(PyObject* obj, PyObject* value) { if (Py_TYPE(descr)->tp_descr_set(descr, obj, value)) { return nullptr; } return Py_None; } PyObject* DataDescrMutator::getAttr(PyObject* obj) { return Py_TYPE(descr)->tp_descr_get(descr, obj, (PyObject*)Py_TYPE(obj)); } PyObject* MemberDescrMutator::setAttr(PyObject* obj, PyObject* value) { if (PyMember_SetOne((char*)obj, memberdef, value)) { return nullptr; } return Py_None; } PyObject* MemberDescrMutator::getAttr(PyObject* obj) { return PyMember_GetOne((char*)obj, memberdef); } PyObject* DescrOrClassVarMutator::setAttr( PyObject* obj, PyObject* name, PyObject* value) { descrsetfunc setter = Py_TYPE(descr)->tp_descr_set; if (setter != nullptr) { Ref<> descr_guard(descr); int st = setter(descr, obj, value); return (st == -1) ? nullptr : Py_None; } PyObject** dictptr = _PyObject_GetDictPtrAtOffset(obj, dictoffset); if (dictptr == nullptr) { PyErr_Format( PyExc_AttributeError, "'%.50s' object attribute '%U' is read-only", Py_TYPE(obj)->tp_name, name); return nullptr; } BorrowedRef<PyTypeObject> type(Py_TYPE(obj)); int st = _PyObjectDict_SetItem(type, dictptr, name, value); if (st < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) { PyErr_SetObject(PyExc_AttributeError, name); } _PyType_ClearNoShadowingInstances(type, descr); return (st == -1) ? nullptr : Py_None; } PyObject* DescrOrClassVarMutator::getAttr(PyObject* obj, PyObject* name) { BorrowedRef<PyTypeObject> descr_type(Py_TYPE(descr)); descrsetfunc setter = descr_type->tp_descr_set; descrgetfunc getter = descr_type->tp_descr_get; Ref<> descr_guard(descr); if (setter != nullptr && getter != nullptr) { BorrowedRef<PyTypeObject> type(Py_TYPE(obj)); return getter(descr, obj, type); } Ref<> dict; PyObject** dictptr = _PyObject_GetDictPtrAtOffset(obj, dictoffset); if (dictptr != nullptr) { dict.reset(*dictptr); } // Check instance dict. if (dict != nullptr) { Ref<> res(_PyDict_GetItem_UnicodeExact(dict, name)); if (res != nullptr) { return res.release(); } } if (getter != nullptr) { // Non-data descriptor BorrowedRef<PyTypeObject> type(Py_TYPE(obj)); return getter(descr, obj, type); } // Class var return descr_guard.release(); } // NB: The logic here needs to be kept in sync with // _PyObject_GenericSetAttrWithDict, with the proviso that this will never be // used to delete attributes. PyObject* __attribute__((noinline)) StoreAttrCache::invokeSlowPath(PyObject* obj, PyObject* name, PyObject* value) { BorrowedRef<PyTypeObject> tp(Py_TYPE(obj)); if (tp->tp_dict == nullptr && PyType_Ready(tp) < 0) { return nullptr; } else if (tp->tp_setattro != PyObject_GenericSetAttr) { int st = PyObject_SetAttr(obj, name, value); return st == 0 ? Py_None : nullptr; } Ref<> name_guard(name); Ref<> descr(_PyType_Lookup(tp, name)); if (descr != nullptr) { descrsetfunc f = descr->ob_type->tp_descr_set; if (f != nullptr) { int res = f(descr, obj, value); fill(tp, name, descr); return (res == -1) ? nullptr : Py_None; } } PyObject** dictptr = _PyObject_GetDictPtr(obj); if (dictptr == nullptr) { if (descr == nullptr) { raise_attribute_error(obj, name); } else { PyErr_Format( PyExc_AttributeError, "'%.50s' object attribute '%U' is read-only", tp->tp_name, name); } return nullptr; } int res = _PyObjectDict_SetItem(tp, dictptr, name, value); if (descr != nullptr) { _PyType_ClearNoShadowingInstances(tp, descr); } if (res != -1) { fill(tp, name, descr); } return (res == -1) ? nullptr : Py_None; } PyObject* StoreAttrCache::invoke( StoreAttrCache* cache, PyObject* obj, PyObject* name, PyObject* value) { return cache->doInvoke(obj, name, value); } PyObject* StoreAttrCache::doInvoke(PyObject* obj, PyObject* name, PyObject* value) { PyTypeObject* tp = Py_TYPE(obj); for (auto& entry : entries_) { if (entry.type() == tp) { return entry.setAttr(obj, name, value); } } return invokeSlowPath(obj, name, value); } // NB: The logic here needs to be kept in-sync with PyObject_GenericGetAttr PyObject* __attribute__((noinline)) LoadAttrCache::invokeSlowPath(PyObject* obj, PyObject* name) { BorrowedRef<PyTypeObject> tp(Py_TYPE(obj)); if (tp->tp_getattro != PyObject_GenericGetAttr) { return PyObject_GetAttr(obj, name); } if (tp->tp_dict == nullptr) { if (PyType_Ready(tp) < 0) return nullptr; } Ref<> name_guard(name); Ref<> descr(_PyType_Lookup(tp, name)); descrgetfunc f = nullptr; if (descr != nullptr) { f = descr->ob_type->tp_descr_get; if (f != nullptr && PyDescr_IsData(descr)) { fill(tp, name, descr); return f(descr, obj, tp); } } Ref<> dict; PyObject** dictptr = _PyObject_GetDictPtr(obj); if (dictptr != nullptr) { dict.reset(*dictptr); } if (dict != nullptr) { Ref<> res(PyDict_GetItem(dict, name)); if (res != nullptr) { fill(tp, name, descr); return res.release(); } } if (f != nullptr) { fill(tp, name, descr); return f(descr, obj, tp); } if (descr != nullptr) { fill(tp, name, descr); return descr.release(); } raise_attribute_error(obj, name); return nullptr; } // Sentinel PyObject that must never escape into user code. static PyObject g_emptyTypeAttrCache = {_PyObject_EXTRA_INIT 1, nullptr}; void LoadTypeAttrCache::fill(PyTypeObject* type, PyObject* value) { if (!PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) { // The type must have a valid version tag in order for us to be able to // invalidate the cache when the type is modified. See the comment at // the top of `PyType_Modified` for more details. return; } items[0] = reinterpret_cast<PyObject*>(type); items[1] = value; ltac_watcher.watch(type, this); } void LoadTypeAttrCache::reset() { // We need to return a PyObject* even in the empty case so that subsequent // refcounting operations work correctly. items[0] = &g_emptyTypeAttrCache; items[1] = nullptr; } void LoadTypeAttrCache::typeChanged(PyTypeObject* /* type */) { reset(); } LoadTypeAttrCache::LoadTypeAttrCache() { reset(); } PyObject* LoadAttrCache::invoke(LoadAttrCache* cache, PyObject* obj, PyObject* name) { return cache->doInvoke(obj, name); } PyObject* LoadAttrCache::doInvoke(PyObject* obj, PyObject* name) { PyTypeObject* tp = Py_TYPE(obj); for (auto& entry : entries_) { if (entry.type() == tp) { return entry.getAttr(obj, name); } } return invokeSlowPath(obj, name); } // NB: This needs to be kept in-sync with the logic in type_getattro PyObject* LoadTypeAttrCache::doInvoke(PyObject* obj, PyObject* name) { PyTypeObject* metatype = Py_TYPE(obj); if (metatype->tp_getattro != PyType_Type.tp_getattro) { return PyObject_GetAttr(obj, name); } PyTypeObject* type = reinterpret_cast<PyTypeObject*>(obj); if (type->tp_dict == nullptr) { if (PyType_Ready(type) < 0) { return nullptr; } } descrgetfunc meta_get = nullptr; PyObject* meta_attribute = _PyType_Lookup(metatype, name); if (meta_attribute != nullptr) { Py_INCREF(meta_attribute); meta_get = Py_TYPE(meta_attribute)->tp_descr_get; if (meta_get != nullptr && PyDescr_IsData(meta_attribute)) { /* Data descriptors implement tp_descr_set to intercept * writes. Assume the attribute is not overridden in * type's tp_dict (and bases): call the descriptor now. */ PyObject* res = meta_get( meta_attribute, reinterpret_cast<PyObject*>(type), reinterpret_cast<PyObject*>(metatype)); Py_DECREF(meta_attribute); return res; } } /* No data descriptor found on metatype. Look in tp_dict of this * type and its bases */ PyObject* attribute = _PyType_Lookup(type, name); if (attribute != nullptr) { /* Implement descriptor functionality, if any */ Py_INCREF(attribute); descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get; Py_XDECREF(meta_attribute); if (local_get != nullptr) { /* NULL 2nd argument indicates the descriptor was * found on the target object itself (or a base) */ PyObject* res = local_get(attribute, nullptr, reinterpret_cast<PyObject*>(type)); Py_DECREF(attribute); return res; } fill(type, attribute); return attribute; } /* No attribute found in local __dict__ (or bases): use the * descriptor from the metatype, if any */ if (meta_get != nullptr) { PyObject* res; res = meta_get( meta_attribute, reinterpret_cast<PyObject*>(type), reinterpret_cast<PyObject*>(metatype)); Py_DECREF(meta_attribute); return res; } /* If an ordinary attribute was found on the metatype, return it now */ if (meta_attribute != nullptr) { return meta_attribute; } /* Give up */ PyErr_Format( PyExc_AttributeError, "type object '%.50s' has no attribute '%U'", type->tp_name, name); return NULL; } PyObject* LoadTypeAttrCache::invoke( LoadTypeAttrCache* cache, PyObject* obj, PyObject* name) { return cache->doInvoke(obj, name); } void GlobalCache::init() const { // We want to try and only watch builtins if this is really a // builtin. So we will start only watching globals, and if // the value gets deleted from globals then we'll start // tracking builtins as well. Once we start tracking builtins // we'll never stop rather than trying to handle all of the // transitions. watchDictKey(key().globals, key().name, *this); PyObject* builtins = key().builtins; // We don't need to immediately watch builtins if it's // defined as a global if (PyObject* globals_value = PyDict_GetItem(key().globals, key().name)) { // the dict getitem could have triggered a lazy import with side effects // that unwatched the dict if (valuePtr()) { *valuePtr() = globals_value; } } else if (_PyDict_CanWatch(builtins)) { *valuePtr() = PyDict_GetItem(builtins, key().name); if (key().globals != builtins) { watchDictKey(builtins, key().name, *this); } } } void GlobalCache::update( PyObject* dict, PyObject* new_value, std::vector<GlobalCache>& to_disable) const { PyObject* builtins = key().builtins; if (dict == key().globals) { if (new_value == nullptr && key().globals != builtins) { if (!_PyDict_CanWatch(builtins)) { // builtins is no longer watchable. Mark this cache for disabling. to_disable.emplace_back(*this); return; } // Fall back to the builtin (which may also be null). *valuePtr() = PyDict_GetItem(builtins, key().name); // it changed, and it changed from something to nothing, so // we weren't watching builtins and need to start now. if (!isWatchedDictKey(builtins, key().name, *this)) { watchDictKey(builtins, key().name, *this); } } else { *valuePtr() = new_value; } } else { JIT_CHECK(dict == builtins, "Unexpected dict"); JIT_CHECK(_PyDict_CanWatch(key().globals), "Bad globals dict"); // Check if this value is shadowed. PyObject* globals_value = PyDict_GetItem(key().globals, key().name); if (globals_value == nullptr) { *valuePtr() = new_value; } } } void GlobalCache::disable() const { *valuePtr() = nullptr; jit::codegen::NativeGeneratorFactory::runtime()->forgetLoadGlobalCache(*this); } void notifyICsTypeChanged(BorrowedRef<PyTypeObject> type) { ac_watcher.typeChanged(type); ltac_watcher.typeChanged(type); } } // namespace jit