blob: c4cf0ee941907bf65d6dd11029c003073b015100 [file] [log] [blame]
//===- subzero/src/IceDefs.h - Common Subzero declarations ------*- C++ -*-===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Declares various useful types and classes that have widespread use
/// across Subzero.
///
//===----------------------------------------------------------------------===//
#ifndef SUBZERO_SRC_ICEDEFS_H
#define SUBZERO_SRC_ICEDEFS_H
#include "IceBuildDefs.h" // TODO(stichnot): move into individual files
#include "IceMemory.h"
#include "IceTLS.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/ilist.h"
#include "llvm/ADT/ilist_node.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ELF.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cstdint>
#include <cstdio> // snprintf
#include <functional> // std::less
#include <limits>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <system_error>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#define XSTRINGIFY(x) STRINGIFY(x)
#define STRINGIFY(x) #x
namespace Ice {
class Assembler;
template <template <typename> class> class BitVectorTmpl;
class Cfg;
class CfgNode;
class Constant;
class ELFFileStreamer;
class ELFObjectWriter;
class ELFStreamer;
class FunctionDeclaration;
class GlobalContext;
class GlobalDeclaration;
class Inst;
class InstAssign;
class InstJumpTable;
class InstPhi;
class InstSwitch;
class InstTarget;
class LiveRange;
class Liveness;
class Operand;
class TargetDataLowering;
class TargetLowering;
class Variable;
class VariableDeclaration;
class VariablesMetadata;
/// SizeT is for holding small-ish limits like number of source operands in an
/// instruction. It is used instead of size_t (which may be 64-bits wide) when
/// we want to save space.
using SizeT = uint32_t;
constexpr char GlobalOffsetTable[] = "_GLOBAL_OFFSET_TABLE_";
// makeUnique should be used when memory is expected to be allocated from the
// heap (as opposed to allocated from some Allocator.) It is intended to be
// used instead of new.
//
// The expected usage is as follows
//
// class MyClass {
// public:
// static std::unique_ptr<MyClass> create(<ctor_args>) {
// return makeUnique<MyClass>(<ctor_args>);
// }
//
// private:
// ENABLE_MAKE_UNIQUE;
//
// MyClass(<ctor_args>) ...
// }
//
// ENABLE_MAKE_UNIQUE is a trick that is necessary if MyClass' ctor is private.
// Private ctors are highly encouraged when you're writing a class that you'd
// like to have allocated with makeUnique as it would prevent users from
// declaring stack allocated variables.
namespace Internal {
struct MakeUniqueEnabler {
template <class T, class... Args>
static std::unique_ptr<T> create(Args &&... TheArgs) {
std::unique_ptr<T> Unique(new T(std::forward<Args>(TheArgs)...));
return Unique;
}
};
} // end of namespace Internal
template <class T, class... Args>
static std::unique_ptr<T> makeUnique(Args &&... TheArgs) {
return ::Ice::Internal::MakeUniqueEnabler::create<T>(
std::forward<Args>(TheArgs)...);
}
#define ENABLE_MAKE_UNIQUE friend struct ::Ice::Internal::MakeUniqueEnabler
using InstList = llvm::ilist<Inst>;
// Ideally PhiList would be llvm::ilist<InstPhi>, and similar for AssignList,
// but this runs into issues with SFINAE.
using PhiList = InstList;
using AssignList = InstList;
// Standard library containers with CfgLocalAllocator.
template <typename T> using CfgList = std::list<T, CfgLocalAllocator<T>>;
template <typename T, typename H = std::hash<T>, typename Eq = std::equal_to<T>>
using CfgUnorderedSet = std::unordered_set<T, H, Eq, CfgLocalAllocator<T>>;
template <typename T, typename Cmp = std::less<T>>
using CfgSet = std::set<T, Cmp, CfgLocalAllocator<T>>;
template <typename T, typename U, typename H = std::hash<T>,
typename Eq = std::equal_to<T>>
using CfgUnorderedMap =
std::unordered_map<T, U, H, Eq, CfgLocalAllocator<std::pair<const T, U>>>;
template <typename T> using CfgVector = std::vector<T, CfgLocalAllocator<T>>;
// Containers that are arena-allocated from the Cfg's allocator.
using OperandList = CfgVector<Operand *>;
using VarList = CfgVector<Variable *>;
using NodeList = CfgVector<CfgNode *>;
// Containers that use the default (global) allocator.
using ConstantList = std::vector<Constant *>;
using FunctionDeclarationList = std::vector<FunctionDeclaration *>;
/// VariableDeclarationList is a container for holding VariableDeclarations --
/// i.e., Global Variables. It is also used to create said variables, and their
/// initializers in an arena.
class VariableDeclarationList {
VariableDeclarationList(const VariableDeclarationList &) = delete;
VariableDeclarationList &operator=(const VariableDeclarationList &) = delete;
VariableDeclarationList(VariableDeclarationList &&) = delete;
VariableDeclarationList &operator=(VariableDeclarationList &&) = delete;
public:
using VariableDeclarationArray = std::vector<VariableDeclaration *>;
VariableDeclarationList() : Arena(new ArenaAllocator()) {}
~VariableDeclarationList() { clearAndPurge(); }
template <typename T> T *allocate_initializer(SizeT Count = 1) {
static_assert(
std::is_trivially_destructible<T>::value,
"allocate_initializer can only allocate trivially destructible types.");
return Arena->Allocate<T>(Count);
}
template <typename T> T *allocate_variable_declaration() {
static_assert(!std::is_trivially_destructible<T>::value,
"allocate_variable_declaration expects non-trivially "
"destructible types.");
T *Ret = Arena->Allocate<T>();
Dtors.emplace_back([Ret]() { Ret->~T(); });
return Ret;
}
// This do nothing method is invoked when a global variable is created, but it
// will not be emitted. If we ever need to track the created variable, having
// this hook is handy.
void willNotBeEmitted(VariableDeclaration *) {}
/// Merges Other with this, effectively resetting Other to an empty state.
void merge(VariableDeclarationList *Other) {
assert(Other != nullptr);
addArena(std::move(Other->Arena));
for (std::size_t i = 0; i < Other->MergedArenas.size(); ++i) {
addArena(std::move(Other->MergedArenas[i]));
}
Other->MergedArenas.clear();
Dtors.insert(Dtors.end(), Other->Dtors.begin(), Other->Dtors.end());
Other->Dtors.clear();
Globals.insert(Globals.end(), Other->Globals.begin(), Other->Globals.end());
Other->Globals.clear();
}
/// Destroys all GlobalVariables and initializers that this knows about
/// (including those merged with it), and releases memory.
void clearAndPurge() {
if (Arena == nullptr) {
// Arena is only null if this was merged, so we ensure there's no state
// being held by this.
assert(Dtors.empty());
assert(Globals.empty());
assert(MergedArenas.empty());
return;
}
// Invokes destructors in reverse creation order.
for (auto Dtor = Dtors.rbegin(); Dtor != Dtors.rend(); ++Dtor) {
(*Dtor)();
}
Dtors.clear();
Globals.clear();
MergedArenas.clear();
Arena->Reset();
}
/// Adapt the relevant parts of the std::vector<VariableDeclaration *>
/// interface.
/// @{
VariableDeclarationArray::iterator begin() { return Globals.begin(); }
VariableDeclarationArray::iterator end() { return Globals.end(); }
VariableDeclarationArray::const_iterator begin() const {
return Globals.begin();
}
VariableDeclarationArray::const_iterator end() const { return Globals.end(); }
bool empty() const { return Globals.empty(); }
VariableDeclarationArray::size_type size() const { return Globals.size(); }
VariableDeclarationArray::reference
at(VariableDeclarationArray::size_type Pos) {
return Globals.at(Pos);
}
void push_back(VariableDeclaration *Global) { Globals.push_back(Global); }
void reserve(VariableDeclarationArray::size_type Capacity) {
Globals.reserve(Capacity);
}
void clear() { Globals.clear(); }
VariableDeclarationArray::reference back() { return Globals.back(); }
/// @}
private:
using ArenaPtr = std::unique_ptr<ArenaAllocator>;
using DestructorsArray = std::vector<std::function<void()>>;
void addArena(ArenaPtr NewArena) {
MergedArenas.emplace_back(std::move(NewArena));
}
ArenaPtr Arena;
VariableDeclarationArray Globals;
DestructorsArray Dtors;
std::vector<ArenaPtr> MergedArenas;
};
/// InstNumberT is for holding an instruction number. Instruction numbers are
/// used for representing Variable live ranges.
using InstNumberT = int32_t;
/// A LiveBeginEndMapEntry maps a Variable::Number value to an Inst::Number
/// value, giving the instruction number that begins or ends a variable's live
/// range.
template <typename T>
using LivenessVector = std::vector<T, LivenessAllocator<T>>;
using LiveBeginEndMapEntry = std::pair<SizeT, InstNumberT>;
using LiveBeginEndMap = LivenessVector<LiveBeginEndMapEntry>;
using LivenessBV = BitVectorTmpl<LivenessAllocator>;
using TimerStackIdT = uint32_t;
using TimerIdT = uint32_t;
/// Use alignas(MaxCacheLineSize) to isolate variables/fields that might be
/// contended while multithreading. Assumes the maximum cache line size is 64.
enum { MaxCacheLineSize = 64 };
// Use ICE_CACHELINE_BOUNDARY to force the next field in a declaration
// list to be aligned to the next cache line.
#if defined(_MSC_VER)
#define ICE_CACHELINE_BOUNDARY __declspec(align(MaxCacheLineSize)) int : 0;
#else // !defined(_MSC_VER)
// Note: zero is added to work around the following GCC 4.8 bug (fixed in 4.9):
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55382
#define ICE_CACHELINE_BOUNDARY \
__attribute__((aligned(MaxCacheLineSize + 0))) int : 0
#endif // !defined(_MSC_VER)
using RelocOffsetT = int32_t;
enum { RelocAddrSize = 4 };
enum LivenessMode {
/// Basic version of live-range-end calculation. Marks the last uses of
/// variables based on dataflow analysis. Records the set of live-in and
/// live-out variables for each block. Identifies and deletes dead
/// instructions (primarily stores).
Liveness_Basic,
/// In addition to Liveness_Basic, also calculate the complete live range for
/// each variable in a form suitable for interference calculation and register
/// allocation.
Liveness_Intervals
};
enum LCSEOptions {
LCSE_Disabled,
LCSE_EnabledSSA, // Default Mode, assumes SSA.
LCSE_EnabledNoSSA // Does not assume SSA, to be enabled if CSE is done later.
};
enum RegAllocKind {
RAK_Unknown,
RAK_Global, /// full, global register allocation
RAK_SecondChance, /// second-chance bin-packing after full regalloc attempt
RAK_Phi, /// infinite-weight Variables with active spilling/filling
RAK_InfOnly /// allocation only for infinite-weight Variables
};
enum VerboseItem {
IceV_None = 0,
IceV_Instructions = 1 << 0,
IceV_Deleted = 1 << 1,
IceV_InstNumbers = 1 << 2,
IceV_Preds = 1 << 3,
IceV_Succs = 1 << 4,
IceV_Liveness = 1 << 5,
IceV_RegOrigins = 1 << 6,
IceV_LinearScan = 1 << 7,
IceV_Frame = 1 << 8,
IceV_AddrOpt = 1 << 9,
IceV_Folding = 1 << 10,
IceV_RMW = 1 << 11,
IceV_Loop = 1 << 12,
IceV_Mem = 1 << 13,
// Leave some extra space to make it easier to add new per-pass items.
IceV_NO_PER_PASS_DUMP_BEYOND = 1 << 19,
// Items greater than IceV_NO_PER_PASS_DUMP_BEYOND don't by themselves trigger
// per-pass Cfg dump output.
IceV_Status = 1 << 20,
IceV_AvailableRegs = 1 << 21,
IceV_GlobalInit = 1 << 22,
IceV_ConstPoolStats = 1 << 23,
IceV_Wasm = 1 << 24,
IceV_ShufMat = 1 << 25,
IceV_All = ~IceV_None,
IceV_Most =
IceV_All & ~IceV_LinearScan & ~IceV_GlobalInit & ~IceV_ConstPoolStats
};
using VerboseMask = uint32_t;
enum FileType {
FT_Elf, /// ELF .o file
FT_Asm, /// Assembly .s file
FT_Iasm /// "Integrated assembler" .byte-style .s file
};
using Ostream = llvm::raw_ostream;
using Fdstream = llvm::raw_fd_ostream;
using GlobalLockType = std::mutex;
/// LockedPtr is an RAII wrapper that allows automatically locked access to a
/// given pointer, automatically unlocking it when when the LockedPtr goes out
/// of scope.
template <typename T> class LockedPtr {
LockedPtr() = delete;
LockedPtr(const LockedPtr &) = delete;
LockedPtr &operator=(const LockedPtr &) = delete;
public:
LockedPtr(T *Value, GlobalLockType *Lock) : Value(Value), Lock(Lock) {
Lock->lock();
}
LockedPtr(LockedPtr &&Other) : Value(Other.Value), Lock(Other.Lock) {
Other.Value = nullptr;
Other.Lock = nullptr;
}
~LockedPtr() {
if (Lock != nullptr)
Lock->unlock();
}
T *operator->() const { return Value; }
T &operator*() const { return *Value; }
T *get() { return Value; }
private:
T *Value;
GlobalLockType *Lock;
};
enum ErrorCodes { EC_None = 0, EC_Args, EC_Bitcode, EC_Translation };
/// Wrapper around std::error_code for allowing multiple errors to be folded
/// into one. The current implementation keeps track of the first error, which
/// is likely to be the most useful one, and this could be extended to e.g.
/// collect a vector of errors.
class ErrorCode : public std::error_code {
ErrorCode(const ErrorCode &) = delete;
ErrorCode &operator=(const ErrorCode &) = delete;
public:
ErrorCode() = default;
void assign(ErrorCodes Code) {
if (!HasError) {
HasError = true;
std::error_code::assign(Code, std::generic_category());
}
}
void assign(int Code) { assign(static_cast<ErrorCodes>(Code)); }
private:
bool HasError = false;
};
/// Reverse range adaptors written in terms of llvm::make_range().
template <typename T>
llvm::iterator_range<typename T::const_reverse_iterator>
reverse_range(const T &Container) {
return llvm::make_range(Container.rbegin(), Container.rend());
}
template <typename T>
llvm::iterator_range<typename T::reverse_iterator> reverse_range(T &Container) {
return llvm::make_range(Container.rbegin(), Container.rend());
}
using RelocOffsetArray = llvm::SmallVector<class RelocOffset *, 4>;
} // end of namespace Ice
#endif // SUBZERO_SRC_ICEDEFS_H