//===- subzero/src/IceInst.h - High-level instructions ----------*- 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 the Inst class and its target-independent subclasses.
///
/// These represent the high-level Vanilla ICE instructions and map roughly 1:1
/// to LLVM instructions.
///
//===----------------------------------------------------------------------===//
#ifndef SUBZERO_SRC_ICEINST_H
#define SUBZERO_SRC_ICEINST_H
#include "IceCfg.h"
#include "IceDefs.h"
#include "IceInst.def"
#include "IceIntrinsics.h"
#include "IceOperand.h"
#include "IceSwitchLowering.h"
#include "IceTypes.h"
// TODO: The Cfg structure, and instructions in particular, need to be
// validated for things like valid operand types, valid branch targets, proper
// ordering of Phi and non-Phi instructions, etc. Most of the validity checking
// will be done in the bitcode reader. We need a list of everything that should
// be validated, and tests for each.
namespace Ice {
/// Base instruction class for ICE. Inst has two subclasses: InstHighLevel and
/// InstTarget. High-level ICE instructions inherit from InstHighLevel, and
/// low-level (target-specific) ICE instructions inherit from InstTarget.
class Inst : public llvm::ilist_node<Inst> {
Inst() = delete;
Inst(const Inst &) = delete;
Inst &operator=(const Inst &) = delete;
public:
enum InstKind {
// Arbitrary (alphabetical) order, except put Unreachable first.
Unreachable,
Alloca,
Arithmetic,
Br,
Call,
Cast,
ExtractElement,
Fcmp,
Icmp,
IntrinsicCall,
InsertElement,
Load,
Phi,
Ret,
Select,
Store,
Switch,
Assign, // not part of LLVM/PNaCl bitcode
Breakpoint, // not part of LLVM/PNaCl bitcode
BundleLock, // not part of LLVM/PNaCl bitcode
BundleUnlock, // not part of LLVM/PNaCl bitcode
FakeDef, // not part of LLVM/PNaCl bitcode
FakeUse, // not part of LLVM/PNaCl bitcode
FakeKill, // not part of LLVM/PNaCl bitcode
JumpTable, // not part of LLVM/PNaCl bitcode
ShuffleVector, // not part of LLVM/PNaCl bitcode
// Anything >= Target is an InstTarget subclass. Note that the value-spaces
// are shared across targets. To avoid confusion over the definition of
// shared values, an object specific to one target should never be passed
// to a different target.
Target,
Target_Max = std::numeric_limits<uint8_t>::max(),
};
static_assert(Target <= Target_Max, "Must not be above max.");
InstKind getKind() const { return Kind; }
virtual const char *getInstName() const;
InstNumberT getNumber() const { return Number; }
void renumber(Cfg *Func);
enum {
NumberDeleted = -1,
NumberSentinel = 0,
NumberInitial = 2,
NumberExtended = NumberInitial - 1
};
bool isDeleted() const { return Deleted; }
void setDeleted() { Deleted = true; }
void setDead(bool Value = true) { Dead = Value; }
void deleteIfDead();
bool hasSideEffects() const { return HasSideEffects; }
bool isDestRedefined() const { return IsDestRedefined; }
void setDestRedefined() { IsDestRedefined = true; }
Variable *getDest() const { return Dest; }
SizeT getSrcSize() const { return Srcs.size(); }
Operand *getSrc(SizeT I) const {
assert(I < getSrcSize());
return Srcs[I];
}
void replaceSource(SizeT Index, Operand *Replacement) {
assert(Index < getSrcSize());
assert(!isDeleted());
Srcs[Index] = Replacement;
}
bool isLastUse(const Operand *Src) const;
void spliceLivenessInfo(Inst *OrigInst, Inst *SpliceAssn);
/// Returns a list of out-edges corresponding to a terminator instruction,
/// which is the last instruction of the block. The list must not contain
/// duplicates.
virtual NodeList getTerminatorEdges() const {
// All valid terminator instructions override this method. For the default
// implementation, we assert in case some CfgNode is constructed without a
// terminator instruction at the end.
llvm_unreachable(
"getTerminatorEdges() called on a non-terminator instruction");
return NodeList();
}
virtual bool isUnconditionalBranch() const { return false; }
/// If the instruction is a branch-type instruction with OldNode as a target,
/// repoint it to NewNode and return true, otherwise return false. Repoint all
/// instances of OldNode as a target.
virtual bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) {
(void)OldNode;
(void)NewNode;
return false;
}
/// Returns true if the instruction is equivalent to a simple
/// "var_dest=var_src" assignment where the dest and src are both variables.
virtual bool isVarAssign() const { return false; }
/// Returns true if the instruction has a possible side effect of changing
/// memory, in which case a memory load should not be reordered with respect
/// to this instruction. It should really be pure virtual, but we can't
/// because of g++ and llvm::ilist<>, so we implement it as
/// report_fatal_error().
virtual bool isMemoryWrite() const;
/// Returns true if the (target-specific) instruction represents an
/// intra-block label, i.e. branch target. This is meant primarily for
/// Cfg::splitLocalVars().
virtual bool isLabel() const { return false; }
/// If the (target-specific) instruction represents an intra-block branch to
/// some Label instruction, return that Label branch target instruction;
/// otherwise return nullptr.
virtual const Inst *getIntraBlockBranchTarget() const { return nullptr; }
void livenessLightweight(Cfg *Func, LivenessBV &Live);
/// Calculates liveness for this instruction. Returns true if this instruction
/// is (tentatively) still live and should be retained, and false if this
/// instruction is (tentatively) dead and should be deleted. The decision is
/// tentative until the liveness dataflow algorithm has converged, and then a
/// separate pass permanently deletes dead instructions.
bool liveness(InstNumberT InstNumber, LivenessBV &Live, Liveness *Liveness,
LiveBeginEndMap *LiveBegin, LiveBeginEndMap *LiveEnd);
/// Get the number of native instructions that this instruction ultimately
/// emits. By default, high-level instructions don't result in any native
/// instructions, and a target-specific instruction results in a single native
/// instruction.
virtual uint32_t getEmitInstCount() const { return 0; }
// TODO(stichnot): Change Inst back to abstract once the g++ build issue is
// fixed. llvm::ilist<Ice::Inst> doesn't work under g++ because the
// resize(size_t, Ice::Inst) method is incorrectly declared and thus doesn't
// allow the abstract class Ice::Inst. The method should be declared
// resize(size_t, const Ice::Inst &). virtual void emit(const Cfg *Func)
// const = 0; virtual void emitIAS(const Cfg *Func) const = 0;
virtual void emit(const Cfg *) const {
llvm_unreachable("emit on abstract class");
}
virtual void emitIAS(const Cfg *Func) const { emit(Func); }
virtual void dump(const Cfg *Func) const;
virtual void dumpExtras(const Cfg *Func) const;
void dumpDecorated(const Cfg *Func) const;
void emitSources(const Cfg *Func) const;
void dumpSources(const Cfg *Func) const;
void dumpDest(const Cfg *Func) const;
virtual bool isRedundantAssign() const { return false; }
virtual ~Inst() = default;
void replaceDest(Variable *Var) { Dest = Var; }
void operator delete(void *Ptr, std::size_t Size) {
assert(CfgAllocatorTraits::current() != nullptr);
CfgAllocatorTraits::current()->Deallocate(Ptr, Size);
llvm::report_fatal_error("Inst unexpectedly deleted");
}
inline void* getExternalData() const { return externalData; }
inline void setExternalData(void* data) { externalData = data; }
protected:
Inst(Cfg *Func, InstKind Kind, SizeT MaxSrcs, Variable *Dest);
void addSource(Operand *Src) {
assert(Src);
Srcs.push_back(Src);
}
void setLastUse(SizeT VarIndex) {
if (VarIndex < CHAR_BIT * sizeof(LiveRangesEnded))
LiveRangesEnded |= (((LREndedBits)1u) << VarIndex);
}
void resetLastUses() { LiveRangesEnded = 0; }
/// The destroy() method lets the instruction cleanly release any memory that
/// was allocated via the Cfg's allocator.
virtual void destroy(Cfg *) {}
const InstKind Kind;
/// Number is the instruction number for describing live ranges.
InstNumberT Number;
/// Deleted means irrevocably deleted.
bool Deleted = false;
/// Dead means one of two things depending on context: (1) pending deletion
/// after liveness analysis converges, or (2) marked for deletion during
/// lowering due to a folded bool operation.
bool Dead = false;
/// HasSideEffects means the instruction is something like a function call or
/// a volatile load that can't be removed even if its Dest variable is not
/// live.
bool HasSideEffects = false;
/// IsDestRedefined indicates that this instruction is not the first
/// definition of Dest in the basic block. The effect is that liveness
/// analysis shouldn't consider this instruction to be the start of Dest's
/// live range; rather, there is some other instruction earlier in the basic
/// block with the same Dest. This is maintained because liveness analysis
/// has an invariant (primarily for performance reasons) that any Variable's
/// live range recorded in a basic block has at most one start and at most one
/// end.
bool IsDestRedefined = false;
/// External data can be set by an optimizer to compute and retain any
/// information related to the current instruction. All the memory used to
/// store this information must be managed by the optimizer.
void* externalData = nullptr;
Variable *Dest;
const SizeT MaxSrcs; // only used for assert
CfgVector<Operand *> Srcs;
/// LiveRangesEnded marks which Variables' live ranges end in this
/// instruction. An instruction can have an arbitrary number of source
/// operands (e.g. a call instruction), and each source operand can contain 0
/// or 1 Variable (and target-specific operands could contain more than 1
/// Variable). All the variables in an instruction are conceptually flattened
/// and each variable is mapped to one bit position of the LiveRangesEnded bit
/// vector. Only the first CHAR_BIT * sizeof(LREndedBits) variables are
/// tracked this way.
using LREndedBits = uint32_t; // only first 32 src operands tracked, sorry
LREndedBits LiveRangesEnded;
};
class InstHighLevel : public Inst {
InstHighLevel() = delete;
InstHighLevel(const InstHighLevel &) = delete;
InstHighLevel &operator=(const InstHighLevel &) = delete;
protected:
InstHighLevel(Cfg *Func, InstKind Kind, SizeT MaxSrcs, Variable *Dest)
: Inst(Func, Kind, MaxSrcs, Dest) {}
void emit(const Cfg * /*Func*/) const override {
llvm_unreachable("emit() called on a non-lowered instruction");
}
void emitIAS(const Cfg * /*Func*/) const override {
llvm_unreachable("emitIAS() called on a non-lowered instruction");
}
};
/// Alloca instruction. This captures the size in bytes as getSrc(0), and the
/// required alignment in bytes. The alignment must be either 0 (no alignment
/// required) or a power of 2.
class InstAlloca : public InstHighLevel {
InstAlloca() = delete;
InstAlloca(const InstAlloca &) = delete;
InstAlloca &operator=(const InstAlloca &) = delete;
public:
static InstAlloca *create(Cfg *Func, Variable *Dest, Operand *ByteCount,
uint32_t AlignInBytes) {
return new (Func->allocate<InstAlloca>())
InstAlloca(Func, Dest, ByteCount, AlignInBytes);
}
uint32_t getAlignInBytes() const { return AlignInBytes; }
Operand *getSizeInBytes() const { return getSrc(0); }
bool getKnownFrameOffset() const { return KnownFrameOffset; }
void setKnownFrameOffset() { KnownFrameOffset = true; }
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Alloca; }
private:
InstAlloca(Cfg *Func, Variable *Dest, Operand *ByteCount,
uint32_t AlignInBytes);
const uint32_t AlignInBytes;
bool KnownFrameOffset = false;
};
/// Binary arithmetic instruction. The source operands are captured in getSrc(0)
/// and getSrc(1).
class InstArithmetic : public InstHighLevel {
InstArithmetic() = delete;
InstArithmetic(const InstArithmetic &) = delete;
InstArithmetic &operator=(const InstArithmetic &) = delete;
public:
enum OpKind {
#define X(tag, str, commutative) tag,
ICEINSTARITHMETIC_TABLE
#undef X
_num
};
static InstArithmetic *create(Cfg *Func, OpKind Op, Variable *Dest,
Operand *Source1, Operand *Source2) {
return new (Func->allocate<InstArithmetic>())
InstArithmetic(Func, Op, Dest, Source1, Source2);
}
OpKind getOp() const { return Op; }
virtual const char *getInstName() const override;
static const char *getOpName(OpKind Op);
bool isCommutative() const;
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == Arithmetic;
}
private:
InstArithmetic(Cfg *Func, OpKind Op, Variable *Dest, Operand *Source1,
Operand *Source2);
const OpKind Op;
};
/// Assignment instruction. The source operand is captured in getSrc(0). This is
/// not part of the LLVM bitcode, but is a useful abstraction for some of the
/// lowering. E.g., if Phi instruction lowering happens before target lowering,
/// or for representing an Inttoptr instruction, or as an intermediate step for
/// lowering a Load instruction.
class InstAssign : public InstHighLevel {
InstAssign() = delete;
InstAssign(const InstAssign &) = delete;
InstAssign &operator=(const InstAssign &) = delete;
public:
static InstAssign *create(Cfg *Func, Variable *Dest, Operand *Source) {
return new (Func->allocate<InstAssign>()) InstAssign(Func, Dest, Source);
}
bool isVarAssign() const override;
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Assign; }
private:
InstAssign(Cfg *Func, Variable *Dest, Operand *Source);
};
/// Branch instruction. This represents both conditional and unconditional
/// branches.
class InstBr : public InstHighLevel {
InstBr() = delete;
InstBr(const InstBr &) = delete;
InstBr &operator=(const InstBr &) = delete;
public:
/// Create a conditional branch. If TargetTrue==TargetFalse, it is optimized
/// to an unconditional branch.
static InstBr *create(Cfg *Func, Operand *Source, CfgNode *TargetTrue,
CfgNode *TargetFalse) {
return new (Func->allocate<InstBr>())
InstBr(Func, Source, TargetTrue, TargetFalse);
}
/// Create an unconditional branch.
static InstBr *create(Cfg *Func, CfgNode *Target) {
return new (Func->allocate<InstBr>()) InstBr(Func, Target);
}
bool isUnconditional() const { return getTargetTrue() == nullptr; }
Operand *getCondition() const {
assert(!isUnconditional());
return getSrc(0);
}
CfgNode *getTargetTrue() const { return TargetTrue; }
CfgNode *getTargetFalse() const { return TargetFalse; }
CfgNode *getTargetUnconditional() const {
assert(isUnconditional());
return getTargetFalse();
}
NodeList getTerminatorEdges() const override;
bool isUnconditionalBranch() const override { return isUnconditional(); }
bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Br; }
private:
/// Conditional branch
InstBr(Cfg *Func, Operand *Source, CfgNode *TargetTrue, CfgNode *TargetFalse);
/// Unconditional branch
InstBr(Cfg *Func, CfgNode *Target);
CfgNode *TargetFalse; /// Doubles as unconditional branch target
CfgNode *TargetTrue; /// nullptr if unconditional branch
};
/// Call instruction. The call target is captured as getSrc(0), and arg I is
/// captured as getSrc(I+1).
class InstCall : public InstHighLevel {
InstCall() = delete;
InstCall(const InstCall &) = delete;
InstCall &operator=(const InstCall &) = delete;
public:
static InstCall *create(Cfg *Func, SizeT NumArgs, Variable *Dest,
Operand *CallTarget, bool HasTailCall,
bool IsTargetHelperCall = false) {
/// Set HasSideEffects to true so that the call instruction can't be
/// dead-code eliminated. IntrinsicCalls can override this if the particular
/// intrinsic is deletable and has no side-effects.
constexpr bool HasSideEffects = true;
constexpr InstKind Kind = Inst::Call;
return new (Func->allocate<InstCall>())
InstCall(Func, NumArgs, Dest, CallTarget, HasTailCall,
IsTargetHelperCall, HasSideEffects, Kind);
}
void addArg(Operand *Arg) { addSource(Arg); }
Operand *getCallTarget() const { return getSrc(0); }
Operand *getArg(SizeT I) const { return getSrc(I + 1); }
SizeT getNumArgs() const { return getSrcSize() - 1; }
bool isTailcall() const { return HasTailCall; }
bool isTargetHelperCall() const { return IsTargetHelperCall; }
bool isMemoryWrite() const override { return true; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Call; }
Type getReturnType() const;
protected:
InstCall(Cfg *Func, SizeT NumArgs, Variable *Dest, Operand *CallTarget,
bool HasTailCall, bool IsTargetHelperCall, bool HasSideEff,
InstKind Kind)
: InstHighLevel(Func, Kind, NumArgs + 1, Dest), HasTailCall(HasTailCall),
IsTargetHelperCall(IsTargetHelperCall) {
HasSideEffects = HasSideEff;
addSource(CallTarget);
}
private:
const bool HasTailCall;
const bool IsTargetHelperCall;
};
/// Cast instruction (a.k.a. conversion operation).
class InstCast : public InstHighLevel {
InstCast() = delete;
InstCast(const InstCast &) = delete;
InstCast &operator=(const InstCast &) = delete;
public:
enum OpKind {
#define X(tag, str) tag,
ICEINSTCAST_TABLE
#undef X
_num
};
static const char *getCastName(OpKind Kind);
static InstCast *create(Cfg *Func, OpKind CastKind, Variable *Dest,
Operand *Source) {
return new (Func->allocate<InstCast>())
InstCast(Func, CastKind, Dest, Source);
}
OpKind getCastKind() const { return CastKind; }
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Cast; }
private:
InstCast(Cfg *Func, OpKind CastKind, Variable *Dest, Operand *Source);
const OpKind CastKind;
};
/// ExtractElement instruction.
class InstExtractElement : public InstHighLevel {
InstExtractElement() = delete;
InstExtractElement(const InstExtractElement &) = delete;
InstExtractElement &operator=(const InstExtractElement &) = delete;
public:
static InstExtractElement *create(Cfg *Func, Variable *Dest, Operand *Source1,
Operand *Source2) {
return new (Func->allocate<InstExtractElement>())
InstExtractElement(Func, Dest, Source1, Source2);
}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == ExtractElement;
}
private:
InstExtractElement(Cfg *Func, Variable *Dest, Operand *Source1,
Operand *Source2);
};
/// Floating-point comparison instruction. The source operands are captured in
/// getSrc(0) and getSrc(1).
class InstFcmp : public InstHighLevel {
InstFcmp() = delete;
InstFcmp(const InstFcmp &) = delete;
InstFcmp &operator=(const InstFcmp &) = delete;
public:
enum FCond {
#define X(tag, str) tag,
ICEINSTFCMP_TABLE
#undef X
_num
};
static InstFcmp *create(Cfg *Func, FCond Condition, Variable *Dest,
Operand *Source1, Operand *Source2) {
return new (Func->allocate<InstFcmp>())
InstFcmp(Func, Condition, Dest, Source1, Source2);
}
FCond getCondition() const { return Condition; }
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Fcmp; }
private:
InstFcmp(Cfg *Func, FCond Condition, Variable *Dest, Operand *Source1,
Operand *Source2);
const FCond Condition;
};
/// Integer comparison instruction. The source operands are captured in
/// getSrc(0) and getSrc(1).
class InstIcmp : public InstHighLevel {
InstIcmp() = delete;
InstIcmp(const InstIcmp &) = delete;
InstIcmp &operator=(const InstIcmp &) = delete;
public:
enum ICond {
#define X(tag, inverse, str) tag,
ICEINSTICMP_TABLE
#undef X
_num
};
static InstIcmp *create(Cfg *Func, ICond Condition, Variable *Dest,
Operand *Source1, Operand *Source2) {
return new (Func->allocate<InstIcmp>())
InstIcmp(Func, Condition, Dest, Source1, Source2);
}
ICond getCondition() const { return Condition; }
void reverseConditionAndOperands();
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Icmp; }
private:
InstIcmp(Cfg *Func, ICond Condition, Variable *Dest, Operand *Source1,
Operand *Source2);
ICond Condition;
};
/// InsertElement instruction.
class InstInsertElement : public InstHighLevel {
InstInsertElement() = delete;
InstInsertElement(const InstInsertElement &) = delete;
InstInsertElement &operator=(const InstInsertElement &) = delete;
public:
static InstInsertElement *create(Cfg *Func, Variable *Dest, Operand *Source1,
Operand *Source2, Operand *Source3) {
return new (Func->allocate<InstInsertElement>())
InstInsertElement(Func, Dest, Source1, Source2, Source3);
}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == InsertElement;
}
private:
InstInsertElement(Cfg *Func, Variable *Dest, Operand *Source1,
Operand *Source2, Operand *Source3);
};
/// Call to an intrinsic function. The call target is captured as getSrc(0), and
/// arg I is captured as getSrc(I+1).
class InstIntrinsicCall : public InstCall {
InstIntrinsicCall() = delete;
InstIntrinsicCall(const InstIntrinsicCall &) = delete;
InstIntrinsicCall &operator=(const InstIntrinsicCall &) = delete;
public:
static InstIntrinsicCall *create(Cfg *Func, SizeT NumArgs, Variable *Dest,
Operand *CallTarget,
const Intrinsics::IntrinsicInfo &Info) {
return new (Func->allocate<InstIntrinsicCall>())
InstIntrinsicCall(Func, NumArgs, Dest, CallTarget, Info);
}
static bool classof(const Inst *Instr) {
return Instr->getKind() == IntrinsicCall;
}
Intrinsics::IntrinsicInfo getIntrinsicInfo() const { return Info; }
bool isMemoryWrite() const override {
return getIntrinsicInfo().IsMemoryWrite;
}
private:
InstIntrinsicCall(Cfg *Func, SizeT NumArgs, Variable *Dest,
Operand *CallTarget, const Intrinsics::IntrinsicInfo &Info)
: InstCall(Func, NumArgs, Dest, CallTarget, false, false,
Info.HasSideEffects, Inst::IntrinsicCall),
Info(Info) {}
const Intrinsics::IntrinsicInfo Info;
};
/// Load instruction. The source address is captured in getSrc(0).
class InstLoad : public InstHighLevel {
InstLoad() = delete;
InstLoad(const InstLoad &) = delete;
InstLoad &operator=(const InstLoad &) = delete;
public:
static InstLoad *create(Cfg *Func, Variable *Dest, Operand *SourceAddr,
uint32_t Align = 1) {
// TODO(kschimpf) Stop ignoring alignment specification.
(void)Align;
return new (Func->allocate<InstLoad>()) InstLoad(Func, Dest, SourceAddr);
}
Operand *getSourceAddress() const { return getSrc(0); }
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Load; }
private:
InstLoad(Cfg *Func, Variable *Dest, Operand *SourceAddr);
};
/// Phi instruction. For incoming edge I, the node is Labels[I] and the Phi
/// source operand is getSrc(I).
class InstPhi : public InstHighLevel {
InstPhi() = delete;
InstPhi(const InstPhi &) = delete;
InstPhi &operator=(const InstPhi &) = delete;
public:
static InstPhi *create(Cfg *Func, SizeT MaxSrcs, Variable *Dest) {
return new (Func->allocate<InstPhi>()) InstPhi(Func, MaxSrcs, Dest);
}
void addArgument(Operand *Source, CfgNode *Label);
Operand *getOperandForTarget(CfgNode *Target) const;
void clearOperandForTarget(CfgNode *Target);
CfgNode *getLabel(SizeT Index) const { return Labels[Index]; }
void setLabel(SizeT Index, CfgNode *Label) { Labels[Index] = Label; }
void livenessPhiOperand(LivenessBV &Live, CfgNode *Target,
Liveness *Liveness);
Inst *lower(Cfg *Func);
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Phi; }
private:
InstPhi(Cfg *Func, SizeT MaxSrcs, Variable *Dest);
void destroy(Cfg *Func) override { Inst::destroy(Func); }
/// Labels[] duplicates the InEdges[] information in the enclosing CfgNode,
/// but the Phi instruction is created before InEdges[] is available, so it's
/// more complicated to share the list.
CfgVector<CfgNode *> Labels;
};
/// Ret instruction. The return value is captured in getSrc(0), but if there is
/// no return value (void-type function), then getSrcSize()==0 and
/// hasRetValue()==false.
class InstRet : public InstHighLevel {
InstRet() = delete;
InstRet(const InstRet &) = delete;
InstRet &operator=(const InstRet &) = delete;
public:
static InstRet *create(Cfg *Func, Operand *RetValue = nullptr) {
return new (Func->allocate<InstRet>()) InstRet(Func, RetValue);
}
bool hasRetValue() const { return getSrcSize(); }
Operand *getRetValue() const {
assert(hasRetValue());
return getSrc(0);
}
NodeList getTerminatorEdges() const override { return NodeList(); }
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Ret; }
private:
InstRet(Cfg *Func, Operand *RetValue);
};
/// Select instruction. The condition, true, and false operands are captured.
class InstSelect : public InstHighLevel {
InstSelect() = delete;
InstSelect(const InstSelect &) = delete;
InstSelect &operator=(const InstSelect &) = delete;
public:
static InstSelect *create(Cfg *Func, Variable *Dest, Operand *Condition,
Operand *SourceTrue, Operand *SourceFalse) {
return new (Func->allocate<InstSelect>())
InstSelect(Func, Dest, Condition, SourceTrue, SourceFalse);
}
Operand *getCondition() const { return getSrc(0); }
Operand *getTrueOperand() const { return getSrc(1); }
Operand *getFalseOperand() const { return getSrc(2); }
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Select; }
private:
InstSelect(Cfg *Func, Variable *Dest, Operand *Condition, Operand *Source1,
Operand *Source2);
};
/// Store instruction. The address operand is captured, along with the data
/// operand to be stored into the address.
class InstStore : public InstHighLevel {
InstStore() = delete;
InstStore(const InstStore &) = delete;
InstStore &operator=(const InstStore &) = delete;
public:
static InstStore *create(Cfg *Func, Operand *Data, Operand *Addr,
uint32_t Align = 1) {
// TODO(kschimpf) Stop ignoring alignment specification.
(void)Align;
return new (Func->allocate<InstStore>()) InstStore(Func, Data, Addr);
}
Operand *getAddr() const { return getSrc(1); }
Operand *getData() const { return getSrc(0); }
Variable *getRmwBeacon() const;
void setRmwBeacon(Variable *Beacon);
bool isMemoryWrite() const override { return true; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Store; }
private:
InstStore(Cfg *Func, Operand *Data, Operand *Addr);
};
/// Switch instruction. The single source operand is captured as getSrc(0).
class InstSwitch : public InstHighLevel {
InstSwitch() = delete;
InstSwitch(const InstSwitch &) = delete;
InstSwitch &operator=(const InstSwitch &) = delete;
public:
static InstSwitch *create(Cfg *Func, SizeT NumCases, Operand *Source,
CfgNode *LabelDefault) {
return new (Func->allocate<InstSwitch>())
InstSwitch(Func, NumCases, Source, LabelDefault);
}
Operand *getComparison() const { return getSrc(0); }
CfgNode *getLabelDefault() const { return LabelDefault; }
SizeT getNumCases() const { return NumCases; }
uint64_t getValue(SizeT I) const {
assert(I < NumCases);
return Values[I];
}
CfgNode *getLabel(SizeT I) const {
assert(I < NumCases);
return Labels[I];
}
void addBranch(SizeT CaseIndex, uint64_t Value, CfgNode *Label);
NodeList getTerminatorEdges() const override;
bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == Switch; }
private:
InstSwitch(Cfg *Func, SizeT NumCases, Operand *Source, CfgNode *LabelDefault);
void destroy(Cfg *Func) override {
Func->deallocateArrayOf<uint64_t>(Values);
Func->deallocateArrayOf<CfgNode *>(Labels);
Inst::destroy(Func);
}
CfgNode *LabelDefault;
SizeT NumCases; /// not including the default case
uint64_t *Values; /// size is NumCases
CfgNode **Labels; /// size is NumCases
};
/// Unreachable instruction. This is a terminator instruction with no operands.
class InstUnreachable : public InstHighLevel {
InstUnreachable() = delete;
InstUnreachable(const InstUnreachable &) = delete;
InstUnreachable &operator=(const InstUnreachable &) = delete;
public:
static InstUnreachable *create(Cfg *Func) {
return new (Func->allocate<InstUnreachable>()) InstUnreachable(Func);
}
NodeList getTerminatorEdges() const override { return NodeList(); }
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == Unreachable;
}
private:
explicit InstUnreachable(Cfg *Func);
};
/// BundleLock instruction. There are no operands. Contains an option
/// indicating whether align_to_end is specified.
class InstBundleLock : public InstHighLevel {
InstBundleLock() = delete;
InstBundleLock(const InstBundleLock &) = delete;
InstBundleLock &operator=(const InstBundleLock &) = delete;
public:
enum Option { Opt_None, Opt_AlignToEnd, Opt_PadToEnd };
static InstBundleLock *create(Cfg *Func, Option BundleOption) {
return new (Func->allocate<InstBundleLock>())
InstBundleLock(Func, BundleOption);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg * /* Func */) const override {}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
Option getOption() const { return BundleOption; }
static bool classof(const Inst *Instr) {
return Instr->getKind() == BundleLock;
}
private:
Option BundleOption;
InstBundleLock(Cfg *Func, Option BundleOption);
};
/// BundleUnlock instruction. There are no operands.
class InstBundleUnlock : public InstHighLevel {
InstBundleUnlock() = delete;
InstBundleUnlock(const InstBundleUnlock &) = delete;
InstBundleUnlock &operator=(const InstBundleUnlock &) = delete;
public:
static InstBundleUnlock *create(Cfg *Func) {
return new (Func->allocate<InstBundleUnlock>()) InstBundleUnlock(Func);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg * /* Func */) const override {}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == BundleUnlock;
}
private:
explicit InstBundleUnlock(Cfg *Func);
};
/// FakeDef instruction. This creates a fake definition of a variable, which is
/// how we represent the case when an instruction produces multiple results.
/// This doesn't happen with high-level ICE instructions, but might with lowered
/// instructions. For example, this would be a way to represent condition flags
/// being modified by an instruction.
///
/// It's generally useful to set the optional source operand to be the dest
/// variable of the instruction that actually produces the FakeDef dest.
/// Otherwise, the original instruction could be dead-code eliminated if its
/// dest operand is unused, and therefore the FakeDef dest wouldn't be properly
/// initialized.
class InstFakeDef : public InstHighLevel {
InstFakeDef() = delete;
InstFakeDef(const InstFakeDef &) = delete;
InstFakeDef &operator=(const InstFakeDef &) = delete;
public:
static InstFakeDef *create(Cfg *Func, Variable *Dest,
Variable *Src = nullptr) {
return new (Func->allocate<InstFakeDef>()) InstFakeDef(Func, Dest, Src);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg * /* Func */) const override {}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == FakeDef; }
private:
InstFakeDef(Cfg *Func, Variable *Dest, Variable *Src);
};
/// FakeUse instruction. This creates a fake use of a variable, to keep the
/// instruction that produces that variable from being dead-code eliminated.
/// This is useful in a variety of lowering situations. The FakeUse instruction
/// has no dest, so it can itself never be dead-code eliminated. A weight can
/// be provided to provide extra bias to the register allocator - for simplicity
/// of implementation, weight=N is handled by holding N copies of the variable
/// as source operands.
class InstFakeUse : public InstHighLevel {
InstFakeUse() = delete;
InstFakeUse(const InstFakeUse &) = delete;
InstFakeUse &operator=(const InstFakeUse &) = delete;
public:
static InstFakeUse *create(Cfg *Func, Variable *Src, uint32_t Weight = 1) {
return new (Func->allocate<InstFakeUse>()) InstFakeUse(Func, Src, Weight);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg * /* Func */) const override {}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() == FakeUse; }
private:
InstFakeUse(Cfg *Func, Variable *Src, uint32_t Weight);
};
/// FakeKill instruction. This "kills" a set of variables by modeling a trivial
/// live range at this instruction for each (implicit) variable. The primary use
/// is to indicate that scratch registers are killed after a call, so that the
/// register allocator won't assign a scratch register to a variable whose live
/// range spans a call.
///
/// The FakeKill instruction also holds a pointer to the instruction that kills
/// the set of variables, so that if that linked instruction gets dead-code
/// eliminated, the FakeKill instruction will as well.
class InstFakeKill : public InstHighLevel {
InstFakeKill() = delete;
InstFakeKill(const InstFakeKill &) = delete;
InstFakeKill &operator=(const InstFakeKill &) = delete;
public:
static InstFakeKill *create(Cfg *Func, const Inst *Linked) {
return new (Func->allocate<InstFakeKill>()) InstFakeKill(Func, Linked);
}
const Inst *getLinked() const { return Linked; }
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg * /* Func */) const override {}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == FakeKill;
}
private:
InstFakeKill(Cfg *Func, const Inst *Linked);
/// This instruction is ignored if Linked->isDeleted() is true.
const Inst *Linked;
};
/// ShuffleVector instruction. This represents a shuffle operation on vector
/// types. This instruction is not part of the PNaCl bitcode: it is generated
/// by Subzero when it matches the pattern used by pnacl-clang when compiling
/// to bitcode.
class InstShuffleVector : public InstHighLevel {
InstShuffleVector() = delete;
InstShuffleVector(const InstShuffleVector &) = delete;
InstShuffleVector &operator=(const InstShuffleVector &) = delete;
public:
static InstShuffleVector *create(Cfg *Func, Variable *Dest, Operand *Src0,
Operand *Src1) {
return new (Func->allocate<InstShuffleVector>())
InstShuffleVector(Func, Dest, Src0, Src1);
}
SizeT getNumIndexes() const { return NumIndexes; }
void addIndex(ConstantInteger32 *Index) {
assert(CurrentIndex < NumIndexes);
Indexes[CurrentIndex++] = Index;
}
ConstantInteger32 *getIndex(SizeT Pos) const {
assert(Pos < NumIndexes);
return Indexes[Pos];
}
int32_t getIndexValue(SizeT Pos) const { return getIndex(Pos)->getValue(); }
bool indexesAre(int32_t i0, int32_t i1, int32_t i2, int32_t i3) const {
static constexpr SizeT ExpectedNumElements = 4;
assert(ExpectedNumElements == getNumIndexes());
(void)ExpectedNumElements;
return getIndexValue(0) == i0 && getIndexValue(1) == i1 &&
getIndexValue(2) == i2 && getIndexValue(3) == i3;
}
bool indexesAre(int32_t i0, int32_t i1, int32_t i2, int32_t i3, int32_t i4,
int32_t i5, int32_t i6, int32_t i7) const {
static constexpr SizeT ExpectedNumElements = 8;
assert(ExpectedNumElements == getNumIndexes());
(void)ExpectedNumElements;
return getIndexValue(0) == i0 && getIndexValue(1) == i1 &&
getIndexValue(2) == i2 && getIndexValue(3) == i3 &&
getIndexValue(4) == i4 && getIndexValue(5) == i5 &&
getIndexValue(6) == i6 && getIndexValue(7) == i7;
}
bool indexesAre(int32_t i0, int32_t i1, int32_t i2, int32_t i3, int32_t i4,
int32_t i5, int32_t i6, int32_t i7, int32_t i8, int32_t i9,
int32_t i10, int32_t i11, int32_t i12, int32_t i13,
int32_t i14, int32_t i15) const {
static constexpr SizeT ExpectedNumElements = 16;
assert(ExpectedNumElements == getNumIndexes());
(void)ExpectedNumElements;
return getIndexValue(0) == i0 && getIndexValue(1) == i1 &&
getIndexValue(2) == i2 && getIndexValue(3) == i3 &&
getIndexValue(4) == i4 && getIndexValue(5) == i5 &&
getIndexValue(6) == i6 && getIndexValue(7) == i7 &&
getIndexValue(8) == i8 && getIndexValue(9) == i9 &&
getIndexValue(10) == i10 && getIndexValue(11) == i11 &&
getIndexValue(12) == i12 && getIndexValue(13) == i13 &&
getIndexValue(14) == i14 && getIndexValue(15) == i15;
}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == ShuffleVector;
}
private:
InstShuffleVector(Cfg *Func, Variable *Dest, Operand *Src0, Operand *Src1);
void destroy(Cfg *Func) override {
Func->deallocateArrayOf<ConstantInteger32 *>(Indexes);
Inst::destroy(Func);
}
ConstantInteger32 **Indexes;
SizeT CurrentIndex = 0;
const SizeT NumIndexes;
};
/// JumpTable instruction. This represents a jump table that will be stored in
/// the .rodata section. This is used to track and repoint the target CfgNodes
/// which may change, for example due to splitting for phi lowering.
class InstJumpTable : public InstHighLevel {
InstJumpTable() = delete;
InstJumpTable(const InstJumpTable &) = delete;
InstJumpTable &operator=(const InstJumpTable &) = delete;
public:
static InstJumpTable *create(Cfg *Func, SizeT NumTargets, CfgNode *Default) {
return new (Func->allocate<InstJumpTable>())
InstJumpTable(Func, NumTargets, Default);
}
void addTarget(SizeT TargetIndex, CfgNode *Target) {
assert(TargetIndex < NumTargets);
Targets[TargetIndex] = Target;
}
bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
SizeT getId() const { return Id; }
SizeT getNumTargets() const { return NumTargets; }
CfgNode *getTarget(SizeT I) const {
assert(I < NumTargets);
return Targets[I];
}
bool isMemoryWrite() const override { return false; }
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) {
return Instr->getKind() == JumpTable;
}
// Creates a JumpTableData struct (used for ELF emission) that represents this
// InstJumpTable.
JumpTableData toJumpTableData(Assembler *Asm) const;
// InstJumpTable is just a placeholder for the switch targets, and it does not
// need to emit any code, so we redefine emit and emitIAS to do nothing.
void emit(const Cfg *) const override {}
void emitIAS(const Cfg * /* Func */) const override {}
const std::string getName() const {
assert(Name.hasStdString());
return Name.toString();
}
std::string getSectionName() const {
return JumpTableData::createSectionName(FuncName);
}
private:
InstJumpTable(Cfg *Func, SizeT NumTargets, CfgNode *Default);
void destroy(Cfg *Func) override {
Func->deallocateArrayOf<CfgNode *>(Targets);
Inst::destroy(Func);
}
const SizeT Id;
const SizeT NumTargets;
CfgNode **Targets;
GlobalString Name; // This JumpTable's name in the output.
GlobalString FuncName;
};
/// This instruction inserts an unconditional breakpoint.
///
/// On x86, this assembles into an INT 3 instruction.
///
/// This instruction is primarily meant for debugging the code generator.
class InstBreakpoint : public InstHighLevel {
public:
InstBreakpoint() = delete;
InstBreakpoint(const InstBreakpoint &) = delete;
InstBreakpoint &operator=(const InstBreakpoint &) = delete;
explicit InstBreakpoint(Cfg *Func);
bool isMemoryWrite() const override { return false; }
public:
static InstBreakpoint *create(Cfg *Func) {
return new (Func->allocate<InstBreakpoint>()) InstBreakpoint(Func);
}
static bool classof(const Inst *Instr) {
return Instr->getKind() == Breakpoint;
}
};
/// The Target instruction is the base class for all target-specific
/// instructions.
class InstTarget : public Inst {
InstTarget() = delete;
InstTarget(const InstTarget &) = delete;
InstTarget &operator=(const InstTarget &) = delete;
public:
uint32_t getEmitInstCount() const override { return 1; }
bool isMemoryWrite() const override {
return true; // conservative answer
}
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Instr) { return Instr->getKind() >= Target; }
protected:
InstTarget(Cfg *Func, InstKind Kind, SizeT MaxSrcs, Variable *Dest)
: Inst(Func, Kind, MaxSrcs, Dest) {
assert(Kind >= Target);
assert(Kind <= Target_Max);
}
};
bool checkForRedundantAssign(const Variable *Dest, const Operand *Source);
} // end of namespace Ice
#ifdef PNACL_LLVM
namespace llvm {
/// Override the default ilist traits so that Inst's private ctor and deleted
/// dtor aren't invoked.
template <>
struct ilist_traits<Ice::Inst> : public ilist_default_traits<Ice::Inst> {
Ice::Inst *createSentinel() const {
return static_cast<Ice::Inst *>(&Sentinel);
}
static void destroySentinel(Ice::Inst *) {}
Ice::Inst *provideInitialHead() const { return createSentinel(); }
Ice::Inst *ensureHead(Ice::Inst *) const { return createSentinel(); }
static void noteHead(Ice::Inst *, Ice::Inst *) {}
void deleteNode(Ice::Inst *) {}
private:
mutable ilist_half_node<Ice::Inst> Sentinel;
};
} // end of namespace llvm
#endif // PNACL_LLVM
namespace Ice {
inline InstList::iterator instToIterator(Inst *Instr) {
#ifdef PNACL_LLVM
return Instr;
#else // !PNACL_LLVM
return Instr->getIterator();
#endif // !PNACL_LLVM
}
inline Inst *iteratorToInst(InstList::iterator Iter) { return &*Iter; }
inline const Inst *iteratorToInst(InstList::const_iterator Iter) {
return &*Iter;
}
inline InstList::iterator
reverseToForwardIterator(InstList::reverse_iterator RI) {
#ifdef PNACL_LLVM
return RI.base();
#else // !PNACL_LLVM
return ++RI.getReverse();
#endif // !PNACL_LLVM
}
} // end of namespace Ice
#endif // SUBZERO_SRC_ICEINST_H