//===- 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