blob: 7af98dc89bd3fa7d9a72ef75936d58bea770e317 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef jit_arm_Assembler_arm_h
#define jit_arm_Assembler_arm_h
#include "mozilla/Attributes.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Util.h"
#include "jit/shared/Assembler-shared.h"
#include "assembler/assembler/AssemblerBufferWithConstantPool.h"
#include "jit/CompactBuffer.h"
#include "jit/IonCode.h"
#include "jit/arm/Architecture-arm.h"
#include "jit/shared/IonAssemblerBufferWithConstantPools.h"
namespace js {
namespace jit {
//NOTE: there are duplicates in this list!
// sometimes we want to specifically refer to the
// link register as a link register (bl lr is much
// clearer than bl r14). HOWEVER, this register can
// easily be a gpr when it is not busy holding the return
// address.
static const MOZ_CONSTEXPR Register r0 = { Registers::r0 };
static const MOZ_CONSTEXPR Register r1 = { Registers::r1 };
static const MOZ_CONSTEXPR Register r2 = { Registers::r2 };
static const MOZ_CONSTEXPR Register r3 = { Registers::r3 };
static const MOZ_CONSTEXPR Register r4 = { Registers::r4 };
static const MOZ_CONSTEXPR Register r5 = { Registers::r5 };
static const MOZ_CONSTEXPR Register r6 = { Registers::r6 };
static const MOZ_CONSTEXPR Register r7 = { Registers::r7 };
static const MOZ_CONSTEXPR Register r8 = { Registers::r8 };
static const MOZ_CONSTEXPR Register r9 = { Registers::r9 };
static const MOZ_CONSTEXPR Register r10 = { Registers::r10 };
static const MOZ_CONSTEXPR Register r11 = { Registers::r11 };
static const MOZ_CONSTEXPR Register r12 = { Registers::ip };
static const MOZ_CONSTEXPR Register ip = { Registers::ip };
static const MOZ_CONSTEXPR Register sp = { Registers::sp };
static const MOZ_CONSTEXPR Register r14 = { Registers::lr };
static const MOZ_CONSTEXPR Register lr = { Registers::lr };
static const MOZ_CONSTEXPR Register pc = { Registers::pc };
static const MOZ_CONSTEXPR Register ScratchRegister = {Registers::ip};
static const MOZ_CONSTEXPR Register OsrFrameReg = r3;
static const MOZ_CONSTEXPR Register ArgumentsRectifierReg = r8;
static const MOZ_CONSTEXPR Register CallTempReg0 = r5;
static const MOZ_CONSTEXPR Register CallTempReg1 = r6;
static const MOZ_CONSTEXPR Register CallTempReg2 = r7;
static const MOZ_CONSTEXPR Register CallTempReg3 = r8;
static const MOZ_CONSTEXPR Register CallTempReg4 = r0;
static const MOZ_CONSTEXPR Register CallTempReg5 = r1;
static const MOZ_CONSTEXPR Register CallTempReg6 = r2;
static const MOZ_CONSTEXPR Register IntArgReg0 = r0;
static const MOZ_CONSTEXPR Register IntArgReg1 = r1;
static const MOZ_CONSTEXPR Register IntArgReg2 = r2;
static const MOZ_CONSTEXPR Register IntArgReg3 = r3;
static const MOZ_CONSTEXPR Register GlobalReg = r10;
static const MOZ_CONSTEXPR Register HeapReg = r11;
static const MOZ_CONSTEXPR Register CallTempNonArgRegs[] = { r5, r6, r7, r8 };
static const uint32_t NumCallTempNonArgRegs =
mozilla::ArrayLength(CallTempNonArgRegs);
class ABIArgGenerator
{
#if defined(JS_CPU_ARM_HARDFP)
unsigned intRegIndex_;
unsigned floatRegIndex_;
#else
unsigned argRegIndex_;
#endif
uint32_t stackOffset_;
ABIArg current_;
public:
ABIArgGenerator();
ABIArg next(MIRType argType);
ABIArg &current() { return current_; }
uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
static const Register NonArgReturnVolatileReg0;
static const Register NonArgReturnVolatileReg1;
};
static const MOZ_CONSTEXPR Register PreBarrierReg = r1;
static const MOZ_CONSTEXPR Register InvalidReg = { Registers::invalid_reg };
static const MOZ_CONSTEXPR FloatRegister InvalidFloatReg = { FloatRegisters::invalid_freg };
static const MOZ_CONSTEXPR Register JSReturnReg_Type = r3;
static const MOZ_CONSTEXPR Register JSReturnReg_Data = r2;
static const MOZ_CONSTEXPR Register StackPointer = sp;
static const MOZ_CONSTEXPR Register FramePointer = InvalidReg;
static const MOZ_CONSTEXPR Register ReturnReg = r0;
static const MOZ_CONSTEXPR FloatRegister ReturnFloatReg = { FloatRegisters::d0 };
static const MOZ_CONSTEXPR FloatRegister ScratchFloatReg = { FloatRegisters::d1 };
static const MOZ_CONSTEXPR FloatRegister NANReg = { FloatRegisters::d15 };
static const MOZ_CONSTEXPR FloatRegister d0 = {FloatRegisters::d0};
static const MOZ_CONSTEXPR FloatRegister d1 = {FloatRegisters::d1};
static const MOZ_CONSTEXPR FloatRegister d2 = {FloatRegisters::d2};
static const MOZ_CONSTEXPR FloatRegister d3 = {FloatRegisters::d3};
static const MOZ_CONSTEXPR FloatRegister d4 = {FloatRegisters::d4};
static const MOZ_CONSTEXPR FloatRegister d5 = {FloatRegisters::d5};
static const MOZ_CONSTEXPR FloatRegister d6 = {FloatRegisters::d6};
static const MOZ_CONSTEXPR FloatRegister d7 = {FloatRegisters::d7};
static const MOZ_CONSTEXPR FloatRegister d8 = {FloatRegisters::d8};
static const MOZ_CONSTEXPR FloatRegister d9 = {FloatRegisters::d9};
static const MOZ_CONSTEXPR FloatRegister d10 = {FloatRegisters::d10};
static const MOZ_CONSTEXPR FloatRegister d11 = {FloatRegisters::d11};
static const MOZ_CONSTEXPR FloatRegister d12 = {FloatRegisters::d12};
static const MOZ_CONSTEXPR FloatRegister d13 = {FloatRegisters::d13};
static const MOZ_CONSTEXPR FloatRegister d14 = {FloatRegisters::d14};
static const MOZ_CONSTEXPR FloatRegister d15 = {FloatRegisters::d15};
// For maximal awesomeness, 8 should be sufficent.
// ldrd/strd (dual-register load/store) operate in a single cycle
// when the address they are dealing with is 8 byte aligned.
// Also, the ARM abi wants the stack to be 8 byte aligned at
// function boundaries. I'm trying to make sure this is always true.
static const uint32_t StackAlignment = 8;
static const uint32_t CodeAlignment = 8;
static const bool StackKeptAligned = true;
static const uint32_t NativeFrameSize = sizeof(void*);
static const uint32_t AlignmentAtPrologue = 0;
static const uint32_t AlignmentMidPrologue = 4;
static const Scale ScalePointer = TimesFour;
class Instruction;
class InstBranchImm;
uint32_t RM(Register r);
uint32_t RS(Register r);
uint32_t RD(Register r);
uint32_t RT(Register r);
uint32_t RN(Register r);
uint32_t maybeRD(Register r);
uint32_t maybeRT(Register r);
uint32_t maybeRN(Register r);
Register toRN (Instruction &i);
Register toRM (Instruction &i);
Register toRD (Instruction &i);
Register toR (Instruction &i);
class VFPRegister;
uint32_t VD(VFPRegister vr);
uint32_t VN(VFPRegister vr);
uint32_t VM(VFPRegister vr);
class VFPRegister
{
public:
// What type of data is being stored in this register?
// UInt / Int are specifically for vcvt, where we need
// to know how the data is supposed to be converted.
enum RegType {
Double = 0x0,
Single = 0x1,
UInt = 0x2,
Int = 0x3
};
protected:
RegType kind : 2;
// ARM doesn't have more than 32 registers...
// don't take more bits than we'll need.
// Presently, I don't have plans to address the upper
// and lower halves of the double registers seprately, so
// 5 bits should suffice. If I do decide to address them seprately
// (vmov, I'm looking at you), I will likely specify it as a separate
// field.
uint32_t _code : 5;
bool _isInvalid : 1;
bool _isMissing : 1;
VFPRegister(int r, RegType k)
: kind(k), _code (r), _isInvalid(false), _isMissing(false)
{ }
public:
VFPRegister()
: _isInvalid(true), _isMissing(false)
{ }
VFPRegister(bool b)
: _isInvalid(false), _isMissing(b)
{ }
VFPRegister(FloatRegister fr)
: kind(Double), _code(fr.code()), _isInvalid(false), _isMissing(false)
{
JS_ASSERT(_code == (unsigned)fr.code());
}
VFPRegister(FloatRegister fr, RegType k)
: kind(k), _code (fr.code()), _isInvalid(false), _isMissing(false)
{
JS_ASSERT(_code == (unsigned)fr.code());
}
bool isDouble() { return kind == Double; }
bool isSingle() { return kind == Single; }
bool isFloat() { return (kind == Double) || (kind == Single); }
bool isInt() { return (kind == UInt) || (kind == Int); }
bool isSInt() { return kind == Int; }
bool isUInt() { return kind == UInt; }
bool equiv(VFPRegister other) { return other.kind == kind; }
size_t size() { return (kind == Double) ? 8 : 4; }
bool isInvalid();
bool isMissing();
VFPRegister doubleOverlay();
VFPRegister singleOverlay();
VFPRegister sintOverlay();
VFPRegister uintOverlay();
struct VFPRegIndexSplit;
VFPRegIndexSplit encode();
// for serializing values
struct VFPRegIndexSplit {
const uint32_t block : 4;
const uint32_t bit : 1;
private:
friend VFPRegIndexSplit js::jit::VFPRegister::encode();
VFPRegIndexSplit (uint32_t block_, uint32_t bit_)
: block(block_), bit(bit_)
{
JS_ASSERT (block == block_);
JS_ASSERT(bit == bit_);
}
};
uint32_t code() const {
return _code;
}
};
// For being passed into the generic vfp instruction generator when
// there is an instruction that only takes two registers
extern VFPRegister NoVFPRegister;
struct ImmTag : public Imm32
{
ImmTag(JSValueTag mask)
: Imm32(int32_t(mask))
{ }
};
struct ImmType : public ImmTag
{
ImmType(JSValueType type)
: ImmTag(JSVAL_TYPE_TO_TAG(type))
{ }
};
enum Index {
Offset = 0 << 21 | 1<<24,
PreIndex = 1<<21 | 1 << 24,
PostIndex = 0 << 21 | 0 << 24
// The docs were rather unclear on this. it sounds like
// 1<<21 | 0 << 24 encodes dtrt
};
// Seriously, wtf arm
enum IsImmOp2_ {
IsImmOp2 = 1 << 25,
IsNotImmOp2 = 0 << 25
};
enum IsImmDTR_ {
IsImmDTR = 0 << 25,
IsNotImmDTR = 1 << 25
};
// For the extra memory operations, ldrd, ldrsb, ldrh
enum IsImmEDTR_ {
IsImmEDTR = 1 << 22,
IsNotImmEDTR = 0 << 22
};
enum ShiftType {
LSL = 0, // << 5
LSR = 1, // << 5
ASR = 2, // << 5
ROR = 3, // << 5
RRX = ROR // RRX is encoded as ROR with a 0 offset.
};
// The actual codes that get set by instructions
// and the codes that are checked by the conditions below.
struct ConditionCodes
{
bool Zero : 1;
bool Overflow : 1;
bool Carry : 1;
bool Minus : 1;
};
// Modes for STM/LDM.
// Names are the suffixes applied to
// the instruction.
enum DTMMode {
A = 0 << 24, // empty / after
B = 1 << 24, // full / before
D = 0 << 23, // decrement
I = 1 << 23, // increment
DA = D | A,
DB = D | B,
IA = I | A,
IB = I | B
};
enum DTMWriteBack {
WriteBack = 1 << 21,
NoWriteBack = 0 << 21
};
enum SetCond_ {
SetCond = 1 << 20,
NoSetCond = 0 << 20
};
enum LoadStore {
IsLoad = 1 << 20,
IsStore = 0 << 20
};
// You almost never want to use this directly.
// Instead, you wantto pass in a signed constant,
// and let this bit be implicitly set for you.
// this is however, necessary if we want a negative index
enum IsUp_ {
IsUp = 1 << 23,
IsDown = 0 << 23
};
enum ALUOp {
op_mov = 0xd << 21,
op_mvn = 0xf << 21,
op_and = 0x0 << 21,
op_bic = 0xe << 21,
op_eor = 0x1 << 21,
op_orr = 0xc << 21,
op_adc = 0x5 << 21,
op_add = 0x4 << 21,
op_sbc = 0x6 << 21,
op_sub = 0x2 << 21,
op_rsb = 0x3 << 21,
op_rsc = 0x7 << 21,
op_cmn = 0xb << 21,
op_cmp = 0xa << 21,
op_teq = 0x9 << 21,
op_tst = 0x8 << 21,
op_invalid = -1
};
enum MULOp {
opm_mul = 0 << 21,
opm_mla = 1 << 21,
opm_umaal = 2 << 21,
opm_mls = 3 << 21,
opm_umull = 4 << 21,
opm_umlal = 5 << 21,
opm_smull = 6 << 21,
opm_smlal = 7 << 21
};
enum BranchTag {
op_b = 0x0a000000,
op_b_mask = 0x0f000000,
op_b_dest_mask = 0x00ffffff,
op_bl = 0x0b000000,
op_blx = 0x012fff30,
op_bx = 0x012fff10
};
// Just like ALUOp, but for the vfp instruction set.
enum VFPOp {
opv_mul = 0x2 << 20,
opv_add = 0x3 << 20,
opv_sub = 0x3 << 20 | 0x1 << 6,
opv_div = 0x8 << 20,
opv_mov = 0xB << 20 | 0x1 << 6,
opv_abs = 0xB << 20 | 0x3 << 6,
opv_neg = 0xB << 20 | 0x1 << 6 | 0x1 << 16,
opv_sqrt = 0xB << 20 | 0x3 << 6 | 0x1 << 16,
opv_cmp = 0xB << 20 | 0x1 << 6 | 0x4 << 16,
opv_cmpz = 0xB << 20 | 0x1 << 6 | 0x5 << 16
};
// Negate the operation, AND negate the immediate that we were passed in.
ALUOp ALUNeg(ALUOp op, Register dest, Imm32 *imm, Register *negDest);
bool can_dbl(ALUOp op);
bool condsAreSafe(ALUOp op);
// If there is a variant of op that has a dest (think cmp/sub)
// return that variant of it.
ALUOp getDestVariant(ALUOp op);
static const ValueOperand JSReturnOperand = ValueOperand(JSReturnReg_Type, JSReturnReg_Data);
static const ValueOperand softfpReturnOperand = ValueOperand(r1, r0);
// All of these classes exist solely to shuffle data into the various operands.
// For example Operand2 can be an imm8, a register-shifted-by-a-constant or
// a register-shifted-by-a-register. I represent this in C++ by having a
// base class Operand2, which just stores the 32 bits of data as they will be
// encoded in the instruction. You cannot directly create an Operand2
// since it is tricky, and not entirely sane to do so. Instead, you create
// one of its child classes, e.g. Imm8. Imm8's constructor takes a single
// integer argument. Imm8 will verify that its argument can be encoded
// as an ARM 12 bit imm8, encode it using an Imm8data, and finally call
// its parent's (Operand2) constructor with the Imm8data. The Operand2
// constructor will then call the Imm8data's encode() function to extract
// the raw bits from it. In the future, we should be able to extract
// data from the Operand2 by asking it for its component Imm8data
// structures. The reason this is so horribly round-about is I wanted
// to have Imm8 and RegisterShiftedRegister inherit directly from Operand2
// but have all of them take up only a single word of storage.
// I also wanted to avoid passing around raw integers at all
// since they are error prone.
class Op2Reg;
class O2RegImmShift;
class O2RegRegShift;
namespace datastore {
struct Reg
{
// the "second register"
uint32_t RM : 4;
// do we get another register for shifting
uint32_t RRS : 1;
ShiftType Type : 2;
// I'd like this to be a more sensible encoding, but that would
// need to be a struct and that would not pack :(
uint32_t ShiftAmount : 5;
uint32_t pad : 20;
Reg(uint32_t rm, ShiftType type, uint32_t rsr, uint32_t shiftamount)
: RM(rm), RRS(rsr), Type(type), ShiftAmount(shiftamount), pad(0)
{ }
uint32_t encode() {
return RM | RRS << 4 | Type << 5 | ShiftAmount << 7;
}
explicit Reg(const Op2Reg &op) {
memcpy(this, &op, sizeof(*this));
}
};
// Op2 has a mode labelled "<imm8m>", which is arm's magical
// immediate encoding. Some instructions actually get 8 bits of
// data, which is called Imm8Data below. These should have edit
// distance > 1, but this is how it is for now.
struct Imm8mData
{
private:
uint32_t data : 8;
uint32_t rot : 4;
// Throw in an extra bit that will be 1 if we can't encode this
// properly. if we can encode it properly, a simple "|" will still
// suffice to meld it into the instruction.
uint32_t buff : 19;
public:
uint32_t invalid : 1;
uint32_t encode() {
JS_ASSERT(!invalid);
return data | rot << 8;
};
// Default constructor makes an invalid immediate.
Imm8mData()
: data(0xff), rot(0xf), invalid(1)
{ }
Imm8mData(uint32_t data_, uint32_t rot_)
: data(data_), rot(rot_), invalid(0)
{
JS_ASSERT(data == data_);
JS_ASSERT(rot == rot_);
}
};
struct Imm8Data
{
private:
uint32_t imm4L : 4;
uint32_t pad : 4;
uint32_t imm4H : 4;
public:
uint32_t encode() {
return imm4L | (imm4H << 8);
};
Imm8Data(uint32_t imm) : imm4L(imm&0xf), imm4H(imm>>4) {
JS_ASSERT(imm <= 0xff);
}
};
// VLDR/VSTR take an 8 bit offset, which is implicitly left shifted
// by 2.
struct Imm8VFPOffData
{
private:
uint32_t data;
public:
uint32_t encode() {
return data;
};
Imm8VFPOffData(uint32_t imm) : data (imm) {
JS_ASSERT((imm & ~(0xff)) == 0);
}
};
// ARM can magically encode 256 very special immediates to be moved
// into a register.
struct Imm8VFPImmData
{
private:
uint32_t imm4L : 4;
uint32_t pad : 12;
uint32_t imm4H : 4;
int32_t isInvalid : 12;
public:
Imm8VFPImmData()
: imm4L(-1U & 0xf), imm4H(-1U & 0xf), isInvalid(-1)
{ }
Imm8VFPImmData(uint32_t imm)
: imm4L(imm&0xf), imm4H(imm>>4), isInvalid(0)
{
JS_ASSERT(imm <= 0xff);
}
uint32_t encode() {
if (isInvalid != 0)
return -1;
return imm4L | (imm4H << 16);
};
};
struct Imm12Data
{
uint32_t data : 12;
uint32_t encode() {
return data;
}
Imm12Data(uint32_t imm)
: data(imm)
{
JS_ASSERT(data == imm);
}
};
struct RIS
{
uint32_t ShiftAmount : 5;
uint32_t encode () {
return ShiftAmount;
}
RIS(uint32_t imm)
: ShiftAmount(imm)
{
JS_ASSERT(ShiftAmount == imm);
}
explicit RIS(Reg r) : ShiftAmount(ShiftAmount) { }
};
struct RRS
{
uint32_t MustZero : 1;
// the register that holds the shift amount
uint32_t RS : 4;
RRS(uint32_t rs)
: RS(rs)
{
JS_ASSERT(rs == RS);
}
uint32_t encode () {
return RS << 1;
}
};
} // namespace datastore
class MacroAssemblerARM;
class Operand;
class Operand2
{
friend class Operand;
friend class MacroAssemblerARM;
friend class InstALU;
public:
uint32_t oper : 31;
uint32_t invalid : 1;
bool isO2Reg() {
return !(oper & IsImmOp2);
}
Op2Reg toOp2Reg();
bool isImm8() {
return oper & IsImmOp2;
}
protected:
Operand2(datastore::Imm8mData base)
: oper(base.invalid ? -1 : (base.encode() | (uint32_t)IsImmOp2)),
invalid(base.invalid)
{ }
Operand2(datastore::Reg base)
: oper(base.encode() | (uint32_t)IsNotImmOp2)
{ }
private:
Operand2(int blob)
: oper(blob)
{ }
public:
uint32_t encode() {
return oper;
}
};
class Imm8 : public Operand2
{
public:
static datastore::Imm8mData encodeImm(uint32_t imm) {
// In gcc, clz is undefined if you call it with 0.
if (imm == 0)
return datastore::Imm8mData(0, 0);
int left = js_bitscan_clz32(imm) & 30;
// See if imm is a simple value that can be encoded with a rotate of 0.
// This is effectively imm <= 0xff, but I assume this can be optimized
// more
if (left >= 24)
return datastore::Imm8mData(imm, 0);
// Mask out the 8 bits following the first bit that we found, see if we
// have 0 yet.
int no_imm = imm & ~(0xff << (24 - left));
if (no_imm == 0) {
return datastore::Imm8mData(imm >> (24 - left), ((8+left) >> 1));
}
// Look for the most signifigant bit set, once again.
int right = 32 - (js_bitscan_clz32(no_imm) & 30);
// If it is in the bottom 8 bits, there is a chance that this is a
// wraparound case.
if (right >= 8)
return datastore::Imm8mData();
// Rather than masking out bits and checking for 0, just rotate the
// immediate that we were passed in, and see if it fits into 8 bits.
unsigned int mask = imm << (8 - right) | imm >> (24 + right);
if (mask <= 0xff)
return datastore::Imm8mData(mask, (8-right) >> 1);
return datastore::Imm8mData();
}
// pair template?
struct TwoImm8mData
{
datastore::Imm8mData fst, snd;
TwoImm8mData()
: fst(), snd()
{ }
TwoImm8mData(datastore::Imm8mData _fst, datastore::Imm8mData _snd)
: fst(_fst), snd(_snd)
{ }
};
static TwoImm8mData encodeTwoImms(uint32_t);
Imm8(uint32_t imm)
: Operand2(encodeImm(imm))
{ }
};
class Op2Reg : public Operand2
{
public:
Op2Reg(Register rm, ShiftType type, datastore::RIS shiftImm)
: Operand2(datastore::Reg(rm.code(), type, 0, shiftImm.encode()))
{ }
Op2Reg(Register rm, ShiftType type, datastore::RRS shiftReg)
: Operand2(datastore::Reg(rm.code(), type, 1, shiftReg.encode()))
{ }
bool isO2RegImmShift() {
datastore::Reg r(*this);
return !r.RRS;
}
O2RegImmShift toO2RegImmShift();
bool isO2RegRegShift() {
datastore::Reg r(*this);
return r.RRS;
}
O2RegRegShift toO2RegRegShift();
bool checkType(ShiftType type) {
datastore::Reg r(*this);
return r.Type == type;
}
bool checkRM(Register rm) {
datastore::Reg r(*this);
return r.RM == rm.code();
}
bool getRM(Register *rm) {
datastore::Reg r(*this);
*rm = Register::FromCode(r.RM);
return true;
}
};
class O2RegImmShift : public Op2Reg
{
public:
O2RegImmShift(Register rn, ShiftType type, uint32_t shift)
: Op2Reg(rn, type, datastore::RIS(shift))
{ }
int getShift() {
datastore::Reg r(*this);
datastore::RIS ris(r);
return ris.ShiftAmount;
}
};
class O2RegRegShift : public Op2Reg
{
public:
O2RegRegShift(Register rn, ShiftType type, Register rs)
: Op2Reg(rn, type, datastore::RRS(rs.code()))
{ }
};
O2RegImmShift O2Reg(Register r);
O2RegImmShift lsl (Register r, int amt);
O2RegImmShift lsr (Register r, int amt);
O2RegImmShift asr (Register r, int amt);
O2RegImmShift rol (Register r, int amt);
O2RegImmShift ror (Register r, int amt);
O2RegRegShift lsl (Register r, Register amt);
O2RegRegShift lsr (Register r, Register amt);
O2RegRegShift asr (Register r, Register amt);
O2RegRegShift ror (Register r, Register amt);
// An offset from a register to be used for ldr/str. This should include
// the sign bit, since ARM has "signed-magnitude" offsets. That is it encodes
// an unsigned offset, then the instruction specifies if the offset is positive
// or negative. The +/- bit is necessary if the instruction set wants to be
// able to have a negative register offset e.g. ldr pc, [r1,-r2];
class DtrOff
{
uint32_t data;
protected:
DtrOff(datastore::Imm12Data immdata, IsUp_ iu)
: data(immdata.encode() | (uint32_t)IsImmDTR | ((uint32_t)iu))
{ }
DtrOff(datastore::Reg reg, IsUp_ iu = IsUp)
: data(reg.encode() | (uint32_t) IsNotImmDTR | iu)
{ }
public:
uint32_t encode() { return data; }
};
class DtrOffImm : public DtrOff
{
public:
DtrOffImm(int32_t imm)
: DtrOff(datastore::Imm12Data(mozilla::Abs(imm)), imm >= 0 ? IsUp : IsDown)
{
JS_ASSERT(mozilla::Abs(imm) < 4096);
}
};
class DtrOffReg : public DtrOff
{
// These are designed to be called by a constructor of a subclass.
// Constructing the necessary RIS/RRS structures are annoying
protected:
DtrOffReg(Register rn, ShiftType type, datastore::RIS shiftImm, IsUp_ iu = IsUp)
: DtrOff(datastore::Reg(rn.code(), type, 0, shiftImm.encode()), iu)
{ }
DtrOffReg(Register rn, ShiftType type, datastore::RRS shiftReg, IsUp_ iu = IsUp)
: DtrOff(datastore::Reg(rn.code(), type, 1, shiftReg.encode()), iu)
{ }
};
class DtrRegImmShift : public DtrOffReg
{
public:
DtrRegImmShift(Register rn, ShiftType type, uint32_t shift, IsUp_ iu = IsUp)
: DtrOffReg(rn, type, datastore::RIS(shift), iu)
{ }
};
class DtrRegRegShift : public DtrOffReg
{
public:
DtrRegRegShift(Register rn, ShiftType type, Register rs, IsUp_ iu = IsUp)
: DtrOffReg(rn, type, datastore::RRS(rs.code()), iu)
{ }
};
// we will frequently want to bundle a register with its offset so that we have
// an "operand" to a load instruction.
class DTRAddr
{
uint32_t data;
public:
DTRAddr(Register reg, DtrOff dtr)
: data(dtr.encode() | (reg.code() << 16))
{ }
uint32_t encode() {
return data;
}
Register getBase() {
return Register::FromCode((data >> 16) &0xf);
}
private:
friend class Operand;
DTRAddr(uint32_t blob)
: data(blob)
{ }
};
// Offsets for the extended data transfer instructions:
// ldrsh, ldrd, ldrsb, etc.
class EDtrOff
{
uint32_t data;
protected:
EDtrOff(datastore::Imm8Data imm8, IsUp_ iu = IsUp)
: data(imm8.encode() | IsImmEDTR | (uint32_t)iu)
{ }
EDtrOff(Register rm, IsUp_ iu = IsUp)
: data(rm.code() | IsNotImmEDTR | iu)
{ }
public:
uint32_t encode() {
return data;
}
};
class EDtrOffImm : public EDtrOff
{
public:
EDtrOffImm(int32_t imm)
: EDtrOff(datastore::Imm8Data(mozilla::Abs(imm)), (imm >= 0) ? IsUp : IsDown)
{
JS_ASSERT(mozilla::Abs(imm) < 256);
}
};
// this is the most-derived class, since the extended data
// transfer instructions don't support any sort of modifying the
// "index" operand
class EDtrOffReg : public EDtrOff
{
public:
EDtrOffReg(Register rm)
: EDtrOff(rm)
{ }
};
class EDtrAddr
{
uint32_t data;
public:
EDtrAddr(Register r, EDtrOff off)
: data(RN(r) | off.encode())
{ }
uint32_t encode() {
return data;
}
};
class VFPOff
{
uint32_t data;
protected:
VFPOff(datastore::Imm8VFPOffData imm, IsUp_ isup)
: data(imm.encode() | (uint32_t)isup)
{ }
public:
uint32_t encode() {
return data;
}
};
class VFPOffImm : public VFPOff
{
public:
VFPOffImm(int32_t imm)
: VFPOff(datastore::Imm8VFPOffData(mozilla::Abs(imm) / 4), imm < 0 ? IsDown : IsUp)
{
JS_ASSERT(mozilla::Abs(imm) <= 255 * 4);
}
};
class VFPAddr
{
friend class Operand;
uint32_t data;
protected:
VFPAddr(uint32_t blob)
: data(blob)
{ }
public:
VFPAddr(Register base, VFPOff off)
: data(RN(base) | off.encode())
{ }
uint32_t encode() {
return data;
}
};
class VFPImm {
uint32_t data;
public:
VFPImm(uint32_t top);
uint32_t encode() {
return data;
}
bool isValid() {
return data != -1U;
}
};
// A BOffImm is an immediate that is used for branches. Namely, it is the offset that will
// be encoded in the branch instruction. This is the only sane way of constructing a branch.
class BOffImm
{
uint32_t data;
public:
uint32_t encode() {
return data;
}
int32_t decode() {
return ((((int32_t)data) << 8) >> 6) + 8;
}
explicit BOffImm(int offset)
: data ((offset - 8) >> 2 & 0x00ffffff)
{
JS_ASSERT((offset & 0x3) == 0);
JS_ASSERT(isInRange(offset));
}
static bool isInRange(int offset)
{
if ((offset - 8) < -33554432)
return false;
if ((offset - 8) > 33554428)
return false;
return true;
}
static const int INVALID = 0x00800000;
BOffImm()
: data(INVALID)
{ }
bool isInvalid() {
return data == uint32_t(INVALID);
}
Instruction *getDest(Instruction *src);
private:
friend class InstBranchImm;
BOffImm(Instruction &inst);
};
class Imm16
{
uint32_t lower : 12;
uint32_t pad : 4;
uint32_t upper : 4;
uint32_t invalid : 12;
public:
Imm16();
Imm16(uint32_t imm);
Imm16(Instruction &inst);
uint32_t encode() {
return lower | upper << 16;
}
uint32_t decode() {
return lower | upper << 12;
}
bool isInvalid () {
return invalid;
}
};
// FP Instructions use a different set of registers,
// with a different encoding, so this calls for a different class.
// which will be implemented later
// IIRC, this has been subsumed by vfpreg.
class FloatOp
{
uint32_t data;
};
/* I would preffer that these do not exist, since there are essentially
* no instructions that would ever take more than one of these, however,
* the MIR wants to only have one type of arguments to functions, so bugger.
*/
class Operand
{
// the encoding of registers is the same for OP2, DTR and EDTR
// yet the type system doesn't let us express this, so choices
// must be made.
public:
enum Tag_ {
OP2,
MEM,
FOP
};
private:
Tag_ Tag : 3;
uint32_t reg : 5;
int32_t offset;
uint32_t data;
public:
Operand (Register reg_)
: Tag(OP2), reg(reg_.code())
{ }
Operand (FloatRegister freg)
: Tag(FOP), reg(freg.code())
{ }
Operand (Register base, Imm32 off)
: Tag(MEM), reg(base.code()), offset(off.value)
{ }
Operand (Register base, int32_t off)
: Tag(MEM), reg(base.code()), offset(off)
{ }
Operand (const Address &addr)
: Tag(MEM), reg(addr.base.code()), offset(addr.offset)
{ }
Tag_ getTag() const {
return Tag;
}
Operand2 toOp2() const {
JS_ASSERT(Tag == OP2);
return O2Reg(Register::FromCode(reg));
}
Register toReg() const {
JS_ASSERT(Tag == OP2);
return Register::FromCode(reg);
}
void toAddr(Register *r, Imm32 *dest) const {
JS_ASSERT(Tag == MEM);
*r = Register::FromCode(reg);
*dest = Imm32(offset);
}
Address toAddress() const {
return Address(Register::FromCode(reg), offset);
}
int32_t disp() const {
JS_ASSERT(Tag == MEM);
return offset;
}
int32_t base() const {
JS_ASSERT(Tag == MEM);
return reg;
}
Register baseReg() const {
return Register::FromCode(reg);
}
DTRAddr toDTRAddr() const {
return DTRAddr(baseReg(), DtrOffImm(offset));
}
VFPAddr toVFPAddr() const {
return VFPAddr(baseReg(), VFPOffImm(offset));
}
};
void
PatchJump(CodeLocationJump &jump_, CodeLocationLabel label);
class InstructionIterator;
class Assembler;
typedef js::jit::AssemblerBufferWithConstantPool<1024, 4, Instruction, Assembler, 1> ARMBuffer;
class Assembler
{
public:
// ARM conditional constants
typedef enum {
EQ = 0x00000000, // Zero
NE = 0x10000000, // Non-zero
CS = 0x20000000,
CC = 0x30000000,
MI = 0x40000000,
PL = 0x50000000,
VS = 0x60000000,
VC = 0x70000000,
HI = 0x80000000,
LS = 0x90000000,
GE = 0xa0000000,
LT = 0xb0000000,
GT = 0xc0000000,
LE = 0xd0000000,
AL = 0xe0000000
} ARMCondition;
enum Condition {
Equal = EQ,
NotEqual = NE,
Above = HI,
AboveOrEqual = CS,
Below = CC,
BelowOrEqual = LS,
GreaterThan = GT,
GreaterThanOrEqual = GE,
LessThan = LT,
LessThanOrEqual = LE,
Overflow = VS,
Signed = MI,
Unsigned = PL,
Zero = EQ,
NonZero = NE,
Always = AL,
VFP_NotEqualOrUnordered = NE,
VFP_Equal = EQ,
VFP_Unordered = VS,
VFP_NotUnordered = VC,
VFP_GreaterThanOrEqualOrUnordered = CS,
VFP_GreaterThanOrEqual = GE,
VFP_GreaterThanOrUnordered = HI,
VFP_GreaterThan = GT,
VFP_LessThanOrEqualOrUnordered = LE,
VFP_LessThanOrEqual = LS,
VFP_LessThanOrUnordered = LT,
VFP_LessThan = CC // MI is valid too.
};
// Bit set when a DoubleCondition does not map to a single ARM condition.
// The macro assembler has to special-case these conditions, or else
// ConditionFromDoubleCondition will complain.
static const int DoubleConditionBitSpecial = 0x1;
enum DoubleCondition {
// These conditions will only evaluate to true if the comparison is ordered - i.e. neither operand is NaN.
DoubleOrdered = VFP_NotUnordered,
DoubleEqual = VFP_Equal,
DoubleNotEqual = VFP_NotEqualOrUnordered | DoubleConditionBitSpecial,
DoubleGreaterThan = VFP_GreaterThan,
DoubleGreaterThanOrEqual = VFP_GreaterThanOrEqual,
DoubleLessThan = VFP_LessThan,
DoubleLessThanOrEqual = VFP_LessThanOrEqual,
// If either operand is NaN, these conditions always evaluate to true.
DoubleUnordered = VFP_Unordered,
DoubleEqualOrUnordered = VFP_Equal | DoubleConditionBitSpecial,
DoubleNotEqualOrUnordered = VFP_NotEqualOrUnordered,
DoubleGreaterThanOrUnordered = VFP_GreaterThanOrUnordered,
DoubleGreaterThanOrEqualOrUnordered = VFP_GreaterThanOrEqualOrUnordered,
DoubleLessThanOrUnordered = VFP_LessThanOrUnordered,
DoubleLessThanOrEqualOrUnordered = VFP_LessThanOrEqualOrUnordered
};
Condition getCondition(uint32_t inst) {
return (Condition) (0xf0000000 & inst);
}
static inline Condition ConditionFromDoubleCondition(DoubleCondition cond) {
JS_ASSERT(!(cond & DoubleConditionBitSpecial));
return static_cast<Condition>(cond);
}
// :( this should be protected, but since CodeGenerator
// wants to use it, It needs to go out here :(
BufferOffset nextOffset() {
return m_buffer.nextOffset();
}
protected:
BufferOffset labelOffset (Label *l) {
return BufferOffset(l->bound());
}
Instruction * editSrc (BufferOffset bo) {
return m_buffer.getInst(bo);
}
public:
void resetCounter();
uint32_t actualOffset(uint32_t) const;
uint32_t actualIndex(uint32_t) const;
static uint8_t *PatchableJumpAddress(IonCode *code, uint32_t index);
BufferOffset actualOffset(BufferOffset) const;
protected:
// structure for fixing up pc-relative loads/jumps when a the machine code
// gets moved (executable copy, gc, etc.)
struct RelativePatch
{
// the offset within the code buffer where the value is loaded that
// we want to fix-up
BufferOffset offset;
void *target;
Relocation::Kind kind;
void fixOffset(ARMBuffer &m_buffer) {
offset = BufferOffset(offset.getOffset() + m_buffer.poolSizeBefore(offset.getOffset()));
}
RelativePatch(BufferOffset offset, void *target, Relocation::Kind kind)
: offset(offset),
target(target),
kind(kind)
{ }
};
// TODO: this should actually be a pool-like object
// It is currently a big hack, and probably shouldn't exist
js::Vector<CodeLabel, 0, SystemAllocPolicy> codeLabels_;
js::Vector<RelativePatch, 8, SystemAllocPolicy> jumps_;
js::Vector<BufferOffset, 0, SystemAllocPolicy> tmpJumpRelocations_;
js::Vector<BufferOffset, 0, SystemAllocPolicy> tmpDataRelocations_;
js::Vector<BufferOffset, 0, SystemAllocPolicy> tmpPreBarriers_;
CompactBufferWriter jumpRelocations_;
CompactBufferWriter dataRelocations_;
CompactBufferWriter relocations_;
CompactBufferWriter preBarriers_;
bool enoughMemory_;
//typedef JSC::AssemblerBufferWithConstantPool<1024, 4, 4, js::jit::Assembler> ARMBuffer;
ARMBuffer m_buffer;
// There is now a semi-unified interface for instruction generation.
// During assembly, there is an active buffer that instructions are
// being written into, but later, we may wish to modify instructions
// that have already been created. In order to do this, we call the
// same assembly function, but pass it a destination address, which
// will be overwritten with a new instruction. In order to do this very
// after assembly buffers no longer exist, when calling with a third
// dest parameter, a this object is still needed. dummy always happens
// to be null, but we shouldn't be looking at it in any case.
static Assembler *dummy;
Pool pools_[4];
Pool *int32Pool;
Pool *doublePool;
public:
Assembler()
: enoughMemory_(true),
m_buffer(4, 4, 0, &pools_[0], 8),
int32Pool(m_buffer.getPool(1)),
doublePool(m_buffer.getPool(0)),
isFinished(false),
dtmActive(false),
dtmCond(Always)
{
}
// We need to wait until an AutoIonContextAlloc is created by the
// IonMacroAssembler, before allocating any space.
void initWithAllocator() {
m_buffer.initWithAllocator();
// Set up the backwards double region
new (&pools_[2]) Pool (1024, 8, 4, 8, 8, m_buffer.LifoAlloc_, true);
// Set up the backwards 32 bit region
new (&pools_[3]) Pool (4096, 4, 4, 8, 4, m_buffer.LifoAlloc_, true, true);
// Set up the forwards double region
new (doublePool) Pool (1024, 8, 4, 8, 8, m_buffer.LifoAlloc_, false, false, &pools_[2]);
// Set up the forwards 32 bit region
new (int32Pool) Pool (4096, 4, 4, 8, 4, m_buffer.LifoAlloc_, false, true, &pools_[3]);
for (int i = 0; i < 4; i++) {
if (pools_[i].poolData == NULL) {
m_buffer.fail_oom();
return;
}
}
}
static Condition InvertCondition(Condition cond);
// MacroAssemblers hold onto gcthings, so they are traced by the GC.
void trace(JSTracer *trc);
void writeRelocation(BufferOffset src) {
tmpJumpRelocations_.append(src);
}
// As opposed to x86/x64 version, the data relocation has to be executed
// before to recover the pointer, and not after.
void writeDataRelocation(const ImmGCPtr &ptr) {
if (ptr.value)
tmpDataRelocations_.append(nextOffset());
}
void writePrebarrierOffset(CodeOffsetLabel label) {
tmpPreBarriers_.append(BufferOffset(label.offset()));
}
enum RelocBranchStyle {
B_MOVWT,
B_LDR_BX,
B_LDR,
B_MOVW_ADD
};
enum RelocStyle {
L_MOVWT,
L_LDR
};
public:
// Given the start of a Control Flow sequence, grab the value that is finally branched to
// given the start of a function that loads an address into a register get the address that
// ends up in the register.
template <class Iter>
static const uint32_t * getCF32Target(Iter *iter);
static uintptr_t getPointer(uint8_t *);
template <class Iter>
static const uint32_t * getPtr32Target(Iter *iter, Register *dest = NULL, RelocStyle *rs = NULL);
bool oom() const;
void setPrinter(Sprinter *sp) {
}
private:
bool isFinished;
public:
void finish();
void executableCopy(void *buffer);
void copyJumpRelocationTable(uint8_t *dest);
void copyDataRelocationTable(uint8_t *dest);
void copyPreBarrierTable(uint8_t *dest);
bool addCodeLabel(CodeLabel label);
// Size of the instruction stream, in bytes.
size_t size() const;
// Size of the jump relocation table, in bytes.
size_t jumpRelocationTableBytes() const;
size_t dataRelocationTableBytes() const;
size_t preBarrierTableBytes() const;
// Size of the data table, in bytes.
size_t bytesNeeded() const;
// Write a blob of binary into the instruction stream *OR*
// into a destination address. If dest is NULL (the default), then the
// instruction gets written into the instruction stream. If dest is not null
// it is interpreted as a pointer to the location that we want the
// instruction to be written.
BufferOffset writeInst(uint32_t x, uint32_t *dest = NULL);
// A static variant for the cases where we don't want to have an assembler
// object at all. Normally, you would use the dummy (NULL) object.
static void writeInstStatic(uint32_t x, uint32_t *dest);
public:
void writeCodePointer(AbsoluteLabel *label);
BufferOffset align(int alignment);
BufferOffset as_nop();
BufferOffset as_alu(Register dest, Register src1, Operand2 op2,
ALUOp op, SetCond_ sc = NoSetCond, Condition c = Always, Instruction *instdest = NULL);
BufferOffset as_mov(Register dest,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always, Instruction *instdest = NULL);
BufferOffset as_mvn(Register dest, Operand2 op2,
SetCond_ sc = NoSetCond, Condition c = Always);
// logical operations
BufferOffset as_and(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_bic(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_eor(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_orr(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
// mathematical operations
BufferOffset as_adc(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_add(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_sbc(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_sub(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_rsb(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_rsc(Register dest, Register src1,
Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always);
// test operations
BufferOffset as_cmn(Register src1, Operand2 op2,
Condition c = Always);
BufferOffset as_cmp(Register src1, Operand2 op2,
Condition c = Always);
BufferOffset as_teq(Register src1, Operand2 op2,
Condition c = Always);
BufferOffset as_tst(Register src1, Operand2 op2,
Condition c = Always);
// Not quite ALU worthy, but useful none the less:
// These also have the isue of these being formatted
// completly differently from the standard ALU operations.
BufferOffset as_movw(Register dest, Imm16 imm, Condition c = Always, Instruction *pos = NULL);
BufferOffset as_movt(Register dest, Imm16 imm, Condition c = Always, Instruction *pos = NULL);
BufferOffset as_genmul(Register d1, Register d2, Register rm, Register rn,
MULOp op, SetCond_ sc, Condition c = Always);
BufferOffset as_mul(Register dest, Register src1, Register src2,
SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_mla(Register dest, Register acc, Register src1, Register src2,
SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_umaal(Register dest1, Register dest2, Register src1, Register src2,
Condition c = Always);
BufferOffset as_mls(Register dest, Register acc, Register src1, Register src2,
Condition c = Always);
BufferOffset as_umull(Register dest1, Register dest2, Register src1, Register src2,
SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_umlal(Register dest1, Register dest2, Register src1, Register src2,
SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_smull(Register dest1, Register dest2, Register src1, Register src2,
SetCond_ sc = NoSetCond, Condition c = Always);
BufferOffset as_smlal(Register dest1, Register dest2, Register src1, Register src2,
SetCond_ sc = NoSetCond, Condition c = Always);
// Data transfer instructions: ldr, str, ldrb, strb.
// Using an int to differentiate between 8 bits and 32 bits is
// overkill, but meh
BufferOffset as_dtr(LoadStore ls, int size, Index mode,
Register rt, DTRAddr addr, Condition c = Always, uint32_t *dest = NULL);
// Handles all of the other integral data transferring functions:
// ldrsb, ldrsh, ldrd, etc.
// size is given in bits.
BufferOffset as_extdtr(LoadStore ls, int size, bool IsSigned, Index mode,
Register rt, EDtrAddr addr, Condition c = Always, uint32_t *dest = NULL);
BufferOffset as_dtm(LoadStore ls, Register rn, uint32_t mask,
DTMMode mode, DTMWriteBack wb, Condition c = Always);
//overwrite a pool entry with new data.
void as_WritePoolEntry(Instruction *addr, Condition c, uint32_t data);
// load a 32 bit immediate from a pool into a register
BufferOffset as_Imm32Pool(Register dest, uint32_t value, ARMBuffer::PoolEntry *pe = NULL, Condition c = Always);
// make a patchable jump that can target the entire 32 bit address space.
BufferOffset as_BranchPool(uint32_t value, RepatchLabel *label, ARMBuffer::PoolEntry *pe = NULL, Condition c = Always);
// load a 64 bit floating point immediate from a pool into a register
BufferOffset as_FImm64Pool(VFPRegister dest, double value, ARMBuffer::PoolEntry *pe = NULL, Condition c = Always);
// Control flow stuff:
// bx can *only* branch to a register
// never to an immediate.
BufferOffset as_bx(Register r, Condition c = Always, bool isPatchable = false);
// Branch can branch to an immediate *or* to a register.
// Branches to immediates are pc relative, branches to registers
// are absolute
BufferOffset as_b(BOffImm off, Condition c, bool isPatchable = false);
BufferOffset as_b(Label *l, Condition c = Always, bool isPatchable = false);
BufferOffset as_b(BOffImm off, Condition c, BufferOffset inst);
// blx can go to either an immediate or a register.
// When blx'ing to a register, we change processor mode
// depending on the low bit of the register
// when blx'ing to an immediate, we *always* change processor state.
BufferOffset as_blx(Label *l);
BufferOffset as_blx(Register r, Condition c = Always);
BufferOffset as_bl(BOffImm off, Condition c);
// bl can only branch+link to an immediate, never to a register
// it never changes processor state
BufferOffset as_bl();
// bl #imm can have a condition code, blx #imm cannot.
// blx reg can be conditional.
BufferOffset as_bl(Label *l, Condition c);
BufferOffset as_bl(BOffImm off, Condition c, BufferOffset inst);
BufferOffset as_mrs(Register r, Condition c = Always);
BufferOffset as_msr(Register r, Condition c = Always);
// VFP instructions!
private:
enum vfp_size {
isDouble = 1 << 8,
isSingle = 0 << 8
};
BufferOffset writeVFPInst(vfp_size sz, uint32_t blob, uint32_t *dest=NULL);
// Unityped variants: all registers hold the same (ieee754 single/double)
// notably not included are vcvt; vmov vd, #imm; vmov rt, vn.
BufferOffset as_vfp_float(VFPRegister vd, VFPRegister vn, VFPRegister vm,
VFPOp op, Condition c = Always);
public:
BufferOffset as_vadd(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c = Always);
BufferOffset as_vdiv(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c = Always);
BufferOffset as_vmul(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c = Always);
BufferOffset as_vnmul(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c = Always);
BufferOffset as_vnmla(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c = Always);
BufferOffset as_vnmls(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c = Always);
BufferOffset as_vneg(VFPRegister vd, VFPRegister vm, Condition c = Always);
BufferOffset as_vsqrt(VFPRegister vd, VFPRegister vm, Condition c = Always);
BufferOffset as_vabs(VFPRegister vd, VFPRegister vm, Condition c = Always);
BufferOffset as_vsub(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c = Always);
BufferOffset as_vcmp(VFPRegister vd, VFPRegister vm,
Condition c = Always);
BufferOffset as_vcmpz(VFPRegister vd, Condition c = Always);
// specifically, a move between two same sized-registers
BufferOffset as_vmov(VFPRegister vd, VFPRegister vsrc, Condition c = Always);
/*xfer between Core and VFP*/
enum FloatToCore_ {
FloatToCore = 1 << 20,
CoreToFloat = 0 << 20
};
private:
enum VFPXferSize {
WordTransfer = 0x02000010,
DoubleTransfer = 0x00400010
};
public:
// Unlike the next function, moving between the core registers and vfp
// registers can't be *that* properly typed. Namely, since I don't want to
// munge the type VFPRegister to also include core registers. Thus, the core
// and vfp registers are passed in based on their type, and src/dest is
// determined by the float2core.
BufferOffset as_vxfer(Register vt1, Register vt2, VFPRegister vm, FloatToCore_ f2c,
Condition c = Always, int idx = 0);
// our encoding actually allows just the src and the dest (and theiyr types)
// to uniquely specify the encoding that we are going to use.
BufferOffset as_vcvt(VFPRegister vd, VFPRegister vm, bool useFPSCR = false,
Condition c = Always);
// hard coded to a 32 bit fixed width result for now
BufferOffset as_vcvtFixed(VFPRegister vd, bool isSigned, uint32_t fixedPoint, bool toFixed, Condition c = Always);
/* xfer between VFP and memory*/
BufferOffset as_vdtr(LoadStore ls, VFPRegister vd, VFPAddr addr,
Condition c = Always /* vfp doesn't have a wb option*/,
uint32_t *dest = NULL);
// VFP's ldm/stm work differently from the standard arm ones.
// You can only transfer a range
BufferOffset as_vdtm(LoadStore st, Register rn, VFPRegister vd, int length,
/*also has update conditions*/Condition c = Always);
BufferOffset as_vimm(VFPRegister vd, VFPImm imm, Condition c = Always);
BufferOffset as_vmrs(Register r, Condition c = Always);
BufferOffset as_vmsr(Register r, Condition c = Always);
// label operations
bool nextLink(BufferOffset b, BufferOffset *next);
void bind(Label *label, BufferOffset boff = BufferOffset());
void bind(RepatchLabel *label);
uint32_t currentOffset() {
return nextOffset().getOffset();
}
void retarget(Label *label, Label *target);
// I'm going to pretend this doesn't exist for now.
void retarget(Label *label, void *target, Relocation::Kind reloc);
// void Bind(IonCode *code, AbsoluteLabel *label, const void *address);
void Bind(uint8_t *rawCode, AbsoluteLabel *label, const void *address);
void call(Label *label);
void call(void *target);
void as_bkpt();
public:
static void TraceJumpRelocations(JSTracer *trc, IonCode *code, CompactBufferReader &reader);
static void TraceDataRelocations(JSTracer *trc, IonCode *code, CompactBufferReader &reader);
protected:
void addPendingJump(BufferOffset src, void *target, Relocation::Kind kind) {
enoughMemory_ &= jumps_.append(RelativePatch(src, target, kind));
if (kind == Relocation::IONCODE)
writeRelocation(src);
}
public:
// The buffer is about to be linked, make sure any constant pools or excess
// bookkeeping has been flushed to the instruction stream.
void flush() {
JS_ASSERT(!isFinished);
m_buffer.flushPool();
return;
}
// Copy the assembly code to the given buffer, and perform any pending
// relocations relying on the target address.
void executableCopy(uint8_t *buffer);
// Actual assembly emitting functions.
// Since I can't think of a reasonable default for the mode, I'm going to
// leave it as a required argument.
void startDataTransferM(LoadStore ls, Register rm,
DTMMode mode, DTMWriteBack update = NoWriteBack,
Condition c = Always)
{
JS_ASSERT(!dtmActive);
dtmUpdate = update;
dtmBase = rm;
dtmLoadStore = ls;
dtmLastReg = -1;
dtmRegBitField = 0;
dtmActive = 1;
dtmCond = c;
dtmMode = mode;
}
void transferReg(Register rn) {
JS_ASSERT(dtmActive);
JS_ASSERT(rn.code() > dtmLastReg);
dtmRegBitField |= 1 << rn.code();
if (dtmLoadStore == IsLoad && rn.code() == 13 && dtmBase.code() == 13) {
JS_ASSERT("ARM Spec says this is invalid");
}
}
void finishDataTransfer() {
dtmActive = false;
as_dtm(dtmLoadStore, dtmBase, dtmRegBitField, dtmMode, dtmUpdate, dtmCond);
}
void startFloatTransferM(LoadStore ls, Register rm,
DTMMode mode, DTMWriteBack update = NoWriteBack,
Condition c = Always)
{
JS_ASSERT(!dtmActive);
dtmActive = true;
dtmUpdate = update;
dtmLoadStore = ls;
dtmBase = rm;
dtmCond = c;
dtmLastReg = -1;
dtmMode = mode;
dtmDelta = 0;
}
void transferFloatReg(VFPRegister rn)
{
if (dtmLastReg == -1) {
vdtmFirstReg = rn.code();
} else {
if (dtmDelta == 0) {
dtmDelta = rn.code() - dtmLastReg;
JS_ASSERT(dtmDelta == 1 || dtmDelta == -1);
}
JS_ASSERT(dtmLastReg >= 0);
JS_ASSERT(rn.code() == unsigned(dtmLastReg) + dtmDelta);
}
dtmLastReg = rn.code();
}
void finishFloatTransfer() {
JS_ASSERT(dtmActive);
dtmActive = false;
JS_ASSERT(dtmLastReg != -1);
dtmDelta = dtmDelta ? dtmDelta : 1;
// fencepost problem.
int len = dtmDelta * (dtmLastReg - vdtmFirstReg) + 1;
as_vdtm(dtmLoadStore, dtmBase,
VFPRegister(FloatRegister::FromCode(Min(vdtmFirstReg, dtmLastReg))),
len, dtmCond);
}
private:
int dtmRegBitField;
int vdtmFirstReg;
int dtmLastReg;
int dtmDelta;
Register dtmBase;
DTMWriteBack dtmUpdate;
DTMMode dtmMode;
LoadStore dtmLoadStore;
bool dtmActive;
Condition dtmCond;
public:
enum {
padForAlign8 = (int)0x00,
padForAlign16 = (int)0x0000,
padForAlign32 = (int)0xe12fff7f // 'bkpt 0xffff'
};
// API for speaking with the IonAssemblerBufferWithConstantPools
// generate an initial placeholder instruction that we want to later fix up
static void insertTokenIntoTag(uint32_t size, uint8_t *load, int32_t token);
// take the stub value that was written in before, and write in an actual load
// using the index we'd computed previously as well as the address of the pool start.
static bool patchConstantPoolLoad(void* loadAddr, void* constPoolAddr);
// this is a callback for when we have filled a pool, and MUST flush it now.
// The pool requires the assembler to place a branch past the pool, and it
// calls this function.
static uint32_t placeConstantPoolBarrier(int offset);
// END API
// move our entire pool into the instruction stream
// This is to force an opportunistic dump of the pool, prefferably when it
// is more convenient to do a dump.
void dumpPool();
void flushBuffer();
void enterNoPool();
void leaveNoPool();
// this should return a BOffImm, but I didn't want to require everyplace that used the
// AssemblerBuffer to make that class.
static ptrdiff_t getBranchOffset(const Instruction *i);
static void retargetNearBranch(Instruction *i, int offset, Condition cond, bool final = true);
static void retargetNearBranch(Instruction *i, int offset, bool final = true);
static void retargetFarBranch(Instruction *i, uint8_t **slot, uint8_t *dest, Condition cond);
static void writePoolHeader(uint8_t *start, Pool *p, bool isNatural);
static void writePoolFooter(uint8_t *start, Pool *p, bool isNatural);
static void writePoolGuard(BufferOffset branch, Instruction *inst, BufferOffset dest);
static uint32_t patchWrite_NearCallSize();
static uint32_t nopSize() { return 4; }
static void patchWrite_NearCall(CodeLocationLabel start, CodeLocationLabel toCall);
static void patchDataWithValueCheck(CodeLocationLabel label, ImmWord newValue,
ImmWord expectedValue);
static void patchWrite_Imm32(CodeLocationLabel label, Imm32 imm);
static uint32_t alignDoubleArg(uint32_t offset) {
return (offset+1)&~1;
}
static uint8_t *nextInstruction(uint8_t *instruction, uint32_t *count = NULL);
// Toggle a jmp or cmp emitted by toggledJump().
static void ToggleToJmp(CodeLocationLabel inst_);
static void ToggleToCmp(CodeLocationLabel inst_);
static void ToggleCall(CodeLocationLabel inst_, bool enabled);
static void updateBoundsCheck(uint32_t logHeapSize, Instruction *inst);
void processCodeLabels(uint8_t *rawCode);
}; // Assembler
// An Instruction is a structure for both encoding and decoding any and all ARM instructions.
// many classes have not been implemented thusfar.
class Instruction
{
uint32_t data;
protected:
// This is not for defaulting to always, this is for instructions that
// cannot be made conditional, and have the usually invalid 4b1111 cond field
Instruction (uint32_t data_, bool fake = false) : data(data_ | 0xf0000000) {
JS_ASSERT (fake || ((data_ & 0xf0000000) == 0));
}
// Standard constructor
Instruction (uint32_t data_, Assembler::Condition c) : data(data_ | (uint32_t) c) {
JS_ASSERT ((data_ & 0xf0000000) == 0);
}
// You should never create an instruction directly. You should create a
// more specific instruction which will eventually call one of these
// constructors for you.
public:
uint32_t encode() const {
return data;
}
// Check if this instruction is really a particular case
template <class C>
bool is() const { return C::isTHIS(*this); }
// safely get a more specific variant of this pointer
template <class C>
C *as() const { return C::asTHIS(*this); }
const Instruction & operator=(const Instruction &src) {
data = src.data;
return *this;
}
// Since almost all instructions have condition codes, the condition
// code extractor resides in the base class.
void extractCond(Assembler::Condition *c) {
if (data >> 28 != 0xf )
*c = (Assembler::Condition)(data & 0xf0000000);
}
// Get the next instruction in the instruction stream.
// This does neat things like ignoreconstant pools and their guards.
Instruction *next();
// Sometimes, an api wants a uint32_t (or a pointer to it) rather than
// an instruction. raw() just coerces this into a pointer to a uint32_t
const uint32_t *raw() const { return &data; }
uint32_t size() const { return 4; }
}; // Instruction
// make sure that it is the right size
JS_STATIC_ASSERT(sizeof(Instruction) == 4);
// Data Transfer Instructions
class InstDTR : public Instruction
{
public:
enum IsByte_ {
IsByte = 0x00400000,
IsWord = 0x00000000
};
static const int IsDTR = 0x04000000;
static const int IsDTRMask = 0x0c000000;
// TODO: Replace the initialization with something that is safer.
InstDTR(LoadStore ls, IsByte_ ib, Index mode, Register rt, DTRAddr addr, Assembler::Condition c)
: Instruction(ls | ib | mode | RT(rt) | addr.encode() | IsDTR, c)
{ }
static bool isTHIS(const Instruction &i);
static InstDTR *asTHIS(const Instruction &i);
};
JS_STATIC_ASSERT(sizeof(InstDTR) == sizeof(Instruction));
class InstLDR : public InstDTR
{
public:
InstLDR(Index mode, Register rt, DTRAddr addr, Assembler::Condition c)
: InstDTR(IsLoad, IsWord, mode, rt, addr, c)
{ }
static bool isTHIS(const Instruction &i);
static InstLDR *asTHIS(const Instruction &i);
};
JS_STATIC_ASSERT(sizeof(InstDTR) == sizeof(InstLDR));
class InstNOP : public Instruction
{
static const uint32_t NopInst = 0x0320f000;
public:
InstNOP()
: Instruction(NopInst, Assembler::Always)
{ }
static bool isTHIS(const Instruction &i);
static InstNOP *asTHIS(Instruction &i);
};
// Branching to a register, or calling a register
class InstBranchReg : public Instruction
{
protected:
// Don't use BranchTag yourself, use a derived instruction.
enum BranchTag {
IsBX = 0x012fff10,
IsBLX = 0x012fff30
};
static const uint32_t IsBRegMask = 0x0ffffff0;
InstBranchReg(BranchTag tag, Register rm, Assembler::Condition c)
: Instruction(tag | rm.code(), c)
{ }
public:
static bool isTHIS (const Instruction &i);
static InstBranchReg *asTHIS (const Instruction &i);
// Get the register that is being branched to
void extractDest(Register *dest);
// Make sure we are branching to a pre-known register
bool checkDest(Register dest);
};
JS_STATIC_ASSERT(sizeof(InstBranchReg) == sizeof(Instruction));
// Branching to an immediate offset, or calling an immediate offset
class InstBranchImm : public Instruction
{
protected:
enum BranchTag {
IsB = 0x0a000000,
IsBL = 0x0b000000
};
static const uint32_t IsBImmMask = 0x0f000000;
InstBranchImm(BranchTag tag, BOffImm off, Assembler::Condition c)
: Instruction(tag | off.encode(), c)
{ }
public:
static bool isTHIS (const Instruction &i);
static InstBranchImm *asTHIS (const Instruction &i);
void extractImm(BOffImm *dest);
};
JS_STATIC_ASSERT(sizeof(InstBranchImm) == sizeof(Instruction));
// Very specific branching instructions.
class InstBXReg : public InstBranchReg
{
public:
static bool isTHIS (const Instruction &i);
static InstBXReg *asTHIS (const Instruction &i);
};
class InstBLXReg : public InstBranchReg
{
public:
InstBLXReg(Register reg, Assembler::Condition c)
: InstBranchReg(IsBLX, reg, c)
{ }
static bool isTHIS (const Instruction &i);
static InstBLXReg *asTHIS (const Instruction &i);
};
class InstBImm : public InstBranchImm
{
public:
InstBImm(BOffImm off, Assembler::Condition c)
: InstBranchImm(IsB, off, c)
{ }
static bool isTHIS (const Instruction &i);
static InstBImm *asTHIS (const Instruction &i);
};
class InstBLImm : public InstBranchImm
{
public:
InstBLImm(BOffImm off, Assembler::Condition c)
: InstBranchImm(IsBL, off, c)
{ }
static bool isTHIS (const Instruction &i);
static InstBLImm *asTHIS (Instruction &i);
};
// Both movw and movt. The layout of both the immediate and the destination
// register is the same so the code is being shared.
class InstMovWT : public Instruction
{
protected:
enum WT {
IsW = 0x03000000,
IsT = 0x03400000
};
static const uint32_t IsWTMask = 0x0ff00000;
InstMovWT(Register rd, Imm16 imm, WT wt, Assembler::Condition c)
: Instruction (RD(rd) | imm.encode() | wt, c)
{ }
public:
void extractImm(Imm16 *dest);
void extractDest(Register *dest);
bool checkImm(Imm16 dest);
bool checkDest(Register dest);
static bool isTHIS (Instruction &i);
static InstMovWT *asTHIS (Instruction &i);
};
JS_STATIC_ASSERT(sizeof(InstMovWT) == sizeof(Instruction));
class InstMovW : public InstMovWT
{
public:
InstMovW (Register rd, Imm16 imm, Assembler::Condition c)
: InstMovWT(rd, imm, IsW, c)
{ }
static bool isTHIS (const Instruction &i);
static InstMovW *asTHIS (const Instruction &i);
};
class InstMovT : public InstMovWT
{
public:
InstMovT (Register rd, Imm16 imm, Assembler::Condition c)
: InstMovWT(rd, imm, IsT, c)
{ }
static bool isTHIS (const Instruction &i);
static InstMovT *asTHIS (const Instruction &i);
};
class InstALU : public Instruction
{
static const int32_t ALUMask = 0xc << 24;
public:
InstALU (Register rd, Register rn, Operand2 op2, ALUOp op, SetCond_ sc, Assembler::Condition c)
: Instruction(maybeRD(rd) | maybeRN(rn) | op2.encode() | op | sc, c)
{ }
static bool isTHIS (const Instruction &i);
static InstALU *asTHIS (const Instruction &i);
void extractOp(ALUOp *ret);
bool checkOp(ALUOp op);
void extractDest(Register *ret);
bool checkDest(Register rd);
void extractOp1(Register *ret);
bool checkOp1(Register rn);
Operand2 extractOp2();
};
class InstCMP : public InstALU
{
public:
static bool isTHIS (const Instruction &i);
static InstCMP *asTHIS (const Instruction &i);
};
class InstMOV : public InstALU
{
public:
static bool isTHIS (const Instruction &i);
static InstMOV *asTHIS (const Instruction &i);
};
class InstructionIterator {
private:
Instruction *i;
public:
InstructionIterator(Instruction *i_);
Instruction *next() {
i = i->next();
return cur();
}
Instruction *cur() const {
return i;
}
};
static const uint32_t NumIntArgRegs = 4;
static const uint32_t NumFloatArgRegs = 8;
#ifdef JS_CPU_ARM_HARDFP
static inline bool
GetIntArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register *out)
{
if (usedIntArgs >= NumIntArgRegs)
return false;
*out = Register::FromCode(usedIntArgs);
return true;
}
// Get a register in which we plan to put a quantity that will be used as an
// integer argument. This differs from GetIntArgReg in that if we have no more
// actual argument registers to use we will fall back on using whatever
// CallTempReg* don't overlap the argument registers, and only fail once those
// run out too.
static inline bool
GetTempRegForIntArg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register *out)
{
if (GetIntArgReg(usedIntArgs, usedFloatArgs, out))
return true;
// Unfortunately, we have to assume things about the point at which
// GetIntArgReg returns false, because we need to know how many registers it
// can allocate.
usedIntArgs -= NumIntArgRegs;
if (usedIntArgs >= NumCallTempNonArgRegs)
return false;
*out = CallTempNonArgRegs[usedIntArgs];
return true;
}
static inline bool
GetFloatArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, FloatRegister *out)
{
if (usedFloatArgs >= NumFloatArgRegs)
return false;
*out = FloatRegister::FromCode(usedFloatArgs);
return true;
}
static inline uint32_t
GetIntArgStackDisp(uint32_t usedIntArgs, uint32_t usedFloatArgs, uint32_t *padding)
{
JS_ASSERT(usedIntArgs >= NumIntArgRegs);
uint32_t doubleSlots = Max(0, (int32_t)usedFloatArgs - (int32_t)NumFloatArgRegs);
doubleSlots *= 2;
int intSlots = usedIntArgs - NumIntArgRegs;
return (intSlots + doubleSlots + *padding) * STACK_SLOT_SIZE;
}
static inline uint32_t
GetFloatArgStackDisp(uint32_t usedIntArgs, uint32_t usedFloatArgs, uint32_t *padding)
{
JS_ASSERT(usedFloatArgs >= NumFloatArgRegs);
uint32_t intSlots = 0;
if (usedIntArgs > NumIntArgRegs) {
intSlots = usedIntArgs - NumIntArgRegs;
// update the amount of padding required.
*padding += (*padding + usedIntArgs) % 2;
}
uint32_t doubleSlots = usedFloatArgs - NumFloatArgRegs;
doubleSlots *= 2;
return (intSlots + doubleSlots + *padding) * STACK_SLOT_SIZE;
}
#else
static inline bool
GetIntArgReg(uint32_t arg, uint32_t floatArg, Register *out)
{
if (arg < NumIntArgRegs) {
*out = Register::FromCode(arg);
return true;
}
return false;
}
// Get a register in which we plan to put a quantity that will be used as an
// integer argument. This differs from GetIntArgReg in that if we have no more
// actual argument registers to use we will fall back on using whatever
// CallTempReg* don't overlap the argument registers, and only fail once those
// run out too.
static inline bool
GetTempRegForIntArg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register *out)
{
if (GetIntArgReg(usedIntArgs, usedFloatArgs, out))
return true;
// Unfortunately, we have to assume things about the point at which
// GetIntArgReg returns false, because we need to know how many registers it
// can allocate.
usedIntArgs -= NumIntArgRegs;
if (usedIntArgs >= NumCallTempNonArgRegs)
return false;
*out = CallTempNonArgRegs[usedIntArgs];
return true;
}
static inline uint32_t
GetArgStackDisp(uint32_t arg)
{
JS_ASSERT(arg >= NumIntArgRegs);
return (arg - NumIntArgRegs) * STACK_SLOT_SIZE;
}
#endif
class DoubleEncoder {
uint32_t rep(bool b, uint32_t count) {
uint32_t ret = 0;
for (uint32_t i = 0; i < count; i++)
ret = (ret << 1) | b;
return ret;
}
uint32_t encode(uint8_t value) {
//ARM ARM "VFP modified immediate constants"
// aBbbbbbb bbcdefgh 000...
// we want to return the top 32 bits of the double
// the rest are 0.
bool a = value >> 7;
bool b = value >> 6 & 1;
bool B = !b;
uint32_t cdefgh = value & 0x3f;
return a << 31 |
B << 30 |
rep(b, 8) << 22 |
cdefgh << 16;
}
struct DoubleEntry
{
uint32_t dblTop;
datastore::Imm8VFPImmData data;
DoubleEntry()
: dblTop(-1)
{ }
DoubleEntry(uint32_t dblTop_, datastore::Imm8VFPImmData data_)
: dblTop(dblTop_), data(data_)
{ }
};
DoubleEntry table [256];
// grumble singleton, grumble
static DoubleEncoder _this;
DoubleEncoder()
{
for (int i = 0; i < 256; i++) {
table[i] = DoubleEntry(encode(i), datastore::Imm8VFPImmData(i));
}
}
public:
static bool lookup(uint32_t top, datastore::Imm8VFPImmData *ret) {
for (int i = 0; i < 256; i++) {
if (_this.table[i].dblTop == top) {
*ret = _this.table[i].data;
return true;
}
}
return false;
}
};
class AutoForbidPools {
Assembler *masm_;
public:
AutoForbidPools(Assembler *masm) : masm_(masm) {
masm_->enterNoPool();
}
~AutoForbidPools() {
masm_->leaveNoPool();
}
};
} // namespace jit
} // namespace js
#endif /* jit_arm_Assembler_arm_h */