//===- AttributeDetail.h - MLIR Affine Map details Class --------*- 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 // //===----------------------------------------------------------------------===// // // This holds implementation details of Attribute. // //===----------------------------------------------------------------------===// #ifndef ATTRIBUTEDETAIL_H_ #define ATTRIBUTEDETAIL_H_ #include "mlir/IR/AffineMap.h" #include "mlir/IR/AttributeSupport.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/BuiltinTypeInterfaces.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/IntegerSet.h" #include "mlir/IR/MLIRContext.h" #include "llvm/ADT/APFloat.h" #include "llvm/Support/Allocator.h" #include namespace mlir { namespace detail { //===----------------------------------------------------------------------===// // Elements Attributes //===----------------------------------------------------------------------===// /// Return the bit width which DenseElementsAttr should use for this type. inline size_t getDenseElementBitWidth(Type eltType) { if (auto denseEltType = llvm::dyn_cast(eltType)) return denseEltType.getDenseElementBitSize(); llvm_unreachable("unsupported element type"); } /// An attribute representing a reference to a dense vector or tensor object. struct DenseElementsAttributeStorage : public AttributeStorage { public: DenseElementsAttributeStorage(ShapedType type) : type(type) {} ShapedType type; }; /// An attribute representing a reference to a dense vector or tensor object. struct DenseTypedElementsAttrStorage : public DenseElementsAttributeStorage { DenseTypedElementsAttrStorage(ShapedType ty, ArrayRef data) : DenseElementsAttributeStorage(ty), data(data) {} struct KeyTy { KeyTy(ShapedType type, ArrayRef data, llvm::hash_code hashCode) : type(type), data(data), hashCode(hashCode) {} /// The type of the dense elements. ShapedType type; /// The raw buffer for the data storage. ArrayRef data; /// The computed hash code for the storage data. llvm::hash_code hashCode; }; /// Compare this storage instance with the provided key. bool operator==(const KeyTy &key) const { return key.type == type && key.data == data; } /// Construct a key from a shaped type and raw data buffer. static KeyTy getKey(ShapedType ty, ArrayRef data) { // Handle an empty storage instance. if (data.empty()) return KeyTy(ty, data, 0); size_t elementWidth = getDenseElementBitWidth(ty.getElementType()); // Dense elements are padded to 8-bits. size_t storageSize = llvm::divideCeil(elementWidth, CHAR_BIT); // If the data buffer holds a single element, it is a known splat. if (data.size() == storageSize) return KeyTy(ty, data, llvm::hash_value(data)); assert(((data.size() / storageSize) == static_cast(ty.getNumElements())) && "data does not hold expected number of elements"); // Create the initial hash value with just the first element. auto firstElt = data.take_front(storageSize); auto hashVal = llvm::hash_value(firstElt); // Check to see if this storage represents a splat. If it doesn't then // combine the hash for the data starting with the first non splat element. for (size_t i = storageSize, e = data.size(); i != e; i += storageSize) if (memcmp(data.data(), &data[i], storageSize)) return KeyTy(ty, data, llvm::hash_combine(hashVal, data.drop_front(i))); // Otherwise, this is a splat so just return the hash of the first element. return KeyTy(ty, firstElt, hashVal); } /// Hash the key for the storage. static llvm::hash_code hashKey(const KeyTy &key) { return llvm::hash_combine(key.type, key.hashCode); } /// Construct a new storage instance. static DenseTypedElementsAttrStorage * construct(AttributeStorageAllocator &allocator, KeyTy key) { // If the data buffer is non-empty, we copy it into the allocator with a // 64-bit alignment. ArrayRef copy, data = key.data; if (!data.empty()) { char *rawData = reinterpret_cast( allocator.allocate(data.size(), alignof(uint64_t))); std::memcpy(rawData, data.data(), data.size()); copy = ArrayRef(rawData, data.size()); } return new (allocator.allocate()) DenseTypedElementsAttrStorage(key.type, copy); } ArrayRef data; }; /// An attribute representing a reference to a dense vector or tensor object /// containing strings. struct DenseStringElementsAttrStorage : public DenseElementsAttributeStorage { DenseStringElementsAttrStorage(ShapedType ty, ArrayRef data) : DenseElementsAttributeStorage(ty), data(data) {} struct KeyTy { KeyTy(ShapedType type, ArrayRef data, llvm::hash_code hashCode) : type(type), data(data), hashCode(hashCode) {} /// The type of the dense elements. ShapedType type; /// The raw buffer for the data storage. ArrayRef data; /// The computed hash code for the storage data. llvm::hash_code hashCode; }; /// Compare this storage instance with the provided key. bool operator==(const KeyTy &key) const { if (key.type != type) return false; // Otherwise, we can default to just checking the data. StringRefs compare // by contents. return key.data == data; } /// Construct a key from a shaped type and StringRef data buffer. static KeyTy getKey(ShapedType ty, ArrayRef data) { // Handle an empty storage instance. if (data.empty()) return KeyTy(ty, data, 0); // If the data buffer holds a single element, it is a known splat. if (data.size() == 1) return KeyTy(ty, data, llvm::hash_value(data.front())); // Create the initial hash value with just the first element. const auto &firstElt = data.front(); auto hashVal = llvm::hash_value(firstElt); // Check to see if this storage represents a splat. If it doesn't then // combine the hash for the data starting with the first non splat element. for (size_t i = 1, e = data.size(); i != e; ++i) if (firstElt != data[i]) return KeyTy(ty, data, llvm::hash_combine(hashVal, data.drop_front(i))); // Otherwise, this is a splat so just return the hash of the first element. return KeyTy(ty, data.take_front(), hashVal); } /// Hash the key for the storage. static llvm::hash_code hashKey(const KeyTy &key) { return llvm::hash_combine(key.type, key.hashCode); } /// Construct a new storage instance. static DenseStringElementsAttrStorage * construct(AttributeStorageAllocator &allocator, KeyTy key) { // If the data buffer is non-empty, we copy it into the allocator with a // 64-bit alignment. ArrayRef copy, data = key.data; if (data.empty()) { return new (allocator.allocate()) DenseStringElementsAttrStorage(key.type, copy); } size_t numEntries = data.size(); // Compute the amount data needed to store the ArrayRef and StringRef // contents. size_t dataSize = sizeof(StringRef) * numEntries; for (size_t i = 0; i < numEntries; ++i) dataSize += data[i].size(); char *rawData = reinterpret_cast( allocator.allocate(dataSize, alignof(uint64_t))); // Setup a mutable array ref of our string refs so that we can update their // contents. auto mutableCopy = MutableArrayRef( reinterpret_cast(rawData), numEntries); auto *stringData = rawData + numEntries * sizeof(StringRef); for (size_t i = 0; i < numEntries; ++i) { memcpy(stringData, data[i].data(), data[i].size()); mutableCopy[i] = StringRef(stringData, data[i].size()); stringData += data[i].size(); } copy = ArrayRef(reinterpret_cast(rawData), numEntries); return new (allocator.allocate()) DenseStringElementsAttrStorage(key.type, copy); } ArrayRef data; }; //===----------------------------------------------------------------------===// // StringAttr //===----------------------------------------------------------------------===// struct StringAttrStorage : public AttributeStorage { StringAttrStorage(StringRef value, Type type) : type(type), value(value), referencedDialect(nullptr) {} /// The hash key is a tuple of the parameter types. using KeyTy = std::pair; bool operator==(const KeyTy &key) const { return value == key.first && type == key.second; } static ::llvm::hash_code hashKey(const KeyTy &key) { return DenseMapInfo::getHashValue(key); } /// Define a construction method for creating a new instance of this /// storage. static StringAttrStorage *construct(AttributeStorageAllocator &allocator, const KeyTy &key) { return new (allocator.allocate()) StringAttrStorage(allocator.copyInto(key.first), key.second); } /// Initialize the storage given an MLIRContext. void initialize(MLIRContext *context); /// The type of the string. Type type; /// The raw string value. StringRef value; /// If the string value contains a dialect namespace prefix (e.g. /// dialect.blah), this is the dialect referenced. Dialect *referencedDialect; }; //===----------------------------------------------------------------------===// // DistinctAttr //===----------------------------------------------------------------------===// /// An attribute to store a distinct reference to another attribute. struct DistinctAttrStorage : public AttributeStorage { using KeyTy = Attribute; DistinctAttrStorage(Attribute referencedAttr) : referencedAttr(referencedAttr) {} /// Returns the referenced attribute as key. KeyTy getAsKey() const { return KeyTy(referencedAttr); } /// The referenced attribute. Attribute referencedAttr; }; /// A specialized attribute uniquer for distinct attributes that always /// allocates since the distinct attribute instances use the address of their /// storage as unique identifier. class DistinctAttributeUniquer { public: /// Creates a distinct attribute storage. Allocates every time since the /// address of the storage serves as unique identifier. template static T get(MLIRContext *context, Args &&...args) { static_assert(std::is_same_v, "expects a distinct attribute storage"); DistinctAttrStorage *storage = DistinctAttributeUniquer::allocateStorage( context, std::forward(args)...); storage->initializeAbstractAttribute( AbstractAttribute::lookup(DistinctAttr::getTypeID(), context)); return storage; } private: /// Allocates a distinct attribute storage. static DistinctAttrStorage *allocateStorage(MLIRContext *context, Attribute referencedAttr); }; /// An allocator for distinct attribute storage instances. Uses a synchronized /// BumpPtrAllocator to ensure thread-safety. The allocated storage is deleted /// when the DistinctAttributeAllocator is destroyed. class DistinctAttributeAllocator final { public: DistinctAttributeAllocator() = default; DistinctAttributeAllocator(DistinctAttributeAllocator &&) = delete; DistinctAttributeAllocator(const DistinctAttributeAllocator &) = delete; DistinctAttributeAllocator & operator=(const DistinctAttributeAllocator &) = delete; DistinctAttrStorage *allocate(Attribute referencedAttr) { std::scoped_lock guard(allocatorMutex); return new (allocator.Allocate()) DistinctAttrStorage(referencedAttr); }; private: /// Used to allocate distict attribute storages. The managed memory is freed /// automatically when the allocator instance is destroyed. llvm::BumpPtrAllocator allocator; /// Used to lock access to the allocator. std::mutex allocatorMutex; }; } // namespace detail } // namespace mlir #endif // ATTRIBUTEDETAIL_H_