Jit/inline_cache.h (189 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #pragma once #include "Python.h" #include "classloader.h" #include "Jit/log.h" #include "Jit/ref.h" #include "Jit/util.h" #include <array> #include <memory> #include <unordered_map> namespace jit { // A pool of objects where the number of objects is known and the address of // the objects needs to remain stable. // // This is oddly specific set of requirements is used during code generation. // When emitting code for a function, we calculate the number of caches that // need to be allocated prior to emitting any code. Then, we allocate a cache // from the pool on-demand as we emit code and burn the address of the cache // into the emitted code. template <typename T> class InlineCachePool { public: explicit InlineCachePool(std::size_t num_entries) { if (num_entries > 0) { entries_ = std::make_unique<T[]>(num_entries); } else { entries_ = nullptr; } num_entries_ = num_entries; num_allocated_ = 0; } T* allocate() { JIT_CHECK(num_allocated_ < num_entries_, "no free entries"); T* entry = &entries_[num_allocated_]; num_allocated_++; return entry; } private: DISALLOW_COPY_AND_ASSIGN(InlineCachePool); std::unique_ptr<T[]> entries_; std::size_t num_entries_; std::size_t num_allocated_; }; // Mutator for an instance attribute that is stored in a split dictionary struct SplitMutator { PyObject* setAttr(PyObject* obj, PyObject* name, PyObject* value); PyObject* getAttr(PyObject* obj, PyObject* name); Py_ssize_t dict_offset; Py_ssize_t val_offset; PyDictKeysObject* keys; // Borrowed }; // Mutator for an instance attribute that is stored in a combined dictionary struct CombinedMutator { PyObject* setAttr(PyObject* obj, PyObject* name, PyObject* value); PyObject* getAttr(PyObject* obj, PyObject* name); Py_ssize_t dict_offset; }; // Mutator for a data descriptor struct DataDescrMutator { PyObject* setAttr(PyObject* obj, PyObject* value); PyObject* getAttr(PyObject* obj); BorrowedRef<> descr; }; // Mutator for a member descriptor struct MemberDescrMutator { PyObject* setAttr(PyObject* obj, PyObject* value); PyObject* getAttr(PyObject* obj); PyMemberDef* memberdef; }; // Attribute corresponds to a non-data descriptor or a class variable struct DescrOrClassVarMutator { PyObject* setAttr(PyObject* obj, PyObject* name, PyObject* value); PyObject* getAttr(PyObject* obj, PyObject* name); BorrowedRef<> descr; Py_ssize_t dictoffset; }; // An instance of AttributeMutator is specialized to more efficiently perform a // get/set of a particular kind of attribute. class AttributeMutator { public: enum class Kind { kEmpty, kSplit, kCombined, kDataDescr, kMemberDescr, kDescrOrClassVar, }; AttributeMutator(); PyTypeObject* type() const; void reset(); bool isEmpty() const; void set_combined(PyTypeObject* type); void set_data_descr(PyTypeObject* type, PyObject* descr); void set_member_descr(PyTypeObject* type, PyObject* descr); void set_descr_or_classvar(PyTypeObject* type, PyObject* descr); void set_split(PyTypeObject* type, Py_ssize_t val_offset, PyDictKeysObject* keys); PyObject* setAttr(PyObject* obj, PyObject* name, PyObject* value); PyObject* getAttr(PyObject* obj, PyObject* name); private: Kind kind_; PyTypeObject* type_; // borrowed union { SplitMutator split_; CombinedMutator combined_; DataDescrMutator data_descr_; MemberDescrMutator member_descr_; DescrOrClassVarMutator descr_or_cvar_; }; }; class AttributeCache { public: void typeChanged(PyTypeObject* type); protected: AttributeMutator* findEmptyEntry(); void fill(BorrowedRef<PyTypeObject> type, BorrowedRef<> name, BorrowedRef<> descr); std::array<AttributeMutator, 4> entries_; }; // A cache for an individual StoreAttr instruction. // // The logic of StoreAttrCache::invoke is equivalent to PyObject_SetAttr, // however, it can be specialized and accelerated depending on the kinds of // receiver types that are seen. class StoreAttrCache : public AttributeCache { public: StoreAttrCache() = default; // Returns a borrowed reference to Py_None on success; nullptr otherwise. static PyObject* invoke(StoreAttrCache* cache, PyObject* obj, PyObject* name, PyObject* value); private: DISALLOW_COPY_AND_ASSIGN(StoreAttrCache); PyObject* doInvoke(PyObject* obj, PyObject* name, PyObject* value); PyObject* invokeSlowPath(PyObject* obj, PyObject* name, PyObject* value); }; // A cache for an individual LoadAttr instruction. // // The logic of LoadAttrCache::invoke is equivalent to PyObject_GetAttr, // however, it can be specialized and accelerated depending on the kinds of // receiver types that are seen. class LoadAttrCache : public AttributeCache { public: LoadAttrCache() = default; // Returns a new reference to the value or NULL on error. static PyObject* invoke(LoadAttrCache* cache, PyObject* obj, PyObject* name); private: DISALLOW_COPY_AND_ASSIGN(LoadAttrCache); PyObject* doInvoke(PyObject* obj, PyObject* name); PyObject* invokeSlowPath(PyObject* obj, PyObject* name); }; // A cache for LoadAttr instructions where we expect the receiver to be a type // object. // // The first entry in `items` is a type object. The second entry in `items` is // the cached value. Both are borrowed references. // // The code for loading an attribute where the expected receiver is a type is // specialized into a fast path and a slow path. The first element is loaded // from the cache and compared against the receiver. If they are equal, the // second element (the cached value) is loaded. If they are not equal, // `invoke()` is called, which performs the full lookup and potentially fills // the cache. class LoadTypeAttrCache { public: LoadTypeAttrCache(); static PyObject* invoke(LoadTypeAttrCache* cache, PyObject* obj, PyObject* name); PyObject* doInvoke(PyObject* obj, PyObject* name); void typeChanged(PyTypeObject* type); std::array<PyObject*, 2> items; // Borrowed private: void fill(PyTypeObject* type, PyObject* value); void reset(); }; struct GlobalCacheKey { // builtins and globals are weak references; the invalidation code is // responsible for erasing any relevant keys when a dict is freed. PyObject* builtins; PyObject* globals; Ref<PyObject> name; GlobalCacheKey(PyObject* builtins, PyObject* globals, PyObject* name) : builtins(builtins), globals(globals), name(name) {} bool operator==(const GlobalCacheKey& other) const { return builtins == other.builtins && globals == other.globals && name == other.name; } }; struct GlobalCacheKeyHash { std::size_t operator()(const GlobalCacheKey& key) const { std::hash<PyObject*> hasher; std::size_t hash = combineHash(hasher(key.builtins), hasher(key.globals)); return combineHash(hash, hasher(key.name)); } }; struct GlobalCacheValue { GlobalCacheValue() : ptr_(std::make_unique<PyObject*>()) {} std::unique_ptr<PyObject*> ptr_; }; using GlobalCacheMap = std::unordered_map<GlobalCacheKey, GlobalCacheValue, GlobalCacheKeyHash>; // Functions to initialize, update, and disable a global cache. The actual // cache lives in a GlobalCacheMap, so this is a thin wrapper around a pointer // to that data. class GlobalCache { public: GlobalCache(GlobalCacheMap::value_type* pair) : pair_(pair) {} const GlobalCacheKey& key() const { return pair_->first; } PyObject** valuePtr() const { return pair_->second.ptr_.get(); } // Initialize the cache: subscribe to both dicts and fill in the current // value. void init() const; // Update the cached value after an update to one of the dicts. // // to_disable collects caches that must be disabled because their builtins // dict is unwatchable and the value has been deleted from the globals // dict. The caller is responsible for safely disabling any caches in this // list. void update( PyObject* dict, PyObject* new_value, std::vector<GlobalCache>& to_disable) const; // Disable the cache by clearing out its value. Unsubscribing from any // watched dicts is left to the caller since it can involve complicated // dances with iterators. void disable() const; bool operator<(const GlobalCache& other) const { return pair_ < other.pair_; } private: GlobalCacheMap::value_type* pair_; }; // Invalidate all load/store attr caches for type void notifyICsTypeChanged(BorrowedRef<PyTypeObject> type); } // namespace jit struct FunctionEntryCacheValue { FunctionEntryCacheValue() : ptr_(std::make_unique<void*>()) {} std::unique_ptr<void*> ptr_; Ref<_PyTypedArgsInfo> arg_info; }; using FunctionEntryCacheMap = std::unordered_map<PyFunctionObject*, FunctionEntryCacheValue>;