blob: 29e435d34c92384b02cedc8a8160e3c7c6bd277a [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
/// \brief 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"
#include "IceRegistersARM32.h"
namespace Ice {
namespace ARM32 {
/// Encoding of an ARM 32-bit instruction.
using IValueT = uint32_t;
/// An Offset value (+/-) used in an ARM 32-bit instruction.
using IOffsetT = int32_t;
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,
kShAmtImm,
kFlexStart,
kFlexImm = kFlexStart,
kFlexFpImm,
kFlexFpZero,
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 0 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.
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;
};
/// OperandARM32ShAmtImm represents an Immediate that is used in one of the
/// shift-by-immediate instructions (lsl, lsr, and asr), and shift-by-immediate
/// shifted registers.
class OperandARM32ShAmtImm : public OperandARM32 {
OperandARM32ShAmtImm() = delete;
OperandARM32ShAmtImm(const OperandARM32ShAmtImm &) = delete;
OperandARM32ShAmtImm &operator=(const OperandARM32ShAmtImm &) = delete;
public:
static OperandARM32ShAmtImm *create(Cfg *Func, ConstantInteger32 *ShAmt) {
return new (Func->allocate<OperandARM32ShAmtImm>())
OperandARM32ShAmtImm(ShAmt);
}
static bool classof(const Operand *Operand) {
return Operand->getKind() == static_cast<OperandKind>(kShAmtImm);
}
void emit(const Cfg *Func) const override;
using OperandARM32::dump;
void dump(const Cfg *Func, Ostream &Str) const override;
uint32_t getShAmtImm() const { return ShAmt->getValue(); }
private:
explicit OperandARM32ShAmtImm(ConstantInteger32 *SA);
const ConstantInteger32 *const ShAmt;
};
/// 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);
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;
};
/// Modified Floating-point constant.
class OperandARM32FlexFpImm : public OperandARM32Flex {
OperandARM32FlexFpImm() = delete;
OperandARM32FlexFpImm(const OperandARM32FlexFpImm &) = delete;
OperandARM32FlexFpImm &operator=(const OperandARM32FlexFpImm &) = delete;
public:
static OperandARM32FlexFpImm *create(Cfg *Func, Type Ty,
uint32_t ModifiedImm) {
return new (Func->allocate<OperandARM32FlexFpImm>())
OperandARM32FlexFpImm(Func, Ty, ModifiedImm);
}
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>(kFlexFpImm);
}
static bool canHoldImm(const Operand *C, uint32_t *ModifiedImm);
uint32_t getModifiedImm() const { return ModifiedImm; }
private:
OperandARM32FlexFpImm(Cfg *Func, Type Ty, uint32_t ModifiedImm);
const uint32_t ModifiedImm;
};
/// An operand for representing the 0.0 immediate in vcmp.
class OperandARM32FlexFpZero : public OperandARM32Flex {
OperandARM32FlexFpZero() = delete;
OperandARM32FlexFpZero(const OperandARM32FlexFpZero &) = delete;
OperandARM32FlexFpZero &operator=(const OperandARM32FlexFpZero &) = delete;
public:
static OperandARM32FlexFpZero *create(Cfg *Func, Type Ty) {
return new (Func->allocate<OperandARM32FlexFpZero>())
OperandARM32FlexFpZero(Func, Ty);
}
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>(kFlexFpZero);
}
private:
OperandARM32FlexFpZero(Cfg *Func, Type Ty);
};
/// 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;
};
/// StackVariable represents a Var that isn't assigned a register (stack-only).
/// It is assigned a stack slot, but the slot's offset may be too large to
/// represent in the native addressing mode, and so it has a separate base
/// register from SP/FP, where the offset from that base register is then in
/// range.
class StackVariable final : public Variable {
StackVariable() = delete;
StackVariable(const StackVariable &) = delete;
StackVariable &operator=(const StackVariable &) = delete;
public:
static StackVariable *create(Cfg *Func, Type Ty, SizeT Index) {
return new (Func->allocate<StackVariable>()) StackVariable(Func, Ty, Index);
}
constexpr static auto StackVariableKind =
static_cast<OperandKind>(kVariable_Target);
static bool classof(const Operand *Operand) {
return Operand->getKind() == StackVariableKind;
}
void setBaseRegNum(RegNumT RegNum) { BaseRegNum = RegNum; }
RegNumT getBaseRegNum() const override { return BaseRegNum; }
// Inherit dump() and emit() from Variable.
private:
StackVariable(const Cfg *Func, Type Ty, SizeT Index)
: Variable(Func, StackVariableKind, Ty, Index) {}
RegNumT BaseRegNum;
};
/// 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:
// Defines form that assembly instruction should be synthesized.
enum EmitForm { Emit_Text, Emit_Binary };
enum InstKindARM32 {
k__Start = Inst::Target,
Adc,
Add,
And,
Asr,
Bic,
Br,
Call,
Clz,
Cmn,
Cmp,
Dmb,
Eor,
Extract,
Insert,
Label,
Ldr,
Ldrex,
Lsl,
Lsr,
Nop,
Mla,
Mls,
Mov,
Movt,
Movw,
Mul,
Mvn,
Orr,
Pop,
Push,
Rbit,
Ret,
Rev,
Rsb,
Rsc,
Sbc,
Sdiv,
Str,
Strex,
Sub,
Sxt,
Trap,
Tst,
Udiv,
Umull,
Uxt,
Vabs,
Vadd,
Vand,
Vbsl,
Vceq,
Vcge,
Vcgt,
Vcmp,
Vcvt,
Vdiv,
Vdup,
Veor,
Vldr1d,
Vldr1q,
Vmla,
Vmlap,
Vmls,
Vmovl,
Vmovh,
Vmovhl,
Vmovlh,
Vmrs,
Vmul,
Vmulh,
Vmvn,
Vneg,
Vorr,
Vqadd,
Vqmovn2,
Vqsub,
Vshl,
Vshr,
Vsqrt,
Vstr1,
Vsub,
Vzip
};
static constexpr size_t InstSize = sizeof(uint32_t);
static CondARM32::Cond getOppositeCondition(CondARM32::Cond Cond);
/// Called inside derived methods emit() to communicate that multiple
/// instructions are being generated. Used by emitIAS() methods to
/// generate textual fixups for instructions that are not yet
/// implemented.
void startNextInst(const Cfg *Func) const;
/// FPSign is used for certain vector instructions (particularly, right
/// shifts) that require an operand sign specification.
enum FPSign {
FS_None,
FS_Signed,
FS_Unsigned,
};
/// Shared emit routines for common forms of instructions.
/// @{
static void emitThreeAddrFP(const char *Opcode, FPSign Sign,
const InstARM32 *Instr, const Cfg *Func,
Type OpType);
static void emitFourAddrFP(const char *Opcode, FPSign Sign,
const InstARM32 *Instr, const Cfg *Func);
/// @}
void dump(const Cfg *Func) const override;
void emitIAS(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 *Instr, InstKindARM32 MyKind) {
return Instr->getKind() == static_cast<InstKind>(MyKind);
}
// Generates text of assembly instruction using method emit(), and then adds
// to the assembly buffer as a Fixup.
void emitUsingTextFixup(const Cfg *Func) const;
};
/// 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 *Instr,
const Cfg *Func, bool NeedsWidthSuffix);
static void emitUnaryopFP(const char *Opcode, FPSign Sign,
const InstARM32Pred *Instr, const Cfg *Func);
static void emitTwoAddr(const char *Opcode, const InstARM32Pred *Instr,
const Cfg *Func);
static void emitThreeAddr(const char *Opcode, const InstARM32Pred *Instr,
const Cfg *Func, bool SetFlags);
static void emitFourAddr(const char *Opcode, const InstARM32Pred *Instr,
const Cfg *Func);
static void emitCmpLike(const char *Opcode, const InstARM32Pred *Instr,
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, bool NeedsWidthSuffix>
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, NeedsWidthSuffix);
}
void emitIAS(const Cfg *Func) const override;
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 *Instr) { return isClassof(Instr, K); }
private:
InstARM32UnaryopGPR(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 1, Dest, Predicate) {
addSource(Src);
}
static const char *const Opcode;
};
/// Instructions of the form x := op(y), for vector/FP.
template <InstARM32::InstKindARM32 K>
class InstARM32UnaryopFP : public InstARM32Pred {
InstARM32UnaryopFP() = delete;
InstARM32UnaryopFP(const InstARM32UnaryopFP &) = delete;
InstARM32UnaryopFP &operator=(const InstARM32UnaryopFP &) = delete;
public:
static InstARM32UnaryopFP *create(Cfg *Func, Variable *Dest, Variable *Src,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32UnaryopFP>())
InstARM32UnaryopFP(Func, Dest, Src, Predicate);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
emitUnaryopFP(Opcode, Sign, this, Func);
}
void emitIAS(const Cfg *Func) const override;
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 *Instr) { return isClassof(Instr, K); }
protected:
InstARM32UnaryopFP(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 1, Dest, Predicate) {
addSource(Src);
}
FPSign Sign = FS_None;
static const char *const Opcode;
};
template <InstARM32::InstKindARM32 K>
class InstARM32UnaryopSignAwareFP : public InstARM32UnaryopFP<K> {
InstARM32UnaryopSignAwareFP() = delete;
InstARM32UnaryopSignAwareFP(const InstARM32UnaryopSignAwareFP &) = delete;
InstARM32UnaryopSignAwareFP &
operator=(const InstARM32UnaryopSignAwareFP &) = delete;
public:
static InstARM32UnaryopSignAwareFP *
create(Cfg *Func, Variable *Dest, Variable *Src, CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32UnaryopSignAwareFP>())
InstARM32UnaryopSignAwareFP(Func, Dest, Src, Predicate);
}
void emitIAS(const Cfg *Func) const override;
void setSignType(InstARM32::FPSign SignType) { this->Sign = SignType; }
private:
InstARM32UnaryopSignAwareFP(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate)
: InstARM32UnaryopFP<K>(Func, Dest, Src, Predicate) {}
};
/// 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 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 *Instr) { return isClassof(Instr, 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 *const Opcode;
};
/// Base class for load instructions.
template <InstARM32::InstKindARM32 K>
class InstARM32LoadBase : public InstARM32Pred {
InstARM32LoadBase() = delete;
InstARM32LoadBase(const InstARM32LoadBase &) = delete;
InstARM32LoadBase &operator=(const InstARM32LoadBase &) = delete;
public:
static InstARM32LoadBase *create(Cfg *Func, Variable *Dest, Operand *Source,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32LoadBase>())
InstARM32LoadBase(Func, Dest, Source, Predicate);
}
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 *Instr) { return isClassof(Instr, K); }
private:
InstARM32LoadBase(Cfg *Func, Variable *Dest, Operand *Source,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 1, Dest, Predicate) {
addSource(Source);
}
static const char *const 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 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 *Instr) { return isClassof(Instr, K); }
private:
InstARM32ThreeAddrGPR(Cfg *Func, Variable *Dest, Variable *Src0,
Operand *Src1, CondARM32::Cond Predicate, bool SetFlags)
: InstARM32Pred(Func, K, 2, Dest, Predicate), SetFlags(SetFlags) {
HasSideEffects = SetFlags;
addSource(Src0);
addSource(Src1);
}
static const char *const Opcode;
bool SetFlags;
};
/// Instructions of the form x := y op z, for vector/FP. We leave these as
/// unconditional: "ARM deprecates the conditional execution of any instruction
/// encoding provided by the Advanced SIMD Extension that is not also provided
/// by the floating-point (VFP) extension". They do not set flags.
template <InstARM32::InstKindARM32 K>
class InstARM32ThreeAddrFP : public InstARM32 {
InstARM32ThreeAddrFP() = delete;
InstARM32ThreeAddrFP(const InstARM32ThreeAddrFP &) = delete;
InstARM32ThreeAddrFP &operator=(const InstARM32ThreeAddrFP &) = delete;
public:
/// Create a vector/FP binary-op instruction like vadd, and vsub. Everything
/// must be a register.
static InstARM32ThreeAddrFP *create(Cfg *Func, Variable *Dest, Variable *Src0,
Variable *Src1) {
return new (Func->allocate<InstARM32ThreeAddrFP>())
InstARM32ThreeAddrFP(Func, Dest, Src0, Src1);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
const Type OpType = (isVectorCompare() ? getSrc(0) : getDest())->getType();
emitThreeAddrFP(Opcode, Sign, this, Func, OpType);
}
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
const Type OpType = (isVectorCompare() ? getSrc(0) : getDest())->getType();
Str << " = " << Opcode << "." << OpType << " ";
dumpSources(Func);
}
static bool classof(const Inst *Instr) { return isClassof(Instr, K); }
protected:
FPSign Sign = FS_None;
InstARM32ThreeAddrFP(Cfg *Func, Variable *Dest, Variable *Src0, Operand *Src1)
: InstARM32(Func, K, 2, Dest) {
addSource(Src0);
addSource(Src1);
}
static const char *const Opcode;
private:
static constexpr bool isVectorCompare() {
return K == InstARM32::Vceq || K == InstARM32::Vcgt || K == InstARM32::Vcge;
}
};
template <InstARM32::InstKindARM32 K>
class InstARM32ThreeAddrSignAwareFP : public InstARM32ThreeAddrFP<K> {
InstARM32ThreeAddrSignAwareFP() = delete;
InstARM32ThreeAddrSignAwareFP(const InstARM32ThreeAddrSignAwareFP &) = delete;
InstARM32ThreeAddrSignAwareFP &
operator=(const InstARM32ThreeAddrSignAwareFP &) = delete;
public:
/// Create a vector/FP binary-op instruction like vadd, and vsub. Everything
/// must be a register.
static InstARM32ThreeAddrSignAwareFP *create(Cfg *Func, Variable *Dest,
Variable *Src0, Variable *Src1) {
return new (Func->allocate<InstARM32ThreeAddrSignAwareFP>())
InstARM32ThreeAddrSignAwareFP(Func, Dest, Src0, Src1);
}
static InstARM32ThreeAddrSignAwareFP *
create(Cfg *Func, Variable *Dest, Variable *Src0, ConstantInteger32 *Src1) {
return new (Func->allocate<InstARM32ThreeAddrSignAwareFP>())
InstARM32ThreeAddrSignAwareFP(Func, Dest, Src0, Src1);
}
void emitIAS(const Cfg *Func) const override;
void setSignType(InstARM32::FPSign SignType) { this->Sign = SignType; }
private:
InstARM32ThreeAddrSignAwareFP(Cfg *Func, Variable *Dest, Variable *Src0,
Operand *Src1)
: InstARM32ThreeAddrFP<K>(Func, Dest, Src0, Src1) {}
};
/// 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 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 *Instr) { return isClassof(Instr, 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 *const Opcode;
};
/// Instructions of the form x := x op1 (y op2 z). E.g., multiply accumulate.
/// We leave these as unconditional: "ARM deprecates the conditional execution
/// of any instruction encoding provided by the Advanced SIMD Extension that is
/// not also provided by the floating-point (VFP) extension". They do not set
/// flags.
template <InstARM32::InstKindARM32 K>
class InstARM32FourAddrFP : public InstARM32 {
InstARM32FourAddrFP() = delete;
InstARM32FourAddrFP(const InstARM32FourAddrFP &) = delete;
InstARM32FourAddrFP &operator=(const InstARM32FourAddrFP &) = delete;
public:
// Every operand must be a register.
static InstARM32FourAddrFP *create(Cfg *Func, Variable *Dest, Variable *Src0,
Variable *Src1) {
return new (Func->allocate<InstARM32FourAddrFP>())
InstARM32FourAddrFP(Func, Dest, Src0, Src1);
}
void emit(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
emitFourAddrFP(Opcode, Sign, this, Func);
}
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
dumpDest(Func);
Str << " = ";
Str << Opcode << "." << getDest()->getType() << " ";
dumpDest(Func);
Str << ", ";
dumpSources(Func);
}
static bool classof(const Inst *Instr) { return isClassof(Instr, K); }
private:
InstARM32FourAddrFP(Cfg *Func, Variable *Dest, Variable *Src0, Variable *Src1)
: InstARM32(Func, K, 3, Dest) {
addSource(Dest);
addSource(Src0);
addSource(Src1);
}
FPSign Sign = FS_None;
static const char *const 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 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 *Instr) { return isClassof(Instr, K); }
private:
InstARM32CmpLike(Cfg *Func, Variable *Src0, Operand *Src1,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, K, 2, nullptr, Predicate) {
HasSideEffects = true;
addSource(Src0);
addSource(Src1);
}
static const char *const Opcode;
};
using InstARM32Adc = InstARM32ThreeAddrGPR<InstARM32::Adc>;
using InstARM32Add = InstARM32ThreeAddrGPR<InstARM32::Add>;
using InstARM32And = InstARM32ThreeAddrGPR<InstARM32::And>;
using InstARM32Asr = InstARM32ThreeAddrGPR<InstARM32::Asr>;
using InstARM32Bic = InstARM32ThreeAddrGPR<InstARM32::Bic>;
using InstARM32Eor = InstARM32ThreeAddrGPR<InstARM32::Eor>;
using InstARM32Lsl = InstARM32ThreeAddrGPR<InstARM32::Lsl>;
using InstARM32Lsr = InstARM32ThreeAddrGPR<InstARM32::Lsr>;
using InstARM32Mul = InstARM32ThreeAddrGPR<InstARM32::Mul>;
using InstARM32Orr = InstARM32ThreeAddrGPR<InstARM32::Orr>;
using InstARM32Rsb = InstARM32ThreeAddrGPR<InstARM32::Rsb>;
using InstARM32Rsc = InstARM32ThreeAddrGPR<InstARM32::Rsc>;
using InstARM32Sbc = InstARM32ThreeAddrGPR<InstARM32::Sbc>;
using InstARM32Sdiv = InstARM32ThreeAddrGPR<InstARM32::Sdiv>;
using InstARM32Sub = InstARM32ThreeAddrGPR<InstARM32::Sub>;
using InstARM32Udiv = InstARM32ThreeAddrGPR<InstARM32::Udiv>;
using InstARM32Vadd = InstARM32ThreeAddrFP<InstARM32::Vadd>;
using InstARM32Vand = InstARM32ThreeAddrFP<InstARM32::Vand>;
using InstARM32Vbsl = InstARM32ThreeAddrFP<InstARM32::Vbsl>;
using InstARM32Vceq = InstARM32ThreeAddrFP<InstARM32::Vceq>;
using InstARM32Vcge = InstARM32ThreeAddrSignAwareFP<InstARM32::Vcge>;
using InstARM32Vcgt = InstARM32ThreeAddrSignAwareFP<InstARM32::Vcgt>;
using InstARM32Vdiv = InstARM32ThreeAddrFP<InstARM32::Vdiv>;
using InstARM32Veor = InstARM32ThreeAddrFP<InstARM32::Veor>;
using InstARM32Vmla = InstARM32FourAddrFP<InstARM32::Vmla>;
using InstARM32Vmls = InstARM32FourAddrFP<InstARM32::Vmls>;
using InstARM32Vmovl = InstARM32ThreeAddrFP<InstARM32::Vmovl>;
using InstARM32Vmovh = InstARM32ThreeAddrFP<InstARM32::Vmovh>;
using InstARM32Vmovhl = InstARM32ThreeAddrFP<InstARM32::Vmovhl>;
using InstARM32Vmovlh = InstARM32ThreeAddrFP<InstARM32::Vmovlh>;
using InstARM32Vmul = InstARM32ThreeAddrFP<InstARM32::Vmul>;
using InstARM32Vmvn = InstARM32UnaryopFP<InstARM32::Vmvn>;
using InstARM32Vneg = InstARM32UnaryopSignAwareFP<InstARM32::Vneg>;
using InstARM32Vorr = InstARM32ThreeAddrFP<InstARM32::Vorr>;
using InstARM32Vqadd = InstARM32ThreeAddrSignAwareFP<InstARM32::Vqadd>;
using InstARM32Vqsub = InstARM32ThreeAddrSignAwareFP<InstARM32::Vqsub>;
using InstARM32Vqmovn2 = InstARM32ThreeAddrSignAwareFP<InstARM32::Vqmovn2>;
using InstARM32Vmulh = InstARM32ThreeAddrSignAwareFP<InstARM32::Vmulh>;
using InstARM32Vmlap = InstARM32ThreeAddrFP<InstARM32::Vmlap>;
using InstARM32Vshl = InstARM32ThreeAddrSignAwareFP<InstARM32::Vshl>;
using InstARM32Vshr = InstARM32ThreeAddrSignAwareFP<InstARM32::Vshr>;
using InstARM32Vsub = InstARM32ThreeAddrFP<InstARM32::Vsub>;
using InstARM32Ldr = InstARM32LoadBase<InstARM32::Ldr>;
using InstARM32Ldrex = InstARM32LoadBase<InstARM32::Ldrex>;
using InstARM32Vldr1d = InstARM32LoadBase<InstARM32::Vldr1d>;
using InstARM32Vldr1q = InstARM32LoadBase<InstARM32::Vldr1q>;
using InstARM32Vzip = InstARM32ThreeAddrFP<InstARM32::Vzip>;
/// 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.
using InstARM32Movt = InstARM32TwoAddrGPR<InstARM32::Movt>;
using InstARM32Movw = InstARM32UnaryopGPR<InstARM32::Movw, false>;
using InstARM32Clz = InstARM32UnaryopGPR<InstARM32::Clz, false>;
using InstARM32Mvn = InstARM32UnaryopGPR<InstARM32::Mvn, false>;
using InstARM32Rbit = InstARM32UnaryopGPR<InstARM32::Rbit, false>;
using InstARM32Rev = InstARM32UnaryopGPR<InstARM32::Rev, false>;
// 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.
using InstARM32Sxt = InstARM32UnaryopGPR<InstARM32::Sxt, true>;
using InstARM32Uxt = InstARM32UnaryopGPR<InstARM32::Uxt, true>;
using InstARM32Vsqrt = InstARM32UnaryopFP<InstARM32::Vsqrt>;
using InstARM32Mla = InstARM32FourAddrGPR<InstARM32::Mla>;
using InstARM32Mls = InstARM32FourAddrGPR<InstARM32::Mls>;
using InstARM32Cmn = InstARM32CmpLike<InstARM32::Cmn>;
using InstARM32Cmp = InstARM32CmpLike<InstARM32::Cmp>;
using InstARM32Tst = InstARM32CmpLike<InstARM32::Tst>;
// 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; }
GlobalString getLabelName() const { return Name; }
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;
void setRelocOffset(RelocOffset *Value) { OffsetReloc = Value; }
private:
InstARM32Label(Cfg *Func, TargetARM32 *Target);
RelocOffset *OffsetReloc = nullptr;
SizeT Number; // used for unique label generation.
GlobalString Name;
};
/// 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 repointEdges(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 *Instr) { return isClassof(Instr, 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
};
/// 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 *Instr) { return isClassof(Instr, Call); }
private:
InstARM32Call(Cfg *Func, Variable *Dest, Operand *CallTarget);
};
class InstARM32RegisterStackOp : public InstARM32 {
InstARM32RegisterStackOp() = delete;
InstARM32RegisterStackOp(const InstARM32RegisterStackOp &) = delete;
InstARM32RegisterStackOp &
operator=(const InstARM32RegisterStackOp &) = delete;
public:
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override;
protected:
InstARM32RegisterStackOp(Cfg *Func, InstKindARM32 Kind, SizeT Maxsrcs,
Variable *Dest)
: InstARM32(Func, Kind, Maxsrcs, Dest) {}
void emitUsingForm(const Cfg *Func, const EmitForm Form) const;
void emitGPRsAsText(const Cfg *Func) const;
void emitSRegsAsText(const Cfg *Func, const Variable *BaseReg,
SizeT Regcount) const;
void emitSRegsOp(const Cfg *Func, const EmitForm, const Variable *BaseReg,
SizeT RegCount, SizeT InstIndex) const;
virtual const char *getDumpOpcode() const { return getGPROpcode(); }
virtual const char *getGPROpcode() const = 0;
virtual const char *getSRegOpcode() const = 0;
virtual Variable *getStackReg(SizeT Index) const = 0;
virtual SizeT getNumStackRegs() const = 0;
virtual void emitSingleGPR(const Cfg *Func, const EmitForm Form,
const Variable *Reg) const = 0;
virtual void emitMultipleGPRs(const Cfg *Func, const EmitForm Form,
IValueT Registers) const = 0;
virtual void emitSRegs(const Cfg *Func, const EmitForm Form,
const Variable *BaseReg, SizeT RegCount) const = 0;
};
/// Pops a list of registers. It may be a list of GPRs, or a list of VFP "s"
/// regs, but not both. In any case, the list must be sorted.
class InstARM32Pop final : public InstARM32RegisterStackOp {
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);
}
static bool classof(const Inst *Instr) { return isClassof(Instr, Pop); }
private:
InstARM32Pop(Cfg *Func, const VarList &Dests);
virtual const char *getGPROpcode() const final;
virtual const char *getSRegOpcode() const final;
Variable *getStackReg(SizeT Index) const final;
SizeT getNumStackRegs() const final;
void emitSingleGPR(const Cfg *Func, const EmitForm Form,
const Variable *Reg) const final;
void emitMultipleGPRs(const Cfg *Func, const EmitForm Form,
IValueT Registers) const final;
void emitSRegs(const Cfg *Func, const EmitForm Form, const Variable *BaseReg,
SizeT RegCount) const final;
VarList Dests;
};
/// Pushes a list of registers. Just like Pop (see above), the list may be of
/// GPRs, or VFP "s" registers, but not both.
class InstARM32Push final : public InstARM32RegisterStackOp {
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);
}
static bool classof(const Inst *Instr) { return isClassof(Instr, Push); }
private:
InstARM32Push(Cfg *Func, const VarList &Srcs);
const char *getGPROpcode() const final;
const char *getSRegOpcode() const final;
Variable *getStackReg(SizeT Index) const final;
SizeT getNumStackRegs() const final;
void emitSingleGPR(const Cfg *Func, const EmitForm Form,
const Variable *Reg) const final;
void emitMultipleGPRs(const Cfg *Func, const EmitForm Form,
IValueT Registers) const final;
void emitSRegs(const Cfg *Func, const EmitForm Form, const Variable *BaseReg,
SizeT RegCount) const final;
};
/// 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 *Instr) { return isClassof(Instr, 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 final : 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 *Instr) { return isClassof(Instr, Str); }
private:
InstARM32Str(Cfg *Func, Variable *Value, OperandARM32Mem *Mem,
CondARM32::Cond Predicate);
};
/// Exclusive Store instruction. Like its non-exclusive sibling, it's important
/// for liveness that there is no Dest operand (OperandARM32Mem instead of Dest
/// Variable).
class InstARM32Strex final : public InstARM32Pred {
InstARM32Strex() = delete;
InstARM32Strex(const InstARM32Strex &) = delete;
InstARM32Strex &operator=(const InstARM32Strex &) = delete;
public:
/// Value must be a register.
static InstARM32Strex *create(Cfg *Func, Variable *Dest, Variable *Value,
OperandARM32Mem *Mem,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Strex>())
InstARM32Strex(Func, Dest, 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 *Instr) { return isClassof(Instr, Strex); }
private:
InstARM32Strex(Cfg *Func, Variable *Dest, Variable *Value,
OperandARM32Mem *Mem, CondARM32::Cond Predicate);
};
/// Sub-vector store instruction. It's important for liveness that there is no
/// Dest operand (OperandARM32Mem instead of Dest Variable).
class InstARM32Vstr1 final : public InstARM32Pred {
InstARM32Vstr1() = delete;
InstARM32Vstr1(const InstARM32Vstr1 &) = delete;
InstARM32Vstr1 &operator=(const InstARM32Vstr1 &) = delete;
public:
/// Value must be a register.
static InstARM32Vstr1 *create(Cfg *Func, Variable *Value,
OperandARM32Mem *Mem, CondARM32::Cond Predicate,
SizeT Size) {
return new (Func->allocate<InstARM32Vstr1>())
InstARM32Vstr1(Func, Value, Mem, Predicate, Size);
}
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 *Instr) { return isClassof(Instr, Vstr1); }
private:
InstARM32Vstr1(Cfg *Func, Variable *Value, OperandARM32Mem *Mem,
CondARM32::Cond Predicate, SizeT Size);
SizeT Size;
};
/// Vector element duplication/replication instruction.
class InstARM32Vdup final : public InstARM32Pred {
InstARM32Vdup() = delete;
InstARM32Vdup(const InstARM32Vdup &) = delete;
InstARM32Vdup &operator=(const InstARM32Vdup &) = delete;
public:
/// Value must be a register.
static InstARM32Vdup *create(Cfg *Func, Variable *Dest, Variable *Src,
IValueT Idx) {
return new (Func->allocate<InstARM32Vdup>())
InstARM32Vdup(Func, Dest, Src, Idx);
}
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 *Instr) { return isClassof(Instr, Vdup); }
private:
InstARM32Vdup(Cfg *Func, Variable *Dest, Variable *Src, IValueT Idx);
const IValueT Idx;
};
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 *Instr) { return isClassof(Instr, 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 *Instr) { return isClassof(Instr, Umull); }
private:
InstARM32Umull(Cfg *Func, Variable *DestLo, Variable *DestHi, Variable *Src0,
Variable *Src1, CondARM32::Cond Predicate);
Variable *DestHi;
};
/// Handles fp2int, int2fp, and fp2fp conversions.
class InstARM32Vcvt final : public InstARM32Pred {
InstARM32Vcvt() = delete;
InstARM32Vcvt(const InstARM32Vcvt &) = delete;
InstARM32Vcvt &operator=(const InstARM32Vcvt &) = delete;
public:
enum VcvtVariant {
S2si,
S2ui,
Si2s,
Ui2s,
D2si,
D2ui,
Si2d,
Ui2d,
S2d,
D2s,
Vs2si,
Vs2ui,
Vsi2s,
Vui2s,
};
static InstARM32Vcvt *create(Cfg *Func, Variable *Dest, Variable *Src,
VcvtVariant Variant, CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Vcvt>())
InstARM32Vcvt(Func, Dest, Src, Variant, 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 *Instr) { return isClassof(Instr, Vcvt); }
private:
InstARM32Vcvt(Cfg *Func, Variable *Dest, Variable *Src, VcvtVariant Variant,
CondARM32::Cond Predicate);
const VcvtVariant Variant;
};
/// Handles (some of) vmov's various formats.
class InstARM32Mov final : public InstARM32Pred {
InstARM32Mov() = delete;
InstARM32Mov(const InstARM32Mov &) = delete;
InstARM32Mov &operator=(const InstARM32Mov &) = delete;
public:
static InstARM32Mov *create(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Mov>())
InstARM32Mov(Func, Dest, Src, Predicate);
}
bool isRedundantAssign() const override {
return !isMultiDest() && !isMultiSource() &&
getPredicate() == CondARM32::AL &&
checkForRedundantAssign(getDest(), getSrc(0));
}
bool isVarAssign() const override { return llvm::isa<Variable>(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 *Instr) { return isClassof(Instr, Mov); }
bool isMultiDest() const { return DestHi != nullptr; }
bool isMultiSource() const {
assert(getSrcSize() == 1 || getSrcSize() == 2);
return getSrcSize() == 2;
}
Variable *getDestHi() const { return DestHi; }
private:
InstARM32Mov(Cfg *Func, Variable *Dest, Operand *Src,
CondARM32::Cond Predicate);
void emitMultiDestSingleSource(const Cfg *Func) const;
void emitSingleDestMultiSource(const Cfg *Func) const;
void emitSingleDestSingleSource(const Cfg *Func) const;
Variable *DestHi = nullptr;
};
/// Generates vmov Rd, Dn[x] instructions, and their related floating point
/// versions.
class InstARM32Extract final : public InstARM32Pred {
InstARM32Extract() = delete;
InstARM32Extract(const InstARM32Extract &) = delete;
InstARM32Extract &operator=(const InstARM32Extract &) = delete;
public:
static InstARM32Extract *create(Cfg *Func, Variable *Dest, Variable *Src0,
uint32_t Index, CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Extract>())
InstARM32Extract(Func, Dest, Src0, Index, Predicate);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Extract); }
private:
InstARM32Extract(Cfg *Func, Variable *Dest, Variable *Src0, uint32_t Index,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, InstARM32::Extract, 1, Dest, Predicate),
Index(Index) {
assert(Index < typeNumElements(Src0->getType()));
addSource(Src0);
}
const uint32_t Index;
};
/// Generates vmov Dn[x], Rd instructions, and their related floating point
/// versions.
class InstARM32Insert final : public InstARM32Pred {
InstARM32Insert() = delete;
InstARM32Insert(const InstARM32Insert &) = delete;
InstARM32Insert &operator=(const InstARM32Insert &) = delete;
public:
static InstARM32Insert *create(Cfg *Func, Variable *Dest, Variable *Src0,
uint32_t Index, CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Insert>())
InstARM32Insert(Func, Dest, Src0, Index, Predicate);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return isClassof(Inst, Insert); }
private:
InstARM32Insert(Cfg *Func, Variable *Dest, Variable *Src0, uint32_t Index,
CondARM32::Cond Predicate)
: InstARM32Pred(Func, InstARM32::Insert, 1, Dest, Predicate),
Index(Index) {
assert(Index < typeNumElements(Dest->getType()));
addSource(Src0);
}
const uint32_t Index;
};
class InstARM32Vcmp final : public InstARM32Pred {
InstARM32Vcmp() = delete;
InstARM32Vcmp(const InstARM32Vcmp &) = delete;
InstARM32Vcmp &operator=(const InstARM32Vcmp &) = delete;
public:
static InstARM32Vcmp *create(Cfg *Func, Variable *Src0, Variable *Src1,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Vcmp>())
InstARM32Vcmp(Func, Src0, Src1, Predicate);
}
static InstARM32Vcmp *create(Cfg *Func, Variable *Src0,
OperandARM32FlexFpZero *Src1,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Vcmp>())
InstARM32Vcmp(Func, 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 *Instr) { return isClassof(Instr, Vcmp); }
private:
InstARM32Vcmp(Cfg *Func, Variable *Src0, Operand *Src1,
CondARM32::Cond Predicate);
};
/// Copies the FP Status and Control Register the core flags.
class InstARM32Vmrs final : public InstARM32Pred {
InstARM32Vmrs() = delete;
InstARM32Vmrs(const InstARM32Vmrs &) = delete;
InstARM32Vmrs &operator=(const InstARM32Vmrs &) = delete;
public:
static InstARM32Vmrs *create(Cfg *Func, CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Vmrs>()) InstARM32Vmrs(Func, 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 *Instr) { return isClassof(Instr, Vmrs); }
private:
InstARM32Vmrs(Cfg *Func, CondARM32::Cond Predicate);
};
class InstARM32Vabs final : public InstARM32Pred {
InstARM32Vabs() = delete;
InstARM32Vabs(const InstARM32Vabs &) = delete;
InstARM32Vabs &operator=(const InstARM32Vabs &) = delete;
public:
static InstARM32Vabs *create(Cfg *Func, Variable *Dest, Variable *Src,
CondARM32::Cond Predicate) {
return new (Func->allocate<InstARM32Vabs>())
InstARM32Vabs(Func, Dest, Src, 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 *Instr) { return isClassof(Instr, Vabs); }
private:
InstARM32Vabs(Cfg *Func, Variable *Dest, Variable *Src,
CondARM32::Cond Predicate);
};
class InstARM32Dmb final : public InstARM32Pred {
InstARM32Dmb() = delete;
InstARM32Dmb(const InstARM32Dmb &) = delete;
InstARM32Dmb &operator=(const InstARM32Dmb &) = delete;
public:
static InstARM32Dmb *create(Cfg *Func) {
return new (Func->allocate<InstARM32Dmb>()) InstARM32Dmb(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 *Instr) { return isClassof(Instr, Dmb); }
private:
explicit InstARM32Dmb(Cfg *Func);
};
class InstARM32Nop final : public InstARM32Pred {
InstARM32Nop() = delete;
InstARM32Nop(const InstARM32Nop &) = delete;
InstARM32Nop &operator=(const InstARM32Nop &) = delete;
public:
static InstARM32Nop *create(Cfg *Func) {
return new (Func->allocate<InstARM32Nop>()) InstARM32Nop(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 *Instr) { return isClassof(Instr, Nop); }
private:
explicit InstARM32Nop(Cfg *Func);
};
// 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 InstARM32Ldr::emit(const Cfg *Func) const;
template <> void InstARM32Movw::emit(const Cfg *Func) const;
template <> void InstARM32Movt::emit(const Cfg *Func) const;
template <> void InstARM32Vldr1d::emit(const Cfg *Func) const;
template <> void InstARM32Vldr1q::emit(const Cfg *Func) const;
// Two-addr ops
template <> constexpr const char *InstARM32Movt::Opcode = "movt";
// Unary ops
template <> constexpr const char *InstARM32Movw::Opcode = "movw";
template <> constexpr const char *InstARM32Clz::Opcode = "clz";
template <> constexpr const char *InstARM32Mvn::Opcode = "mvn";
template <> constexpr const char *InstARM32Rbit::Opcode = "rbit";
template <> constexpr const char *InstARM32Rev::Opcode = "rev";
template <>
constexpr const char *InstARM32Sxt::Opcode = "sxt"; // still requires b/h
template <>
constexpr const char *InstARM32Uxt::Opcode = "uxt"; // still requires b/h
// FP
template <> constexpr const char *InstARM32Vsqrt::Opcode = "vsqrt";
// Mov-like ops
template <> constexpr const char *InstARM32Ldr::Opcode = "ldr";
template <> constexpr const char *InstARM32Ldrex::Opcode = "ldrex";
template <> constexpr const char *InstARM32Vldr1d::Opcode = "vldr1d";
template <> constexpr const char *InstARM32Vldr1q::Opcode = "vldr1q";
// Three-addr ops
template <> constexpr const char *InstARM32Adc::Opcode = "adc";
template <> constexpr const char *InstARM32Add::Opcode = "add";
template <> constexpr const char *InstARM32And::Opcode = "and";
template <> constexpr const char *InstARM32Asr::Opcode = "asr";
template <> constexpr const char *InstARM32Bic::Opcode = "bic";
template <> constexpr const char *InstARM32Eor::Opcode = "eor";
template <> constexpr const char *InstARM32Lsl::Opcode = "lsl";
template <> constexpr const char *InstARM32Lsr::Opcode = "lsr";
template <> constexpr const char *InstARM32Mul::Opcode = "mul";
template <> constexpr const char *InstARM32Orr::Opcode = "orr";
template <> constexpr const char *InstARM32Rsb::Opcode = "rsb";
template <> constexpr const char *InstARM32Rsc::Opcode = "rsc";
template <> constexpr const char *InstARM32Sbc::Opcode = "sbc";
template <> constexpr const char *InstARM32Sdiv::Opcode = "sdiv";
template <> constexpr const char *InstARM32Sub::Opcode = "sub";
template <> constexpr const char *InstARM32Udiv::Opcode = "udiv";
// FP
template <> constexpr const char *InstARM32Vadd::Opcode = "vadd";
template <> constexpr const char *InstARM32Vand::Opcode = "vand";
template <> constexpr const char *InstARM32Vbsl::Opcode = "vbsl";
template <> constexpr const char *InstARM32Vceq::Opcode = "vceq";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vcge>::Opcode = "vcge";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vcgt>::Opcode = "vcgt";
template <> constexpr const char *InstARM32Vdiv::Opcode = "vdiv";
template <> constexpr const char *InstARM32Veor::Opcode = "veor";
template <> constexpr const char *InstARM32Vmla::Opcode = "vmla";
template <> constexpr const char *InstARM32Vmls::Opcode = "vmls";
template <> constexpr const char *InstARM32Vmul::Opcode = "vmul";
template <> constexpr const char *InstARM32Vmvn::Opcode = "vmvn";
template <> constexpr const char *InstARM32Vmovl::Opcode = "vmovl";
template <> constexpr const char *InstARM32Vmovh::Opcode = "vmovh";
template <> constexpr const char *InstARM32Vmovhl::Opcode = "vmovhl";
template <> constexpr const char *InstARM32Vmovlh::Opcode = "vmovlh";
template <> constexpr const char *InstARM32Vorr::Opcode = "vorr";
template <>
constexpr const char *InstARM32UnaryopFP<InstARM32::Vneg>::Opcode = "vneg";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vshl>::Opcode = "vshl";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vshr>::Opcode = "vshr";
template <> constexpr const char *InstARM32Vsub::Opcode = "vsub";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vqadd>::Opcode = "vqadd";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vqsub>::Opcode = "vqsub";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vqmovn2>::Opcode =
"vqmovn2";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vmulh>::Opcode = "vmulh";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vmlap>::Opcode = "vmlap";
template <>
constexpr const char *InstARM32ThreeAddrFP<InstARM32::Vzip>::Opcode = "vzip";
// Four-addr ops
template <> constexpr const char *InstARM32Mla::Opcode = "mla";
template <> constexpr const char *InstARM32Mls::Opcode = "mls";
// Cmp-like ops
template <> constexpr const char *InstARM32Cmn::Opcode = "cmn";
template <> constexpr const char *InstARM32Cmp::Opcode = "cmp";
template <> constexpr const char *InstARM32Tst::Opcode = "tst";
} // end of namespace ARM32
} // end of namespace Ice
#endif // SUBZERO_SRC_ICEINSTARM32_H