//===- StorageUniquer.h - Common Storage Class Uniquer ----------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #ifndef MLIR_SUPPORT_STORAGEUNIQUER_H #define MLIR_SUPPORT_STORAGEUNIQUER_H #include "mlir/Support/LLVM.h" #include "mlir/Support/TypeID.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include namespace mlir { namespace detail { struct StorageUniquerImpl; /// Trait to check if ImplTy provides a 'getKey' method with types 'Args'. template using has_impltype_getkey_t = decltype(ImplTy::getKey(std::declval()...)); /// Trait to check if ImplTy provides a 'hashKey' method for 'T'. template using has_impltype_hash_t = decltype(ImplTy::hashKey(std::declval())); } // namespace detail /// A utility class to get or create instances of "storage classes". These /// storage classes must derive from 'StorageUniquer::BaseStorage'. /// /// For non-parametric storage classes, i.e. singleton classes, nothing else is /// needed. Instances of these classes can be created by calling `get` without /// trailing arguments. /// /// Otherwise, the parametric storage classes may be created with `get`, /// and must respect the following: /// - Define a type alias, KeyTy, to a type that uniquely identifies the /// instance of the storage class. /// * The key type must be constructible from the values passed into the /// getComplex call. /// * If the KeyTy does not have an llvm::DenseMapInfo specialization, the /// storage class must define a hashing method: /// 'static unsigned hashKey(const KeyTy &)' /// /// - Provide a method, 'bool operator==(const KeyTy &) const', to /// compare the storage instance against an instance of the key type. /// /// - Provide a static construction method: /// 'DerivedStorage *construct(StorageAllocator &, const KeyTy &key)' /// that builds a unique instance of the derived storage. The arguments to /// this function are an allocator to store any uniqued data and the key /// type for this storage. /// /// - Provide a cleanup method: /// 'void cleanup()' /// that is called when erasing a storage instance. This should cleanup any /// fields of the storage as necessary and not attempt to free the memory /// of the storage itself. /// /// Storage classes may have an optional mutable component, which must not take /// part in the unique immutable key. In this case, storage classes may be /// mutated with `mutate` and must additionally respect the following: /// - Provide a mutation method: /// 'LogicalResult mutate(StorageAllocator &, <...>)' /// that is called when mutating a storage instance. The first argument is /// an allocator to store any mutable data, and the remaining arguments are /// forwarded from the call site. The storage can be mutated at any time /// after creation. Care must be taken to avoid excessive mutation since /// the allocated storage can keep containing previous states. The return /// value of the function is used to indicate whether the mutation was /// successful, e.g., to limit the number of mutations or enable deferred /// one-time assignment of the mutable component. /// /// All storage classes must be registered with the uniquer via /// `registerParametricStorageType` or `registerSingletonStorageType` /// using an appropriate unique `TypeID` for the storage class. class StorageUniquer { public: /// This class acts as the base storage that all storage classes must derived /// from. class alignas(8) BaseStorage { protected: BaseStorage() = default; }; /// This is a utility allocator used to allocate memory for instances of /// derived types. class StorageAllocator { public: /// Copy the specified array of elements into memory managed by our bump /// pointer allocator. This assumes the elements are all PODs. template ArrayRef copyInto(ArrayRef elements) { if (elements.empty()) return {}; auto result = allocator.Allocate(elements.size()); llvm::uninitialized_copy(elements, result); return ArrayRef(result, elements.size()); } /// Copy the provided string into memory managed by our bump pointer /// allocator. StringRef copyInto(StringRef str) { if (str.empty()) return StringRef(); char *result = allocator.Allocate(str.size() + 1); llvm::uninitialized_copy(str, result); result[str.size()] = 0; return StringRef(result, str.size()); } /// Allocate an instance of the provided type. template T *allocate() { return allocator.Allocate(); } /// Allocate 'size' bytes of 'alignment' aligned memory. void *allocate(size_t size, size_t alignment) { return allocator.Allocate(size, alignment); } /// Returns true if this allocator allocated the provided object pointer. bool allocated(const void *ptr) { return allocator.identifyObject(ptr).has_value(); } private: /// The raw allocator for type storage objects. llvm::BumpPtrAllocator allocator; }; StorageUniquer(); ~StorageUniquer(); /// Set the flag specifying if multi-threading is disabled within the uniquer. void disableMultithreading(bool disable = true); /// Register a new parametric storage class, this is necessary to create /// instances of this class type. `id` is the type identifier that will be /// used to identify this type when creating instances of it via 'get'. template void registerParametricStorageType(TypeID id) { // If the storage is trivially destructible, we don't need a destructor // function. if constexpr (std::is_trivially_destructible_v) return registerParametricStorageTypeImpl(id, nullptr); registerParametricStorageTypeImpl(id, [](BaseStorage *storage) { static_cast(storage)->~Storage(); }); } /// Utility override when the storage type represents the type id. template void registerParametricStorageType() { registerParametricStorageType(TypeID::get()); } /// Register a new singleton storage class, this is necessary to get the /// singletone instance. `id` is the type identifier that will be used to /// access the singleton instance via 'get'. An optional initialization /// function may also be provided to initialize the newly created storage /// instance, and used when the singleton instance is created. template void registerSingletonStorageType(TypeID id, function_ref initFn) { auto ctorFn = [&](StorageAllocator &allocator) { auto *storage = new (allocator.allocate()) Storage(); if (initFn) initFn(storage); return storage; }; registerSingletonImpl(id, ctorFn); } template void registerSingletonStorageType(TypeID id) { registerSingletonStorageType(id, std::nullopt); } /// Utility override when the storage type represents the type id. template void registerSingletonStorageType(function_ref initFn = {}) { registerSingletonStorageType(TypeID::get(), initFn); } /// Gets a uniqued instance of 'Storage'. 'id' is the type id used when /// registering the storage instance. 'initFn' is an optional parameter that /// can be used to initialize a newly inserted storage instance. This function /// is used for derived types that have complex storage or uniquing /// constraints. template Storage *get(function_ref initFn, TypeID id, Args &&...args) { // Construct a value of the derived key type. auto derivedKey = getKey(std::forward(args)...); // Create a hash of the derived key. unsigned hashValue = getHash(derivedKey); // Generate an equality function for the derived storage. auto isEqual = [&derivedKey](const BaseStorage *existing) { return static_cast(*existing) == derivedKey; }; // Generate a constructor function for the derived storage. auto ctorFn = [&](StorageAllocator &allocator) { auto *storage = Storage::construct(allocator, std::move(derivedKey)); if (initFn) initFn(storage); return storage; }; // Get an instance for the derived storage. return static_cast( getParametricStorageTypeImpl(id, hashValue, isEqual, ctorFn)); } /// Utility override when the storage type represents the type id. template Storage *get(function_ref initFn, Args &&...args) { return get(initFn, TypeID::get(), std::forward(args)...); } /// Gets a uniqued instance of 'Storage' which is a singleton storage type. /// 'id' is the type id used when registering the storage instance. template Storage *get(TypeID id) { return static_cast(getSingletonImpl(id)); } /// Utility override when the storage type represents the type id. template Storage *get() { return get(TypeID::get()); } /// Test if there is a singleton storage uniquer initialized for the provided /// TypeID. This is only useful for debugging/diagnostic purpose: the uniquer /// is initialized when a dialect is loaded. bool isSingletonStorageInitialized(TypeID id); /// Test if there is a parametric storage uniquer initialized for the provided /// TypeID. This is only useful for debugging/diagnostic purpose: the uniquer /// is initialized when a dialect is loaded. bool isParametricStorageInitialized(TypeID id); /// Changes the mutable component of 'storage' by forwarding the trailing /// arguments to the 'mutate' function of the derived class. template LogicalResult mutate(TypeID id, Storage *storage, Args &&...args) { auto mutationFn = [&](StorageAllocator &allocator) -> LogicalResult { return static_cast(*storage).mutate( allocator, std::forward(args)...); }; return mutateImpl(id, storage, mutationFn); } private: /// Implementation for getting/creating an instance of a derived type with /// parametric storage. BaseStorage *getParametricStorageTypeImpl( TypeID id, unsigned hashValue, function_ref isEqual, function_ref ctorFn); /// Implementation for registering an instance of a derived type with /// parametric storage. This method takes an optional destructor function that /// destructs storage instances when necessary. void registerParametricStorageTypeImpl( TypeID id, function_ref destructorFn); /// Implementation for getting an instance of a derived type with default /// storage. BaseStorage *getSingletonImpl(TypeID id); /// Implementation for registering an instance of a derived type with default /// storage. void registerSingletonImpl(TypeID id, function_ref ctorFn); /// Implementation for mutating an instance of a derived storage. LogicalResult mutateImpl(TypeID id, BaseStorage *storage, function_ref mutationFn); /// The internal implementation class. std::unique_ptr impl; //===--------------------------------------------------------------------===// // Key Construction //===--------------------------------------------------------------------===// /// Used to construct an instance of 'ImplTy::KeyTy' if there is an /// 'ImplTy::getKey' function for the provided arguments. Otherwise, then we /// try to directly construct the 'ImplTy::KeyTy' with the provided arguments. template static typename ImplTy::KeyTy getKey(Args &&...args) { if constexpr (llvm::is_detected::value) return ImplTy::getKey(std::forward(args)...); else return typename ImplTy::KeyTy(std::forward(args)...); } //===--------------------------------------------------------------------===// // Key Hashing //===--------------------------------------------------------------------===// /// Used to generate a hash for the `ImplTy` of a storage instance if /// there is a `ImplTy::hashKey. Otherwise, if there is no `ImplTy::hashKey` /// then default to using the 'llvm::DenseMapInfo' definition for /// 'DerivedKey' for generating a hash. template static ::llvm::hash_code getHash(const DerivedKey &derivedKey) { if constexpr (llvm::is_detected::value) return ImplTy::hashKey(derivedKey); else return DenseMapInfo::getHashValue(derivedKey); } }; } // namespace mlir #endif