| /* -*- 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 "jit/arm/Assembler-arm.h" |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/MathAlgorithms.h" |
| |
| #include "jscompartment.h" |
| #ifdef JS_DISASM_ARM |
| #include "jsprf.h" |
| #endif |
| #include "jsutil.h" |
| |
| #include "gc/Marking.h" |
| #include "jit/arm/disasm/Disasm-arm.h" |
| #include "jit/arm/MacroAssembler-arm.h" |
| #include "jit/ExecutableAllocator.h" |
| #include "jit/JitCompartment.h" |
| #include "jit/MacroAssembler.h" |
| |
| using namespace js; |
| using namespace js::jit; |
| |
| using mozilla::CountLeadingZeroes32; |
| |
| void dbg_break() {} |
| |
| // The ABIArgGenerator is used for making system ABI calls and for inter-AsmJS |
| // calls. The system ABI can either be SoftFp or HardFp, and inter-AsmJS calls |
| // are always HardFp calls. The initialization defaults to HardFp, and the ABI |
| // choice is made before any system ABI calls with the method "setUseHardFp". |
| ABIArgGenerator::ABIArgGenerator() |
| : intRegIndex_(0), |
| floatRegIndex_(0), |
| stackOffset_(0), |
| current_(), |
| useHardFp_(true) |
| { } |
| |
| // See the "Parameter Passing" section of the "Procedure Call Standard for the |
| // ARM Architecture" documentation. |
| ABIArg |
| ABIArgGenerator::softNext(MIRType type) |
| { |
| 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_Float32: |
| if (intRegIndex_ == NumIntArgRegs) { |
| current_ = ABIArg(stackOffset_); |
| stackOffset_ += sizeof(uint32_t); |
| break; |
| } |
| current_ = ABIArg(Register::FromCode(intRegIndex_)); |
| intRegIndex_++; |
| break; |
| case MIRType_Double: |
| // Make sure to use an even register index. Increase to next even number |
| // when odd. |
| intRegIndex_ = (intRegIndex_ + 1) & ~1; |
| if (intRegIndex_ == NumIntArgRegs) { |
| // Align the stack on 8 bytes. |
| static const int align = sizeof(double) - 1; |
| stackOffset_ = (stackOffset_ + align) & ~align; |
| current_ = ABIArg(stackOffset_); |
| stackOffset_ += sizeof(double); |
| break; |
| } |
| current_ = ABIArg(Register::FromCode(intRegIndex_), Register::FromCode(intRegIndex_ + 1)); |
| intRegIndex_ += 2; |
| break; |
| default: |
| MOZ_CRASH("Unexpected argument type"); |
| } |
| |
| return current_; |
| } |
| |
| ABIArg |
| ABIArgGenerator::hardNext(MIRType type) |
| { |
| 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_Float32: |
| if (floatRegIndex_ == NumFloatArgRegs) { |
| static const int align = sizeof(double) - 1; |
| stackOffset_ = (stackOffset_ + align) & ~align; |
| current_ = ABIArg(stackOffset_); |
| stackOffset_ += sizeof(uint64_t); |
| break; |
| } |
| current_ = ABIArg(VFPRegister(floatRegIndex_, VFPRegister::Single)); |
| floatRegIndex_++; |
| break; |
| case MIRType_Double: |
| // Double register are composed of 2 float registers, thus we have to |
| // skip any float register which cannot be used in a pair of float |
| // registers in which a double value can be stored. |
| floatRegIndex_ = (floatRegIndex_ + 1) & ~1; |
| if (floatRegIndex_ == NumFloatArgRegs) { |
| static const int align = sizeof(double) - 1; |
| stackOffset_ = (stackOffset_ + align) & ~align; |
| current_ = ABIArg(stackOffset_); |
| stackOffset_ += sizeof(uint64_t); |
| break; |
| } |
| current_ = ABIArg(VFPRegister(floatRegIndex_ >> 1, VFPRegister::Double)); |
| floatRegIndex_ += 2; |
| break; |
| default: |
| MOZ_CRASH("Unexpected argument type"); |
| } |
| |
| return current_; |
| } |
| |
| ABIArg |
| ABIArgGenerator::next(MIRType type) |
| { |
| if (useHardFp_) |
| return hardNext(type); |
| return softNext(type); |
| } |
| |
| const Register ABIArgGenerator::NonArgReturnReg0 = r4; |
| const Register ABIArgGenerator::NonArgReturnReg1 = r5; |
| const Register ABIArgGenerator::NonReturn_VolatileReg0 = r2; |
| const Register ABIArgGenerator::NonReturn_VolatileReg1 = r3; |
| |
| // 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) |
| { |
| MOZ_ASSERT((r.code() & ~0xf) == 0); |
| return r.code() << 12; |
| } |
| |
| uint32_t |
| js::jit::RN(Register r) |
| { |
| MOZ_ASSERT((r.code() & ~0xf) == 0); |
| return r.code() << 16; |
| } |
| |
| uint32_t |
| js::jit::RD(Register r) |
| { |
| MOZ_ASSERT((r.code() & ~0xf) == 0); |
| return r.code() << 12; |
| } |
| |
| uint32_t |
| js::jit::RM(Register r) |
| { |
| MOZ_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; |
| |
| MOZ_ASSERT((r.code() & ~0xf) == 0); |
| return r.code() << 12; |
| } |
| |
| uint32_t |
| js::jit::maybeRN(Register r) |
| { |
| if (r == InvalidReg) |
| return 0; |
| |
| MOZ_ASSERT((r.code() & ~0xf) == 0); |
| return r.code() << 16; |
| } |
| |
| uint32_t |
| js::jit::maybeRD(Register r) |
| { |
| if (r == InvalidReg) |
| return 0; |
| |
| MOZ_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() |
| { |
| MOZ_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); |
| } |
| } |
| |
| 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 nullptr; |
| } |
| |
| 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 nullptr; |
| } |
| |
| InstNOP* |
| InstNOP::AsTHIS(Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstNOP*)&i; |
| return nullptr; |
| } |
| |
| 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 nullptr; |
| } |
| 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 nullptr; |
| } |
| |
| 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 nullptr; |
| } |
| |
| bool |
| InstBLXReg::IsTHIS(const Instruction& i) |
| { |
| return (i.encode() & IsBRegMask) == IsBLX; |
| |
| } |
| InstBLXReg* |
| InstBLXReg::AsTHIS(const Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstBLXReg*)&i; |
| return nullptr; |
| } |
| |
| bool |
| InstBImm::IsTHIS(const Instruction& i) |
| { |
| return (i.encode () & IsBImmMask) == IsB; |
| } |
| InstBImm* |
| InstBImm::AsTHIS(const Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstBImm*)&i; |
| return nullptr; |
| } |
| |
| bool |
| InstBLImm::IsTHIS(const Instruction& i) |
| { |
| return (i.encode () & IsBImmMask) == IsBL; |
| |
| } |
| InstBLImm* |
| InstBLImm::AsTHIS(const Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstBLImm*)&i; |
| return nullptr; |
| } |
| |
| bool |
| InstMovWT::IsTHIS(Instruction& i) |
| { |
| return InstMovW::IsTHIS(i) || InstMovT::IsTHIS(i); |
| } |
| InstMovWT* |
| InstMovWT::AsTHIS(Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstMovWT*)&i; |
| return nullptr; |
| } |
| |
| 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 nullptr; |
| } |
| InstMovT* |
| InstMovT::AsTHIS(const Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstMovT*)&i; |
| return nullptr; |
| } |
| |
| bool |
| InstMovT::IsTHIS(const Instruction& i) |
| { |
| return (i.encode() & IsWTMask) == IsT; |
| } |
| |
| InstALU* |
| InstALU::AsTHIS(const Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstALU*)&i; |
| return nullptr; |
| } |
| 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 nullptr; |
| } |
| |
| bool |
| InstCMP::IsTHIS(const Instruction& i) |
| { |
| return InstALU::IsTHIS(i) && InstALU::AsTHIS(i)->checkDest(r0) && InstALU::AsTHIS(i)->checkOp(OpCmp); |
| } |
| |
| InstMOV* |
| InstMOV::AsTHIS(const Instruction& i) |
| { |
| if (IsTHIS(i)) |
| return (InstMOV*)&i; |
| return nullptr; |
| } |
| |
| bool |
| InstMOV::IsTHIS(const Instruction& i) |
| { |
| return InstALU::IsTHIS(i) && InstALU::AsTHIS(i)->checkOp1(r0) && InstALU::AsTHIS(i)->checkOp(OpMov); |
| } |
| |
| Op2Reg |
| Operand2::toOp2Reg() const { |
| return *(Op2Reg*)this; |
| } |
| |
| O2RegImmShift |
| Op2Reg::toO2RegImmShift() const { |
| return *(O2RegImmShift*)this; |
| } |
| |
| O2RegRegShift |
| Op2Reg::toO2RegRegShift() const { |
| 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) |
| { |
| MOZ_ASSERT(decode() == imm); |
| } |
| |
| Imm16::Imm16() |
| : invalid(0xfff) |
| { } |
| |
| void |
| jit::PatchJump(CodeLocationJump& jump_, CodeLocationLabel label, ReprotectCode reprotect) |
| { |
| // 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(); |
| // jumpWithPatch() returns the offset of the jump and never a pool or nop. |
| Assembler::Condition c = jump->extractCond(); |
| MOZ_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. |
| MaybeAutoWritableJitCode awjc(jump, sizeof(Instruction), reprotect); |
| 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()); |
| |
| // Ensure both the branch and the slot are writable. |
| MOZ_ASSERT(uintptr_t(slot) > uintptr_t(jump)); |
| size_t size = uintptr_t(slot) - uintptr_t(jump) + sizeof(void*); |
| MaybeAutoWritableJitCode awjc(jump, size, reprotect); |
| |
| Assembler::RetargetFarBranch(jump, slot, label.raw(), c); |
| } |
| } |
| |
| void |
| Assembler::finish() |
| { |
| flush(); |
| MOZ_ASSERT(!isFinished); |
| isFinished = true; |
| } |
| |
| bool |
| Assembler::asmMergeWith(const Assembler& other) |
| { |
| flush(); |
| if (!AssemblerShared::asmMergeWith(size(), other)) |
| return false; |
| return m_buffer.appendBuffer(other.m_buffer); |
| } |
| |
| void |
| Assembler::executableCopy(uint8_t* buffer) |
| { |
| MOZ_ASSERT(isFinished); |
| m_buffer.executableCopy(buffer); |
| AutoFlushICache::setRange(uintptr_t(buffer), m_buffer.size()); |
| } |
| |
| uint32_t |
| Assembler::actualIndex(uint32_t idx_) const |
| { |
| ARMBuffer::PoolEntry pe(idx_); |
| return m_buffer.poolEntryOffset(pe); |
| } |
| |
| uint8_t* |
| Assembler::PatchableJumpAddress(JitCode* code, uint32_t pe_) |
| { |
| return code->raw() + pe_; |
| } |
| |
| 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(); |
| |
| 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>()) |
| { |
| // 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. |
| Instruction* inst2 = iter->next(); |
| MOZ_ASSERT(inst2->is<InstMovT>()); |
| InstMovT* top = inst2->as<InstMovT>(); |
| top->extractImm(&targ_top); |
| |
| // Make sure they are being loaded into the same register. |
| MOZ_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. |
| Instruction* inst3 = iter->next(); |
| if (!inst3->is<InstNOP>()) { |
| InstBranchReg* realBranch = nullptr; |
| if (inst3->is<InstBranchReg>()) { |
| realBranch = inst3->as<InstBranchReg>(); |
| } else { |
| Instruction* inst4 = iter->next(); |
| realBranch = inst4->as<InstBranchReg>(); |
| } |
| MOZ_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; |
| |
| } |
| |
| MOZ_CRASH("unsupported branch relocation"); |
| } |
| |
| uintptr_t |
| Assembler::GetPointer(uint8_t* instPtr) |
| { |
| InstructionIterator iter((Instruction*)instPtr); |
| uintptr_t ret = (uintptr_t)GetPtr32Target(&iter, nullptr, nullptr); |
| 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 into the same register. |
| MOZ_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; |
| } |
| |
| MOZ_CRASH("unsupported relocation"); |
| } |
| |
| static JitCode* |
| CodeFromJump(InstructionIterator* jump) |
| { |
| uint8_t* target = (uint8_t*)Assembler::GetCF32Target(jump); |
| return JitCode::FromExecutable(target); |
| } |
| |
| void |
| Assembler::TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader) |
| { |
| RelocationIterator iter(reader); |
| while (iter.read()) { |
| InstructionIterator institer((Instruction*) (code->raw() + iter.offset())); |
| JitCode* child = CodeFromJump(&institer); |
| TraceManuallyBarrieredEdge(trc, &child, "rel32"); |
| } |
| } |
| |
| template <class Iter> |
| static void |
| TraceOneDataRelocation(JSTracer* trc, Iter* iter) |
| { |
| Instruction* ins = iter->cur(); |
| Register dest; |
| Assembler::RelocStyle rs; |
| const void* prior = Assembler::GetPtr32Target(iter, &dest, &rs); |
| void* ptr = const_cast<void*>(prior); |
| |
| // No barrier needed since these are constants. |
| TraceManuallyBarrieredGenericPointerEdge(trc, reinterpret_cast<gc::Cell**>(&ptr), |
| "ion-masm-ptr"); |
| |
| if (ptr != prior) { |
| MacroAssemblerARM::ma_mov_patch(Imm32(int32_t(ptr)), dest, Assembler::Always, rs, ins); |
| |
| // L_LDR won't cause any instructions to be updated. |
| if (rs != Assembler::L_LDR) { |
| AutoFlushICache::flush(uintptr_t(ins), 4); |
| AutoFlushICache::flush(uintptr_t(ins->next()), 4); |
| } |
| } |
| } |
| |
| static void |
| TraceDataRelocations(JSTracer* trc, uint8_t* buffer, CompactBufferReader& reader) |
| { |
| while (reader.more()) { |
| size_t offset = reader.readUnsigned(); |
| InstructionIterator iter((Instruction*)(buffer + offset)); |
| TraceOneDataRelocation(trc, &iter); |
| } |
| } |
| |
| static void |
| TraceDataRelocations(JSTracer* trc, ARMBuffer* buffer, CompactBufferReader& reader) |
| { |
| while (reader.more()) { |
| BufferOffset offset(reader.readUnsigned()); |
| ARMBuffer::AssemblerBufferInstIterator iter(offset, buffer); |
| TraceOneDataRelocation(trc, &iter); |
| } |
| } |
| |
| void |
| Assembler::TraceDataRelocations(JSTracer* trc, JitCode* 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::JITCODE) { |
| JitCode* code = JitCode::FromExecutable((uint8_t*)rp.target); |
| TraceManuallyBarrieredEdge(trc, &code, "masmrel32"); |
| MOZ_ASSERT(code == JitCode::FromExecutable((uint8_t*)rp.target)); |
| } |
| } |
| |
| if (dataRelocations_.length()) { |
| CompactBufferReader reader(dataRelocations_); |
| ::TraceDataRelocations(trc, &m_buffer, reader); |
| } |
| } |
| |
| void |
| Assembler::processCodeLabels(uint8_t* rawCode) |
| { |
| for (size_t i = 0; i < codeLabels_.length(); i++) { |
| CodeLabel label = codeLabels_[i]; |
| Bind(rawCode, label.patchAt(), rawCode + label.target()->offset()); |
| } |
| } |
| |
| void |
| Assembler::writeCodePointer(CodeOffset* label) { |
| BufferOffset off = writeInst(LabelBase::INVALID_OFFSET); |
| label->bind(off.getOffset()); |
| } |
| |
| void |
| Assembler::Bind(uint8_t* rawCode, CodeOffset* label, const void* address) |
| { |
| *reinterpret_cast<const void**>(rawCode + label->offset()) = 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 = CountLeadingZeroes32(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 = CountLeadingZeroes32(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, in fact, this would lead to |
| // some incredibly subtle bugs. |
| // assert((imm & 0xff) == no_n1); |
| imm2 = no_n1; |
| } else { |
| imm2 = ((imm >> (32 - imm2shift)) | (imm << imm2shift)) & 0xff; |
| MOZ_ASSERT( ((no_n1 >> (32 - imm2shift)) | (no_n1 << imm2shift)) == |
| imm2); |
| } |
| MOZ_ASSERT((imm1shift & 0x1) == 0); |
| MOZ_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 - (CountLeadingZeroes32(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 = CountLeadingZeroes32(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))); |
| MOZ_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; |
| MOZ_ASSERT((imm1shift & 0x1) == 0); |
| MOZ_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 OpMov: |
| *imm = Imm32(~imm->value); |
| return OpMvn; |
| case OpMvn: |
| *imm = Imm32(~imm->value); |
| return OpMov; |
| case OpAnd: |
| *imm = Imm32(~imm->value); |
| return OpBic; |
| case OpBic: |
| *imm = Imm32(~imm->value); |
| return OpAnd; |
| case OpAdd: |
| *imm = Imm32(-imm->value); |
| return OpSub; |
| case OpSub: |
| *imm = Imm32(-imm->value); |
| return OpAdd; |
| case OpCmp: |
| *imm = Imm32(-imm->value); |
| return OpCmn; |
| case OpCmn: |
| *imm = Imm32(-imm->value); |
| return OpCmp; |
| case OpTst: |
| MOZ_ASSERT(dest == InvalidReg); |
| *imm = Imm32(~imm->value); |
| *negDest = ScratchRegister; |
| return OpBic; |
| // orr has orn on thumb2 only. |
| default: |
| return OpInvalid; |
| } |
| } |
| |
| 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 OpBic: |
| case OpAdd: |
| case OpSub: |
| case OpEor: |
| case OpOrr: |
| 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 OpBic: |
| case OpOrr: |
| case OpEor: |
| 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 OpCmp: |
| return OpSub; |
| case OpCmn: |
| return OpAdd; |
| case OpTst: |
| return OpAnd; |
| case OpTeq: |
| return OpEor; |
| default: |
| return op; |
| } |
| } |
| |
| O2RegImmShift |
| jit::O2Reg(Register r) { |
| return O2RegImmShift(r, LSL, 0); |
| } |
| |
| O2RegImmShift |
| jit::lsl(Register r, int amt) |
| { |
| MOZ_ASSERT(0 <= amt && amt <= 31); |
| return O2RegImmShift(r, LSL, amt); |
| } |
| |
| O2RegImmShift |
| jit::lsr(Register r, int amt) |
| { |
| MOZ_ASSERT(1 <= amt && amt <= 32); |
| return O2RegImmShift(r, LSR, amt); |
| } |
| |
| O2RegImmShift |
| jit::ror(Register r, int amt) |
| { |
| MOZ_ASSERT(1 <= amt && amt <= 31); |
| return O2RegImmShift(r, ROR, amt); |
| } |
| O2RegImmShift |
| jit::rol(Register r, int amt) |
| { |
| MOZ_ASSERT(1 <= amt && amt <= 31); |
| return O2RegImmShift(r, ROR, 32 - amt); |
| } |
| |
| O2RegImmShift |
| jit::asr (Register r, int amt) |
| { |
| MOZ_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); |
| } |
| |
| static js::jit::DoubleEncoder doubleEncoder; |
| |
| /* static */ const js::jit::VFPImm js::jit::VFPImm::One(0x3FF00000); |
| |
| 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) const |
| { |
| // 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]; |
| } |
| |
| const js::jit::DoubleEncoder::DoubleEntry js::jit::DoubleEncoder::table[256] = { |
| #include "jit/arm/DoubleEntryTable.tbl" |
| }; |
| |
| // VFPRegister implementation |
| VFPRegister |
| VFPRegister::doubleOverlay(unsigned int which) const |
| { |
| MOZ_ASSERT(!_isInvalid); |
| MOZ_ASSERT(which == 0); |
| if (kind != Double) |
| return VFPRegister(code_ >> 1, Double); |
| return *this; |
| } |
| VFPRegister |
| VFPRegister::singleOverlay(unsigned int which) const |
| { |
| MOZ_ASSERT(!_isInvalid); |
| if (kind == Double) { |
| // There are no corresponding float registers for d16-d31. |
| MOZ_ASSERT(code_ < 16); |
| MOZ_ASSERT(which < 2); |
| return VFPRegister((code_ << 1) + which, Single); |
| } |
| MOZ_ASSERT(which == 0); |
| return VFPRegister(code_, Single); |
| } |
| |
| VFPRegister |
| VFPRegister::sintOverlay(unsigned int which) const |
| { |
| MOZ_ASSERT(!_isInvalid); |
| if (kind == Double) { |
| // There are no corresponding float registers for d16-d31. |
| MOZ_ASSERT(code_ < 16); |
| MOZ_ASSERT(which < 2); |
| return VFPRegister((code_ << 1) + which, Int); |
| } |
| MOZ_ASSERT(which == 0); |
| return VFPRegister(code_, Int); |
| } |
| VFPRegister |
| VFPRegister::uintOverlay(unsigned int which) const |
| { |
| MOZ_ASSERT(!_isInvalid); |
| if (kind == Double) { |
| // There are no corresponding float registers for d16-d31. |
| MOZ_ASSERT(code_ < 16); |
| MOZ_ASSERT(which < 2); |
| return VFPRegister((code_ << 1) + which, UInt); |
| } |
| MOZ_ASSERT(which == 0); |
| return VFPRegister(code_, UInt); |
| } |
| |
| bool |
| VFPRegister::isInvalid() const |
| { |
| return _isInvalid; |
| } |
| |
| bool |
| VFPRegister::isMissing() const |
| { |
| MOZ_ASSERT(!_isInvalid); |
| return _isMissing; |
| } |
| |
| |
| bool |
| Assembler::oom() const |
| { |
| return AssemblerShared::oom() || |
| m_buffer.oom() || |
| jumpRelocations_.oom() || |
| dataRelocations_.oom() || |
| preBarriers_.oom(); |
| } |
| |
| // 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(); |
| } |
| |
| #ifdef JS_DISASM_ARM |
| |
| void |
| Assembler::spewInst(Instruction* i) |
| { |
| disasm::NameConverter converter; |
| disasm::Disassembler dasm(converter); |
| disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer; |
| uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw())); |
| dasm.InstructionDecode(buffer, loc); |
| printf(" %08x %s\n", reinterpret_cast<uint32_t>(loc), buffer.start()); |
| } |
| |
| // Labels are named as they are encountered by adding names to a |
| // table, using the Label address as the key. This is made tricky by |
| // the (memory for) Label objects being reused, but reused label |
| // objects are recognizable from being marked as not used or not |
| // bound. See spewResolve(). |
| // |
| // In a number of cases there is no information about the target, and |
| // we just end up printing "patchable constant load to PC". This is |
| // true especially for jumps to bailout handlers (which have no |
| // names). See spewData() and its callers. In some cases (loop back |
| // edges) some information about the intended target may be propagated |
| // from higher levels, and if so it's printed here. |
| |
| void |
| Assembler::spew(Instruction* i) |
| { |
| if (spewDisabled() || !i) |
| return; |
| disasm::NameConverter converter; |
| disasm::Disassembler dasm(converter); |
| disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer; |
| uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw())); |
| dasm.InstructionDecode(buffer, loc); |
| spew(" %08x %s", reinterpret_cast<uint32_t>(loc), buffer.start()); |
| } |
| |
| void |
| Assembler::spewTarget(Label* target) |
| { |
| if (spewDisabled()) |
| return; |
| spew(" -> %d%s", spewResolve(target), !target->bound() ? "f" : ""); |
| } |
| |
| // If a target label is known, always print that and do not attempt to |
| // disassemble the branch operands, as they will often be encoding |
| // metainformation (pointers for a chain of jump instructions), and |
| // not actual branch targets. |
| |
| void |
| Assembler::spewBranch(Instruction* i, Label* target /* may be nullptr */) |
| { |
| if (spewDisabled() || !i) |
| return; |
| disasm::NameConverter converter; |
| disasm::Disassembler dasm(converter); |
| disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer; |
| uint8_t* loc = reinterpret_cast<uint8_t*>(const_cast<uint32_t*>(i->raw())); |
| dasm.InstructionDecode(buffer, loc); |
| char labelBuf[128]; |
| labelBuf[0] = 0; |
| if (!target) |
| JS_snprintf(labelBuf, sizeof(labelBuf), " -> (link-time target)"); |
| if (InstBranchImm::IsTHIS(*i)) { |
| InstBranchImm* bimm = InstBranchImm::AsTHIS(*i); |
| BOffImm destOff; |
| bimm->extractImm(&destOff); |
| if (destOff.isInvalid() || target) { |
| // The target information in the instruction is likely garbage, so remove it. |
| // The target label will in any case be printed if we have it. |
| // |
| // The format of the instruction disassembly is [0-9a-f]{8}\s+\S+\s+.*, |
| // where the \S+ string is the opcode. Strip everything after the opcode, |
| // and attach the label if we have it. |
| int i; |
| for ( i=8 ; i < buffer.length() && buffer[i] == ' ' ; i++ ) |
| ; |
| for ( ; i < buffer.length() && buffer[i] != ' ' ; i++ ) |
| ; |
| buffer[i] = 0; |
| if (target) { |
| JS_snprintf(labelBuf, sizeof(labelBuf), " -> %d%s", spewResolve(target), |
| !target->bound() ? "f" : ""); |
| target = nullptr; |
| } |
| } |
| } |
| spew(" %08x %s%s", reinterpret_cast<uint32_t>(loc), buffer.start(), labelBuf); |
| if (target) |
| spewTarget(target); |
| } |
| |
| void |
| Assembler::spewLabel(Label* l) |
| { |
| if (spewDisabled()) |
| return; |
| spew(" %d:", spewResolve(l)); |
| } |
| |
| void |
| Assembler::spewRetarget(Label* label, Label* target) |
| { |
| if (spewDisabled()) |
| return; |
| spew(" %d: .retarget -> %d%s", |
| spewResolve(label), spewResolve(target), !target->bound() ? "f" : ""); |
| } |
| |
| void |
| Assembler::spewData(BufferOffset addr, size_t numInstr, bool loadToPC) |
| { |
| if (spewDisabled()) |
| return; |
| Instruction* inst = m_buffer.getInstOrNull(addr); |
| if (!inst) |
| return; |
| uint32_t *instr = reinterpret_cast<uint32_t*>(inst); |
| for ( size_t k=0 ; k < numInstr ; k++ ) { |
| spew(" %08x %08x (patchable constant load%s)", |
| reinterpret_cast<uint32_t>(instr+k), *(instr+k), loadToPC ? " to PC" : ""); |
| } |
| } |
| |
| bool |
| Assembler::spewDisabled() |
| { |
| return !(JitSpewEnabled(JitSpew_Codegen) || printer_); |
| } |
| |
| void |
| Assembler::spew(const char* fmt, ...) |
| { |
| va_list args; |
| va_start(args, fmt); |
| spew(fmt, args); |
| va_end(args); |
| } |
| |
| void |
| Assembler::spew(const char* fmt, va_list va) |
| { |
| if (printer_) { |
| printer_->vprintf(fmt, va); |
| printer_->put("\n"); |
| } |
| js::jit::JitSpewVA(js::jit::JitSpew_Codegen, fmt, va); |
| } |
| |
| uint32_t |
| Assembler::spewResolve(Label* l) |
| { |
| // Note, spewResolve will sometimes return 0 when it is triggered |
| // by the profiler and not by a full disassembly, since in that |
| // case a label can be used or bound but not previously have been |
| // defined. |
| return l->used() || l->bound() ? spewProbe(l) : spewDefine(l); |
| } |
| |
| uint32_t |
| Assembler::spewProbe(Label* l) |
| { |
| uint32_t key = reinterpret_cast<uint32_t>(l); |
| uint32_t value = 0; |
| spewNodes_.lookup(key, &value); |
| return value; |
| } |
| |
| uint32_t |
| Assembler::spewDefine(Label* l) |
| { |
| uint32_t key = reinterpret_cast<uint32_t>(l); |
| spewNodes_.remove(key); |
| uint32_t value = spewNext_++; |
| if (!spewNodes_.add(key, value)) |
| return 0; |
| return value; |
| } |
| |
| Assembler::SpewNodes::~SpewNodes() |
| { |
| Node* p = nodes; |
| while (p) { |
| Node* victim = p; |
| p = p->next; |
| js_free(victim); |
| } |
| } |
| |
| bool |
| Assembler::SpewNodes::lookup(uint32_t key, uint32_t* value) |
| { |
| for ( Node* p = nodes ; p ; p = p->next ) { |
| if (p->key == key) { |
| *value = p->value; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool |
| Assembler::SpewNodes::add(uint32_t key, uint32_t value) |
| { |
| Node* node = (Node*)js_malloc(sizeof(Node)); |
| if (!node) |
| return false; |
| node->key = key; |
| node->value = value; |
| node->next = nodes; |
| nodes = node; |
| return true; |
| } |
| |
| bool |
| Assembler::SpewNodes::remove(uint32_t key) |
| { |
| for ( Node* p = nodes, *pp = nullptr ; p ; pp = p, p = p->next ) { |
| if (p->key == key) { |
| if (pp) |
| pp->next = p->next; |
| else |
| nodes = p->next; |
| js_free(p); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| #endif // JS_DISASM_ARM |
| |
| // Write a blob of binary into the instruction stream. |
| BufferOffset |
| Assembler::writeInst(uint32_t x) |
| { |
| BufferOffset offs = m_buffer.putInt(x); |
| #ifdef JS_DISASM_ARM |
| spew(m_buffer.getInstOrNull(offs)); |
| #endif |
| return offs; |
| } |
| |
| BufferOffset |
| Assembler::writeBranchInst(uint32_t x, Label* documentation) |
| { |
| BufferOffset offs = m_buffer.putInt(x, /* markAsBranch = */ true); |
| #ifdef JS_DISASM_ARM |
| spewBranch(m_buffer.getInstOrNull(offs), documentation); |
| #endif |
| return offs; |
| } |
| |
| // Allocate memory for a branch instruction, it will be overwritten |
| // subsequently and should not be disassembled. |
| |
| BufferOffset |
| Assembler::allocBranchInst() |
| { |
| return m_buffer.putInt(Always | InstNOP::NopInst, /* markAsBranch = */ true); |
| } |
| |
| void |
| Assembler::WriteInstStatic(uint32_t x, uint32_t* dest) |
| { |
| MOZ_ASSERT(dest != nullptr); |
| *dest = x; |
| } |
| |
| void |
| Assembler::haltingAlign(int alignment) |
| { |
| // TODO: Implement a proper halting align. |
| nopAlign(alignment); |
| } |
| |
| void |
| Assembler::nopAlign(int alignment) |
| { |
| m_buffer.align(alignment); |
| } |
| |
| BufferOffset |
| Assembler::as_nop() |
| { |
| return writeInst(0xe320f000); |
| } |
| |
| static uint32_t |
| EncodeAlu(Register dest, Register src1, Operand2 op2, ALUOp op, SBit s, Assembler::Condition c) |
| { |
| return (int)op | (int)s | (int)c | op2.encode() | |
| ((dest == InvalidReg) ? 0 : RD(dest)) | |
| ((src1 == InvalidReg) ? 0 : RN(src1)); |
| } |
| |
| BufferOffset |
| Assembler::as_alu(Register dest, Register src1, Operand2 op2, |
| ALUOp op, SBit s, Condition c) |
| { |
| return writeInst(EncodeAlu(dest, src1, op2, op, s, c)); |
| } |
| |
| BufferOffset |
| Assembler::as_mov(Register dest, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, InvalidReg, op2, OpMov, s, c); |
| } |
| |
| /* static */ void |
| Assembler::as_alu_patch(Register dest, Register src1, Operand2 op2, ALUOp op, SBit s, |
| Condition c, uint32_t* pos) |
| { |
| WriteInstStatic(EncodeAlu(dest, src1, op2, op, s, c), pos); |
| } |
| |
| /* static */ void |
| Assembler::as_mov_patch(Register dest, Operand2 op2, SBit s, Condition c, uint32_t* pos) |
| { |
| as_alu_patch(dest, InvalidReg, op2, OpMov, s, c, pos); |
| } |
| |
| BufferOffset |
| Assembler::as_mvn(Register dest, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, InvalidReg, op2, OpMvn, s, c); |
| } |
| |
| // Logical operations. |
| BufferOffset |
| Assembler::as_and(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpAnd, s, c); |
| } |
| BufferOffset |
| Assembler::as_bic(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpBic, s, c); |
| } |
| BufferOffset |
| Assembler::as_eor(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpEor, s, c); |
| } |
| BufferOffset |
| Assembler::as_orr(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpOrr, s, c); |
| } |
| |
| // Mathematical operations. |
| BufferOffset |
| Assembler::as_adc(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpAdc, s, c); |
| } |
| BufferOffset |
| Assembler::as_add(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpAdd, s, c); |
| } |
| BufferOffset |
| Assembler::as_sbc(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpSbc, s, c); |
| } |
| BufferOffset |
| Assembler::as_sub(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpSub, s, c); |
| } |
| BufferOffset |
| Assembler::as_rsb(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpRsb, s, c); |
| } |
| BufferOffset |
| Assembler::as_rsc(Register dest, Register src1, Operand2 op2, SBit s, Condition c) |
| { |
| return as_alu(dest, src1, op2, OpRsc, s, c); |
| } |
| |
| // Test operations. |
| BufferOffset |
| Assembler::as_cmn(Register src1, Operand2 op2, Condition c) |
| { |
| return as_alu(InvalidReg, src1, op2, OpCmn, SetCC, c); |
| } |
| BufferOffset |
| Assembler::as_cmp(Register src1, Operand2 op2, Condition c) |
| { |
| return as_alu(InvalidReg, src1, op2, OpCmp, SetCC, c); |
| } |
| BufferOffset |
| Assembler::as_teq(Register src1, Operand2 op2, Condition c) |
| { |
| return as_alu(InvalidReg, src1, op2, OpTeq, SetCC, c); |
| } |
| BufferOffset |
| Assembler::as_tst(Register src1, Operand2 op2, Condition c) |
| { |
| return as_alu(InvalidReg, src1, op2, OpTst, SetCC, c); |
| } |
| |
| static MOZ_CONSTEXPR_VAR Register NoAddend = { Registers::pc }; |
| |
| static const int SignExtend = 0x06000070; |
| |
| enum SignExtend { |
| SxSxtb = 10 << 20, |
| SxSxth = 11 << 20, |
| SxUxtb = 14 << 20, |
| SxUxth = 15 << 20 |
| }; |
| |
| // Sign extension operations. |
| BufferOffset |
| Assembler::as_sxtb(Register dest, Register src, int rotate, Condition c) |
| { |
| return writeInst((int)c | SignExtend | SxSxtb | RN(NoAddend) | RD(dest) | ((rotate & 3) << 10) | src.code()); |
| } |
| BufferOffset |
| Assembler::as_sxth(Register dest, Register src, int rotate, Condition c) |
| { |
| return writeInst((int)c | SignExtend | SxSxth | RN(NoAddend) | RD(dest) | ((rotate & 3) << 10) | src.code()); |
| } |
| BufferOffset |
| Assembler::as_uxtb(Register dest, Register src, int rotate, Condition c) |
| { |
| return writeInst((int)c | SignExtend | SxUxtb | RN(NoAddend) | RD(dest) | ((rotate & 3) << 10) | src.code()); |
| } |
| BufferOffset |
| Assembler::as_uxth(Register dest, Register src, int rotate, Condition c) |
| { |
| return writeInst((int)c | SignExtend | SxUxth | RN(NoAddend) | RD(dest) | ((rotate & 3) << 10) | src.code()); |
| } |
| |
| static uint32_t |
| EncodeMovW(Register dest, Imm16 imm, Assembler::Condition c) |
| { |
| MOZ_ASSERT(HasMOVWT()); |
| return 0x03000000 | c | imm.encode() | RD(dest); |
| } |
| |
| static uint32_t |
| EncodeMovT(Register dest, Imm16 imm, Assembler::Condition c) |
| { |
| MOZ_ASSERT(HasMOVWT()); |
| return 0x03400000 | c | imm.encode() | RD(dest); |
| } |
| |
| // Not quite ALU worthy, but these are 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) |
| { |
| return writeInst(EncodeMovW(dest, imm, c)); |
| } |
| |
| /* static */ void |
| Assembler::as_movw_patch(Register dest, Imm16 imm, Condition c, Instruction* pos) |
| { |
| WriteInstStatic(EncodeMovW(dest, imm, c), (uint32_t*)pos); |
| } |
| |
| BufferOffset |
| Assembler::as_movt(Register dest, Imm16 imm, Condition c) |
| { |
| return writeInst(EncodeMovT(dest, imm, c)); |
| } |
| |
| /* static */ void |
| Assembler::as_movt_patch(Register dest, Imm16 imm, Condition c, Instruction* pos) |
| { |
| WriteInstStatic(EncodeMovT(dest, imm, c), (uint32_t*)pos); |
| } |
| |
| static const int mull_tag = 0x90; |
| |
| BufferOffset |
| Assembler::as_genmul(Register dhi, Register dlo, Register rm, Register rn, |
| MULOp op, SBit s, Condition c) |
| { |
| |
| return writeInst(RN(dhi) | maybeRD(dlo) | RM(rm) | rn.code() | op | s | c | mull_tag); |
| } |
| BufferOffset |
| Assembler::as_mul(Register dest, Register src1, Register src2, SBit s, Condition c) |
| { |
| return as_genmul(dest, InvalidReg, src1, src2, OpmMul, s, c); |
| } |
| BufferOffset |
| Assembler::as_mla(Register dest, Register acc, Register src1, Register src2, |
| SBit s, Condition c) |
| { |
| return as_genmul(dest, acc, src1, src2, OpmMla, s, c); |
| } |
| BufferOffset |
| Assembler::as_umaal(Register destHI, Register destLO, Register src1, Register src2, Condition c) |
| { |
| return as_genmul(destHI, destLO, src1, src2, OpmUmaal, LeaveCC, c); |
| } |
| BufferOffset |
| Assembler::as_mls(Register dest, Register acc, Register src1, Register src2, Condition c) |
| { |
| return as_genmul(dest, acc, src1, src2, OpmMls, LeaveCC, c); |
| } |
| |
| BufferOffset |
| Assembler::as_umull(Register destHI, Register destLO, Register src1, Register src2, |
| SBit s, Condition c) |
| { |
| return as_genmul(destHI, destLO, src1, src2, OpmUmull, s, c); |
| } |
| |
| BufferOffset |
| Assembler::as_umlal(Register destHI, Register destLO, Register src1, Register src2, |
| SBit s, Condition c) |
| { |
| return as_genmul(destHI, destLO, src1, src2, OpmUmlal, s, c); |
| } |
| |
| BufferOffset |
| Assembler::as_smull(Register destHI, Register destLO, Register src1, Register src2, |
| SBit s, Condition c) |
| { |
| return as_genmul(destHI, destLO, src1, src2, OpmSmull, s, c); |
| } |
| |
| BufferOffset |
| Assembler::as_smlal(Register destHI, Register destLO, Register src1, Register src2, |
| SBit s, Condition c) |
| { |
| return as_genmul(destHI, destLO, src1, src2, OpmSmlal, s, c); |
| } |
| |
| BufferOffset |
| Assembler::as_sdiv(Register rd, Register rn, Register rm, Condition c) |
| { |
| return writeInst(0x0710f010 | c | RN(rd) | RM(rm) | rn.code()); |
| } |
| |
| BufferOffset |
| Assembler::as_udiv(Register rd, Register rn, Register rm, Condition c) |
| { |
| return writeInst(0x0730f010 | c | RN(rd) | RM(rm) | rn.code()); |
| } |
| |
| BufferOffset |
| Assembler::as_clz(Register dest, Register src, Condition c) |
| { |
| return writeInst(RD(dest) | src.code() | c | 0x016f0f10); |
| } |
| |
| // Data transfer instructions: ldr, str, ldrb, strb. Using an int to |
| // differentiate between 8 bits and 32 bits is overkill, but meh. |
| |
| static uint32_t |
| EncodeDtr(LoadStore ls, int size, Index mode, Register rt, DTRAddr addr, Assembler::Condition c) |
| { |
| MOZ_ASSERT(mode == Offset || (rt != addr.getBase() && pc != addr.getBase())); |
| MOZ_ASSERT(size == 32 || size == 8); |
| return 0x04000000 | ls | (size == 8 ? 0x00400000 : 0) | mode | c | RT(rt) | addr.encode(); |
| } |
| |
| BufferOffset |
| Assembler::as_dtr(LoadStore ls, int size, Index mode, Register rt, DTRAddr addr, Condition c) |
| { |
| return writeInst(EncodeDtr(ls, size, mode, rt, addr, c)); |
| } |
| |
| /* static */ void |
| Assembler::as_dtr_patch(LoadStore ls, int size, Index mode, Register rt, DTRAddr addr, Condition c, |
| uint32_t* dest) |
| { |
| WriteInstStatic(EncodeDtr(ls, size, mode, rt, addr, c), 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_ : 16; |
| uint32_t cond_ : 4; |
| LoadType loadType_ : 2; |
| uint32_t destReg_ : 5; |
| uint32_t destType_ : 1; |
| uint32_t ONES : 4; |
| |
| static const uint32_t ExpectedOnes = 0xfu; |
| |
| public: |
| void init(uint32_t index, Assembler::Condition cond, LoadType lt, Register destReg) { |
| index_ = index; |
| MOZ_ASSERT(index_ == index); |
| cond_ = cond >> 28; |
| MOZ_ASSERT(cond_ == cond >> 28); |
| loadType_ = lt; |
| ONES = ExpectedOnes; |
| destReg_ = destReg.code(); |
| destType_ = 0; |
| } |
| void init(uint32_t index, Assembler::Condition cond, LoadType lt, const VFPRegister& destReg) { |
| MOZ_ASSERT(destReg.isFloat()); |
| index_ = index; |
| MOZ_ASSERT(index_ == index); |
| cond_ = cond >> 28; |
| MOZ_ASSERT(cond_ == cond >> 28); |
| loadType_ = lt; |
| ONES = ExpectedOnes; |
| destReg_ = destReg.id(); |
| destType_ = destReg.isDouble(); |
| } |
| Assembler::Condition getCond() const { |
| return Assembler::Condition(cond_ << 28); |
| } |
| |
| Register getReg() const { |
| return Register::FromCode(destReg_); |
| } |
| VFPRegister getVFPReg() const { |
| VFPRegister r = VFPRegister(destReg_, destType_ ? VFPRegister::Double : VFPRegister::Single); |
| return r; |
| } |
| |
| int32_t getIndex() const { |
| return index_; |
| } |
| void setIndex(uint32_t index) { |
| MOZ_ASSERT(ONES == ExpectedOnes && loadType_ != PoolBOGUS); |
| index_ = index; |
| MOZ_ASSERT(index_ == index); |
| } |
| |
| LoadType getLoadType() const { |
| // 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 != ExpectedOnes) |
| return PoolHintData::PoolBranch; |
| return loadType_; |
| } |
| |
| bool isValidPoolHint() const { |
| // 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 == ExpectedOnes; |
| } |
| }; |
| |
| union PoolHintPun |
| { |
| PoolHintData phd; |
| uint32_t raw; |
| }; |
| |
| // Handles all of the other integral data transferring functions: ldrsb, ldrsh, |
| // ldrd, etc. The size is given in bits. |
| BufferOffset |
| Assembler::as_extdtr(LoadStore ls, int size, bool IsSigned, Index mode, |
| Register rt, EDtrAddr addr, Condition c) |
| { |
| int extra_bits2 = 0; |
| int extra_bits1 = 0; |
| switch(size) { |
| case 8: |
| MOZ_ASSERT(IsSigned); |
| MOZ_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) { |
| MOZ_ASSERT(ls != IsStore); |
| extra_bits2 |= 0x2; |
| } |
| break; |
| case 64: |
| extra_bits2 = (ls == IsStore) ? 0x3 : 0x2; |
| extra_bits1 = 0; |
| break; |
| default: |
| MOZ_CRASH("SAY WHAT?"); |
| } |
| return writeInst(extra_bits2 << 5 | extra_bits1 << 20 | 0x90 | |
| addr.encode() | RT(rt) | mode | c); |
| } |
| |
| 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); |
| } |
| |
| // Note, it's possible for markAsBranch and loadToPC to disagree, |
| // because some loads to the PC are not necessarily encoding |
| // instructions that should be marked as branches: only patchable |
| // near branch instructions should be marked. |
| |
| BufferOffset |
| Assembler::allocEntry(size_t numInst, unsigned numPoolEntries, |
| uint8_t* inst, uint8_t* data, ARMBuffer::PoolEntry* pe, |
| bool markAsBranch, bool loadToPC) |
| { |
| BufferOffset offs = m_buffer.allocEntry(numInst, numPoolEntries, inst, data, pe, markAsBranch); |
| propagateOOM(offs.assigned()); |
| #ifdef JS_DISASM_ARM |
| spewData(offs, numInst, loadToPC); |
| #endif |
| return offs; |
| } |
| |
| // This is also used for instructions that might be resolved into branches, |
| // or might not. If dest==pc then it is effectively a branch. |
| |
| BufferOffset |
| Assembler::as_Imm32Pool(Register dest, uint32_t value, Condition c) |
| { |
| PoolHintPun php; |
| php.phd.init(0, c, PoolHintData::PoolDTR, dest); |
| BufferOffset offs = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, nullptr, false, |
| dest == pc); |
| return offs; |
| } |
| |
| /* static */ void |
| Assembler::WritePoolEntry(Instruction* addr, Condition c, uint32_t data) |
| { |
| MOZ_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; |
| MOZ_ASSERT(addr->extractCond() == c); |
| } |
| |
| BufferOffset |
| Assembler::as_BranchPool(uint32_t value, RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c, |
| Label* documentation) |
| { |
| PoolHintPun php; |
| php.phd.init(0, c, PoolHintData::PoolBranch, pc); |
| BufferOffset ret = allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value, pe, |
| /* markAsBranch = */ true, /* loadToPC = */ true); |
| // 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>(ret), c, ret); |
| } else if (!oom()) { |
| label->use(ret.getOffset()); |
| } |
| #ifdef JS_DISASM_ARM |
| if (documentation) |
| spewTarget(documentation); |
| #endif |
| return ret; |
| } |
| |
| BufferOffset |
| Assembler::as_FImm64Pool(VFPRegister dest, double value, Condition c) |
| { |
| MOZ_ASSERT(dest.isDouble()); |
| PoolHintPun php; |
| php.phd.init(0, c, PoolHintData::PoolVDTR, dest); |
| return allocEntry(1, 2, (uint8_t*)&php.raw, (uint8_t*)&value); |
| } |
| |
| BufferOffset |
| Assembler::as_FImm32Pool(VFPRegister dest, float value, Condition c) |
| { |
| // Insert floats into the double pool as they have the same limitations on |
| // immediate offset. This wastes 4 bytes padding per float. An alternative |
| // would be to have a separate pool for floats. |
| MOZ_ASSERT(dest.isSingle()); |
| PoolHintPun php; |
| php.phd.init(0, c, PoolHintData::PoolVDTR, dest); |
| return allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value); |
| } |
| |
| // Pool callbacks stuff: |
| void |
| Assembler::InsertIndexIntoTag(uint8_t* load_, uint32_t index) |
| { |
| uint32_t* load = (uint32_t*)load_; |
| PoolHintPun php; |
| php.raw = *load; |
| php.phd.setIndex(index); |
| *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. |
| void |
| 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: |
| MOZ_CRASH("bogus load type!"); |
| case PoolHintData::PoolDTR: |
| Assembler::as_dtr_patch(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 nullptr so it will crash). |
| if (data.isValidPoolHint()) { |
| Assembler::as_dtr_patch(IsLoad, 32, Offset, pc, |
| DTRAddr(pc, DtrOffImm(offset+4*data.getIndex() - 8)), |
| data.getCond(), instAddr); |
| } |
| break; |
| case PoolHintData::PoolVDTR: { |
| VFPRegister dest = data.getVFPReg(); |
| int32_t imm = offset + (data.getIndex() * 4) - 8; |
| MOZ_ASSERT(-1024 < imm && imm < 1024); |
| Assembler::as_vdtr_patch(IsLoad, dest, VFPAddr(pc, VFPOffImm(imm)), data.getCond(), |
| instAddr); |
| break; |
| } |
| } |
| } |
| |
| // Atomic instruction stuff: |
| |
| BufferOffset |
| Assembler::as_ldrex(Register rt, Register rn, Condition c) |
| { |
| return writeInst(0x01900f9f | (int)c | RT(rt) | RN(rn)); |
| } |
| |
| BufferOffset |
| Assembler::as_ldrexh(Register rt, Register rn, Condition c) |
| { |
| return writeInst(0x01f00f9f | (int)c | RT(rt) | RN(rn)); |
| } |
| |
| BufferOffset |
| Assembler::as_ldrexb(Register rt, Register rn, Condition c) |
| { |
| return writeInst(0x01d00f9f | (int)c | RT(rt) | RN(rn)); |
| } |
| |
| BufferOffset |
| Assembler::as_strex(Register rd, Register rt, Register rn, Condition c) |
| { |
| MOZ_ASSERT(rd != rn && rd != rt); // True restriction on Cortex-A7 (RPi2) |
| return writeInst(0x01800f90 | (int)c | RD(rd) | RN(rn) | rt.code()); |
| } |
| |
| BufferOffset |
| Assembler::as_strexh(Register rd, Register rt, Register rn, Condition c) |
| { |
| MOZ_ASSERT(rd != rn && rd != rt); // True restriction on Cortex-A7 (RPi2) |
| return writeInst(0x01e00f90 | (int)c | RD(rd) | RN(rn) | rt.code()); |
| } |
| |
| BufferOffset |
| Assembler::as_strexb(Register rd, Register rt, Register rn, Condition c) |
| { |
| MOZ_ASSERT(rd != rn && rd != rt); // True restriction on Cortex-A7 (RPi2) |
| return writeInst(0x01c00f90 | (int)c | RD(rd) | RN(rn) | rt.code()); |
| } |
| |
| // Memory barrier stuff: |
| |
| BufferOffset |
| Assembler::as_dmb(BarrierOption option) |
| { |
| return writeInst(0xf57ff050U | (int)option); |
| } |
| BufferOffset |
| Assembler::as_dsb(BarrierOption option) |
| { |
| return writeInst(0xf57ff040U | (int)option); |
| } |
| BufferOffset |
| Assembler::as_isb() |
| { |
| return writeInst(0xf57ff06fU); // option == SY |
| } |
| BufferOffset |
| Assembler::as_dsb_trap() |
| { |
| // DSB is "mcr 15, 0, r0, c7, c10, 4". |
| // See eg https://bugs.kde.org/show_bug.cgi?id=228060. |
| // ARMv7 manual, "VMSA CP15 c7 register summary". |
| // Flagged as "legacy" starting with ARMv8, may be disabled on chip, see |
| // ARMv8 manual E2.7.3 and G3.18.16. |
| return writeInst(0xee070f9a); |
| } |
| BufferOffset |
| Assembler::as_dmb_trap() |
| { |
| // DMB is "mcr 15, 0, r0, c7, c10, 5". |
| // ARMv7 manual, "VMSA CP15 c7 register summary". |
| // Flagged as "legacy" starting with ARMv8, may be disabled on chip, see |
| // ARMv8 manual E2.7.3 and G3.18.16. |
| return writeInst(0xee070fba); |
| } |
| BufferOffset |
| Assembler::as_isb_trap() |
| { |
| // ISB is "mcr 15, 0, r0, c7, c5, 4". |
| // ARMv7 manual, "VMSA CP15 c7 register summary". |
| // Flagged as "legacy" starting with ARMv8, may be disabled on chip, see |
| // ARMv8 manual E2.7.3 and G3.18.16. |
| return writeInst(0xee070f94); |
| } |
| |
| // Control flow stuff: |
| |
| // bx can *only* branch to a register, never to an immediate. |
| BufferOffset |
| Assembler::as_bx(Register r, Condition c) |
| { |
| BufferOffset ret = writeInst(((int) c) | OpBx | r.code()); |
| 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, Label* documentation) |
| { |
| BufferOffset ret = writeBranchInst(((int)c) | OpB | off.encode(), documentation); |
| return ret; |
| } |
| |
| BufferOffset |
| Assembler::as_b(Label* l, Condition c) |
| { |
| if (l->bound()) { |
| // Note only one instruction is emitted here, the NOP is overwritten. |
| BufferOffset ret = allocBranchInst(); |
| if (oom()) |
| return BufferOffset(); |
| |
| as_b(BufferOffset(l).diffB<BOffImm>(ret), c, ret); |
| #ifdef JS_DISASM_ARM |
| spewBranch(m_buffer.getInstOrNull(ret), l); |
| #endif |
| return ret; |
| } |
| |
| if (oom()) |
| return BufferOffset(); |
| |
| 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. |
| if (!BOffImm::IsInRange(old)) { |
| m_buffer.fail_bail(); |
| return ret; |
| } |
| ret = as_b(BOffImm(old), c, l); |
| } else { |
| old = LabelBase::INVALID_OFFSET; |
| BOffImm inv; |
| ret = as_b(inv, c, l); |
| } |
| |
| if (oom()) |
| return BufferOffset(); |
| |
| DebugOnly<int32_t> check = l->use(ret.getOffset()); |
| MOZ_ASSERT(check == old); |
| return ret; |
| } |
| |
| BufferOffset |
| Assembler::as_b(BOffImm off, Condition c, BufferOffset inst) |
| { |
| // JS_DISASM_ARM NOTE: Can't disassemble here, because numerous callers use this to |
| // patchup old code. Must disassemble in caller where it makes sense. Not many callers. |
| *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) | OpBlx | 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, Label* documentation) |
| { |
| return writeBranchInst(((int)c) | OpBl | off.encode(), documentation); |
| } |
| |
| BufferOffset |
| Assembler::as_bl(Label* l, Condition c) |
| { |
| if (l->bound()) { |
| // Note only one instruction is emitted here, the NOP is overwritten. |
| BufferOffset ret = allocBranchInst(); |
| if (oom()) |
| return BufferOffset(); |
| |
| as_bl(BufferOffset(l).diffB<BOffImm>(ret), c, ret); |
| #ifdef JS_DISASM_ARM |
| spewBranch(m_buffer.getInstOrNull(ret), l); |
| #endif |
| return ret; |
| } |
| |
| if (oom()) |
| return BufferOffset(); |
| |
| 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(); |
| if (!BOffImm::IsInRange(old)) { |
| m_buffer.fail_bail(); |
| return ret; |
| } |
| ret = as_bl(BOffImm(old), c, l); |
| } else { |
| old = LabelBase::INVALID_OFFSET; |
| BOffImm inv; |
| ret = as_bl(inv, c, l); |
| } |
| |
| if (oom()) |
| return BufferOffset(); |
| |
| DebugOnly<int32_t> check = l->use(ret.getOffset()); |
| MOZ_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. |
| MOZ_ASSERT((r.code() & ~0xf) == 0); |
| return writeInst(0x012cf000 | int(c) | r.code()); |
| } |
| |
| // VFP instructions! |
| enum vfp_tags { |
| VfpTag = 0x0C000A00, |
| VfpArith = 0x02000000 |
| }; |
| |
| BufferOffset |
| Assembler::writeVFPInst(vfp_size sz, uint32_t blob) |
| { |
| MOZ_ASSERT((sz & blob) == 0); |
| MOZ_ASSERT((VfpTag & blob) == 0); |
| return writeInst(VfpTag | sz | blob); |
| } |
| |
| /* static */ void |
| Assembler::WriteVFPInstStatic(vfp_size sz, uint32_t blob, uint32_t* dest) |
| { |
| MOZ_ASSERT((sz & blob) == 0); |
| MOZ_ASSERT((VfpTag & blob) == 0); |
| WriteInstStatic(VfpTag | 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. |
| MOZ_ASSERT_IF(!vn.isMissing(), vd.equiv(vn)); |
| MOZ_ASSERT_IF(!vm.isMissing(), vd.equiv(vm)); |
| vfp_size sz = vd.isDouble() ? IsDouble : IsSingle; |
| return writeVFPInst(sz, VD(vd) | VN(vn) | VM(vm) | op | VfpArith | c); |
| } |
| |
| BufferOffset |
| Assembler::as_vadd(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, vn, vm, OpvAdd, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vdiv(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, vn, vm, OpvDiv, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, vn, vm, OpvMul, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vnmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, vn, vm, OpvMul, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vnmla(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) |
| { |
| MOZ_CRASH("Feature NYI"); |
| } |
| |
| BufferOffset |
| Assembler::as_vnmls(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) |
| { |
| MOZ_CRASH("Feature NYI"); |
| } |
| |
| BufferOffset |
| Assembler::as_vneg(VFPRegister vd, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, NoVFPRegister, vm, OpvNeg, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vsqrt(VFPRegister vd, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, NoVFPRegister, vm, OpvSqrt, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vabs(VFPRegister vd, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, NoVFPRegister, vm, OpvAbs, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vsub(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, vn, vm, OpvSub, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vcmp(VFPRegister vd, VFPRegister vm, Condition c) |
| { |
| return as_vfp_float(vd, NoVFPRegister, vm, OpvCmp, c); |
| } |
| |
| BufferOffset |
| Assembler::as_vcmpz(VFPRegister vd, Condition c) |
| { |
| return as_vfp_float(vd, NoVFPRegister, NoVFPRegister, OpvCmpz, 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, OpvMov, c); |
| } |
| |
| // Transfer 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; |
| MOZ_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. |
| MOZ_ASSERT_IF(vt2 == InvalidReg, f2c == FloatToCore); |
| idx = idx << 21; |
| } else { |
| MOZ_ASSERT(idx == 0); |
| } |
| |
| if (vt2 == InvalidReg) |
| return writeVFPInst(sz, WordTransfer | f2c | c | RT(vt1) | maybeRN(vt2) | VN(vm) | idx); |
| |
| // We are doing a 64 bit transfer. |
| return writeVFPInst(sz, DoubleTransfer | f2c | c | RT(vt1) | maybeRN(vt2) | VM(vm) | idx); |
| } |
| |
| enum vcvt_destFloatness { |
| VcvtToInteger = 1 << 18, |
| VcvtToFloat = 0 << 18 |
| }; |
| enum vcvt_toZero { |
| VcvtToZero = 1 << 7, // Use the default rounding mode, which rounds truncates. |
| VcvtToFPSCR = 0 << 7 // Use whatever rounding mode the fpscr specifies. |
| }; |
| enum vcvt_Signedness { |
| VcvtToSigned = 1 << 16, |
| VcvtToUnsigned = 0 << 16, |
| VcvtFromSigned = 1 << 7, |
| VcvtFromUnsigned = 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. |
| MOZ_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 = VcvtToFPSCR; |
| MOZ_ASSERT(vd.isFloat() || vm.isFloat()); |
| if (vd.isSingle() || vm.isSingle()) |
| sz = IsSingle; |
| |
| if (vd.isFloat()) { |
| destFloat = VcvtToFloat; |
| opSign = (vm.isSInt()) ? VcvtFromSigned : VcvtFromUnsigned; |
| } else { |
| destFloat = VcvtToInteger; |
| opSign = (vd.isSInt()) ? VcvtToSigned : VcvtToUnsigned; |
| doToZero = useFPSCR ? VcvtToFPSCR : VcvtToZero; |
| } |
| 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) |
| { |
| MOZ_ASSERT(vd.isFloat()); |
| uint32_t sx = 0x1; |
| vfp_size sf = vd.isDouble() ? IsDouble : IsSingle; |
| int32_t imm5 = fixedPoint; |
| imm5 = (sx ? 32 : 16) - imm5; |
| MOZ_ASSERT(imm5 >= 0); |
| imm5 = imm5 >> 1 | (imm5 & 1) << 5; |
| return writeVFPInst(sf, 0x02BA0040 | VD(vd) | toFixed << 18 | sx << 7 | |
| (!isSigned) << 16 | imm5 | c); |
| } |
| |
| // Transfer between VFP and memory. |
| static uint32_t |
| EncodeVdtr(LoadStore ls, VFPRegister vd, VFPAddr addr, Assembler::Condition c) |
| { |
| return ls | 0x01000000 | addr.encode() | VD(vd) | c; |
| } |
| |
| BufferOffset |
| Assembler::as_vdtr(LoadStore ls, VFPRegister vd, VFPAddr addr, |
| Condition c /* vfp doesn't have a wb option */) |
| { |
| vfp_size sz = vd.isDouble() ? IsDouble : IsSingle; |
| return writeVFPInst(sz, EncodeVdtr(ls, vd, addr, c)); |
| } |
| |
| /* static */ void |
| Assembler::as_vdtr_patch(LoadStore ls, VFPRegister vd, VFPAddr addr, Condition c, uint32_t* dest) |
| { |
| vfp_size sz = vd.isDouble() ? IsDouble : IsSingle; |
| WriteVFPInstStatic(sz, EncodeVdtr(ls, vd, addr, 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) |
| { |
| MOZ_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) |
| { |
| MOZ_ASSERT(imm.isValid()); |
| vfp_size sz = vd.isDouble() ? IsDouble : IsSingle; |
| 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); |
| MOZ_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) |
| { |
| #ifdef JS_DISASM_ARM |
| spewLabel(label); |
| #endif |
| 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 { |
| // Even a 0 offset may be invalid if we're out of memory. |
| if (oom()) |
| return; |
| BufferOffset next; |
| more = nextLink(b, &next); |
| Instruction branch = *editSrc(b); |
| Condition c = branch.extractCond(); |
| 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 |
| MOZ_CRASH("crazy fixup!"); |
| b = next; |
| } while (more); |
| } |
| label->bind(nextOffset().getOffset()); |
| } |
| |
| void |
| Assembler::bind(RepatchLabel* label) |
| { |
| // It does not seem to be useful to record this label for |
| // disassembly, as the value that is bound to the label is often |
| // effectively garbage and is replaced by something else later. |
| BufferOffset dest = nextOffset(); |
| if (label->used() && !oom()) { |
| // 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 |
| cond = branch->extractCond(); |
| as_b(dest.diffB<BOffImm>(branchOff), cond, branchOff); |
| } |
| label->bind(dest.getOffset()); |
| } |
| |
| void |
| Assembler::retarget(Label* label, Label* target) |
| { |
| #ifdef JS_DISASM_ARM |
| spewRetarget(label, target); |
| #endif |
| if (label->used() && !oom()) { |
| 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. |
| 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(); |
| 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 |
| MOZ_CRASH("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()); |
| MOZ_ASSERT((int32_t)prev == Label::INVALID_OFFSET); |
| } |
| } |
| label->reset(); |
| |
| } |
| |
| void |
| Assembler::retargetWithOffset(size_t baseOffset, const LabelBase* label, LabelBase* target) |
| { |
| if (!label->used()) |
| return; |
| |
| MOZ_ASSERT(!target->bound()); |
| bool more; |
| BufferOffset labelBranchOffset(label->offset() + baseOffset); |
| do { |
| BufferOffset next; |
| more = nextLink(labelBranchOffset, &next); |
| |
| Instruction branch = *editSrc(labelBranchOffset); |
| Condition c = branch.extractCond(); |
| int32_t prev = target->use(labelBranchOffset.getOffset()); |
| |
| MOZ_RELEASE_ASSERT(prev == Label::INVALID_OFFSET || unsigned(prev) < size()); |
| |
| BOffImm newOffset; |
| if (prev != Label::INVALID_OFFSET) |
| newOffset = BOffImm(prev); |
| |
| if (branch.is<InstBImm>()) |
| as_b(newOffset, c, labelBranchOffset); |
| else if (branch.is<InstBLImm>()) |
| as_bl(newOffset, c, labelBranchOffset); |
| else |
| MOZ_CRASH("crazy fixup!"); |
| |
| labelBranchOffset = BufferOffset(next.getOffset() + baseOffset); |
| } while (more); |
| } |
| |
| 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::flushBuffer() |
| { |
| m_buffer.flushPool(); |
| } |
| |
| void |
| Assembler::enterNoPool(size_t maxInst) |
| { |
|