blob: d12c4ffb9c35581e864fb40d2d0409187d276683 [file] [log] [blame]
//===- subzero/src/IceInstARM32.h - ARM32 machine instructions --*- C++ -*-===//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file declares the InstARM32 and OperandARM32 classes and
/// their subclasses. This represents the machine instructions and
/// operands used for ARM32 code selection.
///
//===----------------------------------------------------------------------===//
#ifndef SUBZERO_SRC_ICEINSTARM32_H
#define SUBZERO_SRC_ICEINSTARM32_H
#include "IceConditionCodesARM32.h"
#include "IceDefs.h"
#include "IceInst.h"
#include "IceInstARM32.def"
#include "IceOperand.h"
namespace Ice {
class TargetARM32;
/// OperandARM32 extends the Operand hierarchy. Its subclasses are
/// OperandARM32Mem and OperandARM32Flex.
class OperandARM32 : public Operand {
OperandARM32() = delete;
OperandARM32(const OperandARM32 &) = delete;
OperandARM32 &operator=(const OperandARM32 &) = delete;
public:
enum OperandKindARM32 {
k__Start = Operand::kTarget,
kMem,
kFlexStart,
kFlexImm = kFlexStart,
kFlexReg,
kFlexEnd = kFlexReg
};
enum ShiftKind {
kNoShift = -1,
#define X(enum, emit) enum,
ICEINSTARM32SHIFT_TABLE
#undef X
};
using Operand::dump;
void dump(const Cfg *, Ostream &Str) const override {
if (BuildDefs::dump())
Str << "<OperandARM32>";
}
protected:
OperandARM32(OperandKindARM32 Kind, Type Ty)
: Operand(static_cast<OperandKind>(Kind), Ty) {}
};
/// OperandARM32Mem represents a memory operand in any of the various ARM32
/// addressing modes.
class OperandARM32Mem : public OperandARM32 {
OperandARM32Mem() = delete;
OperandARM32Mem(const OperandARM32Mem &) = delete;
OperandARM32Mem &operator=(const OperandARM32Mem &) = delete;
public:
/// Memory operand addressing mode.
/// The enum value also carries the encoding.
// TODO(jvoung): unify with the assembler.
enum AddrMode {
// bit encoding P U W
Offset = (8 | 4 | 0) << 21, // offset (w/o writeback to base)
PreIndex = (8 | 4 | 1) << 21, // pre-indexed addressing with writeback
PostIndex = (0 | 4 | 0) << 21, // post-indexed addressing with writeback
NegOffset = (8 | 0 | 0) << 21, // negative offset (w/o writeback to base)
NegPreIndex = (8 | 0 | 1) << 21, // negative pre-indexed with writeback
NegPostIndex = (0 | 0 | 0) << 21 // negative post-indexed with writeback
};
/// Provide two constructors.
/// NOTE: The Variable-typed operands have to be registers.
///
/// (1) Reg + Imm. The Immediate actually has a limited number of bits
/// for encoding, so check canHoldOffset first. It cannot handle
/// general Constant operands like ConstantRelocatable, since a relocatable
/// can potentially take up too many bits.
static OperandARM32Mem *create(Cfg *Func, Type Ty, Variable *Base,
ConstantInteger32 *ImmOffset,
AddrMode Mode = Offset) {
return new (Func->allocate<OperandARM32Mem>())
OperandARM32Mem(Func, Ty, Base, ImmOffset, Mode);
}
/// (2) Reg +/- Reg with an optional shift of some kind and amount.
/// Note that this mode is disallowed in the NaCl sandbox.
static OperandARM32Mem *create(Cfg *Func, Type Ty, Variable *Base,
Variable *Index, ShiftKind ShiftOp = kNoShift,
uint16_t ShiftAmt = 0,
AddrMode Mode = Offset) {
return new (Func->allocate<OperandARM32Mem>())
OperandARM32Mem(Func, Ty, Base, Index, ShiftOp, ShiftAmt, Mode);
}
Variable *getBase() const { return Base; }
ConstantInteger32 *getOffset() const { return ImmOffset; }
Variable *getIndex() const { return Index; }
ShiftKind getShiftOp() const { return ShiftOp; }
uint16_t getShiftAmt() const { return ShiftAmt; }
AddrMode getAddrMode() const { return Mode; }
bool isRegReg() const { return Index != nullptr; }
bool isNegAddrMode() const {
// Positive address modes have the "U" bit set, and negative modes don't.
static_assert((PreIndex & (4 << 21)) != 0,
"Positive addr modes should have U bit set.");
static_assert((NegPreIndex & (4 << 21)) == 0,
"Negative addr modes should have U bit clear.");
return (Mode & (4 << 21)) == 0;
}
void emit(const Cfg *Func) const override;
using OperandARM32::dump;
void dump(const Cfg *Func, Ostream &Str) const override;
static bool classof(const Operand *Operand) {
return Operand->getKind() == static_cast<OperandKind>(kMem);
}
/// Return true if a load/store instruction for an element of type Ty
/// can encode the Offset directly in the immediate field of the 32-bit
/// ARM instruction. For some types, if the load is Sign extending, then
/// the range is reduced.
static bool canHoldOffset(Type Ty, bool SignExt, int32_t Offset);
private:
OperandARM32Mem(Cfg *Func, Type Ty, Variable *Base,
ConstantInteger32 *ImmOffset, AddrMode Mode);
OperandARM32Mem(Cfg *Func, Type Ty, Variable *Base, Variable *Index,
ShiftKind ShiftOp, uint16_t ShiftAmt, AddrMode Mode);
Variable *Base;
ConstantInteger32 *ImmOffset;
Variable *Index;
ShiftKind ShiftOp;
uint16_t ShiftAmt;
AddrMode Mode;
};
/// OperandARM32Flex represent the "flexible second operand" for
/// data-processing instructions. It can be a rotatable 8-bit constant, or
/// a register with an optional shift operand. The shift amount can even be
/// a third register.
class OperandARM32Flex : public OperandARM32 {
OperandARM32Flex() = delete;
OperandARM32Flex(const OperandARM32Flex &) = delete;
OperandARM32Flex &operator=(const OperandARM32Flex &) = delete;
public:
static bool classof(const Operand *Operand) {
return static_cast<OperandKind>(kFlexStart) <= Operand->getKind() &&
Operand->getKind() <= static_cast<OperandKind>(kFlexEnd);
}
protected:
OperandARM32Flex(OperandKindARM32 Kind, Type Ty) : OperandARM32(Kind, Ty) {}
};
/// Rotated immediate variant.
class OperandARM32FlexImm : public OperandARM32Flex {
OperandARM32FlexImm() = delete;
OperandARM32FlexImm(const OperandARM32FlexImm &) = delete;
OperandARM32FlexImm &operator=(const OperandARM32FlexImm &) = delete;
public:
/// Immed_8 rotated by an even number of bits (2 * RotateAmt).
static OperandARM32FlexImm *create(Cfg *Func, Type Ty, uint32_t Imm,
uint32_t RotateAmt) {
return new (Func->allocate<OperandARM32FlexImm>())
OperandARM32FlexImm(Func, Ty, Imm, RotateAmt);
}
void emit(const Cfg *Func) const override;
using OperandARM32::dump;
void dump(const Cfg *Func, Ostream &Str) const override;
static bool classof(const Operand *Operand) {
return Operand->getKind() == static_cast<OperandKind>(kFlexImm);
}
/// Return true if the Immediate can fit in the ARM flexible operand.
/// Fills in the out-params RotateAmt and Immed_8 if Immediate fits.
static bool canHoldImm(uint32_t Immediate, uint32_t *RotateAmt,
uint32_t *Immed_8);
uint32_t getImm() const { return Imm; }
uint32_t getRotateAmt() const { return RotateAmt; }
private:
OperandARM32FlexImm(Cfg *Func, Type Ty, uint32_t Imm, uint32_t RotateAmt);
uint32_t Imm;
uint32_t RotateAmt;
};
/// Shifted register variant.
class OperandARM32FlexReg : public OperandARM32Flex {
OperandARM32FlexReg() = delete;
OperandARM32FlexReg(const OperandARM32FlexReg &) = delete;
OperandARM32FlexReg &operator=(const OperandARM32FlexReg &) = delete;
public:
/// Register with immediate/reg shift amount and shift operation.
static OperandARM32FlexReg *create(Cfg *Func, Type Ty, Variable *Reg,
ShiftKind ShiftOp, Operand *ShiftAmt) {
return new (Func->allocate<OperandARM32FlexReg>())
OperandARM32FlexReg(Func, Ty, Reg, ShiftOp, ShiftAmt);
}
void emit(const Cfg *Func) const override;
using OperandARM32::dump;
void dump(const Cfg *Func, Ostream &Str) const override;
static bool classof(const Operand *Operand) {
return Operand->getKind() == static_cast<OperandKind>(kFlexReg);
}
Variable *getReg() const { return Reg; }
ShiftKind getShiftOp() const { return ShiftOp; }
/// ShiftAmt can represent an immediate or a register.
Operand *getShiftAmt() const { return ShiftAmt; }
private:
OperandARM32FlexReg(Cfg *Func, Type Ty, Variable *Reg, ShiftKind ShiftOp,
Operand *ShiftAmt);
Variable *Reg;
ShiftKind ShiftOp;
Operand *ShiftAmt;
};
/// Base class for ARM instructions. While most ARM instructions can be
/// conditionally executed, a few of them are not predicable (halt,
/// memory barriers, etc.).
class InstARM32 : public InstTarget {
InstARM32() = delete;
InstARM32(const InstARM32 &) = delete;
InstARM32 &operator=(const InstARM32 &) = delete;
public:
enum InstKindARM32 {
k__Start = Inst::Target,
Adc,
Add,
Adjuststack,
And,
Asr,
Bic,
Br,
Call,
Cmp,
Eor,
Label,
Ldr,
Lsl,
Lsr,
Mla,
Mls,
Mov,
Movt,
Movw,
Mul,
Mvn,
Orr,
Pop,
Push,
Ret,
Rsb,
Sbc,
Sdiv,
Str,
Sub,
Sxt,
Trap,
Tst,
Udiv,
Umull,
Uxt
};
static const char *getWidthString(Type Ty);
static CondARM32::Cond getOppositeCondition(CondARM32::Cond Cond);
void dump(const Cfg *Func) const override;
protected:
InstARM32(Cfg *Func, InstKindARM32 Kind, SizeT Maxsrcs, Variable *Dest)
: InstTarget(Func, static_cast<InstKind>(Kind), Maxsrcs, Dest) {}
static bool isClassof(const Inst *Inst, InstKindARM32 MyKind) {
return Inst->getKind() == static_cast<InstKind>(MyKind);
}
};
/// A predicable ARM instruction.
class InstARM32Pred : public InstARM32 {
InstARM32Pred() = delete;
InstARM32Pred(const InstARM32Pred &) = delete;
InstARM32Pred &operator=(const InstARM32Pred &) = delete;
public:
InstARM32Pred(Cfg *Func, InstKindARM32 Kind, SizeT Maxsrcs, Variable *Dest,
CondARM32::Cond Predicate)
: InstARM32(Func, Kind, Maxsrcs, Dest), Predicate(Predicate) {}
CondARM32::Cond getPredicate() const { return Predicate; }
void setPredicate(CondARM32::Cond Pred) { Predicate = Pred; }
static const char *predString(CondARM32::Cond Predicate);
void dumpOpcodePred(Ostream &Str, const char *Opcode, Type Ty) const;
/// Shared emit routines for common forms of instructions.
static void emitUnaryopGPR(const char *Opcode, const InstARM32Pred *Inst,
const Cfg *Func);
static void emitTwoAddr(const char *Opcode, const InstARM32Pred *Inst,
const Cfg *Func);
static void emitThreeAddr(const char *Opcode, const InstARM32Pred *Inst,
const Cfg *Func, bool SetFlags);
static void emitFourAddr(const char *Opcode, const InstARM32Pred *Inst,
const Cfg *Func);
static void emitCmpLike(const char *Opcode, const InstARM32Pred *Inst,
const Cfg *Func);
protected:
CondARM32::Cond Predicate;
};
template <typename StreamType>
inline StreamType &operator<<(StreamType &Stream, CondARM32::Cond Predicate) {
Stream << InstARM32Pred::predString(Predicate);
return Stream;
}
/// Instructions of the form x := op(y).
template <InstARM32::InstKindARM32 K>
class InstARM32UnaryopGPR : public InstARM32Pred {
InstARM32UnaryopGPR() = delete;
InstARM32UnaryopGPR(const InstARM32UnaryopGPR &) = delete;
InstARM32UnaryopGPR &operator=(const InstARM32UnaryopGPR &) = delete;
public:
static InstARM32UnaryopGPR *create(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32UnaryopGPR>())
InstARM32UnaryopGPR(Func, Dest, Src, Predicate);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
emitUnaryopGPR(Opcode, this, Func);
}
void emitIAS(const Cfg *Func) const override {
(void)Func;
llvm_unreachable("Not yet implemented");
}
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
Str << " = ";
dumpOpcodePred(Str, Opcode, getDest()->getType());
Str << " ";
dumpSources(Func);
}
static bool classof(const Inst *Inst) { return isClassof(Inst, K); }
private:
InstARM32UnaryopGPR(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 1, Dest, Predicate) {
addSource(Src);
}
static const char *Opcode;
};
/// Instructions of the form x := x op y.
template <InstARM32::InstKindARM32 K>
class InstARM32TwoAddrGPR : public InstARM32Pred {
InstARM32TwoAddrGPR() = delete;
InstARM32TwoAddrGPR(const InstARM32TwoAddrGPR &) = delete;
InstARM32TwoAddrGPR &operator=(const InstARM32TwoAddrGPR &) = delete;
public:
/// Dest must be a register.
static InstARM32TwoAddrGPR *create(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32TwoAddrGPR>())
InstARM32TwoAddrGPR(Func, Dest, Src, Predicate);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
emitTwoAddr(Opcode, this, Func);
}
void emitIAS(const Cfg *Func) const override {
(void)Func;
llvm::report_fatal_error("Not yet implemented");
}
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
Str << " = ";
dumpOpcodePred(Str, Opcode, getDest()->getType());
Str << " ";
dumpSources(Func);
}
static bool classof(const Inst *Inst) { return isClassof(Inst, K); }
private:
InstARM32TwoAddrGPR(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 2, Dest, Predicate) {
addSource(Dest);
addSource(Src);
}
static const char *Opcode;
};
/// Base class for assignment instructions.
/// These can be tested for redundancy (and elided if redundant).
template <InstARM32::InstKindARM32 K>
class InstARM32Movlike : public InstARM32Pred {
InstARM32Movlike() = delete;
InstARM32Movlike(const InstARM32Movlike &) = delete;
InstARM32Movlike &operator=(const InstARM32Movlike &) = delete;
public:
static InstARM32Movlike *create(Cfg *Func, Variable *Dest, Operand *Source,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Movlike>())
InstARM32Movlike(Func, Dest, Source, Predicate);
}
bool isRedundantAssign() const override {
return checkForRedundantAssign(getDest(), getSrc(0));
}
bool isSimpleAssign() const override { return true; }
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpOpcodePred(Str, Opcode, getDest()->getType());
Str << " ";
dumpDest(Func);
Str << ", ";
dumpSources(Func);
}
static bool classof(const Inst *Inst) { return isClassof(Inst, K); }
private:
InstARM32Movlike(Cfg *Func, Variable *Dest, Operand *Source,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 1, Dest, Predicate) {
addSource(Source);
}
static const char *Opcode;
};
/// Instructions of the form x := y op z. May have the side-effect of setting
/// status flags.
template <InstARM32::InstKindARM32 K>
class InstARM32ThreeAddrGPR : public InstARM32Pred {
InstARM32ThreeAddrGPR() = delete;
InstARM32ThreeAddrGPR(const InstARM32ThreeAddrGPR &) = delete;
InstARM32ThreeAddrGPR &operator=(const InstARM32ThreeAddrGPR &) = delete;
public:
/// Create an ordinary binary-op instruction like add, and sub.
/// Dest and Src1 must be registers.
static InstARM32ThreeAddrGPR *create(Cfg *Func, Variable *Dest,
Variable *Src0, Operand *Src1,
CondARM32::Cond Predicate,
bool SetFlags = false) {
return new (Func->allocate<InstARM32ThreeAddrGPR>())
InstARM32ThreeAddrGPR(Func, Dest, Src0, Src1, Predicate, SetFlags);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
emitThreeAddr(Opcode, this, Func, SetFlags);
}
void emitIAS(const Cfg *Func) const override {
(void)Func;
llvm::report_fatal_error("Not yet implemented");
}
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
Str << " = ";
dumpOpcodePred(Str, Opcode, getDest()->getType());
Str << (SetFlags ? ".s " : " ");
dumpSources(Func);
}
static bool classof(const Inst *Inst) { return isClassof(Inst, K); }
private:
InstARM32ThreeAddrGPR(Cfg *Func, Variable *Dest, Variable *Src0,
Operand *Src1, CondARM32::Cond Predicate, bool SetFlags)
: InstARM32Pred(Func, K, 2, Dest, Predicate), SetFlags(SetFlags) {
addSource(Src0);
addSource(Src1);
}
static const char *Opcode;
bool SetFlags;
};
// Instructions of the form x := a op1 (y op2 z). E.g., multiply accumulate.
template <InstARM32::InstKindARM32 K>
class InstARM32FourAddrGPR : public InstARM32Pred {
InstARM32FourAddrGPR() = delete;
InstARM32FourAddrGPR(const InstARM32FourAddrGPR &) = delete;
InstARM32FourAddrGPR &operator=(const InstARM32FourAddrGPR &) = delete;
public:
// Every operand must be a register.
static InstARM32FourAddrGPR *create(Cfg *Func, Variable *Dest, Variable *Src0,
Variable *Src1, Variable *Src2,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32FourAddrGPR>())
InstARM32FourAddrGPR(Func, Dest, Src0, Src1, Src2, Predicate);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
emitFourAddr(Opcode, this, Func);
}
void emitIAS(const Cfg *Func) const override {
(void)Func;
llvm::report_fatal_error("Not yet implemented");
}
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
Str << " = ";
dumpOpcodePred(Str, Opcode, getDest()->getType());
Str << " ";
dumpSources(Func);
}
static bool classof(const Inst *Inst) { return isClassof(Inst, K); }
private:
InstARM32FourAddrGPR(Cfg *Func, Variable *Dest, Variable *Src0,
Variable *Src1, Variable *Src2,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 3, Dest, Predicate) {
addSource(Src0);
addSource(Src1);
addSource(Src2);
}
static const char *Opcode;
};
// Instructions of the form x cmpop y (setting flags).
template <InstARM32::InstKindARM32 K>
class InstARM32CmpLike : public InstARM32Pred {
InstARM32CmpLike() = delete;
InstARM32CmpLike(const InstARM32CmpLike &) = delete;
InstARM32CmpLike &operator=(const InstARM32CmpLike &) = delete;
public:
static InstARM32CmpLike *create(Cfg *Func, Variable *Src0, Operand *Src1,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32CmpLike>())
InstARM32CmpLike(Func, Src0, Src1, Predicate);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
emitCmpLike(Opcode, this, Func);
}
void emitIAS(const Cfg *Func) const override {
(void)Func;
llvm_unreachable("Not yet implemented");
}
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpOpcodePred(Str, Opcode, getSrc(0)->getType());
Str << " ";
dumpSources(Func);
}
static bool classof(const Inst *Inst) { return isClassof(Inst, K); }
private:
InstARM32CmpLike(Cfg *Func, Variable *Src0, Operand *Src1,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 2, nullptr, Predicate) {
addSource(Src0);
addSource(Src1);
}
static const char *Opcode;
};
typedef InstARM32ThreeAddrGPR<InstARM32::Adc> InstARM32Adc;
typedef InstARM32ThreeAddrGPR<InstARM32::Add> InstARM32Add;
typedef InstARM32ThreeAddrGPR<InstARM32::And> InstARM32And;
typedef InstARM32ThreeAddrGPR<InstARM32::Asr> InstARM32Asr;
typedef InstARM32ThreeAddrGPR<InstARM32::Bic> InstARM32Bic;
typedef InstARM32ThreeAddrGPR<InstARM32::Eor> InstARM32Eor;
typedef InstARM32ThreeAddrGPR<InstARM32::Lsl> InstARM32Lsl;
typedef InstARM32ThreeAddrGPR<InstARM32::Lsr> InstARM32Lsr;
typedef InstARM32ThreeAddrGPR<InstARM32::Mul> InstARM32Mul;
typedef InstARM32ThreeAddrGPR<InstARM32::Orr> InstARM32Orr;
typedef InstARM32ThreeAddrGPR<InstARM32::Rsb> InstARM32Rsb;
typedef InstARM32ThreeAddrGPR<InstARM32::Sbc> InstARM32Sbc;
typedef InstARM32ThreeAddrGPR<InstARM32::Sdiv> InstARM32Sdiv;
typedef InstARM32ThreeAddrGPR<InstARM32::Sub> InstARM32Sub;
typedef InstARM32ThreeAddrGPR<InstARM32::Udiv> InstARM32Udiv;
/// Move instruction (variable <- flex). This is more of a pseudo-inst.
/// If var is a register, then we use "mov". If var is stack, then we use
/// "str" to store to the stack.
typedef InstARM32Movlike<InstARM32::Mov> InstARM32Mov;
/// MovT leaves the bottom bits alone so dest is also a source.
/// This helps indicate that a previous MovW setting dest is not dead code.
typedef InstARM32TwoAddrGPR<InstARM32::Movt> InstARM32Movt;
typedef InstARM32UnaryopGPR<InstARM32::Movw> InstARM32Movw;
typedef InstARM32UnaryopGPR<InstARM32::Mvn> InstARM32Mvn;
// Technically, the uxt{b,h} and sxt{b,h} instructions have a rotation
// operand as well (rotate source by 8, 16, 24 bits prior to extending),
// but we aren't using that for now, so just model as a Unaryop.
typedef InstARM32UnaryopGPR<InstARM32::Sxt> InstARM32Sxt;
typedef InstARM32UnaryopGPR<InstARM32::Uxt> InstARM32Uxt;
typedef InstARM32FourAddrGPR<InstARM32::Mla> InstARM32Mla;
typedef InstARM32FourAddrGPR<InstARM32::Mls> InstARM32Mls;
typedef InstARM32CmpLike<InstARM32::Cmp> InstARM32Cmp;
typedef InstARM32CmpLike<InstARM32::Tst> InstARM32Tst;
// InstARM32Label represents an intra-block label that is the target
// of an intra-block branch. The offset between the label and the
// branch must be fit in the instruction immediate (considered "near").
class InstARM32Label : public InstARM32 {
InstARM32Label() = delete;
InstARM32Label(const InstARM32Label &) = delete;
InstARM32Label &operator=(const InstARM32Label &) = delete;
public:
static InstARM32Label *create(Cfg *Func, TargetARM32 *Target) {
return new (Func->allocate<InstARM32Label>()) InstARM32Label(Func, Target);
}
uint32_t getEmitInstCount() const override { return 0; }
IceString getName(const Cfg *Func) const;
SizeT getNumber() const { return Number; }
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
private:
InstARM32Label(Cfg *Func, TargetARM32 *Target);
SizeT Number; // used for unique label generation.
};
/// Direct branch instruction.
class InstARM32Br : public InstARM32Pred {
InstARM32Br() = delete;
InstARM32Br(const InstARM32Br &) = delete;
InstARM32Br &operator=(const InstARM32Br &) = delete;
public:
/// Create a conditional branch to one of two nodes.
static InstARM32Br *create(Cfg *Func, CfgNode *TargetTrue,
CfgNode *TargetFalse, CondARM32::Cond Predicate) {
assert(Predicate != CondARM32::AL);
constexpr InstARM32Label *NoLabel = nullptr;
return new (Func->allocate<InstARM32Br>())
InstARM32Br(Func, TargetTrue, TargetFalse, NoLabel, Predicate);
}
/// Create an unconditional branch to a node.
static InstARM32Br *create(Cfg *Func, CfgNode *Target) {
constexpr CfgNode *NoCondTarget = nullptr;
constexpr InstARM32Label *NoLabel = nullptr;
return new (Func->allocate<InstARM32Br>())
InstARM32Br(Func, NoCondTarget, Target, NoLabel, CondARM32::AL);
}
/// Create a non-terminator conditional branch to a node, with a
/// fallthrough to the next instruction in the current node. This is
/// used for switch lowering.
static InstARM32Br *create(Cfg *Func, CfgNode *Target,
CondARM32::Cond Predicate) {
assert(Predicate != CondARM32::AL);
constexpr CfgNode *NoUncondTarget = nullptr;
constexpr InstARM32Label *NoLabel = nullptr;
return new (Func->allocate<InstARM32Br>())
InstARM32Br(Func, Target, NoUncondTarget, NoLabel, Predicate);
}
// Create a conditional intra-block branch (or unconditional, if
// Condition==AL) to a label in the current block.
static InstARM32Br *create(Cfg *Func, InstARM32Label *Label,
CondARM32::Cond Predicate) {
constexpr CfgNode *NoCondTarget = nullptr;
constexpr CfgNode *NoUncondTarget = nullptr;
return new (Func->allocate<InstARM32Br>())
InstARM32Br(Func, NoCondTarget, NoUncondTarget, Label, Predicate);
}
const CfgNode *getTargetTrue() const { return TargetTrue; }
const CfgNode *getTargetFalse() const { return TargetFalse; }
bool optimizeBranch(const CfgNode *NextNode);
uint32_t getEmitInstCount() const override {
uint32_t Sum = 0;
if (Label)
++Sum;
if (getTargetTrue())
++Sum;
if (getTargetFalse())
++Sum;
return Sum;
}
bool isUnconditionalBranch() const override {
return getPredicate() == CondARM32::AL;
}
bool repointEdge(CfgNode *OldNode, CfgNode *NewNode) override;
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Br); }
private:
InstARM32Br(Cfg *Func, const CfgNode *TargetTrue, const CfgNode *TargetFalse,
const InstARM32Label *Label, CondARM32::Cond Predicate);
const CfgNode *TargetTrue;
const CfgNode *TargetFalse;
const InstARM32Label *Label; // Intra-block branch target
};
/// AdjustStack instruction - subtracts SP by the given amount and
/// updates the stack offset during code emission.
class InstARM32AdjustStack : public InstARM32 {
InstARM32AdjustStack() = delete;
InstARM32AdjustStack(const InstARM32AdjustStack &) = delete;
InstARM32AdjustStack &operator=(const InstARM32AdjustStack &) = delete;
public:
/// Note: We need both Amount and SrcAmount. If Amount is too large then
/// it needs to be copied to a register (so SrcAmount could be a register).
/// However, we also need the numeric Amount for bookkeeping, and it's
/// hard to pull that from the generic SrcAmount operand.
static InstARM32AdjustStack *create(Cfg *Func, Variable *SP, SizeT Amount,
Operand *SrcAmount) {
return new (Func->allocate<InstARM32AdjustStack>())
InstARM32AdjustStack(Func, SP, Amount, SrcAmount);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Adjuststack); }
private:
InstARM32AdjustStack(Cfg *Func, Variable *SP, SizeT Amount,
Operand *SrcAmount);
const SizeT Amount;
};
/// Call instruction (bl/blx). Arguments should have already been pushed.
/// Technically bl and the register form of blx can be predicated, but we'll
/// leave that out until needed.
class InstARM32Call : public InstARM32 {
InstARM32Call() = delete;
InstARM32Call(const InstARM32Call &) = delete;
InstARM32Call &operator=(const InstARM32Call &) = delete;
public:
static InstARM32Call *create(Cfg *Func, Variable *Dest, Operand *CallTarget) {
return new (Func->allocate<InstARM32Call>())
InstARM32Call(Func, Dest, CallTarget);
}
Operand *getCallTarget() const { return getSrc(0); }
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Call); }
private:
InstARM32Call(Cfg *Func, Variable *Dest, Operand *CallTarget);
};
/// Load instruction.
class InstARM32Ldr : public InstARM32Pred {
InstARM32Ldr() = delete;
InstARM32Ldr(const InstARM32Ldr &) = delete;
InstARM32Ldr &operator=(const InstARM32Ldr &) = delete;
public:
/// Dest must be a register.
static InstARM32Ldr *create(Cfg *Func, Variable *Dest, OperandARM32Mem *Mem,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Ldr>())
InstARM32Ldr(Func, Dest, Mem, Predicate);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Ldr); }
private:
InstARM32Ldr(Cfg *Func, Variable *Dest, OperandARM32Mem *Mem,
CondARM32::Cond Predicate);
};
/// Pop into a list of GPRs. Technically this can be predicated, but we don't
/// need that functionality.
class InstARM32Pop : public InstARM32 {
InstARM32Pop() = delete;
InstARM32Pop(const InstARM32Pop &) = delete;
InstARM32Pop &operator=(const InstARM32Pop &) = delete;
public:
static InstARM32Pop *create(Cfg *Func, const VarList &Dests) {
return new (Func->allocate<InstARM32Pop>()) InstARM32Pop(Func, Dests);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Pop); }
private:
InstARM32Pop(Cfg *Func, const VarList &Dests);
VarList Dests;
};
/// Push a list of GPRs. Technically this can be predicated, but we don't
/// need that functionality.
class InstARM32Push : public InstARM32 {
InstARM32Push() = delete;
InstARM32Push(const InstARM32Push &) = delete;
InstARM32Push &operator=(const InstARM32Push &) = delete;
public:
static InstARM32Push *create(Cfg *Func, const VarList &Srcs) {
return new (Func->allocate<InstARM32Push>()) InstARM32Push(Func, Srcs);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Push); }
private:
InstARM32Push(Cfg *Func, const VarList &Srcs);
};
/// Ret pseudo-instruction. This is actually a "bx" instruction with
/// an "lr" register operand, but epilogue lowering will search for a Ret
/// instead of a generic "bx". This instruction also takes a Source
/// operand (for non-void returning functions) for liveness analysis, though
/// a FakeUse before the ret would do just as well.
///
/// NOTE: Even though "bx" can be predicated, for now leave out the predication
/// since it's not yet known to be useful for Ret. That may complicate finding
/// the terminator instruction if it's not guaranteed to be executed.
class InstARM32Ret : public InstARM32 {
InstARM32Ret() = delete;
InstARM32Ret(const InstARM32Ret &) = delete;
InstARM32Ret &operator=(const InstARM32Ret &) = delete;
public:
static InstARM32Ret *create(Cfg *Func, Variable *LR,
Variable *Source = nullptr) {
return new (Func->allocate<InstARM32Ret>()) InstARM32Ret(Func, LR, Source);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Ret); }
private:
InstARM32Ret(Cfg *Func, Variable *LR, Variable *Source);
};
/// Store instruction. It's important for liveness that there is no Dest
/// operand (OperandARM32Mem instead of Dest Variable).
class InstARM32Str : public InstARM32Pred {
InstARM32Str() = delete;
InstARM32Str(const InstARM32Str &) = delete;
InstARM32Str &operator=(const InstARM32Str &) = delete;
public:
/// Value must be a register.
static InstARM32Str *create(Cfg *Func, Variable *Value, OperandARM32Mem *Mem,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Str>())
InstARM32Str(Func, Value, Mem, Predicate);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Str); }
private:
InstARM32Str(Cfg *Func, Variable *Value, OperandARM32Mem *Mem,
CondARM32::Cond Predicate);
};
class InstARM32Trap : public InstARM32 {
InstARM32Trap() = delete;
InstARM32Trap(const InstARM32Trap &) = delete;
InstARM32Trap &operator=(const InstARM32Trap &) = delete;
public:
static InstARM32Trap *create(Cfg *Func) {
return new (Func->allocate<InstARM32Trap>()) InstARM32Trap(Func);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Trap); }
private:
explicit InstARM32Trap(Cfg *Func);
};
/// Unsigned Multiply Long: d.lo, d.hi := x * y
class InstARM32Umull : public InstARM32Pred {
InstARM32Umull() = delete;
InstARM32Umull(const InstARM32Umull &) = delete;
InstARM32Umull &operator=(const InstARM32Umull &) = delete;
public:
/// Everything must be a register.
static InstARM32Umull *create(Cfg *Func, Variable *DestLo, Variable *DestHi,
Variable *Src0, Variable *Src1,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Umull>())
InstARM32Umull(Func, DestLo, DestHi, Src0, Src1, Predicate);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Umull); }
private:
InstARM32Umull(Cfg *Func, Variable *DestLo, Variable *DestHi, Variable *Src0,
Variable *Src1, CondARM32::Cond Predicate);
Variable *DestHi;
};
// Declare partial template specializations of emit() methods that
// already have default implementations. Without this, there is the
// possibility of ODR violations and link errors.
template <> void InstARM32Movw::emit(const Cfg *Func) const;
template <> void InstARM32Movt::emit(const Cfg *Func) const;
} // end of namespace Ice
#endif // SUBZERO_SRC_ICEINSTARM32_H