| //===- TypeDetail.h - Details of MLIR LLVM dialect types --------*- 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 file contains implementation details, such as storage structures, of |
| // MLIR LLVM dialect types. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef DIALECT_LLVMIR_IR_TYPEDETAIL_H |
| #define DIALECT_LLVMIR_IR_TYPEDETAIL_H |
| |
| #include "mlir/Dialect/LLVMIR/LLVMTypes.h" |
| #include "mlir/IR/TypeSupport.h" |
| #include "mlir/IR/Types.h" |
| |
| #include "llvm/ADT/Bitfields.h" |
| #include "llvm/ADT/PointerIntPair.h" |
| |
| namespace mlir { |
| namespace LLVM { |
| namespace detail { |
| |
| //===----------------------------------------------------------------------===// |
| // LLVMStructTypeStorage. |
| //===----------------------------------------------------------------------===// |
| |
| /// Type storage for LLVM structure types. |
| /// |
| /// Structures are uniqued using: |
| /// - a bit indicating whether a struct is literal or identified; |
| /// - for identified structs, in addition to the bit: |
| /// - a string identifier; |
| /// - for literal structs, in addition to the bit: |
| /// - a list of contained types; |
| /// - a bit indicating whether the literal struct is packed. |
| /// |
| /// Identified structures only have a mutable component consisting of: |
| /// - a list of contained types; |
| /// - a bit indicating whether the identified struct is packed; |
| /// - a bit indicating whether the identified struct is intentionally opaque; |
| /// - a bit indicating whether the identified struct has been initialized. |
| /// Uninitialized structs are considered opaque by the user, and can be mutated. |
| /// Initialized and still opaque structs cannot be mutated. |
| /// |
| /// The struct storage consists of: |
| /// - immutable part: |
| /// - a pointer to the first element of the key (character for identified |
| /// structs, type for literal structs); |
| /// - the number of elements in the key packed together with bits indicating |
| /// whether a type is literal or identified, and the packedness bit for |
| /// literal structs only; |
| /// - mutable part: |
| /// - a pointer to the first contained type for identified structs only; |
| /// - the number of contained types packed together with bits of the mutable |
| /// component, for identified structs only. |
| struct LLVMStructTypeStorage : public TypeStorage { |
| public: |
| /// Construction/uniquing key class for LLVM dialect structure storage. Note |
| /// that this is a transient helper data structure that is NOT stored. |
| /// Therefore, it intentionally avoids bit manipulation and type erasure in |
| /// pointers to make manipulation more straightforward. Not all elements of |
| /// the key participate in uniquing, but all elements participate in |
| /// construction. |
| class Key { |
| public: |
| /// Constructs a key for an identified struct. |
| Key(StringRef name, bool opaque) |
| : name(name), identified(true), packed(false), opaque(opaque) {} |
| /// Constructs a key for a literal struct. |
| Key(ArrayRef<Type> types, bool packed) |
| : types(types), identified(false), packed(packed), opaque(false) {} |
| |
| /// Checks a specific property of the struct. |
| bool isIdentified() const { return identified; } |
| bool isPacked() const { |
| assert(!isIdentified() && |
| "'packed' bit is not part of the key for identified structs"); |
| return packed; |
| } |
| bool isOpaque() const { |
| assert(isIdentified() && |
| "'opaque' bit is meaningless on literal structs"); |
| return opaque; |
| } |
| |
| /// Returns the identifier of a key for identified structs. |
| StringRef getIdentifier() const { |
| assert(isIdentified() && |
| "non-identified struct key cannot have an identifier"); |
| return name; |
| } |
| |
| /// Returns the list of type contained in the key of a literal struct. |
| ArrayRef<Type> getTypeList() const { |
| assert(!isIdentified() && |
| "identified struct key cannot have a type list"); |
| return types; |
| } |
| |
| /// Returns the hash value of the key. This combines various flags into a |
| /// single value: the identified flag sets the first bit, and the packedness |
| /// flag sets the second bit. Opacity bit is only used for construction and |
| /// does not participate in uniquing. |
| llvm::hash_code hashValue() const { |
| constexpr static unsigned kIdentifiedHashFlag = 1; |
| constexpr static unsigned kPackedHashFlag = 2; |
| |
| unsigned flags = 0; |
| if (isIdentified()) { |
| flags |= kIdentifiedHashFlag; |
| return llvm::hash_combine(flags, getIdentifier()); |
| } |
| if (isPacked()) |
| flags |= kPackedHashFlag; |
| return llvm::hash_combine(flags, getTypeList()); |
| } |
| |
| /// Compares two keys. |
| bool operator==(const Key &other) const { |
| if (isIdentified()) |
| return other.isIdentified() && |
| other.getIdentifier().equals(getIdentifier()); |
| |
| return !other.isIdentified() && other.isPacked() == isPacked() && |
| other.getTypeList() == getTypeList(); |
| } |
| |
| /// Copies dynamically-sized components of the key into the given allocator. |
| Key copyIntoAllocator(TypeStorageAllocator &allocator) const { |
| if (isIdentified()) |
| return Key(allocator.copyInto(name), opaque); |
| return Key(allocator.copyInto(types), packed); |
| } |
| |
| private: |
| ArrayRef<Type> types; |
| StringRef name; |
| bool identified; |
| bool packed; |
| bool opaque; |
| }; |
| using KeyTy = Key; |
| |
| /// Returns the string identifier of an identified struct. |
| StringRef getIdentifier() const { |
| assert(isIdentified() && "requested identifier on a non-identified struct"); |
| return StringRef(static_cast<const char *>(keyPtr), keySize()); |
| } |
| |
| /// Returns the list of types (partially) identifying a literal struct. |
| ArrayRef<Type> getTypeList() const { |
| // If this triggers, use getIdentifiedStructBody() instead. |
| assert(!isIdentified() && "requested typelist on an identified struct"); |
| return ArrayRef<Type>(static_cast<const Type *>(keyPtr), keySize()); |
| } |
| |
| /// Returns the list of types contained in an identified struct. |
| ArrayRef<Type> getIdentifiedStructBody() const { |
| // If this triggers, use getTypeList() instead. |
| assert(isIdentified() && |
| "requested struct body on a non-identified struct"); |
| return ArrayRef<Type>(identifiedBodyArray, identifiedBodySize()); |
| } |
| |
| /// Checks whether the struct is identified. |
| bool isIdentified() const { |
| return llvm::Bitfield::get<KeyFlagIdentified>(keySizeAndFlags); |
| } |
| |
| /// Checks whether the struct is packed (both literal and identified structs). |
| bool isPacked() const { |
| return isIdentified() ? llvm::Bitfield::get<MutableFlagPacked>( |
| identifiedBodySizeAndFlags) |
| : llvm::Bitfield::get<KeyFlagPacked>(keySizeAndFlags); |
| } |
| |
| /// Checks whether a struct is marked as intentionally opaque (an |
| /// uninitialized struct is also considered opaque by the user, call |
| /// isInitialized to check that). |
| bool isOpaque() const { |
| return llvm::Bitfield::get<MutableFlagOpaque>(identifiedBodySizeAndFlags); |
| } |
| |
| /// Checks whether an identified struct has been explicitly initialized either |
| /// by setting its body or by marking it as intentionally opaque. |
| bool isInitialized() const { |
| return llvm::Bitfield::get<MutableFlagInitialized>( |
| identifiedBodySizeAndFlags); |
| } |
| |
| /// Constructs the storage from the given key. This sets up the uniquing key |
| /// components and optionally the mutable component if they construction key |
| /// has the relevant information. In the latter case, the struct is considered |
| /// as initialized and can no longer be mutated. |
| LLVMStructTypeStorage(const KeyTy &key) { |
| if (!key.isIdentified()) { |
| ArrayRef<Type> types = key.getTypeList(); |
| keyPtr = static_cast<const void *>(types.data()); |
| setKeySize(types.size()); |
| llvm::Bitfield::set<KeyFlagPacked>(keySizeAndFlags, key.isPacked()); |
| return; |
| } |
| |
| StringRef name = key.getIdentifier(); |
| keyPtr = static_cast<const void *>(name.data()); |
| setKeySize(name.size()); |
| llvm::Bitfield::set<KeyFlagIdentified>(keySizeAndFlags, true); |
| |
| // If the struct is being constructed directly as opaque, mark it as |
| // initialized. |
| llvm::Bitfield::set<MutableFlagInitialized>(identifiedBodySizeAndFlags, |
| key.isOpaque()); |
| llvm::Bitfield::set<MutableFlagOpaque>(identifiedBodySizeAndFlags, |
| key.isOpaque()); |
| } |
| |
| /// Hook into the type uniquing infrastructure. |
| bool operator==(const KeyTy &other) const { return getKey() == other; }; |
| static llvm::hash_code hashKey(const KeyTy &key) { return key.hashValue(); } |
| static LLVMStructTypeStorage *construct(TypeStorageAllocator &allocator, |
| const KeyTy &key) { |
| return new (allocator.allocate<LLVMStructTypeStorage>()) |
| LLVMStructTypeStorage(key.copyIntoAllocator(allocator)); |
| } |
| |
| /// Sets the body of an identified struct. If the struct is already |
| /// initialized, succeeds only if the body is equal to the current body. Fails |
| /// if the struct is marked as intentionally opaque. The struct will be marked |
| /// as initialized as a result of this operation and can no longer be changed. |
| LogicalResult mutate(TypeStorageAllocator &allocator, ArrayRef<Type> body, |
| bool packed) { |
| if (!isIdentified()) |
| return failure(); |
| if (isInitialized()) |
| return success(!isOpaque() && body == getIdentifiedStructBody() && |
| packed == isPacked()); |
| |
| llvm::Bitfield::set<MutableFlagInitialized>(identifiedBodySizeAndFlags, |
| true); |
| llvm::Bitfield::set<MutableFlagPacked>(identifiedBodySizeAndFlags, packed); |
| |
| ArrayRef<Type> typesInAllocator = allocator.copyInto(body); |
| identifiedBodyArray = typesInAllocator.data(); |
| setIdentifiedBodySize(typesInAllocator.size()); |
| |
| return success(); |
| } |
| |
| private: |
| /// Returns the number of elements in the key. |
| unsigned keySize() const { |
| return llvm::Bitfield::get<KeySize>(keySizeAndFlags); |
| } |
| |
| /// Sets the number of elements in the key. |
| void setKeySize(unsigned value) { |
| llvm::Bitfield::set<KeySize>(keySizeAndFlags, value); |
| } |
| |
| /// Returns the number of types contained in an identified struct. |
| unsigned identifiedBodySize() const { |
| return llvm::Bitfield::get<MutableSize>(identifiedBodySizeAndFlags); |
| } |
| /// Sets the number of types contained in an identified struct. |
| void setIdentifiedBodySize(unsigned value) { |
| llvm::Bitfield::set<MutableSize>(identifiedBodySizeAndFlags, value); |
| } |
| |
| /// Returns the key for the current storage. |
| Key getKey() const { |
| if (isIdentified()) |
| return Key(getIdentifier(), isOpaque()); |
| return Key(getTypeList(), isPacked()); |
| } |
| |
| /// Bitfield elements for `keyAndSizeFlags`: |
| /// - bit 0: identified key flag; |
| /// - bit 1: packed key flag; |
| /// - bits 2..bitwidth(unsigned): size of the key. |
| using KeyFlagIdentified = |
| llvm::Bitfield::Element<bool, /*Offset=*/0, /*Size=*/1>; |
| using KeyFlagPacked = llvm::Bitfield::Element<bool, /*Offset=*/1, /*Size=*/1>; |
| using KeySize = |
| llvm::Bitfield::Element<unsigned, /*Offset=*/2, |
| std::numeric_limits<unsigned>::digits - 2>; |
| |
| /// Bitfield elements for `identifiedBodySizeAndFlags`: |
| /// - bit 0: opaque flag; |
| /// - bit 1: packed mutable flag; |
| /// - bit 2: initialized flag; |
| /// - bits 3..bitwidth(unsigned): size of the identified body. |
| using MutableFlagOpaque = |
| llvm::Bitfield::Element<bool, /*Offset=*/0, /*Size=*/1>; |
| using MutableFlagPacked = |
| llvm::Bitfield::Element<bool, /*Offset=*/1, /*Size=*/1>; |
| using MutableFlagInitialized = |
| llvm::Bitfield::Element<bool, /*Offset=*/2, /*Size=*/1>; |
| using MutableSize = |
| llvm::Bitfield::Element<unsigned, /*Offset=*/3, |
| std::numeric_limits<unsigned>::digits - 3>; |
| |
| /// Pointer to the first element of the uniquing key. |
| // Note: cannot use PointerUnion because bump-ptr allocator does not guarantee |
| // address alignment. |
| const void *keyPtr = nullptr; |
| |
| /// Pointer to the first type contained in an identified struct. |
| const Type *identifiedBodyArray = nullptr; |
| |
| /// Size of the uniquing key combined with identified/literal and |
| /// packedness bits. Must only be used through the Key* bitfields. |
| unsigned keySizeAndFlags = 0; |
| |
| /// Number of the types contained in an identified struct combined with |
| /// mutable flags. Must only be used through the Mutable* bitfields. |
| unsigned identifiedBodySizeAndFlags = 0; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // LLVMFunctionTypeStorage. |
| //===----------------------------------------------------------------------===// |
| |
| /// Type storage for LLVM dialect function types. These are uniqued using the |
| /// list of types they contain and the vararg bit. |
| struct LLVMFunctionTypeStorage : public TypeStorage { |
| using KeyTy = std::tuple<Type, ArrayRef<Type>, bool>; |
| |
| /// Construct a storage from the given components. The list is expected to be |
| /// allocated in the context. |
| LLVMFunctionTypeStorage(Type result, ArrayRef<Type> arguments, bool variadic) |
| : argumentTypes(arguments) { |
| returnTypeAndVariadic.setPointerAndInt(result, variadic); |
| } |
| |
| /// Hook into the type uniquing infrastructure. |
| static LLVMFunctionTypeStorage *construct(TypeStorageAllocator &allocator, |
| const KeyTy &key) { |
| return new (allocator.allocate<LLVMFunctionTypeStorage>()) |
| LLVMFunctionTypeStorage(std::get<0>(key), |
| allocator.copyInto(std::get<1>(key)), |
| std::get<2>(key)); |
| } |
| |
| static unsigned hashKey(const KeyTy &key) { |
| // LLVM doesn't like hashing bools in tuples. |
| return llvm::hash_combine(std::get<0>(key), std::get<1>(key), |
| static_cast<int>(std::get<2>(key))); |
| } |
| |
| bool operator==(const KeyTy &key) const { |
| return std::make_tuple(getReturnType(), getArgumentTypes(), isVariadic()) == |
| key; |
| } |
| |
| /// Returns the list of function argument types. |
| ArrayRef<Type> getArgumentTypes() const { return argumentTypes; } |
| |
| /// Checks whether the function type is variadic. |
| bool isVariadic() const { return returnTypeAndVariadic.getInt(); } |
| |
| /// Returns the function result type. |
| Type getReturnType() const { return returnTypeAndVariadic.getPointer(); } |
| |
| private: |
| /// Function result type packed with the variadic bit. |
| llvm::PointerIntPair<Type, 1, bool> returnTypeAndVariadic; |
| /// Argument types. |
| ArrayRef<Type> argumentTypes; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // LLVMPointerTypeStorage. |
| //===----------------------------------------------------------------------===// |
| |
| /// Storage type for LLVM dialect pointer types. These are uniqued by a pair of |
| /// element type and address space. |
| struct LLVMPointerTypeStorage : public TypeStorage { |
| using KeyTy = std::tuple<Type, unsigned>; |
| |
| LLVMPointerTypeStorage(const KeyTy &key) |
| : pointeeType(std::get<0>(key)), addressSpace(std::get<1>(key)) {} |
| |
| static LLVMPointerTypeStorage *construct(TypeStorageAllocator &allocator, |
| const KeyTy &key) { |
| return new (allocator.allocate<LLVMPointerTypeStorage>()) |
| LLVMPointerTypeStorage(key); |
| } |
| |
| bool operator==(const KeyTy &key) const { |
| return std::make_tuple(pointeeType, addressSpace) == key; |
| } |
| |
| Type pointeeType; |
| unsigned addressSpace; |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // LLVMTypeAndSizeStorage. |
| //===----------------------------------------------------------------------===// |
| |
| /// Common storage used for LLVM dialect types that need an element type and a |
| /// number: arrays, fixed and scalable vectors. The actual semantics of the |
| /// type is defined by its kind. |
| struct LLVMTypeAndSizeStorage : public TypeStorage { |
| using KeyTy = std::tuple<Type, unsigned>; |
| |
| LLVMTypeAndSizeStorage(const KeyTy &key) |
| : elementType(std::get<0>(key)), numElements(std::get<1>(key)) {} |
| |
| static LLVMTypeAndSizeStorage *construct(TypeStorageAllocator &allocator, |
| const KeyTy &key) { |
| return new (allocator.allocate<LLVMTypeAndSizeStorage>()) |
| LLVMTypeAndSizeStorage(key); |
| } |
| |
| bool operator==(const KeyTy &key) const { |
| return std::make_tuple(elementType, numElements) == key; |
| } |
| |
| Type elementType; |
| unsigned numElements; |
| }; |
| |
| } // end namespace detail |
| } // end namespace LLVM |
| } // end namespace mlir |
| |
| #endif // DIALECT_LLVMIR_IR_TYPEDETAIL_H |