blob: cf8e46842662a8b250ea47a78e7d3977f2f0efd2 [file] [log] [blame]
//===- 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