2432 lines
77 KiB
C++
2432 lines
77 KiB
C++
/* -*- 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/ArrayUtils.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/MathAlgorithms.h"
|
|
|
|
#include "jit/arm/Architecture-arm.h"
|
|
#include "jit/CompactBuffer.h"
|
|
#include "jit/IonCode.h"
|
|
#include "jit/JitCompartment.h"
|
|
#include "jit/shared/Assembler-shared.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 constexpr Register r0 = { Registers::r0 };
|
|
static constexpr Register r1 = { Registers::r1 };
|
|
static constexpr Register r2 = { Registers::r2 };
|
|
static constexpr Register r3 = { Registers::r3 };
|
|
static constexpr Register r4 = { Registers::r4 };
|
|
static constexpr Register r5 = { Registers::r5 };
|
|
static constexpr Register r6 = { Registers::r6 };
|
|
static constexpr Register r7 = { Registers::r7 };
|
|
static constexpr Register r8 = { Registers::r8 };
|
|
static constexpr Register r9 = { Registers::r9 };
|
|
static constexpr Register r10 = { Registers::r10 };
|
|
static constexpr Register r11 = { Registers::r11 };
|
|
static constexpr Register r12 = { Registers::ip };
|
|
static constexpr Register ip = { Registers::ip };
|
|
static constexpr Register sp = { Registers::sp };
|
|
static constexpr Register r14 = { Registers::lr };
|
|
static constexpr Register lr = { Registers::lr };
|
|
static constexpr Register pc = { Registers::pc };
|
|
|
|
static constexpr Register ScratchRegister = {Registers::ip};
|
|
|
|
// Helper class for ScratchRegister usage. Asserts that only one piece
|
|
// of code thinks it has exclusive ownership of the scratch register.
|
|
struct ScratchRegisterScope : public AutoRegisterScope
|
|
{
|
|
explicit ScratchRegisterScope(MacroAssembler& masm)
|
|
: AutoRegisterScope(masm, ScratchRegister)
|
|
{ }
|
|
};
|
|
|
|
struct SecondScratchRegisterScope : public AutoRegisterScope
|
|
{
|
|
explicit SecondScratchRegisterScope(MacroAssembler& masm);
|
|
};
|
|
|
|
static constexpr Register OsrFrameReg = r3;
|
|
static constexpr Register ArgumentsRectifierReg = r8;
|
|
static constexpr Register CallTempReg0 = r5;
|
|
static constexpr Register CallTempReg1 = r6;
|
|
static constexpr Register CallTempReg2 = r7;
|
|
static constexpr Register CallTempReg3 = r8;
|
|
static constexpr Register CallTempReg4 = r0;
|
|
static constexpr Register CallTempReg5 = r1;
|
|
|
|
static constexpr Register IntArgReg0 = r0;
|
|
static constexpr Register IntArgReg1 = r1;
|
|
static constexpr Register IntArgReg2 = r2;
|
|
static constexpr Register IntArgReg3 = r3;
|
|
static constexpr Register GlobalReg = r10;
|
|
static constexpr Register HeapReg = r11;
|
|
static constexpr Register CallTempNonArgRegs[] = { r5, r6, r7, r8 };
|
|
static const uint32_t NumCallTempNonArgRegs =
|
|
mozilla::ArrayLength(CallTempNonArgRegs);
|
|
|
|
class ABIArgGenerator
|
|
{
|
|
unsigned intRegIndex_;
|
|
unsigned floatRegIndex_;
|
|
uint32_t stackOffset_;
|
|
ABIArg current_;
|
|
|
|
// ARM can either use HardFp (use float registers for float arguments), or
|
|
// SoftFp (use general registers for float arguments) ABI. We keep this
|
|
// switch as a runtime switch because wasm always use the HardFp back-end
|
|
// while the calls to native functions have to use the one provided by the
|
|
// system.
|
|
bool useHardFp_;
|
|
|
|
ABIArg softNext(MIRType argType);
|
|
ABIArg hardNext(MIRType argType);
|
|
|
|
public:
|
|
ABIArgGenerator();
|
|
|
|
void setUseHardFp(bool useHardFp) {
|
|
MOZ_ASSERT(intRegIndex_ == 0 && floatRegIndex_ == 0);
|
|
useHardFp_ = useHardFp;
|
|
}
|
|
ABIArg next(MIRType argType);
|
|
ABIArg& current() { return current_; }
|
|
uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
|
|
};
|
|
|
|
bool IsUnaligned(const wasm::MemoryAccessDesc& access);
|
|
|
|
static constexpr Register ABINonArgReg0 = r4;
|
|
static constexpr Register ABINonArgReg1 = r5;
|
|
static constexpr Register ABINonArgReg2 = r6;
|
|
static constexpr Register ABINonArgReturnReg0 = r4;
|
|
static constexpr Register ABINonArgReturnReg1 = r5;
|
|
|
|
// TLS pointer argument register for WebAssembly functions. This must not alias
|
|
// any other register used for passing function arguments or return values.
|
|
// Preserved by WebAssembly functions.
|
|
static constexpr Register WasmTlsReg = r9;
|
|
|
|
// Registers used for wasm table calls. These registers must be disjoint
|
|
// from the ABI argument registers, WasmTlsReg and each other.
|
|
static constexpr Register WasmTableCallScratchReg = ABINonArgReg0;
|
|
static constexpr Register WasmTableCallSigReg = ABINonArgReg1;
|
|
static constexpr Register WasmTableCallIndexReg = ABINonArgReg2;
|
|
|
|
static constexpr Register PreBarrierReg = r1;
|
|
|
|
static constexpr Register InvalidReg = { Registers::invalid_reg };
|
|
static constexpr FloatRegister InvalidFloatReg;
|
|
|
|
static constexpr Register JSReturnReg_Type = r3;
|
|
static constexpr Register JSReturnReg_Data = r2;
|
|
static constexpr Register StackPointer = sp;
|
|
static constexpr Register FramePointer = InvalidReg;
|
|
static constexpr Register ReturnReg = r0;
|
|
static constexpr Register64 ReturnReg64(r1, r0);
|
|
static constexpr FloatRegister ReturnFloat32Reg = { FloatRegisters::d0, VFPRegister::Single };
|
|
static constexpr FloatRegister ReturnDoubleReg = { FloatRegisters::d0, VFPRegister::Double};
|
|
static constexpr FloatRegister ReturnSimd128Reg = InvalidFloatReg;
|
|
static constexpr FloatRegister ScratchFloat32Reg = { FloatRegisters::d30, VFPRegister::Single };
|
|
static constexpr FloatRegister ScratchDoubleReg = { FloatRegisters::d15, VFPRegister::Double };
|
|
static constexpr FloatRegister ScratchSimd128Reg = InvalidFloatReg;
|
|
static constexpr FloatRegister ScratchUIntReg = { FloatRegisters::d15, VFPRegister::UInt };
|
|
static constexpr FloatRegister ScratchIntReg = { FloatRegisters::d15, VFPRegister::Int };
|
|
|
|
struct ScratchFloat32Scope : public AutoFloatRegisterScope
|
|
{
|
|
explicit ScratchFloat32Scope(MacroAssembler& masm)
|
|
: AutoFloatRegisterScope(masm, ScratchFloat32Reg)
|
|
{ }
|
|
};
|
|
struct ScratchDoubleScope : public AutoFloatRegisterScope
|
|
{
|
|
explicit ScratchDoubleScope(MacroAssembler& masm)
|
|
: AutoFloatRegisterScope(masm, ScratchDoubleReg)
|
|
{ }
|
|
};
|
|
|
|
// A bias applied to the GlobalReg to allow the use of instructions with small
|
|
// negative immediate offsets which doubles the range of global data that can be
|
|
// accessed with a single instruction.
|
|
static const int32_t WasmGlobalRegBias = 1024;
|
|
|
|
// Registers used in the GenerateFFIIonExit Enable Activation block.
|
|
static constexpr Register WasmIonExitRegCallee = r4;
|
|
static constexpr Register WasmIonExitRegE0 = r0;
|
|
static constexpr Register WasmIonExitRegE1 = r1;
|
|
|
|
// Registers used in the GenerateFFIIonExit Disable Activation block.
|
|
// None of these may be the second scratch register (lr).
|
|
static constexpr Register WasmIonExitRegReturnData = r2;
|
|
static constexpr Register WasmIonExitRegReturnType = r3;
|
|
static constexpr Register WasmIonExitRegD0 = r0;
|
|
static constexpr Register WasmIonExitRegD1 = r1;
|
|
static constexpr Register WasmIonExitRegD2 = r4;
|
|
|
|
// Registerd used in RegExpMatcher instruction (do not use JSReturnOperand).
|
|
static constexpr Register RegExpMatcherRegExpReg = CallTempReg0;
|
|
static constexpr Register RegExpMatcherStringReg = CallTempReg1;
|
|
static constexpr Register RegExpMatcherLastIndexReg = CallTempReg2;
|
|
|
|
// Registerd used in RegExpTester instruction (do not use ReturnReg).
|
|
static constexpr Register RegExpTesterRegExpReg = CallTempReg0;
|
|
static constexpr Register RegExpTesterStringReg = CallTempReg1;
|
|
static constexpr Register RegExpTesterLastIndexReg = CallTempReg2;
|
|
|
|
static constexpr FloatRegister d0 = {FloatRegisters::d0, VFPRegister::Double};
|
|
static constexpr FloatRegister d1 = {FloatRegisters::d1, VFPRegister::Double};
|
|
static constexpr FloatRegister d2 = {FloatRegisters::d2, VFPRegister::Double};
|
|
static constexpr FloatRegister d3 = {FloatRegisters::d3, VFPRegister::Double};
|
|
static constexpr FloatRegister d4 = {FloatRegisters::d4, VFPRegister::Double};
|
|
static constexpr FloatRegister d5 = {FloatRegisters::d5, VFPRegister::Double};
|
|
static constexpr FloatRegister d6 = {FloatRegisters::d6, VFPRegister::Double};
|
|
static constexpr FloatRegister d7 = {FloatRegisters::d7, VFPRegister::Double};
|
|
static constexpr FloatRegister d8 = {FloatRegisters::d8, VFPRegister::Double};
|
|
static constexpr FloatRegister d9 = {FloatRegisters::d9, VFPRegister::Double};
|
|
static constexpr FloatRegister d10 = {FloatRegisters::d10, VFPRegister::Double};
|
|
static constexpr FloatRegister d11 = {FloatRegisters::d11, VFPRegister::Double};
|
|
static constexpr FloatRegister d12 = {FloatRegisters::d12, VFPRegister::Double};
|
|
static constexpr FloatRegister d13 = {FloatRegisters::d13, VFPRegister::Double};
|
|
static constexpr FloatRegister d14 = {FloatRegisters::d14, VFPRegister::Double};
|
|
static constexpr FloatRegister d15 = {FloatRegisters::d15, VFPRegister::Double};
|
|
|
|
|
|
// 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 constexpr uint32_t ABIStackAlignment = 8;
|
|
static constexpr uint32_t CodeAlignment = 8;
|
|
static constexpr uint32_t JitStackAlignment = 8;
|
|
|
|
static constexpr uint32_t JitStackValueAlignment = JitStackAlignment / sizeof(Value);
|
|
static_assert(JitStackAlignment % sizeof(Value) == 0 && JitStackValueAlignment >= 1,
|
|
"Stack alignment should be a non-zero multiple of sizeof(Value)");
|
|
|
|
// This boolean indicates whether we support SIMD instructions flavoured for
|
|
// this architecture or not. Rather than a method in the LIRGenerator, it is
|
|
// here such that it is accessible from the entire codebase. Once full support
|
|
// for SIMD is reached on all tier-1 platforms, this constant can be deleted.
|
|
static constexpr bool SupportsSimd = false;
|
|
static constexpr uint32_t SimdMemoryAlignment = 8;
|
|
|
|
static_assert(CodeAlignment % SimdMemoryAlignment == 0,
|
|
"Code alignment should be larger than any of the alignments which are used for "
|
|
"the constant sections of the code buffer. Thus it should be larger than the "
|
|
"alignment for SIMD constants.");
|
|
|
|
static_assert(JitStackAlignment % SimdMemoryAlignment == 0,
|
|
"Stack alignment should be larger than any of the alignments which are used for "
|
|
"spilled values. Thus it should be larger than the alignment for SIMD accesses.");
|
|
|
|
static const uint32_t WasmStackAlignment = SimdMemoryAlignment;
|
|
|
|
// Does this architecture support SIMD conversions between Uint32x4 and Float32x4?
|
|
static constexpr bool SupportsUint32x4FloatConversions = false;
|
|
|
|
// Does this architecture support comparisons of unsigned integer vectors?
|
|
static constexpr bool SupportsUint8x16Compares = false;
|
|
static constexpr bool SupportsUint16x8Compares = false;
|
|
static constexpr bool SupportsUint32x4Compares = false;
|
|
|
|
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);
|
|
|
|
// For being passed into the generic vfp instruction generator when there is an
|
|
// instruction that only takes two registers.
|
|
static constexpr VFPRegister NoVFPRegister(VFPRegister::Double, 0, false, true);
|
|
|
|
struct ImmTag : public Imm32
|
|
{
|
|
explicit ImmTag(JSValueTag mask)
|
|
: Imm32(int32_t(mask))
|
|
{ }
|
|
};
|
|
|
|
struct ImmType : public ImmTag
|
|
{
|
|
explicit 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.
|
|
};
|
|
|
|
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.
|
|
};
|
|
|
|
// 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
|
|
};
|
|
|
|
// Condition code updating mode.
|
|
enum SBit {
|
|
SetCC = 1 << 20, // Set condition code.
|
|
LeaveCC = 0 << 20 // Leave condition code unchanged.
|
|
};
|
|
|
|
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 {
|
|
OpMov = 0xd << 21,
|
|
OpMvn = 0xf << 21,
|
|
OpAnd = 0x0 << 21,
|
|
OpBic = 0xe << 21,
|
|
OpEor = 0x1 << 21,
|
|
OpOrr = 0xc << 21,
|
|
OpAdc = 0x5 << 21,
|
|
OpAdd = 0x4 << 21,
|
|
OpSbc = 0x6 << 21,
|
|
OpSub = 0x2 << 21,
|
|
OpRsb = 0x3 << 21,
|
|
OpRsc = 0x7 << 21,
|
|
OpCmn = 0xb << 21,
|
|
OpCmp = 0xa << 21,
|
|
OpTeq = 0x9 << 21,
|
|
OpTst = 0x8 << 21,
|
|
OpInvalid = -1
|
|
};
|
|
|
|
|
|
enum MULOp {
|
|
OpmMul = 0 << 21,
|
|
OpmMla = 1 << 21,
|
|
OpmUmaal = 2 << 21,
|
|
OpmMls = 3 << 21,
|
|
OpmUmull = 4 << 21,
|
|
OpmUmlal = 5 << 21,
|
|
OpmSmull = 6 << 21,
|
|
OpmSmlal = 7 << 21
|
|
};
|
|
enum BranchTag {
|
|
OpB = 0x0a000000,
|
|
OpBMask = 0x0f000000,
|
|
OpBDestMask = 0x00ffffff,
|
|
OpBl = 0x0b000000,
|
|
OpBlx = 0x012fff30,
|
|
OpBx = 0x012fff10
|
|
};
|
|
|
|
// Just like ALUOp, but for the vfp instruction set.
|
|
enum VFPOp {
|
|
OpvMul = 0x2 << 20,
|
|
OpvAdd = 0x3 << 20,
|
|
OpvSub = 0x3 << 20 | 0x1 << 6,
|
|
OpvDiv = 0x8 << 20,
|
|
OpvMov = 0xB << 20 | 0x1 << 6,
|
|
OpvAbs = 0xB << 20 | 0x3 << 6,
|
|
OpvNeg = 0xB << 20 | 0x1 << 6 | 0x1 << 16,
|
|
OpvSqrt = 0xB << 20 | 0x3 << 6 | 0x1 << 16,
|
|
OpvCmp = 0xB << 20 | 0x1 << 6 | 0x4 << 16,
|
|
OpvCmpz = 0xB << 20 | 0x1 << 6 | 0x5 << 16
|
|
};
|
|
|
|
// Negate the operation, AND negate the immediate that we were passed in.
|
|
ALUOp ALUNeg(ALUOp op, Register dest, Register scratch, 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. We 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 we wanted to have Imm8 and RegisterShiftedRegister inherit
|
|
// directly from Operand2 but have all of them take up only a single word of
|
|
// storage. We also wanted to avoid passing around raw integers at all since
|
|
// they are error prone.
|
|
class Op2Reg;
|
|
class O2RegImmShift;
|
|
class O2RegRegShift;
|
|
|
|
namespace datastore {
|
|
|
|
class Reg
|
|
{
|
|
// The "second register".
|
|
uint32_t rm_ : 4;
|
|
// Do we get another register for shifting.
|
|
bool rrs_ : 1;
|
|
ShiftType type_ : 2;
|
|
// We'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;
|
|
|
|
public:
|
|
Reg(uint32_t rm, ShiftType type, uint32_t rsr, uint32_t shiftAmount)
|
|
: rm_(rm), rrs_(rsr), type_(type), shiftAmount_(shiftAmount), pad_(0)
|
|
{ }
|
|
explicit Reg(const Op2Reg& op) {
|
|
memcpy(this, &op, sizeof(*this));
|
|
}
|
|
|
|
uint32_t shiftAmount() const {
|
|
return shiftAmount_;
|
|
}
|
|
|
|
uint32_t encode() const {
|
|
return rm_ | (rrs_ << 4) | (type_ << 5) | (shiftAmount_ << 7);
|
|
}
|
|
};
|
|
|
|
// 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.
|
|
class Imm8mData
|
|
{
|
|
uint32_t data_ : 8;
|
|
uint32_t rot_ : 4;
|
|
uint32_t buff_ : 19;
|
|
|
|
// 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.
|
|
bool invalid_ : 1;
|
|
|
|
public:
|
|
// Default constructor makes an invalid immediate.
|
|
Imm8mData()
|
|
: data_(0xff), rot_(0xf), buff_(0), invalid_(true)
|
|
{ }
|
|
|
|
Imm8mData(uint32_t data, uint32_t rot)
|
|
: data_(data), rot_(rot), buff_(0), invalid_(false)
|
|
{
|
|
MOZ_ASSERT(data == data_);
|
|
MOZ_ASSERT(rot == rot_);
|
|
}
|
|
|
|
bool invalid() const { return invalid_; }
|
|
|
|
uint32_t encode() const {
|
|
MOZ_ASSERT(!invalid_);
|
|
return data_ | (rot_ << 8);
|
|
};
|
|
};
|
|
|
|
class Imm8Data
|
|
{
|
|
uint32_t imm4L_ : 4;
|
|
uint32_t pad_ : 4;
|
|
uint32_t imm4H_ : 4;
|
|
|
|
public:
|
|
explicit Imm8Data(uint32_t imm)
|
|
: imm4L_(imm & 0xf), imm4H_(imm >> 4)
|
|
{
|
|
MOZ_ASSERT(imm <= 0xff);
|
|
}
|
|
|
|
uint32_t encode() const {
|
|
return imm4L_ | (imm4H_ << 8);
|
|
};
|
|
};
|
|
|
|
// VLDR/VSTR take an 8 bit offset, which is implicitly left shifted by 2.
|
|
class Imm8VFPOffData
|
|
{
|
|
uint32_t data_;
|
|
|
|
public:
|
|
explicit Imm8VFPOffData(uint32_t imm)
|
|
: data_(imm)
|
|
{
|
|
MOZ_ASSERT((imm & ~(0xff)) == 0);
|
|
}
|
|
uint32_t encode() const {
|
|
return data_;
|
|
};
|
|
};
|
|
|
|
// ARM can magically encode 256 very special immediates to be moved into a
|
|
// register.
|
|
struct Imm8VFPImmData
|
|
{
|
|
// This structure's members are public and it has no constructor to
|
|
// initialize them, for a very special reason. Were this structure to
|
|
// have a constructor, the initialization for DoubleEncoder's internal
|
|
// table (see below) would require a rather large static constructor on
|
|
// some of our supported compilers. The known solution to this is to mark
|
|
// the constructor constexpr, but, again, some of our supported
|
|
// compilers don't support constexpr! So we are reduced to public
|
|
// members and eschewing a constructor in hopes that the initialization
|
|
// of DoubleEncoder's table is correct.
|
|
uint32_t imm4L : 4;
|
|
uint32_t imm4H : 4;
|
|
int32_t isInvalid : 24;
|
|
|
|
uint32_t encode() const {
|
|
// This assert is an attempting at ensuring that we don't create random
|
|
// instances of this structure and then asking to encode() it.
|
|
MOZ_ASSERT(isInvalid == 0);
|
|
return imm4L | (imm4H << 16);
|
|
};
|
|
};
|
|
|
|
class Imm12Data
|
|
{
|
|
uint32_t data_ : 12;
|
|
|
|
public:
|
|
explicit Imm12Data(uint32_t imm)
|
|
: data_(imm)
|
|
{
|
|
MOZ_ASSERT(data_ == imm);
|
|
}
|
|
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
};
|
|
|
|
class RIS
|
|
{
|
|
uint32_t shiftAmount_ : 5;
|
|
|
|
public:
|
|
explicit RIS(uint32_t imm)
|
|
: shiftAmount_(imm)
|
|
{
|
|
MOZ_ASSERT(shiftAmount_ == imm);
|
|
}
|
|
|
|
explicit RIS(Reg r)
|
|
: shiftAmount_(r.shiftAmount())
|
|
{ }
|
|
|
|
uint32_t encode() const {
|
|
return shiftAmount_;
|
|
}
|
|
};
|
|
|
|
class RRS
|
|
{
|
|
bool mustZero_ : 1;
|
|
// The register that holds the shift amount.
|
|
uint32_t rs_ : 4;
|
|
|
|
public:
|
|
explicit RRS(uint32_t rs)
|
|
: rs_(rs)
|
|
{
|
|
MOZ_ASSERT(rs_ == rs);
|
|
}
|
|
|
|
uint32_t encode() const {
|
|
return rs_ << 1;
|
|
}
|
|
};
|
|
|
|
} // namespace datastore
|
|
|
|
class MacroAssemblerARM;
|
|
class Operand;
|
|
|
|
class Operand2
|
|
{
|
|
friend class Operand;
|
|
friend class MacroAssemblerARM;
|
|
friend class InstALU;
|
|
|
|
uint32_t oper_ : 31;
|
|
bool invalid_ : 1;
|
|
|
|
protected:
|
|
explicit Operand2(datastore::Imm8mData base)
|
|
: oper_(base.invalid() ? -1 : (base.encode() | uint32_t(IsImmOp2))),
|
|
invalid_(base.invalid())
|
|
{ }
|
|
|
|
explicit Operand2(datastore::Reg base)
|
|
: oper_(base.encode() | uint32_t(IsNotImmOp2)),
|
|
invalid_(false)
|
|
{ }
|
|
|
|
private:
|
|
explicit Operand2(uint32_t blob)
|
|
: oper_(blob),
|
|
invalid_(false)
|
|
{ }
|
|
|
|
public:
|
|
bool isO2Reg() const {
|
|
return !(oper_ & IsImmOp2);
|
|
}
|
|
|
|
Op2Reg toOp2Reg() const;
|
|
|
|
bool isImm8() const {
|
|
return oper_ & IsImmOp2;
|
|
}
|
|
|
|
bool invalid() const {
|
|
return invalid_;
|
|
}
|
|
|
|
uint32_t encode() const {
|
|
return oper_;
|
|
}
|
|
};
|
|
|
|
class Imm8 : public Operand2
|
|
{
|
|
public:
|
|
explicit Imm8(uint32_t imm)
|
|
: Operand2(EncodeImm(imm))
|
|
{ }
|
|
|
|
static datastore::Imm8mData EncodeImm(uint32_t imm) {
|
|
// RotateLeft below may not be called with a shift of zero.
|
|
if (imm <= 0xFF)
|
|
return datastore::Imm8mData(imm, 0);
|
|
|
|
// An encodable integer has a maximum of 8 contiguous set bits,
|
|
// with an optional wrapped left rotation to even bit positions.
|
|
for (int rot = 1; rot < 16; rot++) {
|
|
uint32_t rotimm = mozilla::RotateLeft(imm, rot * 2);
|
|
if (rotimm <= 0xFF)
|
|
return datastore::Imm8mData(rotimm, rot);
|
|
}
|
|
return datastore::Imm8mData();
|
|
}
|
|
|
|
// Pair template?
|
|
struct TwoImm8mData
|
|
{
|
|
datastore::Imm8mData fst_, snd_;
|
|
|
|
TwoImm8mData() = default;
|
|
|
|
TwoImm8mData(datastore::Imm8mData fst, datastore::Imm8mData snd)
|
|
: fst_(fst), snd_(snd)
|
|
{ }
|
|
|
|
datastore::Imm8mData fst() const { return fst_; }
|
|
datastore::Imm8mData snd() const { return snd_; }
|
|
};
|
|
|
|
static TwoImm8mData EncodeTwoImms(uint32_t);
|
|
};
|
|
|
|
class Op2Reg : public Operand2
|
|
{
|
|
public:
|
|
explicit Op2Reg(Register rm, ShiftType type, datastore::RIS shiftImm)
|
|
: Operand2(datastore::Reg(rm.code(), type, 0, shiftImm.encode()))
|
|
{ }
|
|
|
|
explicit Op2Reg(Register rm, ShiftType type, datastore::RRS shiftReg)
|
|
: Operand2(datastore::Reg(rm.code(), type, 1, shiftReg.encode()))
|
|
{ }
|
|
};
|
|
|
|
static_assert(sizeof(Op2Reg) == sizeof(datastore::Reg),
|
|
"datastore::Reg(const Op2Reg&) constructor relies on Reg/Op2Reg having same size");
|
|
|
|
class O2RegImmShift : public Op2Reg
|
|
{
|
|
public:
|
|
explicit O2RegImmShift(Register rn, ShiftType type, uint32_t shift)
|
|
: Op2Reg(rn, type, datastore::RIS(shift))
|
|
{ }
|
|
};
|
|
|
|
class O2RegRegShift : public Op2Reg
|
|
{
|
|
public:
|
|
explicit 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:
|
|
explicit DtrOff(datastore::Imm12Data immdata, IsUp_ iu)
|
|
: data_(immdata.encode() | uint32_t(IsImmDTR) | uint32_t(iu))
|
|
{ }
|
|
|
|
explicit DtrOff(datastore::Reg reg, IsUp_ iu = IsUp)
|
|
: data_(reg.encode() | uint32_t(IsNotImmDTR) | iu)
|
|
{ }
|
|
|
|
public:
|
|
uint32_t encode() const { return data_; }
|
|
};
|
|
|
|
class DtrOffImm : public DtrOff
|
|
{
|
|
public:
|
|
explicit DtrOffImm(int32_t imm)
|
|
: DtrOff(datastore::Imm12Data(mozilla::Abs(imm)), imm >= 0 ? IsUp : IsDown)
|
|
{
|
|
MOZ_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 is annoying.
|
|
|
|
protected:
|
|
explicit DtrOffReg(Register rn, ShiftType type, datastore::RIS shiftImm, IsUp_ iu = IsUp)
|
|
: DtrOff(datastore::Reg(rn.code(), type, 0, shiftImm.encode()), iu)
|
|
{ }
|
|
|
|
explicit 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:
|
|
explicit DtrRegImmShift(Register rn, ShiftType type, uint32_t shift, IsUp_ iu = IsUp)
|
|
: DtrOffReg(rn, type, datastore::RIS(shift), iu)
|
|
{ }
|
|
};
|
|
|
|
class DtrRegRegShift : public DtrOffReg
|
|
{
|
|
public:
|
|
explicit 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
|
|
{
|
|
friend class Operand;
|
|
|
|
uint32_t data_;
|
|
|
|
public:
|
|
explicit DTRAddr(Register reg, DtrOff dtr)
|
|
: data_(dtr.encode() | (reg.code() << 16))
|
|
{ }
|
|
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
|
|
Register getBase() const {
|
|
return Register::FromCode((data_ >> 16) & 0xf);
|
|
}
|
|
};
|
|
|
|
// Offsets for the extended data transfer instructions:
|
|
// ldrsh, ldrd, ldrsb, etc.
|
|
class EDtrOff
|
|
{
|
|
uint32_t data_;
|
|
|
|
protected:
|
|
explicit EDtrOff(datastore::Imm8Data imm8, IsUp_ iu = IsUp)
|
|
: data_(imm8.encode() | IsImmEDTR | uint32_t(iu))
|
|
{ }
|
|
|
|
explicit EDtrOff(Register rm, IsUp_ iu = IsUp)
|
|
: data_(rm.code() | IsNotImmEDTR | iu)
|
|
{ }
|
|
|
|
public:
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
};
|
|
|
|
class EDtrOffImm : public EDtrOff
|
|
{
|
|
public:
|
|
explicit EDtrOffImm(int32_t imm)
|
|
: EDtrOff(datastore::Imm8Data(mozilla::Abs(imm)), (imm >= 0) ? IsUp : IsDown)
|
|
{
|
|
MOZ_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:
|
|
explicit EDtrOffReg(Register rm)
|
|
: EDtrOff(rm)
|
|
{ }
|
|
};
|
|
|
|
class EDtrAddr
|
|
{
|
|
uint32_t data_;
|
|
|
|
public:
|
|
explicit EDtrAddr(Register r, EDtrOff off)
|
|
: data_(RN(r) | off.encode())
|
|
{ }
|
|
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
#ifdef DEBUG
|
|
Register maybeOffsetRegister() const {
|
|
if (data_ & IsImmEDTR)
|
|
return InvalidReg;
|
|
return Register::FromCode(data_ & 0xf);
|
|
}
|
|
#endif
|
|
};
|
|
|
|
class VFPOff
|
|
{
|
|
uint32_t data_;
|
|
|
|
protected:
|
|
explicit VFPOff(datastore::Imm8VFPOffData imm, IsUp_ isup)
|
|
: data_(imm.encode() | uint32_t(isup))
|
|
{ }
|
|
|
|
public:
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
};
|
|
|
|
class VFPOffImm : public VFPOff
|
|
{
|
|
public:
|
|
explicit VFPOffImm(int32_t imm)
|
|
: VFPOff(datastore::Imm8VFPOffData(mozilla::Abs(imm) / 4), imm < 0 ? IsDown : IsUp)
|
|
{
|
|
MOZ_ASSERT(mozilla::Abs(imm) <= 255 * 4);
|
|
}
|
|
};
|
|
|
|
class VFPAddr
|
|
{
|
|
friend class Operand;
|
|
|
|
uint32_t data_;
|
|
|
|
public:
|
|
explicit VFPAddr(Register base, VFPOff off)
|
|
: data_(RN(base) | off.encode())
|
|
{ }
|
|
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
};
|
|
|
|
class VFPImm
|
|
{
|
|
uint32_t data_;
|
|
|
|
public:
|
|
explicit VFPImm(uint32_t topWordOfDouble);
|
|
|
|
static const VFPImm One;
|
|
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
bool isValid() const {
|
|
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
|
|
{
|
|
friend class InstBranchImm;
|
|
|
|
uint32_t data_;
|
|
|
|
public:
|
|
explicit BOffImm(int offset)
|
|
: data_((offset - 8) >> 2 & 0x00ffffff)
|
|
{
|
|
MOZ_ASSERT((offset & 0x3) == 0);
|
|
if (!IsInRange(offset))
|
|
MOZ_CRASH("BOffImm offset out of range");
|
|
}
|
|
|
|
explicit BOffImm()
|
|
: data_(INVALID)
|
|
{ }
|
|
|
|
private:
|
|
explicit BOffImm(const Instruction& inst);
|
|
|
|
public:
|
|
static const uint32_t INVALID = 0x00800000;
|
|
|
|
uint32_t encode() const {
|
|
return data_;
|
|
}
|
|
int32_t decode() const {
|
|
return ((int32_t(data_) << 8) >> 6) + 8;
|
|
}
|
|
|
|
static bool IsInRange(int offset) {
|
|
if ((offset - 8) < -33554432)
|
|
return false;
|
|
if ((offset - 8) > 33554428)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool isInvalid() const {
|
|
return data_ == INVALID;
|
|
}
|
|
Instruction* getDest(Instruction* src) const;
|
|
};
|
|
|
|
class Imm16
|
|
{
|
|
uint32_t lower_ : 12;
|
|
uint32_t pad_ : 4;
|
|
uint32_t upper_ : 4;
|
|
uint32_t invalid_ : 12;
|
|
|
|
public:
|
|
explicit Imm16();
|
|
explicit Imm16(uint32_t imm);
|
|
explicit Imm16(Instruction& inst);
|
|
|
|
uint32_t encode() const {
|
|
return lower_ | (upper_ << 16);
|
|
}
|
|
uint32_t decode() const {
|
|
return lower_ | (upper_ << 12);
|
|
}
|
|
|
|
bool isInvalid() const {
|
|
return invalid_;
|
|
}
|
|
};
|
|
|
|
// 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 class Tag : uint8_t {
|
|
OP2,
|
|
MEM,
|
|
FOP
|
|
};
|
|
|
|
private:
|
|
Tag tag_ : 8;
|
|
uint32_t reg_ : 5;
|
|
int32_t offset_;
|
|
|
|
public:
|
|
explicit Operand(Register reg)
|
|
: tag_(Tag::OP2), reg_(reg.code())
|
|
{ }
|
|
|
|
explicit Operand(FloatRegister freg)
|
|
: tag_(Tag::FOP), reg_(freg.code())
|
|
{ }
|
|
|
|
explicit Operand(Register base, Imm32 off)
|
|
: tag_(Tag::MEM), reg_(base.code()), offset_(off.value)
|
|
{ }
|
|
|
|
explicit Operand(Register base, int32_t off)
|
|
: tag_(Tag::MEM), reg_(base.code()), offset_(off)
|
|
{ }
|
|
|
|
explicit Operand(const Address& addr)
|
|
: tag_(Tag::MEM), reg_(addr.base.code()), offset_(addr.offset)
|
|
{ }
|
|
|
|
public:
|
|
Tag tag() const {
|
|
return tag_;
|
|
}
|
|
|
|
Operand2 toOp2() const {
|
|
MOZ_ASSERT(tag_ == Tag::OP2);
|
|
return O2Reg(Register::FromCode(reg_));
|
|
}
|
|
|
|
Register toReg() const {
|
|
MOZ_ASSERT(tag_ == Tag::OP2);
|
|
return Register::FromCode(reg_);
|
|
}
|
|
|
|
Address toAddress() const {
|
|
MOZ_ASSERT(tag_ == Tag::MEM);
|
|
return Address(Register::FromCode(reg_), offset_);
|
|
}
|
|
int32_t disp() const {
|
|
MOZ_ASSERT(tag_ == Tag::MEM);
|
|
return offset_;
|
|
}
|
|
|
|
int32_t base() const {
|
|
MOZ_ASSERT(tag_ == Tag::MEM);
|
|
return reg_;
|
|
}
|
|
Register baseReg() const {
|
|
MOZ_ASSERT(tag_ == Tag::MEM);
|
|
return Register::FromCode(reg_);
|
|
}
|
|
DTRAddr toDTRAddr() const {
|
|
MOZ_ASSERT(tag_ == Tag::MEM);
|
|
return DTRAddr(baseReg(), DtrOffImm(offset_));
|
|
}
|
|
VFPAddr toVFPAddr() const {
|
|
MOZ_ASSERT(tag_ == Tag::MEM);
|
|
return VFPAddr(baseReg(), VFPOffImm(offset_));
|
|
}
|
|
};
|
|
|
|
inline Imm32
|
|
Imm64::firstHalf() const
|
|
{
|
|
return low();
|
|
}
|
|
|
|
inline Imm32
|
|
Imm64::secondHalf() const
|
|
{
|
|
return hi();
|
|
}
|
|
|
|
void
|
|
PatchJump(CodeLocationJump& jump_, CodeLocationLabel label,
|
|
ReprotectCode reprotect = DontReprotect);
|
|
|
|
static inline void
|
|
PatchBackedge(CodeLocationJump& jump_, CodeLocationLabel label, JitRuntime::BackedgeTarget target)
|
|
{
|
|
PatchJump(jump_, label);
|
|
}
|
|
|
|
class InstructionIterator;
|
|
class Assembler;
|
|
typedef js::jit::AssemblerBufferWithConstantPools<1024, 4, Instruction, Assembler> ARMBuffer;
|
|
|
|
class Assembler : public AssemblerShared
|
|
{
|
|
public:
|
|
// ARM conditional constants:
|
|
enum ARMCondition {
|
|
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
|
|
};
|
|
|
|
enum Condition {
|
|
Equal = EQ,
|
|
NotEqual = NE,
|
|
Above = HI,
|
|
AboveOrEqual = CS,
|
|
Below = CC,
|
|
BelowOrEqual = LS,
|
|
GreaterThan = GT,
|
|
GreaterThanOrEqual = GE,
|
|
LessThan = LT,
|
|
LessThanOrEqual = LE,
|
|
Overflow = VS,
|
|
CarrySet = CS,
|
|
CarryClear = CC,
|
|
Signed = MI,
|
|
NotSigned = 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) {
|
|
MOZ_ASSERT(!(cond & DoubleConditionBitSpecial));
|
|
return static_cast<Condition>(cond);
|
|
}
|
|
|
|
enum BarrierOption {
|
|
BarrierSY = 15, // Full system barrier
|
|
BarrierST = 14 // StoreStore barrier
|
|
};
|
|
|
|
// This should be protected, but since CodeGenerator wants to use it, it
|
|
// needs to go out here :(
|
|
|
|
BufferOffset nextOffset() {
|
|
return m_buffer.nextOffset();
|
|
}
|
|
|
|
protected:
|
|
// Shim around AssemblerBufferWithConstantPools::allocEntry.
|
|
BufferOffset allocEntry(size_t numInst, unsigned numPoolEntries,
|
|
uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe = nullptr,
|
|
bool markAsBranch = false, bool loadToPC = false);
|
|
|
|
Instruction* editSrc(BufferOffset bo) {
|
|
return m_buffer.getInst(bo);
|
|
}
|
|
|
|
#ifdef JS_DISASM_ARM
|
|
static void spewInst(Instruction* i);
|
|
void spew(Instruction* i);
|
|
void spewBranch(Instruction* i, Label* target);
|
|
void spewData(BufferOffset addr, size_t numInstr, bool loadToPC);
|
|
void spewLabel(Label* label);
|
|
void spewRetarget(Label* label, Label* target);
|
|
void spewTarget(Label* l);
|
|
#endif
|
|
|
|
public:
|
|
void resetCounter();
|
|
uint32_t actualIndex(uint32_t) const;
|
|
static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index);
|
|
static uint32_t NopFill;
|
|
static uint32_t GetNopFill();
|
|
static uint32_t AsmPoolMaxOffset;
|
|
static uint32_t GetPoolMaxOffset();
|
|
|
|
protected:
|
|
// Structure for fixing up pc-relative loads/jumps when a the machine code
|
|
// gets moved (executable copy, gc, etc.).
|
|
struct RelativePatch
|
|
{
|
|
void* target_;
|
|
Relocation::Kind kind_;
|
|
|
|
public:
|
|
RelativePatch(void* target, Relocation::Kind kind)
|
|
: target_(target), kind_(kind)
|
|
{ }
|
|
void* target() const { return target_; }
|
|
Relocation::Kind kind() const { return kind_; }
|
|
};
|
|
|
|
// TODO: this should actually be a pool-like object. It is currently a big
|
|
// hack, and probably shouldn't exist.
|
|
js::Vector<RelativePatch, 8, SystemAllocPolicy> jumps_;
|
|
|
|
CompactBufferWriter jumpRelocations_;
|
|
CompactBufferWriter dataRelocations_;
|
|
CompactBufferWriter preBarriers_;
|
|
|
|
ARMBuffer m_buffer;
|
|
|
|
#ifdef JS_DISASM_ARM
|
|
private:
|
|
class SpewNodes {
|
|
struct Node {
|
|
uint32_t key;
|
|
uint32_t value;
|
|
Node* next;
|
|
};
|
|
|
|
Node* nodes;
|
|
|
|
public:
|
|
SpewNodes() : nodes(nullptr) {}
|
|
~SpewNodes();
|
|
|
|
bool lookup(uint32_t key, uint32_t* value);
|
|
bool add(uint32_t key, uint32_t value);
|
|
bool remove(uint32_t key);
|
|
};
|
|
|
|
SpewNodes spewNodes_;
|
|
uint32_t spewNext_;
|
|
Sprinter* printer_;
|
|
|
|
bool spewDisabled();
|
|
uint32_t spewResolve(Label* l);
|
|
uint32_t spewProbe(Label* l);
|
|
uint32_t spewDefine(Label* l);
|
|
void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3);
|
|
void spew(const char* fmt, va_list args);
|
|
#endif
|
|
|
|
public:
|
|
// For the alignment fill use NOP: 0x0320f000 or (Always | InstNOP::NopInst).
|
|
// For the nopFill use a branch to the next instruction: 0xeaffffff.
|
|
Assembler()
|
|
: m_buffer(1, 1, 8, GetPoolMaxOffset(), 8, 0xe320f000, 0xeaffffff, GetNopFill()),
|
|
#ifdef JS_DISASM_ARM
|
|
spewNext_(1000),
|
|
printer_(nullptr),
|
|
#endif
|
|
isFinished(false),
|
|
dtmActive(false),
|
|
dtmCond(Always)
|
|
{ }
|
|
|
|
// We need to wait until an AutoJitContextAlloc is created by the
|
|
// MacroAssembler, before allocating any space.
|
|
void initWithAllocator() {
|
|
m_buffer.initWithAllocator();
|
|
}
|
|
|
|
static Condition InvertCondition(Condition cond);
|
|
static Condition UnsignedCondition(Condition cond);
|
|
static Condition ConditionWithoutEqual(Condition cond);
|
|
|
|
// MacroAssemblers hold onto gcthings, so they are traced by the GC.
|
|
void trace(JSTracer* trc);
|
|
void writeRelocation(BufferOffset src) {
|
|
jumpRelocations_.writeUnsigned(src.getOffset());
|
|
}
|
|
|
|
// As opposed to x86/x64 version, the data relocation has to be executed
|
|
// before to recover the pointer, and not after.
|
|
void writeDataRelocation(ImmGCPtr ptr) {
|
|
if (ptr.value) {
|
|
if (gc::IsInsideNursery(ptr.value))
|
|
embedsNurseryPointers_ = true;
|
|
if (ptr.value)
|
|
dataRelocations_.writeUnsigned(nextOffset().getOffset());
|
|
}
|
|
}
|
|
void writePrebarrierOffset(CodeOffset label) {
|
|
preBarriers_.writeUnsigned(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 = nullptr, RelocStyle* rs = nullptr);
|
|
|
|
bool oom() const;
|
|
|
|
void setPrinter(Sprinter* sp) {
|
|
#ifdef JS_DISASM_ARM
|
|
printer_ = sp;
|
|
#endif
|
|
}
|
|
|
|
static const Register getStackPointer() {
|
|
return StackPointer;
|
|
}
|
|
|
|
private:
|
|
bool isFinished;
|
|
public:
|
|
void finish();
|
|
bool asmMergeWith(Assembler& other);
|
|
void executableCopy(void* buffer);
|
|
void copyJumpRelocationTable(uint8_t* dest);
|
|
void copyDataRelocationTable(uint8_t* dest);
|
|
void copyPreBarrierTable(uint8_t* dest);
|
|
|
|
// Size of the instruction stream, in bytes, after pools are flushed.
|
|
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.
|
|
BufferOffset writeInst(uint32_t x);
|
|
|
|
// As above, but also mark the instruction as a branch.
|
|
BufferOffset writeBranchInst(uint32_t x, Label* documentation = nullptr);
|
|
|
|
// Write a placeholder NOP for a branch into the instruction stream
|
|
// (in order to adjust assembler addresses and mark it as a branch), it will
|
|
// be overwritten subsequently.
|
|
BufferOffset allocBranchInst();
|
|
|
|
// A static variant for the cases where we don't want to have an assembler
|
|
// object.
|
|
static void WriteInstStatic(uint32_t x, uint32_t* dest);
|
|
|
|
public:
|
|
void writeCodePointer(CodeOffset* label);
|
|
|
|
void haltingAlign(int alignment);
|
|
void nopAlign(int alignment);
|
|
BufferOffset as_nop();
|
|
BufferOffset as_alu(Register dest, Register src1, Operand2 op2,
|
|
ALUOp op, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_mov(Register dest,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_mvn(Register dest, Operand2 op2,
|
|
SBit s = LeaveCC, Condition c = Always);
|
|
|
|
static void as_alu_patch(Register dest, Register src1, Operand2 op2,
|
|
ALUOp op, SBit s, Condition c, uint32_t* pos);
|
|
static void as_mov_patch(Register dest,
|
|
Operand2 op2, SBit s, Condition c, uint32_t* pos);
|
|
|
|
// Logical operations:
|
|
BufferOffset as_and(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_bic(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_eor(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_orr(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
// Mathematical operations:
|
|
BufferOffset as_adc(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_add(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_sbc(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_sub(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_rsb(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_rsc(Register dest, Register src1,
|
|
Operand2 op2, SBit s = LeaveCC, 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);
|
|
|
|
// Sign extension operations:
|
|
BufferOffset as_sxtb(Register dest, Register src, int rotate, Condition c = Always);
|
|
BufferOffset as_sxth(Register dest, Register src, int rotate, Condition c = Always);
|
|
BufferOffset as_uxtb(Register dest, Register src, int rotate, Condition c = Always);
|
|
BufferOffset as_uxth(Register dest, Register src, int rotate, Condition c = Always);
|
|
|
|
// Not quite ALU worthy, but useful none the less: These also have the issue
|
|
// of these being formatted completly differently from the standard ALU operations.
|
|
BufferOffset as_movw(Register dest, Imm16 imm, Condition c = Always);
|
|
BufferOffset as_movt(Register dest, Imm16 imm, Condition c = Always);
|
|
|
|
static void as_movw_patch(Register dest, Imm16 imm, Condition c, Instruction* pos);
|
|
static void as_movt_patch(Register dest, Imm16 imm, Condition c, Instruction* pos);
|
|
|
|
BufferOffset as_genmul(Register d1, Register d2, Register rm, Register rn,
|
|
MULOp op, SBit s, Condition c = Always);
|
|
BufferOffset as_mul(Register dest, Register src1, Register src2,
|
|
SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_mla(Register dest, Register acc, Register src1, Register src2,
|
|
SBit s = LeaveCC, 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,
|
|
SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_umlal(Register dest1, Register dest2, Register src1, Register src2,
|
|
SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_smull(Register dest1, Register dest2, Register src1, Register src2,
|
|
SBit s = LeaveCC, Condition c = Always);
|
|
BufferOffset as_smlal(Register dest1, Register dest2, Register src1, Register src2,
|
|
SBit s = LeaveCC, Condition c = Always);
|
|
|
|
BufferOffset as_sdiv(Register dest, Register num, Register div, Condition c = Always);
|
|
BufferOffset as_udiv(Register dest, Register num, Register div, Condition c = Always);
|
|
BufferOffset as_clz(Register dest, Register src, Condition c = Always);
|
|
|
|
// Data transfer instructions: ldr, str, ldrb, strb.
|
|
// Using an int to differentiate between 8 bits and 32 bits is overkill.
|
|
BufferOffset as_dtr(LoadStore ls, int size, Index mode,
|
|
Register rt, DTRAddr addr, Condition c = Always);
|
|
|
|
static void as_dtr_patch(LoadStore ls, int size, Index mode,
|
|
Register rt, DTRAddr addr, Condition c, uint32_t* dest);
|
|
|
|
// Handles all of the other integral data transferring functions:
|
|
// ldrsb, ldrsh, ldrd, etc. The size is given in bits.
|
|
BufferOffset as_extdtr(LoadStore ls, int size, bool IsSigned, Index mode,
|
|
Register rt, EDtrAddr addr, Condition c = Always);
|
|
|
|
BufferOffset as_dtm(LoadStore ls, Register rn, uint32_t mask,
|
|
DTMMode mode, DTMWriteBack wb, Condition c = Always);
|
|
|
|
// Overwrite a pool entry with new data.
|
|
static void 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, 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 = nullptr, Condition c = Always,
|
|
Label* documentation = nullptr);
|
|
|
|
// Load a 64 bit floating point immediate from a pool into a register.
|
|
BufferOffset as_FImm64Pool(VFPRegister dest, wasm::RawF64 value, Condition c = Always);
|
|
// Load a 32 bit floating point immediate from a pool into a register.
|
|
BufferOffset as_FImm32Pool(VFPRegister dest, wasm::RawF32 value, Condition c = Always);
|
|
|
|
// Atomic instructions: ldrex, ldrexh, ldrexb, strex, strexh, strexb.
|
|
//
|
|
// The halfword and byte versions are available from ARMv6K forward.
|
|
//
|
|
// The word versions are available from ARMv6 forward and can be used to
|
|
// implement the halfword and byte versions on older systems.
|
|
|
|
// LDREX rt, [rn]
|
|
BufferOffset as_ldrex(Register rt, Register rn, Condition c = Always);
|
|
BufferOffset as_ldrexh(Register rt, Register rn, Condition c = Always);
|
|
BufferOffset as_ldrexb(Register rt, Register rn, Condition c = Always);
|
|
|
|
// STREX rd, rt, [rn]. Constraint: rd != rn, rd != rt.
|
|
BufferOffset as_strex(Register rd, Register rt, Register rn, Condition c = Always);
|
|
BufferOffset as_strexh(Register rd, Register rt, Register rn, Condition c = Always);
|
|
BufferOffset as_strexb(Register rd, Register rt, Register rn, Condition c = Always);
|
|
|
|
// Memory synchronization.
|
|
// These are available from ARMv7 forward.
|
|
BufferOffset as_dmb(BarrierOption option = BarrierSY);
|
|
BufferOffset as_dsb(BarrierOption option = BarrierSY);
|
|
BufferOffset as_isb();
|
|
|
|
// Memory synchronization for architectures before ARMv7.
|
|
BufferOffset as_dsb_trap();
|
|
BufferOffset as_dmb_trap();
|
|
BufferOffset as_isb_trap();
|
|
|
|
// Control flow stuff:
|
|
|
|
// bx can *only* branch to a register never to an immediate.
|
|
BufferOffset as_bx(Register r, Condition c = Always);
|
|
|
|
// 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, Label* documentation = nullptr);
|
|
|
|
BufferOffset as_b(Label* l, Condition c = Always);
|
|
BufferOffset as_b(wasm::TrapDesc target, Condition c = Always);
|
|
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, Label* documentation = nullptr);
|
|
// 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);
|
|
|
|
static void WriteVFPInstStatic(vfp_size sz, uint32_t blob, uint32_t* dest);
|
|
|
|
// 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);
|
|
|
|
// Transfer 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 their 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);
|
|
|
|
// Transfer between VFP and memory.
|
|
BufferOffset as_vdtr(LoadStore ls, VFPRegister vd, VFPAddr addr,
|
|
Condition c = Always /* vfp doesn't have a wb option*/);
|
|
|
|
static void as_vdtr_patch(LoadStore ls, VFPRegister vd, VFPAddr addr,
|
|
Condition c /* vfp doesn't have a wb option */, uint32_t* dest);
|
|
|
|
// 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);
|
|
void bindLater(Label* label, wasm::TrapDesc target);
|
|
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(uint8_t* rawCode, CodeOffset* label, const void* address);
|
|
|
|
// See Bind
|
|
size_t labelToPatchOffset(CodeOffset label) {
|
|
return label.offset();
|
|
}
|
|
|
|
void as_bkpt();
|
|
|
|
public:
|
|
static void TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
|
|
static void TraceDataRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader);
|
|
|
|
static bool SupportsFloatingPoint() {
|
|
return HasVFP();
|
|
}
|
|
static bool SupportsUnalignedAccesses() {
|
|
return HasARMv7();
|
|
}
|
|
static bool SupportsSimd() {
|
|
return js::jit::SupportsSimd;
|
|
}
|
|
|
|
protected:
|
|
void addPendingJump(BufferOffset src, ImmPtr target, Relocation::Kind kind) {
|
|
enoughMemory_ &= jumps_.append(RelativePatch(target.value, kind));
|
|
if (kind == Relocation::JITCODE)
|
|
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() {
|
|
MOZ_ASSERT(!isFinished);
|
|
m_buffer.flushPool();
|
|
return;
|
|
}
|
|
|
|
void comment(const char* msg) {
|
|
#ifdef JS_DISASM_ARM
|
|
spew("; %s", msg);
|
|
#endif
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
MOZ_ASSERT(!dtmActive);
|
|
dtmUpdate = update;
|
|
dtmBase = rm;
|
|
dtmLoadStore = ls;
|
|
dtmLastReg = -1;
|
|
dtmRegBitField = 0;
|
|
dtmActive = 1;
|
|
dtmCond = c;
|
|
dtmMode = mode;
|
|
}
|
|
|
|
void transferReg(Register rn) {
|
|
MOZ_ASSERT(dtmActive);
|
|
MOZ_ASSERT(rn.code() > dtmLastReg);
|
|
dtmRegBitField |= 1 << rn.code();
|
|
if (dtmLoadStore == IsLoad && rn.code() == 13 && dtmBase.code() == 13) {
|
|
MOZ_CRASH("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)
|
|
{
|
|
MOZ_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;
|
|
MOZ_ASSERT(dtmDelta == 1 || dtmDelta == -1);
|
|
}
|
|
MOZ_ASSERT(dtmLastReg >= 0);
|
|
MOZ_ASSERT(rn.code() == unsigned(dtmLastReg) + dtmDelta);
|
|
}
|
|
|
|
dtmLastReg = rn.code();
|
|
}
|
|
void finishFloatTransfer() {
|
|
MOZ_ASSERT(dtmActive);
|
|
dtmActive = false;
|
|
MOZ_ASSERT(dtmLastReg != -1);
|
|
dtmDelta = dtmDelta ? dtmDelta : 1;
|
|
// The operand for the vstr/vldr instruction is the lowest register in the range.
|
|
int low = Min(dtmLastReg, vdtmFirstReg);
|
|
int high = Max(dtmLastReg, vdtmFirstReg);
|
|
// Fencepost problem.
|
|
int len = high - low + 1;
|
|
// vdtm can only transfer 16 registers at once. If we need to transfer more,
|
|
// then either hoops are necessary, or we need to be updating the register.
|
|
MOZ_ASSERT_IF(len > 16, dtmUpdate == WriteBack);
|
|
|
|
int adjustLow = dtmLoadStore == IsStore ? 0 : 1;
|
|
int adjustHigh = dtmLoadStore == IsStore ? -1 : 0;
|
|
while (len > 0) {
|
|
// Limit the instruction to 16 registers.
|
|
int curLen = Min(len, 16);
|
|
// If it is a store, we want to start at the high end and move down
|
|
// (e.g. vpush d16-d31; vpush d0-d15).
|
|
int curStart = (dtmLoadStore == IsStore) ? high - curLen + 1 : low;
|
|
as_vdtm(dtmLoadStore, dtmBase,
|
|
VFPRegister(FloatRegister::FromCode(curStart)),
|
|
curLen, dtmCond);
|
|
// Update the bounds.
|
|
low += adjustLow * curLen;
|
|
high += adjustHigh * curLen;
|
|
// Update the length parameter.
|
|
len -= curLen;
|
|
}
|
|
}
|
|
|
|
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 InsertIndexIntoTag(uint8_t* load, uint32_t index);
|
|
|
|
// 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 void PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr);
|
|
|
|
// We're not tracking short-range branches for ARM for now.
|
|
static void PatchShortRangeBranchToVeneer(ARMBuffer*, unsigned rangeIdx, BufferOffset deadline,
|
|
BufferOffset veneer)
|
|
{
|
|
MOZ_CRASH();
|
|
}
|
|
// 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 flushBuffer();
|
|
void enterNoPool(size_t maxInst);
|
|
void leaveNoPool();
|
|
// This should return a BOffImm, but we 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 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, PatchedImmPtr newValue,
|
|
PatchedImmPtr expectedValue);
|
|
static void PatchDataWithValueCheck(CodeLocationLabel label, ImmPtr newValue,
|
|
ImmPtr expectedValue);
|
|
static void PatchWrite_Imm32(CodeLocationLabel label, Imm32 imm);
|
|
|
|
static void PatchInstructionImmediate(uint8_t* code, PatchedImmPtr imm) {
|
|
MOZ_CRASH("Unused.");
|
|
}
|
|
|
|
static uint32_t AlignDoubleArg(uint32_t offset) {
|
|
return (offset + 1) & ~1;
|
|
}
|
|
static uint8_t* NextInstruction(uint8_t* instruction, uint32_t* count = nullptr);
|
|
|
|
// Toggle a jmp or cmp emitted by toggledJump().
|
|
static void ToggleToJmp(CodeLocationLabel inst_);
|
|
static void ToggleToCmp(CodeLocationLabel inst_);
|
|
|
|
static uint8_t* BailoutTableStart(uint8_t* code);
|
|
|
|
static size_t ToggledCallSize(uint8_t* code);
|
|
static void ToggleCall(CodeLocationLabel inst_, bool enabled);
|
|
|
|
void processCodeLabels(uint8_t* rawCode);
|
|
|
|
bool bailed() {
|
|
return m_buffer.bail();
|
|
}
|
|
|
|
void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end,
|
|
const Disassembler::HeapAccess& heapAccess)
|
|
{
|
|
// Implement this if we implement a disassembler.
|
|
}
|
|
}; // Assembler
|
|
|
|
// An Instruction is a structure for both encoding and decoding any and all ARM
|
|
// instructions. Many classes have not been implemented thus far.
|
|
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.
|
|
explicit Instruction(uint32_t data_, bool fake = false)
|
|
: data(data_ | 0xf0000000)
|
|
{
|
|
MOZ_ASSERT(fake || ((data_ & 0xf0000000) == 0));
|
|
}
|
|
// Standard constructor.
|
|
Instruction(uint32_t data_, Assembler::Condition c)
|
|
: data(data_ | (uint32_t) c)
|
|
{
|
|
MOZ_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=(Instruction src) {
|
|
data = src.data;
|
|
return *this;
|
|
}
|
|
// Since almost all instructions have condition codes, the condition code
|
|
// extractor resides in the base class.
|
|
Assembler::Condition extractCond() {
|
|
MOZ_ASSERT(data >> 28 != 0xf, "The instruction does not have condition code");
|
|
return (Assembler::Condition)(data & 0xf0000000);
|
|
}
|
|
// Get the next instruction in the instruction stream.
|
|
// This does neat things like ignoreconstant pools and their guards.
|
|
Instruction* next();
|
|
|
|
// Skipping pools with artificial guards.
|
|
Instruction* skipPool();
|
|
|
|
// 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);
|
|
|
|
int32_t signedOffset() const {
|
|
int32_t offset = encode() & 0xfff;
|
|
if (IsUp_(encode() & IsUp) != IsUp)
|
|
return -offset;
|
|
return offset;
|
|
}
|
|
uint32_t* dest() const {
|
|
int32_t offset = signedOffset();
|
|
// When patching the load in PatchConstantPoolLoad, we ensure that the
|
|
// offset is a multiple of 4, offset by 8 bytes from the actual
|
|
// location. Indeed, when the base register is PC, ARM's 3 stages
|
|
// pipeline design makes it that PC is off by 8 bytes (= 2 *
|
|
// sizeof(uint32*)) when we actually executed it.
|
|
MOZ_ASSERT(offset % 4 == 0);
|
|
offset >>= 2;
|
|
return (uint32_t*)raw() + offset + 2;
|
|
}
|
|
};
|
|
JS_STATIC_ASSERT(sizeof(InstDTR) == sizeof(InstLDR));
|
|
|
|
class InstNOP : public Instruction
|
|
{
|
|
public:
|
|
static const uint32_t NopInst = 0x0320f000;
|
|
|
|
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 (const 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, SBit s, Assembler::Condition c)
|
|
: Instruction(maybeRD(rd) | maybeRN(rn) | op2.encode() | op | s, 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:
|
|
explicit InstructionIterator(Instruction* i_);
|
|
|
|
Instruction* next() {
|
|
i = i->next();
|
|
return cur();
|
|
}
|
|
Instruction* cur() const {
|
|
return i;
|
|
}
|
|
};
|
|
|
|
static const uint32_t NumIntArgRegs = 4;
|
|
|
|
// There are 16 *float* registers available for arguments
|
|
// If doubles are used, only half the number of registers are available.
|
|
static const uint32_t NumFloatArgRegs = 16;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
#if !defined(JS_CODEGEN_ARM_HARDFP) || defined(JS_SIMULATOR_ARM)
|
|
|
|
static inline uint32_t
|
|
GetArgStackDisp(uint32_t arg)
|
|
{
|
|
MOZ_ASSERT(!UseHardFpABI());
|
|
MOZ_ASSERT(arg >= NumIntArgRegs);
|
|
return (arg - NumIntArgRegs) * sizeof(intptr_t);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#if defined(JS_CODEGEN_ARM_HARDFP) || defined(JS_SIMULATOR_ARM)
|
|
|
|
static inline bool
|
|
GetFloat32ArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, FloatRegister* out)
|
|
{
|
|
MOZ_ASSERT(UseHardFpABI());
|
|
if (usedFloatArgs >= NumFloatArgRegs)
|
|
return false;
|
|
*out = VFPRegister(usedFloatArgs, VFPRegister::Single);
|
|
return true;
|
|
}
|
|
static inline bool
|
|
GetDoubleArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, FloatRegister* out)
|
|
{
|
|
MOZ_ASSERT(UseHardFpABI());
|
|
MOZ_ASSERT((usedFloatArgs % 2) == 0);
|
|
if (usedFloatArgs >= NumFloatArgRegs)
|
|
return false;
|
|
*out = VFPRegister(usedFloatArgs>>1, VFPRegister::Double);
|
|
return true;
|
|
}
|
|
|
|
static inline uint32_t
|
|
GetIntArgStackDisp(uint32_t usedIntArgs, uint32_t usedFloatArgs, uint32_t* padding)
|
|
{
|
|
MOZ_ASSERT(UseHardFpABI());
|
|
MOZ_ASSERT(usedIntArgs >= NumIntArgRegs);
|
|
uint32_t doubleSlots = Max(0, (int32_t)usedFloatArgs - (int32_t)NumFloatArgRegs);
|
|
doubleSlots *= 2;
|
|
int intSlots = usedIntArgs - NumIntArgRegs;
|
|
return (intSlots + doubleSlots + *padding) * sizeof(intptr_t);
|
|
}
|
|
|
|
static inline uint32_t
|
|
GetFloat32ArgStackDisp(uint32_t usedIntArgs, uint32_t usedFloatArgs, uint32_t* padding)
|
|
{
|
|
MOZ_ASSERT(UseHardFpABI());
|
|
MOZ_ASSERT(usedFloatArgs >= NumFloatArgRegs);
|
|
uint32_t intSlots = 0;
|
|
if (usedIntArgs > NumIntArgRegs)
|
|
intSlots = usedIntArgs - NumIntArgRegs;
|
|
uint32_t float32Slots = usedFloatArgs - NumFloatArgRegs;
|
|
return (intSlots + float32Slots + *padding) * sizeof(intptr_t);
|
|
}
|
|
|
|
static inline uint32_t
|
|
GetDoubleArgStackDisp(uint32_t usedIntArgs, uint32_t usedFloatArgs, uint32_t* padding)
|
|
{
|
|
MOZ_ASSERT(UseHardFpABI());
|
|
MOZ_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) * sizeof(intptr_t);
|
|
}
|
|
|
|
#endif
|
|
|
|
class DoubleEncoder
|
|
{
|
|
struct DoubleEntry
|
|
{
|
|
uint32_t dblTop;
|
|
datastore::Imm8VFPImmData data;
|
|
};
|
|
|
|
static const DoubleEntry table[256];
|
|
|
|
public:
|
|
bool lookup(uint32_t top, datastore::Imm8VFPImmData* ret) const {
|
|
for (int i = 0; i < 256; i++) {
|
|
if (table[i].dblTop == top) {
|
|
*ret = table[i].data;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class AutoForbidPools
|
|
{
|
|
Assembler* masm_;
|
|
|
|
public:
|
|
// The maxInst argument is the maximum number of word sized instructions
|
|
// that will be allocated within this context. It is used to determine if
|
|
// the pool needs to be dumped before entering this content. The debug code
|
|
// checks that no more than maxInst instructions are actually allocated.
|
|
//
|
|
// Allocation of pool entries is not supported within this content so the
|
|
// code can not use large integers or float constants etc.
|
|
AutoForbidPools(Assembler* masm, size_t maxInst)
|
|
: masm_(masm)
|
|
{
|
|
masm_->enterNoPool(maxInst);
|
|
}
|
|
|
|
~AutoForbidPools() {
|
|
masm_->leaveNoPool();
|
|
}
|
|
};
|
|
|
|
} // namespace jit
|
|
} // namespace js
|
|
|
|
#endif /* jit_arm_Assembler_arm_h */
|