blob: 57a3aa2377b36a0c219588d83f2d708fd827ace9 [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/. */
#include "mozilla/DebugOnly.h"
#include "Assembler-arm.h"
#include "MacroAssembler-arm.h"
#include "gc/Marking.h"
#include "jsutil.h"
#include "assembler/jit/ExecutableAllocator.h"
#include "jscompartment.h"
#include "jit/IonCompartment.h"
using namespace js;
using namespace js::jit;
ABIArgGenerator::ABIArgGenerator() :
#if defined(JS_CPU_ARM_HARDFP)
intRegIndex_(0),
floatRegIndex_(0),
#else
argRegIndex_(0),
#endif
stackOffset_(0),
current_()
{}
ABIArg
ABIArgGenerator::next(MIRType type)
{
#if defined(JS_CPU_ARM_HARDFP)
switch (type) {
case MIRType_Int32:
case MIRType_Pointer:
if (intRegIndex_ == NumIntArgRegs) {
current_ = ABIArg(stackOffset_);
stackOffset_ += sizeof(uint32_t);
break;
}
current_ = ABIArg(Register::FromCode(intRegIndex_));
intRegIndex_++;
break;
case MIRType_Double:
if (floatRegIndex_ == NumFloatArgRegs) {
static const int align = sizeof(double) - 1;
stackOffset_ = (stackOffset_ + align) & ~align;
current_ = ABIArg(stackOffset_);
stackOffset_ += sizeof(uint64_t);
break;
}
current_ = ABIArg(FloatRegister::FromCode(floatRegIndex_));
floatRegIndex_++;
break;
default:
JS_NOT_REACHED("Unexpected argument type");
}
return current_;
#else
switch (type) {
case MIRType_Int32:
case MIRType_Pointer:
if (argRegIndex_ == NumIntArgRegs) {
current_ = ABIArg(stackOffset_);
stackOffset_ += sizeof(uint32_t);
break;
}
current_ = ABIArg(Register::FromCode(argRegIndex_));
argRegIndex_++;
break;
case MIRType_Double: {
unsigned alignedArgRegIndex_ = (argRegIndex_ + 1) & ~1;
if (alignedArgRegIndex_ + 1 > NumIntArgRegs) {
static const int align = sizeof(double) - 1;
stackOffset_ = (stackOffset_ + align) & ~align;
current_ = ABIArg(stackOffset_);
stackOffset_ += sizeof(uint64_t);
argRegIndex_ = NumIntArgRegs;
break;
}
argRegIndex_ = alignedArgRegIndex_;
current_ = ABIArg(FloatRegister::FromCode(argRegIndex_ >> 1));
argRegIndex_+=2;
}
break;
default:
JS_NOT_REACHED("Unexpected argument type");
}
return current_;
#endif
}
const Register ABIArgGenerator::NonArgReturnVolatileReg0 = r4;
const Register ABIArgGenerator::NonArgReturnVolatileReg1 = r5;
// Encode a standard register when it is being used as src1, the dest, and
// an extra register. These should never be called with an InvalidReg.
uint32_t
js::jit::RT(Register r)
{
JS_ASSERT((r.code() & ~0xf) == 0);
return r.code() << 12;
}
uint32_t
js::jit::RN(Register r)
{
JS_ASSERT((r.code() & ~0xf) == 0);
return r.code() << 16;
}
uint32_t
js::jit::RD(Register r)
{
JS_ASSERT((r.code() & ~0xf) == 0);
return r.code() << 12;
}
uint32_t
js::jit::RM(Register r)
{
JS_ASSERT((r.code() & ~0xf) == 0);
return r.code() << 8;
}
// Encode a standard register when it is being used as src1, the dest, and
// an extra register. For these, an InvalidReg is used to indicate a optional
// register that has been omitted.
uint32_t
js::jit::maybeRT(Register r)
{
if (r == InvalidReg)
return 0;
JS_ASSERT((r.code() & ~0xf) == 0);
return r.code() << 12;
}
uint32_t
js::jit::maybeRN(Register r)
{
if (r == InvalidReg)
return 0;
JS_ASSERT((r.code() & ~0xf) == 0);
return r.code() << 16;
}
uint32_t
js::jit::maybeRD(Register r)
{
if (r == InvalidReg)
return 0;
JS_ASSERT((r.code() & ~0xf) == 0);
return r.code() << 12;
}
Register
js::jit::toRD(Instruction &i)
{
return Register::FromCode((i.encode()>>12) & 0xf);
}
Register
js::jit::toR(Instruction &i)
{
return Register::FromCode(i.encode() & 0xf);
}
Register
js::jit::toRM(Instruction &i)
{
return Register::FromCode((i.encode()>>8) & 0xf);
}
Register
js::jit::toRN(Instruction &i)
{
return Register::FromCode((i.encode()>>16) & 0xf);
}
uint32_t
js::jit::VD(VFPRegister vr)
{
if (vr.isMissing())
return 0;
//bits 15,14,13,12, 22
VFPRegister::VFPRegIndexSplit s = vr.encode();
return s.bit << 22 | s.block << 12;
}
uint32_t
js::jit::VN(VFPRegister vr)
{
if (vr.isMissing())
return 0;
// bits 19,18,17,16, 7
VFPRegister::VFPRegIndexSplit s = vr.encode();
return s.bit << 7 | s.block << 16;
}
uint32_t
js::jit::VM(VFPRegister vr)
{
if (vr.isMissing())
return 0;
// bits 5, 3,2,1,0
VFPRegister::VFPRegIndexSplit s = vr.encode();
return s.bit << 5 | s.block;
}
VFPRegister::VFPRegIndexSplit
jit::VFPRegister::encode()
{
JS_ASSERT(!_isInvalid);
switch (kind) {
case Double:
return VFPRegIndexSplit(_code &0xf , _code >> 4);
case Single:
return VFPRegIndexSplit(_code >> 1, _code & 1);
default:
// vfp register treated as an integer, NOT a gpr
return VFPRegIndexSplit(_code >> 1, _code & 1);
}
}
VFPRegister js::jit::NoVFPRegister(true);
bool
InstDTR::isTHIS(const Instruction &i)
{
return (i.encode() & IsDTRMask) == (uint32_t)IsDTR;
}
InstDTR *
InstDTR::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstDTR*)&i;
return NULL;
}
bool
InstLDR::isTHIS(const Instruction &i)
{
return (i.encode() & IsDTRMask) == (uint32_t)IsDTR;
}
InstLDR *
InstLDR::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstLDR*)&i;
return NULL;
}
InstNOP *
InstNOP::asTHIS(Instruction &i)
{
if (isTHIS(i))
return (InstNOP*) (&i);
return NULL;
}
bool
InstNOP::isTHIS(const Instruction &i)
{
return (i.encode() & 0x0fffffff) == NopInst;
}
bool
InstBranchReg::isTHIS(const Instruction &i)
{
return InstBXReg::isTHIS(i) || InstBLXReg::isTHIS(i);
}
InstBranchReg *
InstBranchReg::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstBranchReg*)&i;
return NULL;
}
void
InstBranchReg::extractDest(Register *dest)
{
*dest = toR(*this);
}
bool
InstBranchReg::checkDest(Register dest)
{
return dest == toR(*this);
}
bool
InstBranchImm::isTHIS(const Instruction &i)
{
return InstBImm::isTHIS(i) || InstBLImm::isTHIS(i);
}
InstBranchImm *
InstBranchImm::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstBranchImm*)&i;
return NULL;
}
void
InstBranchImm::extractImm(BOffImm *dest)
{
*dest = BOffImm(*this);
}
bool
InstBXReg::isTHIS(const Instruction &i)
{
return (i.encode() & IsBRegMask) == IsBX;
}
InstBXReg *
InstBXReg::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstBXReg*)&i;
return NULL;
}
bool
InstBLXReg::isTHIS(const Instruction &i)
{
return (i.encode() & IsBRegMask) == IsBLX;
}
InstBLXReg *
InstBLXReg::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstBLXReg*)&i;
return NULL;
}
bool
InstBImm::isTHIS(const Instruction &i)
{
return (i.encode () & IsBImmMask) == IsB;
}
InstBImm *
InstBImm::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstBImm*)&i;
return NULL;
}
bool
InstBLImm::isTHIS(const Instruction &i)
{
return (i.encode () & IsBImmMask) == IsBL;
}
InstBLImm *
InstBLImm::asTHIS(Instruction &i)
{
if (isTHIS(i))
return (InstBLImm*)&i;
return NULL;
}
bool
InstMovWT::isTHIS(Instruction &i)
{
return InstMovW::isTHIS(i) || InstMovT::isTHIS(i);
}
InstMovWT *
InstMovWT::asTHIS(Instruction &i)
{
if (isTHIS(i))
return (InstMovWT*)&i;
return NULL;
}
void
InstMovWT::extractImm(Imm16 *imm)
{
*imm = Imm16(*this);
}
bool
InstMovWT::checkImm(Imm16 imm)
{
return (imm.decode() == Imm16(*this).decode());
}
void
InstMovWT::extractDest(Register *dest)
{
*dest = toRD(*this);
}
bool
InstMovWT::checkDest(Register dest)
{
return (dest == toRD(*this));
}
bool
InstMovW::isTHIS(const Instruction &i)
{
return (i.encode() & IsWTMask) == IsW;
}
InstMovW *
InstMovW::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstMovW*) (&i);
return NULL;
}
InstMovT *
InstMovT::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstMovT*) (&i);
return NULL;
}
bool
InstMovT::isTHIS(const Instruction &i)
{
return (i.encode() & IsWTMask) == IsT;
}
InstALU *
InstALU::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstALU*) (&i);
return NULL;
}
bool
InstALU::isTHIS(const Instruction &i)
{
return (i.encode() & ALUMask) == 0;
}
void
InstALU::extractOp(ALUOp *ret)
{
*ret = ALUOp(encode() & (0xf << 21));
}
bool
InstALU::checkOp(ALUOp op)
{
ALUOp mine;
extractOp(&mine);
return mine == op;
}
void
InstALU::extractDest(Register *ret)
{
*ret = toRD(*this);
}
bool
InstALU::checkDest(Register rd)
{
return rd == toRD(*this);
}
void
InstALU::extractOp1(Register *ret)
{
*ret = toRN(*this);
}
bool
InstALU::checkOp1(Register rn)
{
return rn == toRN(*this);
}
Operand2
InstALU::extractOp2()
{
return Operand2(encode());
}
InstCMP *
InstCMP::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstCMP*) (&i);
return NULL;
}
bool
InstCMP::isTHIS(const Instruction &i)
{
return InstALU::isTHIS(i) && InstALU::asTHIS(i)->checkDest(r0) && InstALU::asTHIS(i)->checkOp(op_cmp);
}
InstMOV *
InstMOV::asTHIS(const Instruction &i)
{
if (isTHIS(i))
return (InstMOV*) (&i);
return NULL;
}
bool
InstMOV::isTHIS(const Instruction &i)
{
return InstALU::isTHIS(i) && InstALU::asTHIS(i)->checkOp1(r0) && InstALU::asTHIS(i)->checkOp(op_mov);
}
Op2Reg
Operand2::toOp2Reg() {
return *(Op2Reg*)this;
}
O2RegImmShift
Op2Reg::toO2RegImmShift() {
return *(O2RegImmShift*)this;
}
O2RegRegShift
Op2Reg::toO2RegRegShift() {
return *(O2RegRegShift*)this;
}
Imm16::Imm16(Instruction &inst)
: lower(inst.encode() & 0xfff),
upper(inst.encode() >> 16),
invalid(0xfff)
{ }
Imm16::Imm16(uint32_t imm)
: lower(imm & 0xfff), pad(0),
upper((imm>>12) & 0xf),
invalid(0)
{
JS_ASSERT(decode() == imm);
}
Imm16::Imm16()
: invalid(0xfff)
{ }
void
jit::PatchJump(CodeLocationJump &jump_, CodeLocationLabel label)
{
// We need to determine if this jump can fit into the standard 24+2 bit address
// or if we need a larger branch (or just need to use our pool entry)
Instruction *jump = (Instruction*)jump_.raw();
Assembler::Condition c;
jump->extractCond(&c);
JS_ASSERT(jump->is<InstBranchImm>() || jump->is<InstLDR>());
int jumpOffset = label.raw() - jump_.raw();
if (BOffImm::isInRange(jumpOffset)) {
// This instruction started off as a branch, and will remain one
Assembler::retargetNearBranch(jump, jumpOffset, c);
} else {
// This instruction started off as a branch, but now needs to be demoted to an ldr.
uint8_t **slot = reinterpret_cast<uint8_t**>(jump_.jumpTableEntry());
Assembler::retargetFarBranch(jump, slot, label.raw(), c);
}
}
void
Assembler::finish()
{
flush();
JS_ASSERT(!isFinished);
isFinished = true;
for (size_t i = 0; i < jumps_.length(); i++)
jumps_[i].fixOffset(m_buffer);
for (unsigned int i = 0; i < tmpDataRelocations_.length(); i++) {
int offset = tmpDataRelocations_[i].getOffset();
int real_offset = offset + m_buffer.poolSizeBefore(offset);
dataRelocations_.writeUnsigned(real_offset);
}
for (unsigned int i = 0; i < tmpJumpRelocations_.length(); i++) {
int offset = tmpJumpRelocations_[i].getOffset();
int real_offset = offset + m_buffer.poolSizeBefore(offset);
jumpRelocations_.writeUnsigned(real_offset);
}
for (unsigned int i = 0; i < tmpPreBarriers_.length(); i++) {
int offset = tmpPreBarriers_[i].getOffset();
int real_offset = offset + m_buffer.poolSizeBefore(offset);
preBarriers_.writeUnsigned(real_offset);
}
}
void
Assembler::executableCopy(uint8_t *buffer)
{
JS_ASSERT(isFinished);
m_buffer.executableCopy(buffer);
AutoFlushCache::updateTop((uintptr_t)buffer, m_buffer.size());
}
void
Assembler::resetCounter()
{
m_buffer.resetCounter();
}
uint32_t
Assembler::actualOffset(uint32_t off_) const
{
return off_ + m_buffer.poolSizeBefore(off_);
}
uint32_t
Assembler::actualIndex(uint32_t idx_) const
{
ARMBuffer::PoolEntry pe(idx_);
return m_buffer.poolEntryOffset(pe);
}
uint8_t *
Assembler::PatchableJumpAddress(IonCode *code, uint32_t pe_)
{
return code->raw() + pe_;
}
BufferOffset
Assembler::actualOffset(BufferOffset off_) const
{
return BufferOffset(off_.getOffset() + m_buffer.poolSizeBefore(off_.getOffset()));
}
class RelocationIterator
{
CompactBufferReader reader_;
// offset in bytes
uint32_t offset_;
public:
RelocationIterator(CompactBufferReader &reader)
: reader_(reader)
{ }
bool read() {
if (!reader_.more())
return false;
offset_ = reader_.readUnsigned();
return true;
}
uint32_t offset() const {
return offset_;
}
};
template<class Iter>
const uint32_t *
Assembler::getCF32Target(Iter *iter)
{
Instruction *inst1 = iter->cur();
Instruction *inst2 = iter->next();
Instruction *inst3 = iter->next();
Instruction *inst4 = iter->next();
if (inst1->is<InstBranchImm>()) {
// see if we have a simple case, b #offset
BOffImm imm;
InstBranchImm *jumpB = inst1->as<InstBranchImm>();
jumpB->extractImm(&imm);
return imm.getDest(inst1)->raw();
}
if (inst1->is<InstMovW>() && inst2->is<InstMovT>() &&
(inst3->is<InstNOP>() || inst3->is<InstBranchReg>() || inst4->is<InstBranchReg>()))
{
// see if we have the complex case,
// movw r_temp, #imm1
// movt r_temp, #imm2
// bx r_temp
// OR
// movw r_temp, #imm1
// movt r_temp, #imm2
// str pc, [sp]
// bx r_temp
Imm16 targ_bot;
Imm16 targ_top;
Register temp;
// Extract both the temp register and the bottom immediate.
InstMovW *bottom = inst1->as<InstMovW>();
bottom->extractImm(&targ_bot);
bottom->extractDest(&temp);
// Extract the top part of the immediate.
InstMovT *top = inst2->as<InstMovT>();
top->extractImm(&targ_top);
// Make sure they are being loaded into the same register.
JS_ASSERT(top->checkDest(temp));
// Make sure we're branching to the same register.
#ifdef DEBUG
// A toggled call sometimes has a NOP instead of a branch for the third instruction.
// No way to assert that it's valid in that situation.
if (!inst3->is<InstNOP>()) {
InstBranchReg *realBranch = inst3->is<InstBranchReg>() ? inst3->as<InstBranchReg>()
: inst4->as<InstBranchReg>();
JS_ASSERT(realBranch->checkDest(temp));
}
#endif
uint32_t *dest = (uint32_t*) (targ_bot.decode() | (targ_top.decode() << 16));
return dest;
}
if (inst1->is<InstLDR>()) {
InstLDR *load = inst1->as<InstLDR>();
uint32_t inst = load->encode();
// get the address of the instruction as a raw pointer
char *dataInst = reinterpret_cast<char*>(load);
IsUp_ iu = IsUp_(inst & IsUp);
int32_t offset = inst & 0xfff;
if (iu != IsUp) {
offset = - offset;
}
uint32_t **ptr = (uint32_t **)&dataInst[offset + 8];
return *ptr;
}
JS_NOT_REACHED("unsupported branch relocation");
return NULL;
}
uintptr_t
Assembler::getPointer(uint8_t *instPtr)
{
InstructionIterator iter((Instruction*)instPtr);
uintptr_t ret = (uintptr_t)getPtr32Target(&iter, NULL, NULL);
return ret;
}
template<class Iter>
const uint32_t *
Assembler::getPtr32Target(Iter *start, Register *dest, RelocStyle *style)
{
Instruction *load1 = start->cur();
Instruction *load2 = start->next();
if (load1->is<InstMovW>() && load2->is<InstMovT>()) {
// see if we have the complex case,
// movw r_temp, #imm1
// movt r_temp, #imm2
Imm16 targ_bot;
Imm16 targ_top;
Register temp;
// Extract both the temp register and the bottom immediate.
InstMovW *bottom = load1->as<InstMovW>();
bottom->extractImm(&targ_bot);
bottom->extractDest(&temp);
// Extract the top part of the immediate.
InstMovT *top = load2->as<InstMovT>();
top->extractImm(&targ_top);
// Make sure they are being loaded intothe same register.
JS_ASSERT(top->checkDest(temp));
if (dest)
*dest = temp;
if (style)
*style = L_MOVWT;
uint32_t *value = (uint32_t*) (targ_bot.decode() | (targ_top.decode() << 16));
return value;
}
if (load1->is<InstLDR>()) {
InstLDR *load = load1->as<InstLDR>();
uint32_t inst = load->encode();
// get the address of the instruction as a raw pointer
char *dataInst = reinterpret_cast<char*>(load);
IsUp_ iu = IsUp_(inst & IsUp);
int32_t offset = inst & 0xfff;
if (iu == IsDown)
offset = - offset;
if (dest)
*dest = toRD(*load);
if (style)
*style = L_LDR;
uint32_t **ptr = (uint32_t **)&dataInst[offset + 8];
return *ptr;
}
JS_NOT_REACHED("unsupported relocation");
return NULL;
}
static IonCode *
CodeFromJump(InstructionIterator *jump)
{
uint8_t *target = (uint8_t *)Assembler::getCF32Target(jump);
return IonCode::FromExecutable(target);
}
void
Assembler::TraceJumpRelocations(JSTracer *trc, IonCode *code, CompactBufferReader &reader)
{
RelocationIterator iter(reader);
while (iter.read()) {
InstructionIterator institer((Instruction *) (code->raw() + iter.offset()));
IonCode *child = CodeFromJump(&institer);
MarkIonCodeUnbarriered(trc, &child, "rel32");
}
}
static void
TraceDataRelocations(JSTracer *trc, uint8_t *buffer, CompactBufferReader &reader)
{
while (reader.more()) {
size_t offset = reader.readUnsigned();
InstructionIterator iter((Instruction*)(buffer+offset));
void *ptr = const_cast<uint32_t *>(js::jit::Assembler::getPtr32Target(&iter));
// No barrier needed since these are constants.
gc::MarkGCThingUnbarriered(trc, reinterpret_cast<void **>(&ptr), "ion-masm-ptr");
}
}
static void
TraceDataRelocations(JSTracer *trc, ARMBuffer *buffer,
js::Vector<BufferOffset, 0, SystemAllocPolicy> *locs)
{
for (unsigned int idx = 0; idx < locs->length(); idx++) {
BufferOffset bo = (*locs)[idx];
ARMBuffer::AssemblerBufferInstIterator iter(bo, buffer);
void *ptr = const_cast<uint32_t *>(jit::Assembler::getPtr32Target(&iter));
// No barrier needed since these are constants.
gc::MarkGCThingUnbarriered(trc, reinterpret_cast<void **>(&ptr), "ion-masm-ptr");
}
}
void
Assembler::TraceDataRelocations(JSTracer *trc, IonCode *code, CompactBufferReader &reader)
{
::TraceDataRelocations(trc, code->raw(), reader);
}
void
Assembler::copyJumpRelocationTable(uint8_t *dest)
{
if (jumpRelocations_.length())
memcpy(dest, jumpRelocations_.buffer(), jumpRelocations_.length());
}
void
Assembler::copyDataRelocationTable(uint8_t *dest)
{
if (dataRelocations_.length())
memcpy(dest, dataRelocations_.buffer(), dataRelocations_.length());
}
void
Assembler::copyPreBarrierTable(uint8_t *dest)
{
if (preBarriers_.length())
memcpy(dest, preBarriers_.buffer(), preBarriers_.length());
}
void
Assembler::trace(JSTracer *trc)
{
for (size_t i = 0; i < jumps_.length(); i++) {
RelativePatch &rp = jumps_[i];
if (rp.kind == Relocation::IONCODE) {
IonCode *code = IonCode::FromExecutable((uint8_t*)rp.target);
MarkIonCodeUnbarriered(trc, &code, "masmrel32");
JS_ASSERT(code == IonCode::FromExecutable((uint8_t*)rp.target));
}
}
if (tmpDataRelocations_.length())
::TraceDataRelocations(trc, &m_buffer, &tmpDataRelocations_);
}
void
Assembler::processCodeLabels(uint8_t *rawCode)
{
for (size_t i = 0; i < codeLabels_.length(); i++) {
CodeLabel label = codeLabels_[i];
Bind(rawCode, label.dest(), rawCode + actualOffset(label.src()->offset()));
}
}
void
Assembler::writeCodePointer(AbsoluteLabel *absoluteLabel) {
JS_ASSERT(!absoluteLabel->bound());
BufferOffset off = writeInst(-1);
// x86/x64 makes general use of AbsoluteLabel and weaves a linked list of
// uses of an AbsoluteLabel through the assembly. ARM only uses labels
// for the case statements of switch jump tables. Thus, for simplicity, we
// simply treat the AbsoluteLabel as a label and bind it to the offset of
// the jump table entry that needs to be patched.
LabelBase *label = absoluteLabel;
label->bind(off.getOffset());
}
void
Assembler::Bind(uint8_t *rawCode, AbsoluteLabel *label, const void *address)
{
// See writeCodePointer comment.
uint32_t off = actualOffset(label->offset());
*reinterpret_cast<const void **>(rawCode + off) = address;
}
Assembler::Condition
Assembler::InvertCondition(Condition cond)
{
const uint32_t ConditionInversionBit = 0x10000000;
return Condition(ConditionInversionBit ^ cond);
}
Imm8::TwoImm8mData
Imm8::encodeTwoImms(uint32_t imm)
{
// In the ideal case, we are looking for a number that (in binary) looks like:
// 0b((00)*)n_1((00)*)n_2((00)*)
// left n1 mid n2
// where both n_1 and n_2 fit into 8 bits.
// since this is being done with rotates, we also need to handle the case
// that one of these numbers is in fact split between the left and right
// sides, in which case the constant will look like:
// 0bn_1a((00)*)n_2((00)*)n_1b
// n1a mid n2 rgh n1b
// also remember, values are rotated by multiples of two, and left,
// mid or right can have length zero
uint32_t imm1, imm2;
int left = (js_bitscan_clz32(imm)) & 0x1E;
uint32_t no_n1 = imm & ~(0xff << (24 - left));
// not technically needed: this case only happens if we can encode
// as a single imm8m. There is a perfectly reasonable encoding in this
// case, but we shouldn't encourage people to do things like this.
if (no_n1 == 0)
return TwoImm8mData();
int mid = ((js_bitscan_clz32(no_n1)) & 0x1E);
uint32_t no_n2 = no_n1 & ~((0xff << ((24 - mid) & 0x1f)) | 0xff >> ((8 + mid) & 0x1f));
if (no_n2 == 0) {
// we hit the easy case, no wraparound.
// note: a single constant *may* look like this.
int imm1shift = left + 8;
int imm2shift = mid + 8;
imm1 = (imm >> (32 - imm1shift)) & 0xff;
if (imm2shift >= 32) {
imm2shift = 0;
// this assert does not always hold
//assert((imm & 0xff) == no_n1);
// in fact, this would lead to some incredibly subtle bugs.
imm2 = no_n1;
} else {
imm2 = ((imm >> (32 - imm2shift)) | (imm << imm2shift)) & 0xff;
JS_ASSERT( ((no_n1 >> (32 - imm2shift)) | (no_n1 << imm2shift)) ==
imm2);
}
JS_ASSERT((imm1shift & 0x1) == 0);
JS_ASSERT((imm2shift & 0x1) == 0);
return TwoImm8mData(datastore::Imm8mData(imm1, imm1shift >> 1),
datastore::Imm8mData(imm2, imm2shift >> 1));
}
// either it wraps, or it does not fit.
// if we initially chopped off more than 8 bits, then it won't fit.
if (left >= 8)
return TwoImm8mData();
int right = 32 - (js_bitscan_clz32(no_n2) & 30);
// all remaining set bits *must* fit into the lower 8 bits
// the right == 8 case should be handled by the previous case.
if (right > 8)
return TwoImm8mData();
// make sure the initial bits that we removed for no_n1
// fit into the 8-(32-right) leftmost bits
if (((imm & (0xff << (24 - left))) << (8-right)) != 0) {
// BUT we may have removed more bits than we needed to for no_n1
// 0x04104001 e.g. we can encode 0x104 with a single op, then
// 0x04000001 with a second, but we try to encode 0x0410000
// and find that we need a second op for 0x4000, and 0x1 cannot
// be included in the encoding of 0x04100000
no_n1 = imm & ~((0xff >> (8-right)) | (0xff << (24 + right)));
mid = (js_bitscan_clz32(no_n1)) & 30;
no_n2 =
no_n1 & ~((0xff << ((24 - mid)&31)) | 0xff >> ((8 + mid)&31));
if (no_n2 != 0)
return TwoImm8mData();
}
// now assemble all of this information into a two coherent constants
// it is a rotate right from the lower 8 bits.
int imm1shift = 8 - right;
imm1 = 0xff & ((imm << imm1shift) | (imm >> (32 - imm1shift)));
JS_ASSERT ((imm1shift&~0x1e) == 0);
// left + 8 + mid is the position of the leftmost bit of n_2.
// we needed to rotate 0x000000ab right by 8 in order to get
// 0xab000000, then shift again by the leftmost bit in order to
// get the constant that we care about.
int imm2shift = mid + 8;
imm2 = ((imm >> (32 - imm2shift)) | (imm << imm2shift)) & 0xff;
JS_ASSERT((imm1shift & 0x1) == 0);
JS_ASSERT((imm2shift & 0x1) == 0);
return TwoImm8mData(datastore::Imm8mData(imm1, imm1shift >> 1),
datastore::Imm8mData(imm2, imm2shift >> 1));
}
ALUOp
jit::ALUNeg(ALUOp op, Register dest, Imm32 *imm, Register *negDest)
{
// find an alternate ALUOp to get the job done, and use a different imm.
*negDest = dest;
switch (op) {
case op_mov:
*imm = Imm32(~imm->value);
return op_mvn;
case op_mvn:
*imm = Imm32(~imm->value);
return op_mov;
case op_and:
*imm = Imm32(~imm->value);
return op_bic;
case op_bic:
*imm = Imm32(~imm->value);
return op_and;
case op_add:
*imm = Imm32(-imm->value);
return op_sub;
case op_sub:
*imm = Imm32(-imm->value);
return op_add;
case op_cmp:
*imm = Imm32(-imm->value);
return op_cmn;
case op_cmn:
*imm = Imm32(-imm->value);
return op_cmp;
case op_tst:
JS_ASSERT(dest == InvalidReg);
*imm = Imm32(~imm->value);
*negDest = ScratchRegister;
return op_bic;
// orr has orn on thumb2 only.
default:
return op_invalid;
}
}
bool
jit::can_dbl(ALUOp op)
{
// some instructions can't be processed as two separate instructions
// such as and, and possibly add (when we're setting ccodes).
// there is also some hilarity with *reading* condition codes.
// for example, adc dest, src1, 0xfff; (add with carry) can be split up
// into adc dest, src1, 0xf00; add dest, dest, 0xff, since "reading" the
// condition code increments the result by one conditionally, that only needs
// to be done on one of the two instructions.
switch (op) {
case op_bic:
case op_add:
case op_sub:
case op_eor:
case op_orr:
return true;
default:
return false;
}
}
bool
jit::condsAreSafe(ALUOp op) {
// Even when we are setting condition codes, sometimes we can
// get away with splitting an operation into two.
// for example, if our immediate is 0x00ff00ff, and the operation is eors
// we can split this in half, since x ^ 0x00ff0000 ^ 0x000000ff should
// set all of its condition codes exactly the same as x ^ 0x00ff00ff.
// However, if the operation were adds,
// we cannot split this in half. If the source on the add is
// 0xfff00ff0, the result sholud be 0xef10ef, but do we set the overflow bit
// or not? Depending on which half is performed first (0x00ff0000
// or 0x000000ff) the V bit will be set differently, and *not* updating
// the V bit would be wrong. Theoretically, the following should work
// adds r0, r1, 0x00ff0000;
// addsvs r0, r1, 0x000000ff;
// addvc r0, r1, 0x000000ff;
// but this is 3 instructions, and at that point, we might as well use
// something else.
switch(op) {
case op_bic:
case op_orr:
case op_eor:
return true;
default:
return false;
}
}
ALUOp
jit::getDestVariant(ALUOp op)
{
// all of the compare operations are dest-less variants of a standard
// operation. Given the dest-less variant, return the dest-ful variant.
switch (op) {
case op_cmp:
return op_sub;
case op_cmn:
return op_add;
case op_tst:
return op_and;
case op_teq:
return op_eor;
default:
return op;
}
}
O2RegImmShift
jit::O2Reg(Register r) {
return O2RegImmShift(r, LSL, 0);
}
O2RegImmShift
jit::lsl(Register r, int amt)
{
JS_ASSERT(0 <= amt && amt <= 31);
return O2RegImmShift(r, LSL, amt);
}
O2RegImmShift
jit::lsr(Register r, int amt)
{
JS_ASSERT(1 <= amt && amt <= 32);
return O2RegImmShift(r, LSR, amt);
}
O2RegImmShift
jit::ror(Register r, int amt)
{
JS_ASSERT(1 <= amt && amt <= 31);
return O2RegImmShift(r, ROR, amt);
}
O2RegImmShift
jit::rol(Register r, int amt)
{
JS_ASSERT(1 <= amt && amt <= 31);
return O2RegImmShift(r, ROR, 32 - amt);
}
O2RegImmShift
jit::asr (Register r, int amt)
{
JS_ASSERT(1 <= amt && amt <= 32);
return O2RegImmShift(r, ASR, amt);
}
O2RegRegShift
jit::lsl(Register r, Register amt)
{
return O2RegRegShift(r, LSL, amt);
}
O2RegRegShift
jit::lsr(Register r, Register amt)
{
return O2RegRegShift(r, LSR, amt);
}
O2RegRegShift
jit::ror(Register r, Register amt)
{
return O2RegRegShift(r, ROR, amt);
}
O2RegRegShift
jit::asr (Register r, Register amt)
{
return O2RegRegShift(r, ASR, amt);
}
js::jit::VFPImm::VFPImm(uint32_t top)
{
data = -1;
datastore::Imm8VFPImmData tmp;
if (DoubleEncoder::lookup(top, &tmp))
data = tmp.encode();
}
BOffImm::BOffImm(Instruction &inst)
: data(inst.encode() & 0x00ffffff)
{
}
Instruction *
BOffImm::getDest(Instruction *src)
{
// TODO: It is probably worthwhile to verify that src is actually a branch
// NOTE: This does not explicitly shift the offset of the destination left by 2,
// since it is indexing into an array of instruction sized objects.
return &src[(((int32_t)data<<8)>>8) + 2];
}
js::jit::DoubleEncoder js::jit::DoubleEncoder::_this;
//VFPRegister implementation
VFPRegister
VFPRegister::doubleOverlay()
{
JS_ASSERT(!_isInvalid);
if (kind != Double)
return VFPRegister(_code >> 1, Double);
return *this;
}
VFPRegister
VFPRegister::singleOverlay()
{
JS_ASSERT(!_isInvalid);
if (kind == Double) {
// There are no corresponding float registers for d16-d31
ASSERT(_code < 16);
return VFPRegister(_code << 1, Single);
}
return VFPRegister(_code, Single);
}
VFPRegister
VFPRegister::sintOverlay()
{
JS_ASSERT(!_isInvalid);
if (kind == Double) {
// There are no corresponding float registers for d16-d31
ASSERT(_code < 16);
return VFPRegister(_code << 1, Int);
}
return VFPRegister(_code, Int);
}
VFPRegister
VFPRegister::uintOverlay()
{
JS_ASSERT(!_isInvalid);
if (kind == Double) {
// There are no corresponding float registers for d16-d31
ASSERT(_code < 16);
return VFPRegister(_code << 1, UInt);
}
return VFPRegister(_code, UInt);
}
bool
VFPRegister::isInvalid()
{
return _isInvalid;
}
bool
VFPRegister::isMissing()
{
JS_ASSERT(!_isInvalid);
return _isMissing;
}
bool
Assembler::oom() const
{
return m_buffer.oom() ||
!enoughMemory_ ||
jumpRelocations_.oom() ||
dataRelocations_.oom() ||
preBarriers_.oom();
}
bool
Assembler::addCodeLabel(CodeLabel label)
{
return codeLabels_.append(label);
}
// Size of the instruction stream, in bytes. Including pools. This function expects
// all pools that need to be placed have been placed. If they haven't then we
// need to go an flush the pools :(
size_t
Assembler::size() const
{
return m_buffer.size();
}
// Size of the relocation table, in bytes.
size_t
Assembler::jumpRelocationTableBytes() const
{
return jumpRelocations_.length();
}
size_t
Assembler::dataRelocationTableBytes() const
{
return dataRelocations_.length();
}
size_t
Assembler::preBarrierTableBytes() const
{
return preBarriers_.length();
}
// Size of the data table, in bytes.
size_t
Assembler::bytesNeeded() const
{
return size() +
jumpRelocationTableBytes() +
dataRelocationTableBytes() +
preBarrierTableBytes();
}
// write a blob of binary into the instruction stream
BufferOffset
Assembler::writeInst(uint32_t x, uint32_t *dest)
{
if (dest == NULL)
return m_buffer.putInt(x);
writeInstStatic(x, dest);
return BufferOffset();
}
void
Assembler::writeInstStatic(uint32_t x, uint32_t *dest)
{
JS_ASSERT(dest != NULL);
*dest = x;
}
BufferOffset
Assembler::align(int alignment)
{
BufferOffset ret;
if (alignment == 8) {
while (!m_buffer.isAligned(alignment)) {
BufferOffset tmp = as_nop();
if (!ret.assigned())
ret = tmp;
}
} else {
flush();
JS_ASSERT((alignment & (alignment - 1)) == 0);
while (size() & (alignment-1)) {
BufferOffset tmp = as_nop();
if (!ret.assigned())
ret = tmp;
}
}
return ret;
}
BufferOffset
Assembler::as_nop()
{
return writeInst(0xe320f000);
}
BufferOffset
Assembler::as_alu(Register dest, Register src1, Operand2 op2,
ALUOp op, SetCond_ sc, Condition c, Instruction *instdest)
{
return writeInst((int)op | (int)sc | (int) c | op2.encode() |
((dest == InvalidReg) ? 0 : RD(dest)) |
((src1 == InvalidReg) ? 0 : RN(src1)), (uint32_t*)instdest);
}
BufferOffset
Assembler::as_mov(Register dest, Operand2 op2, SetCond_ sc, Condition c, Instruction *instdest)
{
return as_alu(dest, InvalidReg, op2, op_mov, sc, c, instdest);
}
BufferOffset
Assembler::as_mvn(Register dest, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, InvalidReg, op2, op_mvn, sc, c);
}
// Logical operations.
BufferOffset
Assembler::as_and(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_and, sc, c);
}
BufferOffset
Assembler::as_bic(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_bic, sc, c);
}
BufferOffset
Assembler::as_eor(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_eor, sc, c);
}
BufferOffset
Assembler::as_orr(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_orr, sc, c);
}
// Mathematical operations.
BufferOffset
Assembler::as_adc(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_adc, sc, c);
}
BufferOffset
Assembler::as_add(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_add, sc, c);
}
BufferOffset
Assembler::as_sbc(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_sbc, sc, c);
}
BufferOffset
Assembler::as_sub(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_sub, sc, c);
}
BufferOffset
Assembler::as_rsb(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_rsb, sc, c);
}
BufferOffset
Assembler::as_rsc(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c)
{
return as_alu(dest, src1, op2, op_rsc, sc, c);
}
// Test operations.
BufferOffset
Assembler::as_cmn(Register src1, Operand2 op2, Condition c)
{
return as_alu(InvalidReg, src1, op2, op_cmn, SetCond, c);
}
BufferOffset
Assembler::as_cmp(Register src1, Operand2 op2, Condition c)
{
return as_alu(InvalidReg, src1, op2, op_cmp, SetCond, c);
}
BufferOffset
Assembler::as_teq(Register src1, Operand2 op2, Condition c)
{
return as_alu(InvalidReg, src1, op2, op_teq, SetCond, c);
}
BufferOffset
Assembler::as_tst(Register src1, Operand2 op2, Condition c)
{
return as_alu(InvalidReg, src1, op2, op_tst, SetCond, c);
}
// 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
Assembler::as_movw(Register dest, Imm16 imm, Condition c, Instruction *pos)
{
JS_ASSERT(hasMOVWT());
return writeInst(0x03000000 | c | imm.encode() | RD(dest), (uint32_t*)pos);
}
BufferOffset
Assembler::as_movt(Register dest, Imm16 imm, Condition c, Instruction *pos)
{
JS_ASSERT(hasMOVWT());
return writeInst(0x03400000 | c | imm.encode() | RD(dest), (uint32_t*)pos);
}
const int mull_tag = 0x90;
BufferOffset
Assembler::as_genmul(Register dhi, Register dlo, Register rm, Register rn,
MULOp op, SetCond_ sc, Condition c)
{
return writeInst(RN(dhi) | maybeRD(dlo) | RM(rm) | rn.code() | op | sc | c | mull_tag);
}
BufferOffset
Assembler::as_mul(Register dest, Register src1, Register src2, SetCond_ sc, Condition c)
{
return as_genmul(dest, InvalidReg, src1, src2, opm_mul, sc, c);
}
BufferOffset
Assembler::as_mla(Register dest, Register acc, Register src1, Register src2,
SetCond_ sc, Condition c)
{
return as_genmul(dest, acc, src1, src2, opm_mla, sc, c);
}
BufferOffset
Assembler::as_umaal(Register destHI, Register destLO, Register src1, Register src2, Condition c)
{
return as_genmul(destHI, destLO, src1, src2, opm_umaal, NoSetCond, c);
}
BufferOffset
Assembler::as_mls(Register dest, Register acc, Register src1, Register src2, Condition c)
{
return as_genmul(dest, acc, src1, src2, opm_mls, NoSetCond, c);
}
BufferOffset
Assembler::as_umull(Register destHI, Register destLO, Register src1, Register src2,
SetCond_ sc, Condition c)
{
return as_genmul(destHI, destLO, src1, src2, opm_umull, sc, c);
}
BufferOffset
Assembler::as_umlal(Register destHI, Register destLO, Register src1, Register src2,
SetCond_ sc, Condition c)
{
return as_genmul(destHI, destLO, src1, src2, opm_umlal, sc, c);
}
BufferOffset
Assembler::as_smull(Register destHI, Register destLO, Register src1, Register src2,
SetCond_ sc, Condition c)
{
return as_genmul(destHI, destLO, src1, src2, opm_smull, sc, c);
}
BufferOffset
Assembler::as_smlal(Register destHI, Register destLO, Register src1, Register src2,
SetCond_ sc, Condition c)
{
return as_genmul(destHI, destLO, src1, src2, opm_smlal, sc, c);
}
// Data transfer instructions: ldr, str, ldrb, strb.
// Using an int to differentiate between 8 bits and 32 bits is
// overkill, but meh
BufferOffset
Assembler::as_dtr(LoadStore ls, int size, Index mode,
Register rt, DTRAddr addr, Condition c, uint32_t *dest)
{
JS_ASSERT (mode == Offset || (rt != addr.getBase() && pc != addr.getBase()));
JS_ASSERT(size == 32 || size == 8);
return writeInst( 0x04000000 | ls | (size == 8 ? 0x00400000 : 0) | mode | c |
RT(rt) | addr.encode(), dest);
}
class PoolHintData {
public:
enum LoadType {
// set 0 to bogus, since that is the value most likely to be
// accidentally left somewhere.
poolBOGUS = 0,
poolDTR = 1,
poolBranch = 2,
poolVDTR = 3
};
private:
uint32_t index : 17;
uint32_t cond : 4;
LoadType loadType : 2;
uint32_t destReg : 5;
uint32_t ONES : 4;
public:
void init(uint32_t index_, Assembler::Condition cond_, LoadType lt, const Register &destReg_) {
index = index_;
JS_ASSERT(index == index_);
cond = cond_ >> 28;
JS_ASSERT(cond == cond_ >> 28);
loadType = lt;
ONES = 0xfu;
destReg = destReg_.code();
}
void init(uint32_t index_, Assembler::Condition cond_, LoadType lt, const VFPRegister &destReg_) {
index = index_;
JS_ASSERT(index == index_);
cond = cond_ >> 28;
JS_ASSERT(cond == cond_ >> 28);
loadType = lt;
ONES = 0xfu;
destReg = destReg_.code();
}
Assembler::Condition getCond() {
return Assembler::Condition(cond << 28);
}
Register getReg() {
return Register::FromCode(destReg);
}
VFPRegister getVFPReg() {
return VFPRegister(FloatRegister::FromCode(destReg));
}
int32_t getIndex() {
return index;
}
void setIndex(uint32_t index_) {
JS_ASSERT(ONES == 0xf && loadType != poolBOGUS);
index = index_;
JS_ASSERT(index == index_);
}
LoadType getLoadType() {
// If this *was* a poolBranch, but the branch has already been bound
// then this isn't going to look like a real poolhintdata, but we still
// want to lie about it so everyone knows it *used* to be a branch.
if (ONES != 0xf)
return PoolHintData::poolBranch;
return loadType;
}
bool isValidPoolHint() {
// Most instructions cannot have a condition that is 0xf. Notable exceptions are
// blx and the entire NEON instruction set. For the purposes of pool loads, and
// possibly patched branches, the possible instructions are ldr and b, neither of
// which can have a condition code of 0xf.
return ONES == 0xf;
}
};
union PoolHintPun {
PoolHintData phd;
uint32_t raw;
};
// Handles all of the other integral data transferring functions:
// ldrsb, ldrsh, ldrd, etc.
// size is given in bits.
BufferOffset
Assembler::as_extdtr(LoadStore ls, int size, bool IsSigned, Index mode,
Register rt, EDtrAddr addr, Condition c, uint32_t *dest)
{
int extra_bits2 = 0;
int extra_bits1 = 0;
switch(size) {
case 8:
JS_ASSERT(IsSigned);
JS_ASSERT(ls!=IsStore);
extra_bits1 = 0x1;
extra_bits2 = 0x2;
break;
case 16:
//case 32:
// doesn't need to be handled-- it is handled by the default ldr/str
extra_bits2 = 0x01;
extra_bits1 = (ls == IsStore) ? 0 : 1;
if (IsSigned) {
JS_ASSERT(ls != IsStore);
extra_bits2 |= 0x2;
}
break;
case 64:
extra_bits2 = (ls == IsStore) ? 0x3 : 0x2;
extra_bits1 = 0;
break;
default:
JS_NOT_REACHED("SAY WHAT?");
}
return writeInst(extra_bits2 << 5 | extra_bits1 << 20 | 0x90 |
addr.encode() | RT(rt) | mode | c, dest);
}
BufferOffset
Assembler::as_dtm(LoadStore ls, Register rn, uint32_t mask,
DTMMode mode, DTMWriteBack wb, Condition c)
{
return writeInst(0x08000000 | RN(rn) | ls |
mode | mask | c | wb);
}
BufferOffset
Assembler::as_Imm32Pool(Register dest, uint32_t value, ARMBuffer::PoolEntry *pe, Condition c)
{
PoolHintPun php;
php.phd.init(0, c, PoolHintData::poolDTR, dest);
return m_buffer.insertEntry(4, (uint8_t*)&php.raw, int32Pool, (uint8_t*)&value, pe);
}
void
Assembler::as_WritePoolEntry(Instruction *addr, Condition c, uint32_t data)
{
JS_ASSERT(addr->is<InstLDR>());
int32_t offset = addr->encode() & 0xfff;
if ((addr->encode() & IsUp) != IsUp)
offset = -offset;
char * rawAddr = reinterpret_cast<char*>(addr);
uint32_t * dest = reinterpret_cast<uint32_t*>(&rawAddr[offset + 8]);
*dest = data;
Condition orig_cond;
addr->extractCond(&orig_cond);
JS_ASSERT(orig_cond == c);
}
BufferOffset
Assembler::as_BranchPool(uint32_t value, RepatchLabel *label, ARMBuffer::PoolEntry *pe, Condition c)
{
PoolHintPun php;
BufferOffset next = nextOffset();
php.phd.init(0, c, PoolHintData::poolBranch, pc);
m_buffer.markNextAsBranch();
BufferOffset ret = m_buffer.insertEntry(4, (uint8_t*)&php.raw, int32Pool, (uint8_t*)&value, pe);
// If this label is already bound, then immediately replace the stub load with
// a correct branch.
if (label->bound()) {
BufferOffset dest(label);
as_b(dest.diffB<BOffImm>(next), c, next);
} else {
label->use(next.getOffset());
}
return ret;
}
BufferOffset
Assembler::as_FImm64Pool(VFPRegister dest, double value, ARMBuffer::PoolEntry *pe, Condition c)
{
JS_ASSERT(dest.isDouble());
PoolHintPun php;
php.phd.init(0, c, PoolHintData::poolVDTR, dest);
return m_buffer.insertEntry(4, (uint8_t*)&php.raw, doublePool, (uint8_t*)&value, pe);
}
// Pool callbacks stuff:
void
Assembler::insertTokenIntoTag(uint32_t instSize, uint8_t *load_, int32_t token)
{
uint32_t *load = (uint32_t*) load_;
PoolHintPun php;
php.raw = *load;
php.phd.setIndex(token);
*load = php.raw;
}
// patchConstantPoolLoad takes the address of the instruction that wants to be patched, and
//the address of the start of the constant pool, and figures things out from there.
bool
Assembler::patchConstantPoolLoad(void* loadAddr, void* constPoolAddr)
{
PoolHintData data = *(PoolHintData*)loadAddr;
uint32_t *instAddr = (uint32_t*) loadAddr;
int offset = (char *)constPoolAddr - (char *)loadAddr;
switch(data.getLoadType()) {
case PoolHintData::poolBOGUS:
JS_NOT_REACHED("bogus load type!");
case PoolHintData::poolDTR:
dummy->as_dtr(IsLoad, 32, Offset, data.getReg(),
DTRAddr(pc, DtrOffImm(offset+4*data.getIndex() - 8)), data.getCond(), instAddr);
break;
case PoolHintData::poolBranch:
// Either this used to be a poolBranch, and the label was already bound, so it was
// replaced with a real branch, or this may happen in the future.
// If this is going to happen in the future, then the actual bits that are written here
// don't matter (except the condition code, since that is always preserved across
// patchings) but if it does not get bound later,
// then we want to make sure this is a load from the pool entry (and the pool entry
// should be NULL so it will crash).
if (data.isValidPoolHint()) {
dummy->as_dtr(IsLoad, 32, Offset, pc,
DTRAddr(pc, DtrOffImm(offset+4*data.getIndex() - 8)),
data.getCond(), instAddr);
}
break;
case PoolHintData::poolVDTR:
if ((offset + (8 * data.getIndex()) - 8) < -1023 ||
(offset + (8 * data.getIndex()) - 8) > 1023)
{
return false;
}
dummy->as_vdtr(IsLoad, data.getVFPReg(),
VFPAddr(pc, VFPOffImm(offset+8*data.getIndex() - 8)), data.getCond(), instAddr);
break;
}
return true;
}
uint32_t
Assembler::placeConstantPoolBarrier(int offset)
{
// BUG: 700526
// this is still an active path, however, we do not hit it in the test
// suite at all.
JS_NOT_REACHED("ARMAssembler holdover");
#if 0
offset = (offset - sizeof(ARMWord)) >> 2;
ASSERT((offset <= BOFFSET_MAX && offset >= BOFFSET_MIN));
return AL | B | (offset & BRANCH_MASK);
#endif
return -1;
}
// Control flow stuff:
// bx can *only* branch to a register
// never to an immediate.
BufferOffset
Assembler::as_bx(Register r, Condition c, bool isPatchable)
{
BufferOffset ret = writeInst(((int) c) | op_bx | r.code());
if (c == Always && !isPatchable)
m_buffer.markGuard();
return ret;
}
void
Assembler::writePoolGuard(BufferOffset branch, Instruction *dest, BufferOffset afterPool)
{
BOffImm off = afterPool.diffB<BOffImm>(branch);
*dest = InstBImm(off, Always);
}
// Branch can branch to an immediate *or* to a register.
// Branches to immediates are pc relative, branches to registers
// are absolute
BufferOffset
Assembler::as_b(BOffImm off, Condition c, bool isPatchable)
{
m_buffer.markNextAsBranch();
BufferOffset ret =writeInst(((int)c) | op_b | off.encode());
if (c == Always && !isPatchable)
m_buffer.markGuard();
return ret;
}
BufferOffset
Assembler::as_b(Label *l, Condition c, bool isPatchable)
{
if (m_buffer.oom()) {
BufferOffset ret;
return ret;
}
m_buffer.markNextAsBranch();
if (l->bound()) {
BufferOffset ret = as_nop();
as_b(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
return ret;
}
int32_t old;
BufferOffset ret;
if (l->used()) {
old = l->offset();
// This will currently throw an assertion if we couldn't actually
// encode the offset of the branch.
ret = as_b(BOffImm(old), c, isPatchable);
} else {
old = LabelBase::INVALID_OFFSET;
BOffImm inv;
ret = as_b(inv, c, isPatchable);
}
int32_t check = l->use(ret.getOffset());
JS_ASSERT(check == old);
return ret;
}
BufferOffset
Assembler::as_b(BOffImm off, Condition c, BufferOffset inst)
{
*editSrc(inst) = InstBImm(off, c);
return inst;
}
// blx can go to either an immediate or a register.
// When blx'ing to a register, we change processor state
// depending on the low bit of the register
// when blx'ing to an immediate, we *always* change processor state.
BufferOffset
Assembler::as_blx(Register r, Condition c)
{
return writeInst(((int) c) | op_blx | r.code());
}
// bl can only branch to an pc-relative immediate offset
// It cannot change the processor state.
BufferOffset
Assembler::as_bl(BOffImm off, Condition c)
{
m_buffer.markNextAsBranch();
return writeInst(((int)c) | op_bl | off.encode());
}
BufferOffset
Assembler::as_bl(Label *l, Condition c)
{
if (m_buffer.oom()) {
BufferOffset ret;
return ret;
}
//as_bkpt();
m_buffer.markNextAsBranch();
if (l->bound()) {
BufferOffset ret = as_nop();
as_bl(BufferOffset(l).diffB<BOffImm>(ret), c, ret);
return ret;
}
int32_t old;
BufferOffset ret;
// See if the list was empty :(
if (l->used()) {
// This will currently throw an assertion if we couldn't actually
// encode the offset of the branch.
old = l->offset();
ret = as_bl(BOffImm(old), c);
} else {
old = LabelBase::INVALID_OFFSET;
BOffImm inv;
ret = as_bl(inv, c);
}
int32_t check = l->use(ret.getOffset());
JS_ASSERT(check == old);
return ret;
}
BufferOffset
Assembler::as_bl(BOffImm off, Condition c, BufferOffset inst)
{
*editSrc(inst) = InstBLImm(off, c);
return inst;
}
BufferOffset
Assembler::as_mrs(Register r, Condition c)
{
return writeInst(0x010f0000 | int(c) | RD(r));
}
BufferOffset
Assembler::as_msr(Register r, Condition c)
{
// hardcode the 'mask' field to 0b11 for now. it is bits 18 and 19, which are the two high bits of the 'c' in this constant.
JS_ASSERT((r.code() & ~0xf) == 0);
return writeInst(0x012cf000 | int(c) | r.code());
}
// VFP instructions!
enum vfp_tags {
vfp_tag = 0x0C000A00,
vfp_arith = 0x02000000
};
BufferOffset
Assembler::writeVFPInst(vfp_size sz, uint32_t blob, uint32_t *dest)
{
JS_ASSERT((sz & blob) == 0);
JS_ASSERT((vfp_tag & blob) == 0);
return writeInst(vfp_tag | sz | blob, dest);
}
// Unityped variants: all registers hold the same (ieee754 single/double)
// notably not included are vcvt; vmov vd, #imm; vmov rt, vn.
BufferOffset
Assembler::as_vfp_float(VFPRegister vd, VFPRegister vn, VFPRegister vm,
VFPOp op, Condition c)
{
// Make sure we believe that all of our operands are the same kind
JS_ASSERT(vd.equiv(vn) && vd.equiv(vm));
vfp_size sz = vd.isDouble() ? isDouble : isSingle;
return writeVFPInst(sz, VD(vd) | VN(vn) | VM(vm) | op | vfp_arith | c);
}
BufferOffset
Assembler::as_vadd(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c)
{
return as_vfp_float(vd, vn, vm, opv_add, c);
}
BufferOffset
Assembler::as_vdiv(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c)
{
return as_vfp_float(vd, vn, vm, opv_div, c);
}
BufferOffset
Assembler::as_vmul(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c)
{
return as_vfp_float(vd, vn, vm, opv_mul, c);
}
BufferOffset
Assembler::as_vnmul(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c)
{
return as_vfp_float(vd, vn, vm, opv_mul, c);
JS_NOT_REACHED("Feature NYI");
}
BufferOffset
Assembler::as_vnmla(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c)
{
JS_NOT_REACHED("Feature NYI");
return BufferOffset();
}
BufferOffset
Assembler::as_vnmls(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c)
{
JS_NOT_REACHED("Feature NYI");
return BufferOffset();
}
BufferOffset
Assembler::as_vneg(VFPRegister vd, VFPRegister vm, Condition c)
{
return as_vfp_float(vd, NoVFPRegister, vm, opv_neg, c);
}
BufferOffset
Assembler::as_vsqrt(VFPRegister vd, VFPRegister vm, Condition c)
{
return as_vfp_float(vd, NoVFPRegister, vm, opv_sqrt, c);
}
BufferOffset
Assembler::as_vabs(VFPRegister vd, VFPRegister vm, Condition c)
{
return as_vfp_float(vd, NoVFPRegister, vm, opv_abs, c);
}
BufferOffset
Assembler::as_vsub(VFPRegister vd, VFPRegister vn, VFPRegister vm,
Condition c)
{
return as_vfp_float(vd, vn, vm, opv_sub, c);
}
BufferOffset
Assembler::as_vcmp(VFPRegister vd, VFPRegister vm,
Condition c)
{
return as_vfp_float(vd, NoVFPRegister, vm, opv_cmp, c);
}
BufferOffset
Assembler::as_vcmpz(VFPRegister vd, Condition c)
{
return as_vfp_float(vd, NoVFPRegister, NoVFPRegister, opv_cmpz, c);
}
// Specifically, a move between two same sized-registers.
BufferOffset
Assembler::as_vmov(VFPRegister vd, VFPRegister vsrc, Condition c)
{
return as_vfp_float(vd, NoVFPRegister, vsrc, opv_mov, c);
}
//xfer between Core and VFP
// 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
Assembler::as_vxfer(Register vt1, Register vt2, VFPRegister vm, FloatToCore_ f2c,
Condition c, int idx)
{
vfp_size sz = isSingle;
if (vm.isDouble()) {
// Technically, this can be done with a vmov à la ARM ARM under vmov
// however, that requires at least an extra bit saying if the
// operation should be performed on the lower or upper half of the
// double. Moving a single to/from 2N/2N+1 isn't equivalent,
// since there are 32 single registers, and 32 double registers
// so there is no way to encode the last 16 double registers.
sz = isDouble;
JS_ASSERT(idx == 0 || idx == 1);
// If we are transferring a single half of the double
// then it must be moving a VFP reg to a core reg.
if (vt2 == InvalidReg)
JS_ASSERT(f2c == FloatToCore);
idx = idx << 21;
} else {
JS_ASSERT(idx == 0);
}
VFPXferSize xfersz = WordTransfer;
uint32_t (*encodeVFP)(VFPRegister) = VN;
if (vt2 != InvalidReg) {
// We are doing a 64 bit transfer.
xfersz = DoubleTransfer;
encodeVFP = VM;
}
return writeVFPInst(sz, xfersz | f2c | c |
RT(vt1) | maybeRN(vt2) | encodeVFP(vm) | idx);
}
enum vcvt_destFloatness {
toInteger = 1 << 18,
toFloat = 0 << 18
};
enum vcvt_toZero {
toZero = 1 << 7, // use the default rounding mode, which rounds truncates
toFPSCR = 0 << 7 // use whatever rounding mode the fpscr specifies
};
enum vcvt_Signedness {
toSigned = 1 << 16,
toUnsigned = 0 << 16,
fromSigned = 1 << 7,
fromUnsigned = 0 << 7
};
// 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
Assembler::as_vcvt(VFPRegister vd, VFPRegister vm, bool useFPSCR,
Condition c)
{
// Unlike other cases, the source and dest types cannot be the same
JS_ASSERT(!vd.equiv(vm));
vfp_size sz = isDouble;
if (vd.isFloat() && vm.isFloat()) {
// Doing a float -> float conversion
if (vm.isSingle())
sz = isSingle;
return writeVFPInst(sz, c | 0x02B700C0 |
VM(vm) | VD(vd));
}
// At least one of the registers should be a float.
vcvt_destFloatness destFloat;
vcvt_Signedness opSign;
vcvt_toZero doToZero = toFPSCR;
JS_ASSERT(vd.isFloat() || vm.isFloat());
if (vd.isSingle() || vm.isSingle()) {
sz = isSingle;
}
if (vd.isFloat()) {
destFloat = toFloat;
opSign = (vm.isSInt()) ? fromSigned : fromUnsigned;
} else {
destFloat = toInteger;
opSign = (vd.isSInt()) ? toSigned : toUnsigned;
doToZero = useFPSCR ? toFPSCR : toZero;
}
return writeVFPInst(sz, c | 0x02B80040 | VD(vd) | VM(vm) | destFloat | opSign | doToZero);
}
BufferOffset
Assembler::as_vcvtFixed(VFPRegister vd, bool isSigned, uint32_t fixedPoint, bool toFixed, Condition c)
{
JS_ASSERT(vd.isFloat());
uint32_t sx = 0x1;
vfp_size sf = vd.isDouble() ? isDouble : isSingle;
int32_t imm5 = fixedPoint;
imm5 = (sx ? 32 : 16) - imm5;
JS_ASSERT(imm5 >= 0);
imm5 = imm5 >> 1 | (imm5 & 1) << 6;
return writeVFPInst(sf, 0x02BA0040 | VD(vd) | toFixed << 18 | sx << 7 |
(!isSigned) << 16 | imm5 | c);
}
// xfer between VFP and memory
BufferOffset
Assembler::as_vdtr(LoadStore ls, VFPRegister vd, VFPAddr addr,
Condition c /* vfp doesn't have a wb option*/,
uint32_t *dest)
{
vfp_size sz = vd.isDouble() ? isDouble : isSingle;
return writeVFPInst(sz, ls | 0x01000000 | addr.encode() | VD(vd) | c, dest);
}
// VFP's ldm/stm work differently from the standard arm ones.
// You can only transfer a range
BufferOffset
Assembler::as_vdtm(LoadStore st, Register rn, VFPRegister vd, int length,
/*also has update conditions*/Condition c)
{
JS_ASSERT(length <= 16 && length >= 0);
vfp_size sz = vd.isDouble() ? isDouble : isSingle;
if (vd.isDouble())
length *= 2;
return writeVFPInst(sz, dtmLoadStore | RN(rn) | VD(vd) |
length |
dtmMode | dtmUpdate | dtmCond);
}
BufferOffset
Assembler::as_vimm(VFPRegister vd, VFPImm imm, Condition c)
{
vfp_size sz = vd.isDouble() ? isDouble : isSingle;
// Don't know how to handle this right now.
if (!vd.isDouble())
JS_NOT_REACHED("non-double immediate");
return writeVFPInst(sz, c | imm.encode() | VD(vd) | 0x02B00000);
}
BufferOffset
Assembler::as_vmrs(Register r, Condition c)
{
return writeInst(c | 0x0ef10a10 | RT(r));
}
BufferOffset
Assembler::as_vmsr(Register r, Condition c)
{
return writeInst(c | 0x0ee10a10 | RT(r));
}
bool
Assembler::nextLink(BufferOffset b, BufferOffset *next)
{
Instruction branch = *editSrc(b);
JS_ASSERT(branch.is<InstBranchImm>());
BOffImm destOff;
branch.as<InstBranchImm>()->extractImm(&destOff);
if (destOff.isInvalid())
return false;
// Propagate the next link back to the caller, by
// constructing a new BufferOffset into the space they
// provided.
new (next) BufferOffset(destOff.decode());
return true;
}
void
Assembler::bind(Label *label, BufferOffset boff)
{
if (label->used()) {
bool more;
// If our caller didn't give us an explicit target to bind to
// then we want to bind to the location of the next instruction
BufferOffset dest = boff.assigned() ? boff : nextOffset();
BufferOffset b(label);
do {
BufferOffset next;
more = nextLink(b, &next);
Instruction branch = *editSrc(b);
Condition c;
branch.extractCond(&c);
if (branch.is<InstBImm>())
as_b(dest.diffB<BOffImm>(b), c, b);
else if (branch.is<InstBLImm>())
as_bl(dest.diffB<BOffImm>(b), c, b);
else
JS_NOT_REACHED("crazy fixup!");
b = next;
} while (more);
}
label->bind(nextOffset().getOffset());
}
void
Assembler::bind(RepatchLabel *label)
{
BufferOffset dest = nextOffset();
if (label->used()) {
// If the label has a use, then change this use to refer to
// the bound label;
BufferOffset branchOff(label->offset());
// Since this was created with a RepatchLabel, the value written in the
// instruction stream is not branch shaped, it is PoolHintData shaped.
Instruction *branch = editSrc(branchOff);
PoolHintPun p;
p.raw = branch->encode();
Condition cond;
if (p.phd.isValidPoolHint())
cond = p.phd.getCond();
else
branch->extractCond(&cond);
as_b(dest.diffB<BOffImm>(branchOff), cond, branchOff);
}
label->bind(dest.getOffset());
}
void
Assembler::retarget(Label *label, Label *target)
{
if (label->used()) {
if (target->bound()) {
bind(label, BufferOffset(target));
} else if (target->used()) {
// The target is not bound but used. Prepend label's branch list
// onto target's.
bool more;
BufferOffset labelBranchOffset(label);
BufferOffset next;
// Find the head of the use chain for label.
while (nextLink(labelBranchOffset, &next))
labelBranchOffset = next;
// Then patch the head of label's use chain to the tail of
// target's use chain, prepending the entire use chain of target.
Instruction branch = *editSrc(labelBranchOffset);
Condition c;
branch.extractCond(&c);
int32_t prev = target->use(label->offset());
if (branch.is<InstBImm>())
as_b(BOffImm(prev), c, labelBranchOffset);
else if (branch.is<InstBLImm>())
as_bl(BOffImm(prev), c, labelBranchOffset);
else
JS_NOT_REACHED("crazy fixup!");
} else {
// The target is unbound and unused. We can just take the head of
// the list hanging off of label, and dump that into target.
DebugOnly<uint32_t> prev = target->use(label->offset());
JS_ASSERT((int32_t)prev == Label::INVALID_OFFSET);
}
}
label->reset();
}
void dbg_break() {}
static int stopBKPT = -1;
void
Assembler::as_bkpt()
{
// This is a count of how many times a breakpoint instruction has been generated.
// It is embedded into the instruction for debugging purposes. gdb will print "bkpt xxx"
// when you attempt to dissassemble a breakpoint with the number xxx embedded into it.
// If this breakpoint is being hit, then you can run (in gdb)
// >b dbg_break
// >b main
// >commands
// >set stopBKPT = xxx
// >c
// >end
// which will set a breakpoint on the function dbg_break above
// set a scripted breakpoint on main that will set the (otherwise unmodified)
// value to the number of the breakpoint, so dbg_break will actuall be called
// and finally, when you run the executable, execution will halt when that
// breakpoint is generated
static int hit = 0;
if (stopBKPT == hit)
dbg_break();
writeInst(0xe1200070 | (hit & 0xf) | ((hit & 0xfff0)<<4));
hit++;
}
void
Assembler::dumpPool()
{
m_buffer.flushPool();
}
void
Assembler::flushBuffer()
{
m_buffer.flushPool();
}
void
Assembler::enterNoPool()
{
m_buffer.enterNoPool();
}
void
Assembler::leaveNoPool()
{
m_buffer.leaveNoPool();
}
ptrdiff_t
Assembler::getBranchOffset(const Instruction *i_)
{
if (!i_->is<InstBranchImm>())
return 0;
InstBranchImm *i = i_->as<InstBranchImm>();
BOffImm dest;
i->extractImm(&dest);
return dest.decode();
}
void
Assembler::retargetNearBranch(Instruction *i, int offset, bool final)
{
Assembler::Condition c;
i->extractCond(&c);
retargetNearBranch(i, offset, c, final);
}
void
Assembler::retargetNearBranch(Instruction *i, int offset, Condition cond, bool final)
{
// Retargeting calls is totally unsupported!
JS_ASSERT_IF(i->is<InstBranchImm>(), i->is<InstBImm>() || i->is<InstBLImm>());
if (i->is<InstBLImm>())
new (i) InstBLImm(BOffImm(offset), cond);
else
new (i) InstBImm(BOffImm(offset), cond);
// Flush the cache, since an instruction was overwritten
if (final)
AutoFlushCache::updateTop(uintptr_t(i), 4);
}
void
Assembler::retargetFarBranch(Instruction *i, uint8_t **slot, uint8_t *dest, Condition cond)
{
int32_t offset = reinterpret_cast<uint8_t*>(slot) - reinterpret_cast<uint8_t*>(i);
if (!i->is<InstLDR>()) {
new (i) InstLDR(Offset, pc, DTRAddr(pc, DtrOffImm(offset - 8)), cond);
AutoFlushCache::updateTop(uintptr_t(i), 4);
}
*slot = dest;
}
struct PoolHeader : Instruction {
struct Header
{
// size should take into account the pool header.
// size is in units of Instruction (4bytes), not byte
uint32_t size : 15;
bool isNatural : 1;
uint32_t ONES : 16;
Header(int size_, bool isNatural_)
: size(size_),
isNatural(isNatural_),
ONES(0xffff)
{ }
Header(const Instruction *i) {
JS_STATIC_ASSERT(sizeof(Header) == sizeof(uint32_t));
memcpy(this, i, sizeof(Header));
JS_ASSERT(ONES == 0xffff);
}
uint32_t raw() const {
JS_STATIC_ASSERT(sizeof(Header) == sizeof(uint32_t));
uint32_t dest;
memcpy(&dest, this, sizeof(Header));
return dest;
}
};
PoolHeader(int size_, bool isNatural_)
: Instruction(Header(size_, isNatural_).raw(), true)
{ }
uint32_t size() const {
Header tmp(this);
return tmp.size;
}
uint32_t isNatural() const {
Header tmp(this);
return tmp.isNatural;
}
static bool isTHIS(const Instruction &i) {
return (*i.raw() & 0xffff0000) == 0xffff0000;
}
static const PoolHeader *asTHIS(const Instruction &i) {
if (!isTHIS(i))
return NULL;
return static_cast<const PoolHeader*>(&i);
}
};
void
Assembler::writePoolHeader(uint8_t *start, Pool *p, bool isNatural)
{
STATIC_ASSERT(sizeof(PoolHeader) == 4);
uint8_t *pool = start+4;
// go through the usual rigaramarole to get the size of the pool.
pool = p[0].addPoolSize(pool);
pool = p[1].addPoolSize(pool);
pool = p[1].other->addPoolSize(pool);
pool = p[0].other->addPoolSize(pool);
uint32_t size = pool - start;
JS_ASSERT((size & 3) == 0);
size = size >> 2;
JS_ASSERT(size < (1 << 15));
PoolHeader header(size, isNatural);
*(PoolHeader*)start = header;
}
void
Assembler::writePoolFooter(uint8_t *start, Pool *p, bool isNatural)
{
return;
}
// The size of an arbitrary 32-bit call in the instruction stream.
// On ARM this sequence is |pc = ldr pc - 4; imm32| given that we
// never reach the imm32.
uint32_t
Assembler::patchWrite_NearCallSize()
{
return sizeof(uint32_t);
}
void
Assembler::patchWrite_NearCall(CodeLocationLabel start, CodeLocationLabel toCall)
{
Instruction *inst = (Instruction *) start.raw();
// Overwrite whatever instruction used to be here with a call.
// Since the destination is in the same function, it will be within range of the 24<<2 byte
// bl instruction.
uint8_t *dest = toCall.raw();
new (inst) InstBLImm(BOffImm(dest - (uint8_t*)inst) , Always);
// Ensure everyone sees the code that was just written into memory.
AutoFlushCache::updateTop(uintptr_t(inst), 4);
}
void
Assembler::patchDataWithValueCheck(CodeLocationLabel label, ImmWord newValue, ImmWord expectedValue)
{
Instruction *ptr = (Instruction *) label.raw();
InstructionIterator iter(ptr);
Register dest;
Assembler::RelocStyle rs;
const uint32_t *val = getPtr32Target(&iter, &dest, &rs);
JS_ASSERT((uint32_t)val == expectedValue.value);
reinterpret_cast<MacroAssemblerARM*>(dummy)->ma_movPatchable(Imm32(newValue.value), dest, Always, rs, ptr);
// L_LDR won't cause any instructions to be updated.
if (rs != L_LDR) {
AutoFlushCache::updateTop(uintptr_t(ptr), 4);
AutoFlushCache::updateTop(uintptr_t(ptr->next()), 4);
}
}
// This just stomps over memory with 32 bits of raw data. Its purpose is to
// overwrite the call of JITed code with 32 bits worth of an offset. This will
// is only meant to function on code that has been invalidated, so it should
// be totally safe. Since that instruction will never be executed again, a
// ICache flush should not be necessary
void
Assembler::patchWrite_Imm32(CodeLocationLabel label, Imm32 imm) {
// Raw is going to be the return address.
uint32_t *raw = (uint32_t*)label.raw();
// Overwrite the 4 bytes before the return address, which will
// end up being the call instruction.
*(raw-1) = imm.value;
}
uint8_t *
Assembler::nextInstruction(uint8_t *inst_, uint32_t *count)
{
Instruction *inst = reinterpret_cast<Instruction*>(inst_);
if (count != NULL)
*count += sizeof(Instruction);
return reinterpret_cast<uint8_t*>(inst->next());
}
static bool
InstIsGuard(Instruction *inst, const PoolHeader **ph)
{
Assembler::Condition c;
inst->extractCond(&c);
if (c != Assembler::Always)
return false;
if (!(inst->is<InstBXReg>() || inst->is<InstBImm>()))
return false;
// See if the next instruction is a pool header.
*ph = (inst+1)->as<const PoolHeader>();
return *ph != NULL;
}
static bool
InstIsBNop(Instruction *inst) {
// In some special situations, it is necessary to insert a NOP
// into the instruction stream that nobody knows about, since nobody should know about
// it, make sure it gets skipped when Instruction::next() is called.
// this generates a very specific nop, namely a branch to the next instruction.
Assembler::Condition c;
inst->extractCond(&c);
if (c != Assembler::Always)
return false;
if (!inst->is<InstBImm>())
return false;
InstBImm *b = inst->as<InstBImm>();
BOffImm offset;
b->extractImm(&offset);
return offset.decode() == 4;
}
static bool
InstIsArtificialGuard(Instruction *inst, const PoolHeader **ph)
{
if (!InstIsGuard(inst, ph))
return false;
return !(*ph)->isNatural();
}
// Cases to be handled:
// 1) no pools or branches in sight => return this+1
// 2) branch to next instruction => return this+2, because a nop needed to be inserted into the stream.
// 3) this+1 is an artificial guard for a pool => return first instruction after the pool
// 4) this+1 is a natural guard => return the branch
// 5) this is a branch, right before a pool => return first instruction after the pool
// in assembly form:
// 1) add r0, r0, r0 <= this
// add r1, r1, r1 <= returned value
// add r2, r2, r2
//
// 2) add r0, r0, r0 <= this
// b foo
// foo:
// add r2, r2, r2 <= returned value
//
// 3) add r0, r0, r0 <= this
// b after_pool;
// .word 0xffff0002 # bit 15 being 0 indicates that the branch was not requested by the assembler
// 0xdeadbeef # the 2 indicates that there is 1 pool entry, and the pool header
// add r4, r4, r4 <= returned value
// 4) add r0, r0, r0 <= this
// b after_pool <= returned value
// .word 0xffff8002 # bit 15 being 1 indicates that the branch was requested by the assembler
// 0xdeadbeef
// add r4, r4, r4
// 5) b after_pool <= this
// .word 0xffff8002 # bit 15 has no bearing on the returned value
// 0xdeadbeef
// add r4, r4, r4 <= returned value
Instruction *
Instruction::next()
{
Instruction *ret = this+1;
const PoolHeader *ph;
// If this is a guard, and the next instruction is a header, always work around the pool
// If it isn't a guard, then start looking ahead.
if (InstIsGuard(this, &ph))
return ret + ph->size();
if (InstIsArtificialGuard(ret, &ph))
return ret + 1 + ph->size();
if (InstIsBNop(ret))
return ret + 1;
return ret;
}
void
Assembler::ToggleToJmp(CodeLocationLabel inst_)
{
uint32_t *ptr = (uint32_t *)inst_.raw();
DebugOnly<Instruction *> inst = (Instruction *)inst_.raw();
JS_ASSERT(inst->is<InstCMP>());
// Zero bits 20-27, then set 24-27 to be correct for a branch.
// 20-23 will be party of the B's immediate, and should be 0.
*ptr = (*ptr & ~(0xff << 20)) | (0xa0 << 20);
AutoFlushCache::updateTop((uintptr_t)ptr, 4);
}
void
Assembler::ToggleToCmp(CodeLocationLabel inst_)
{
uint32_t *ptr = (uint32_t *)inst_.raw();
DebugOnly<Instruction *> inst = (Instruction *)inst_.raw();
JS_ASSERT(inst->is<InstBImm>());
// Ensure that this masking operation doesn't affect the offset of the
// branch instruction when it gets toggled back.
JS_ASSERT((*ptr & (0xf << 20)) == 0);
// Also make sure that the CMP is valid. Part of having a valid CMP is that
// all of the bits describing the destination in most ALU instructions are
// all unset (looks like it is encoding r0).
JS_ASSERT(toRD(*inst) == r0);
// Zero out bits 20-27, then set them to be correct for a compare.
*ptr = (*ptr & ~(0xff << 20)) | (0x35 << 20);
AutoFlushCache::updateTop((uintptr_t)ptr, 4);
}
void
Assembler::ToggleCall(CodeLocationLabel inst_, bool enabled)
{
Instruction *inst = (Instruction *)inst_.raw();
JS_ASSERT(inst->is<InstMovW>() || inst->is<InstLDR>());
if (inst->is<InstMovW>()) {
// If it looks like the start of a movw/movt sequence,
// then make sure we have all of it (and advance the iterator
// past the full sequence)
inst = inst->next();
JS_ASSERT(inst->is<InstMovT>());
}
inst = inst->next();
JS_ASSERT(inst->is<InstNOP>() || inst->is<InstBLXReg>());
if (enabled == inst->is<InstBLXReg>()) {
// Nothing to do.
return;
}
if (enabled)
*inst = InstBLXReg(ScratchRegister, Always);
else
*inst = InstNOP();
AutoFlushCache::updateTop(uintptr_t(inst), 4);
}
void Assembler::updateBoundsCheck(uint32_t logHeapSize, Instruction *inst)
{
JS_ASSERT(inst->is<InstMOV>());
InstMOV *mov = inst->as<InstMOV>();
JS_ASSERT(mov->checkDest(ScratchRegister));
Operand2 op = mov->extractOp2();
JS_ASSERT(op.isO2Reg());
Op2Reg reg = op.toOp2Reg();
Register index;
reg.getRM(&index);
JS_ASSERT(reg.isO2RegImmShift());
// O2RegImmShift shift = reg.toO2RegImmShift();
*inst = InstALU(ScratchRegister, InvalidReg, lsr(index, logHeapSize), op_mov, SetCond, Always);
AutoFlushCache::updateTop(uintptr_t(inst), 4);
}
void
AutoFlushCache::update(uintptr_t newStart, size_t len)
{
uintptr_t newStop = newStart + len;
if (this == NULL) {
// just flush right here and now.
JSC::ExecutableAllocator::cacheFlush((void*)newStart, len);
return;
}
used_ = true;
if (!start_) {
IonSpewCont(IonSpew_CacheFlush, ".");
start_ = newStart;
stop_ = newStop;
return;
}
if (newStop < start_ - 4096 || newStart > stop_ + 4096) {
// If this would add too many pages to the range, bail and just do the flush now.
IonSpewCont(IonSpew_CacheFlush, "*");
JSC::ExecutableAllocator::cacheFlush((void*)newStart, len);
return;
}
start_ = Min(start_, newStart);
stop_ = Max(stop_, newStop);
IonSpewCont(IonSpew_CacheFlush, ".");
}
AutoFlushCache::~AutoFlushCache()
{
if (!runtime_)
return;
flushAnyway();
IonSpewCont(IonSpew_CacheFlush, ">", name_);
if (runtime_->flusher() == this) {
IonSpewFin(IonSpew_CacheFlush);
runtime_->setFlusher(NULL);
}
}
void
AutoFlushCache::flushAnyway()
{
if (!runtime_)
return;
IonSpewCont(IonSpew_CacheFlush, "|", name_);
if (!used_)
return;
if (start_) {
JSC::ExecutableAllocator::cacheFlush((void *)start_, size_t(stop_ - start_ + sizeof(Instruction)));
} else {
JSC::ExecutableAllocator::cacheFlush(NULL, 0xff000000);
}
used_ = false;
}
InstructionIterator::InstructionIterator(Instruction *i_) : i(i_) {
const PoolHeader *ph;
// If this is a guard, and the next instruction is a header, always work around the pool
// If it isn't a guard, then start looking ahead.
if (InstIsArtificialGuard(i, &ph)) {
i = i->next();
}
}
Assembler *Assembler::dummy = NULL;