| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| #include "vm/globals.h" // NOLINT | |
| #if defined(TARGET_ARCH_ARM) | |
| #define SHOULD_NOT_INCLUDE_RUNTIME | |
| #include "vm/class_id.h" | |
| #include "vm/compiler/assembler/assembler.h" | |
| #include "vm/compiler/backend/locations.h" | |
| #include "vm/cpu.h" | |
| #include "vm/instructions.h" | |
| #include "vm/tags.h" | |
| // For use by LR related macros (e.g. CLOBBERS_LR). | |
| #define __ this-> | |
| namespace dart { | |
| DECLARE_FLAG(bool, check_code_pointer); | |
| DECLARE_FLAG(bool, precompiled_mode); | |
| namespace compiler { | |
| Assembler::Assembler(ObjectPoolBuilder* object_pool_builder, | |
| intptr_t far_branch_level) | |
| : AssemblerBase(object_pool_builder), | |
| use_far_branches_(far_branch_level != 0), | |
| constant_pool_allowed_(false) { | |
| generate_invoke_write_barrier_wrapper_ = [&](Condition cond, Register reg) { | |
| Call( | |
| Address(THR, target::Thread::write_barrier_wrappers_thread_offset(reg)), | |
| cond); | |
| }; | |
| generate_invoke_array_write_barrier_ = [&](Condition cond) { | |
| Call(Address(THR, target::Thread::array_write_barrier_entry_point_offset()), | |
| cond); | |
| }; | |
| } | |
| uint32_t Address::encoding3() const { | |
| if (kind_ == Immediate) { | |
| uint32_t offset = encoding_ & kOffset12Mask; | |
| ASSERT(offset < 256); | |
| return (encoding_ & ~kOffset12Mask) | B22 | ((offset & 0xf0) << 4) | | |
| (offset & 0xf); | |
| } | |
| ASSERT(kind_ == IndexRegister); | |
| return encoding_; | |
| } | |
| uint32_t Address::vencoding() const { | |
| ASSERT(kind_ == Immediate); | |
| uint32_t offset = encoding_ & kOffset12Mask; | |
| ASSERT(offset < (1 << 10)); // In the range 0 to +1020. | |
| ASSERT(Utils::IsAligned(offset, 4)); // Multiple of 4. | |
| int mode = encoding_ & ((8 | 4 | 1) << 21); | |
| ASSERT((mode == Offset) || (mode == NegOffset)); | |
| uint32_t vencoding = (encoding_ & (0xf << kRnShift)) | (offset >> 2); | |
| if (mode == Offset) { | |
| vencoding |= 1 << 23; | |
| } | |
| return vencoding; | |
| } | |
| void Assembler::Emit(int32_t value) { | |
| AssemblerBuffer::EnsureCapacity ensured(&buffer_); | |
| buffer_.Emit<int32_t>(value); | |
| } | |
| void Assembler::EmitType01(Condition cond, | |
| int type, | |
| Opcode opcode, | |
| int set_cc, | |
| Register rn, | |
| Register rd, | |
| Operand o) { | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = | |
| static_cast<int32_t>(cond) << kConditionShift | type << kTypeShift | | |
| static_cast<int32_t>(opcode) << kOpcodeShift | set_cc << kSShift | | |
| ArmEncode::Rn(rn) | ArmEncode::Rd(rd) | o.encoding(); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitType5(Condition cond, int32_t offset, bool link) { | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = static_cast<int32_t>(cond) << kConditionShift | | |
| 5 << kTypeShift | (link ? 1 : 0) << kLinkShift; | |
| BailoutIfInvalidBranchOffset(offset); | |
| Emit(Assembler::EncodeBranchOffset(offset, encoding)); | |
| } | |
| void Assembler::EmitMemOp(Condition cond, | |
| bool load, | |
| bool byte, | |
| Register rd, | |
| Address ad) { | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| // Unpredictable, illegal on some microarchitectures. | |
| ASSERT(!ad.has_writeback() || (ad.rn() != rd)); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B26 | | |
| (ad.kind() == Address::Immediate ? 0 : B25) | | |
| (load ? L : 0) | (byte ? B : 0) | ArmEncode::Rd(rd) | | |
| ad.encoding(); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitMemOpAddressMode3(Condition cond, | |
| int32_t mode, | |
| Register rd, | |
| Address ad) { | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| // Unpredictable, illegal on some microarchitectures. | |
| ASSERT(!ad.has_writeback() || (ad.rn() != rd)); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | mode | | |
| ArmEncode::Rd(rd) | ad.encoding3(); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitMultiMemOp(Condition cond, | |
| BlockAddressMode am, | |
| bool load, | |
| Register base, | |
| RegList regs) { | |
| ASSERT(base != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| // Unpredictable, illegal on some microarchitectures. | |
| ASSERT(!Address::has_writeback(am) || !(regs & (1 << base))); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| am | (load ? L : 0) | ArmEncode::Rn(base) | regs; | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitShiftImmediate(Condition cond, | |
| Shift opcode, | |
| Register rd, | |
| Register rm, | |
| Operand o) { | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(o.type() == 1); | |
| int32_t encoding = static_cast<int32_t>(cond) << kConditionShift | | |
| static_cast<int32_t>(MOV) << kOpcodeShift | | |
| ArmEncode::Rd(rd) | o.encoding() << kShiftImmShift | | |
| static_cast<int32_t>(opcode) << kShiftShift | | |
| static_cast<int32_t>(rm); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitShiftRegister(Condition cond, | |
| Shift opcode, | |
| Register rd, | |
| Register rm, | |
| Operand o) { | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(o.type() == 0); | |
| int32_t encoding = static_cast<int32_t>(cond) << kConditionShift | | |
| static_cast<int32_t>(MOV) << kOpcodeShift | | |
| ArmEncode::Rd(rd) | o.encoding() << kShiftRegisterShift | | |
| static_cast<int32_t>(opcode) << kShiftShift | B4 | | |
| static_cast<int32_t>(rm); | |
| Emit(encoding); | |
| } | |
| void Assembler::and_(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), AND, 0, rn, rd, o); | |
| } | |
| void Assembler::ands(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), AND, 1, rn, rd, o); | |
| } | |
| void Assembler::eor(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), EOR, 0, rn, rd, o); | |
| } | |
| void Assembler::sub(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), SUB, 0, rn, rd, o); | |
| } | |
| void Assembler::rsb(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), RSB, 0, rn, rd, o); | |
| } | |
| void Assembler::rsbs(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), RSB, 1, rn, rd, o); | |
| } | |
| void Assembler::add(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), ADD, 0, rn, rd, o); | |
| } | |
| void Assembler::adds(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), ADD, 1, rn, rd, o); | |
| } | |
| void Assembler::subs(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), SUB, 1, rn, rd, o); | |
| } | |
| void Assembler::adc(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), ADC, 0, rn, rd, o); | |
| } | |
| void Assembler::adcs(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), ADC, 1, rn, rd, o); | |
| } | |
| void Assembler::sbc(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), SBC, 0, rn, rd, o); | |
| } | |
| void Assembler::sbcs(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), SBC, 1, rn, rd, o); | |
| } | |
| void Assembler::rsc(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), RSC, 0, rn, rd, o); | |
| } | |
| void Assembler::tst(Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), TST, 1, rn, R0, o); | |
| } | |
| void Assembler::teq(Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), TEQ, 1, rn, R0, o); | |
| } | |
| void Assembler::cmp(Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), CMP, 1, rn, R0, o); | |
| } | |
| void Assembler::cmn(Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), CMN, 1, rn, R0, o); | |
| } | |
| void Assembler::orr(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), ORR, 0, rn, rd, o); | |
| } | |
| void Assembler::orrs(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), ORR, 1, rn, rd, o); | |
| } | |
| void Assembler::mov(Register rd, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), MOV, 0, R0, rd, o); | |
| } | |
| void Assembler::movs(Register rd, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), MOV, 1, R0, rd, o); | |
| } | |
| void Assembler::bic(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), BIC, 0, rn, rd, o); | |
| } | |
| void Assembler::bics(Register rd, Register rn, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), BIC, 1, rn, rd, o); | |
| } | |
| void Assembler::mvn_(Register rd, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), MVN, 0, R0, rd, o); | |
| } | |
| void Assembler::mvns(Register rd, Operand o, Condition cond) { | |
| EmitType01(cond, o.type(), MVN, 1, R0, rd, o); | |
| } | |
| void Assembler::clz(Register rd, Register rm, Condition cond) { | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(rm != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(rd != PC); | |
| ASSERT(rm != PC); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B24 | | |
| B22 | B21 | (0xf << 16) | ArmEncode::Rd(rd) | (0xf << 8) | | |
| B4 | static_cast<int32_t>(rm); | |
| Emit(encoding); | |
| } | |
| void Assembler::rbit(Register rd, Register rm, Condition cond) { | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(rm != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(rd != PC); | |
| ASSERT(rm != PC); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B26 | | |
| B25 | B23 | B22 | B21 | B20 | (0xf << 16) | | |
| ArmEncode::Rd(rd) | (0xf << 8) | B5 | B4 | | |
| static_cast<int32_t>(rm); | |
| Emit(encoding); | |
| } | |
| void Assembler::movw(Register rd, uint16_t imm16, Condition cond) { | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = static_cast<int32_t>(cond) << kConditionShift | B25 | B24 | | |
| ((imm16 >> 12) << 16) | ArmEncode::Rd(rd) | | |
| (imm16 & 0xfff); | |
| Emit(encoding); | |
| } | |
| void Assembler::movt(Register rd, uint16_t imm16, Condition cond) { | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = static_cast<int32_t>(cond) << kConditionShift | B25 | B24 | | |
| B22 | ((imm16 >> 12) << 16) | ArmEncode::Rd(rd) | | |
| (imm16 & 0xfff); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitMulOp(Condition cond, | |
| int32_t opcode, | |
| Register rd, | |
| Register rn, | |
| Register rm, | |
| Register rs) { | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(rn != kNoRegister); | |
| ASSERT(rm != kNoRegister); | |
| ASSERT(rs != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = opcode | (static_cast<int32_t>(cond) << kConditionShift) | | |
| ArmEncode::Rn(rn) | ArmEncode::Rd(rd) | ArmEncode::Rs(rs) | | |
| B7 | B4 | ArmEncode::Rm(rm); | |
| Emit(encoding); | |
| } | |
| void Assembler::mul(Register rd, Register rn, Register rm, Condition cond) { | |
| // Assembler registers rd, rn, rm are encoded as rn, rm, rs. | |
| EmitMulOp(cond, 0, R0, rd, rn, rm); | |
| } | |
| // Like mul, but sets condition flags. | |
| void Assembler::muls(Register rd, Register rn, Register rm, Condition cond) { | |
| EmitMulOp(cond, B20, R0, rd, rn, rm); | |
| } | |
| void Assembler::mla(Register rd, | |
| Register rn, | |
| Register rm, | |
| Register ra, | |
| Condition cond) { | |
| // rd <- ra + rn * rm. | |
| // Assembler registers rd, rn, rm, ra are encoded as rn, rm, rs, rd. | |
| EmitMulOp(cond, B21, ra, rd, rn, rm); | |
| } | |
| void Assembler::mls(Register rd, | |
| Register rn, | |
| Register rm, | |
| Register ra, | |
| Condition cond) { | |
| // rd <- ra - rn * rm. | |
| // Assembler registers rd, rn, rm, ra are encoded as rn, rm, rs, rd. | |
| EmitMulOp(cond, B22 | B21, ra, rd, rn, rm); | |
| } | |
| void Assembler::smull(Register rd_lo, | |
| Register rd_hi, | |
| Register rn, | |
| Register rm, | |
| Condition cond) { | |
| // Assembler registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs. | |
| EmitMulOp(cond, B23 | B22, rd_lo, rd_hi, rn, rm); | |
| } | |
| void Assembler::umull(Register rd_lo, | |
| Register rd_hi, | |
| Register rn, | |
| Register rm, | |
| Condition cond) { | |
| // Assembler registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs. | |
| EmitMulOp(cond, B23, rd_lo, rd_hi, rn, rm); | |
| } | |
| void Assembler::umlal(Register rd_lo, | |
| Register rd_hi, | |
| Register rn, | |
| Register rm, | |
| Condition cond) { | |
| // Assembler registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs. | |
| EmitMulOp(cond, B23 | B21, rd_lo, rd_hi, rn, rm); | |
| } | |
| void Assembler::umaal(Register rd_lo, | |
| Register rd_hi, | |
| Register rn, | |
| Register rm) { | |
| ASSERT(rd_lo != IP); | |
| ASSERT(rd_hi != IP); | |
| ASSERT(rn != IP); | |
| ASSERT(rm != IP); | |
| // Assembler registers rd_lo, rd_hi, rn, rm are encoded as rd, rn, rm, rs. | |
| EmitMulOp(AL, B22, rd_lo, rd_hi, rn, rm); | |
| } | |
| void Assembler::EmitDivOp(Condition cond, | |
| int32_t opcode, | |
| Register rd, | |
| Register rn, | |
| Register rm) { | |
| ASSERT(TargetCPUFeatures::integer_division_supported()); | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(rn != kNoRegister); | |
| ASSERT(rm != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = opcode | (static_cast<int32_t>(cond) << kConditionShift) | | |
| (static_cast<int32_t>(rn) << kDivRnShift) | | |
| (static_cast<int32_t>(rd) << kDivRdShift) | B26 | B25 | | |
| B24 | B20 | B15 | B14 | B13 | B12 | B4 | | |
| (static_cast<int32_t>(rm) << kDivRmShift); | |
| Emit(encoding); | |
| } | |
| void Assembler::sdiv(Register rd, Register rn, Register rm, Condition cond) { | |
| EmitDivOp(cond, 0, rd, rn, rm); | |
| } | |
| void Assembler::udiv(Register rd, Register rn, Register rm, Condition cond) { | |
| EmitDivOp(cond, B21, rd, rn, rm); | |
| } | |
| void Assembler::ldr(Register rd, Address ad, Condition cond) { | |
| EmitMemOp(cond, true, false, rd, ad); | |
| } | |
| void Assembler::str(Register rd, Address ad, Condition cond) { | |
| EmitMemOp(cond, false, false, rd, ad); | |
| } | |
| void Assembler::ldrb(Register rd, Address ad, Condition cond) { | |
| EmitMemOp(cond, true, true, rd, ad); | |
| } | |
| void Assembler::strb(Register rd, Address ad, Condition cond) { | |
| EmitMemOp(cond, false, true, rd, ad); | |
| } | |
| void Assembler::ldrh(Register rd, Address ad, Condition cond) { | |
| EmitMemOpAddressMode3(cond, L | B7 | H | B4, rd, ad); | |
| } | |
| void Assembler::strh(Register rd, Address ad, Condition cond) { | |
| EmitMemOpAddressMode3(cond, B7 | H | B4, rd, ad); | |
| } | |
| void Assembler::ldrsb(Register rd, Address ad, Condition cond) { | |
| EmitMemOpAddressMode3(cond, L | B7 | B6 | B4, rd, ad); | |
| } | |
| void Assembler::ldrsh(Register rd, Address ad, Condition cond) { | |
| EmitMemOpAddressMode3(cond, L | B7 | B6 | H | B4, rd, ad); | |
| } | |
| void Assembler::ldrd(Register rd, | |
| Register rd2, | |
| Register rn, | |
| int32_t offset, | |
| Condition cond) { | |
| ASSERT((rd % 2) == 0); | |
| ASSERT(rd2 == rd + 1); | |
| EmitMemOpAddressMode3(cond, B7 | B6 | B4, rd, Address(rn, offset)); | |
| } | |
| void Assembler::strd(Register rd, | |
| Register rd2, | |
| Register rn, | |
| int32_t offset, | |
| Condition cond) { | |
| ASSERT((rd % 2) == 0); | |
| ASSERT(rd2 == rd + 1); | |
| EmitMemOpAddressMode3(cond, B7 | B6 | B5 | B4, rd, Address(rn, offset)); | |
| } | |
| void Assembler::ldm(BlockAddressMode am, | |
| Register base, | |
| RegList regs, | |
| Condition cond) { | |
| ASSERT(regs != 0); | |
| EmitMultiMemOp(cond, am, true, base, regs); | |
| } | |
| void Assembler::stm(BlockAddressMode am, | |
| Register base, | |
| RegList regs, | |
| Condition cond) { | |
| ASSERT(regs != 0); | |
| EmitMultiMemOp(cond, am, false, base, regs); | |
| } | |
| void Assembler::ldrex(Register rt, Register rn, Condition cond) { | |
| ASSERT(rn != kNoRegister); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rn != R15); | |
| ASSERT(rt != R15); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B24 | | |
| B23 | L | (static_cast<int32_t>(rn) << kLdrExRnShift) | | |
| (static_cast<int32_t>(rt) << kLdrExRtShift) | B11 | B10 | | |
| B9 | B8 | B7 | B4 | B3 | B2 | B1 | B0; | |
| Emit(encoding); | |
| } | |
| void Assembler::strex(Register rd, Register rt, Register rn, Condition cond) { | |
| ASSERT(rn != kNoRegister); | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rn != R15); | |
| ASSERT(rd != R15); | |
| ASSERT(rt != R15); | |
| ASSERT(rd != kNoRegister); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(rd != rn); | |
| ASSERT(rd != rt); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B24 | | |
| B23 | (static_cast<int32_t>(rn) << kStrExRnShift) | | |
| (static_cast<int32_t>(rd) << kStrExRdShift) | B11 | B10 | | |
| B9 | B8 | B7 | B4 | | |
| (static_cast<int32_t>(rt) << kStrExRtShift); | |
| Emit(encoding); | |
| } | |
| static int32_t BitFieldExtractEncoding(bool sign_extend, | |
| Register rd, | |
| Register rn, | |
| int32_t lsb, | |
| int32_t width, | |
| Condition cond) { | |
| ASSERT(rn != kNoRegister && rn != PC); | |
| ASSERT(rd != kNoRegister && rd != PC); | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(Utils::IsUint(kBitFieldExtractLSBBits, lsb)); | |
| ASSERT(width >= 1); | |
| ASSERT(lsb + width <= kBitsPerInt32); | |
| const int32_t widthm1 = width - 1; | |
| ASSERT(Utils::IsUint(kBitFieldExtractWidthBits, widthm1)); | |
| return (static_cast<int32_t>(cond) << kConditionShift) | B26 | B25 | B24 | | |
| B23 | (sign_extend ? 0 : B22) | B21 | | |
| (widthm1 << kBitFieldExtractWidthShift) | | |
| (static_cast<int32_t>(rd) << kRdShift) | | |
| (lsb << kBitFieldExtractLSBShift) | B6 | B4 | | |
| (static_cast<int32_t>(rn) << kBitFieldExtractRnShift); | |
| } | |
| void Assembler::sbfx(Register rd, | |
| Register rn, | |
| int32_t lsb, | |
| int32_t width, | |
| Condition cond) { | |
| const bool sign_extend = true; | |
| Emit(BitFieldExtractEncoding(sign_extend, rd, rn, lsb, width, cond)); | |
| } | |
| void Assembler::ubfx(Register rd, | |
| Register rn, | |
| int32_t lsb, | |
| int32_t width, | |
| Condition cond) { | |
| const bool sign_extend = false; | |
| Emit(BitFieldExtractEncoding(sign_extend, rd, rn, lsb, width, cond)); | |
| } | |
| void Assembler::EnterFullSafepoint(Register addr, Register state) { | |
| // We generate the same number of instructions whether or not the slow-path is | |
| // forced. This simplifies GenerateJitCallbackTrampolines. | |
| Label slow_path, done, retry; | |
| if (FLAG_use_slow_path) { | |
| b(&slow_path); | |
| } | |
| LoadImmediate(addr, target::Thread::safepoint_state_offset()); | |
| add(addr, THR, Operand(addr)); | |
| Bind(&retry); | |
| ldrex(state, addr); | |
| cmp(state, Operand(target::Thread::native_safepoint_state_unacquired())); | |
| b(&slow_path, NE); | |
| mov(state, Operand(target::Thread::native_safepoint_state_acquired())); | |
| strex(TMP, state, addr); | |
| cmp(TMP, Operand(0)); // 0 means strex was successful. | |
| b(&done, EQ); | |
| if (!FLAG_use_slow_path) { | |
| b(&retry); | |
| } | |
| Bind(&slow_path); | |
| ldr(TMP, Address(THR, target::Thread::enter_safepoint_stub_offset())); | |
| ldr(TMP, FieldAddress(TMP, target::Code::entry_point_offset())); | |
| blx(TMP); | |
| Bind(&done); | |
| } | |
| void Assembler::TransitionGeneratedToNative(Register destination_address, | |
| Register exit_frame_fp, | |
| Register exit_through_ffi, | |
| Register tmp1, | |
| bool enter_safepoint) { | |
| // Save exit frame information to enable stack walking. | |
| StoreToOffset(exit_frame_fp, THR, | |
| target::Thread::top_exit_frame_info_offset()); | |
| StoreToOffset(exit_through_ffi, THR, | |
| target::Thread::exit_through_ffi_offset()); | |
| Register tmp2 = exit_through_ffi; | |
| VerifyInGenerated(tmp1); | |
| // Mark that the thread is executing native code. | |
| StoreToOffset(destination_address, THR, target::Thread::vm_tag_offset()); | |
| LoadImmediate(tmp1, target::Thread::native_execution_state()); | |
| StoreToOffset(tmp1, THR, target::Thread::execution_state_offset()); | |
| if (enter_safepoint) { | |
| EnterFullSafepoint(tmp1, tmp2); | |
| } | |
| } | |
| void Assembler::ExitFullSafepoint(Register tmp1, Register tmp2) { | |
| Register addr = tmp1; | |
| Register state = tmp2; | |
| // We generate the same number of instructions whether or not the slow-path is | |
| // forced, for consistency with EnterFullSafepoint. | |
| Label slow_path, done, retry; | |
| if (FLAG_use_slow_path) { | |
| b(&slow_path); | |
| } | |
| LoadImmediate(addr, target::Thread::safepoint_state_offset()); | |
| add(addr, THR, Operand(addr)); | |
| Bind(&retry); | |
| ldrex(state, addr); | |
| cmp(state, Operand(target::Thread::native_safepoint_state_acquired())); | |
| b(&slow_path, NE); | |
| mov(state, Operand(target::Thread::native_safepoint_state_unacquired())); | |
| strex(TMP, state, addr); | |
| cmp(TMP, Operand(0)); // 0 means strex was successful. | |
| b(&done, EQ); | |
| if (!FLAG_use_slow_path) { | |
| b(&retry); | |
| } | |
| Bind(&slow_path); | |
| ldr(TMP, Address(THR, target::Thread::exit_safepoint_stub_offset())); | |
| ldr(TMP, FieldAddress(TMP, target::Code::entry_point_offset())); | |
| blx(TMP); | |
| Bind(&done); | |
| } | |
| void Assembler::TransitionNativeToGenerated(Register addr, | |
| Register state, | |
| bool exit_safepoint, | |
| bool set_tag) { | |
| if (exit_safepoint) { | |
| ExitFullSafepoint(addr, state); | |
| } else { | |
| #if defined(DEBUG) | |
| // Ensure we've already left the safepoint. | |
| ASSERT(target::Thread::native_safepoint_state_acquired() != 0); | |
| LoadImmediate(state, target::Thread::native_safepoint_state_acquired()); | |
| ldr(TMP, Address(THR, target::Thread::safepoint_state_offset())); | |
| ands(TMP, TMP, Operand(state)); | |
| Label ok; | |
| b(&ok, ZERO); | |
| Breakpoint(); | |
| Bind(&ok); | |
| #endif | |
| } | |
| VerifyNotInGenerated(TMP); | |
| // Mark that the thread is executing Dart code. | |
| if (set_tag) { | |
| LoadImmediate(state, target::Thread::vm_tag_dart_id()); | |
| StoreToOffset(state, THR, target::Thread::vm_tag_offset()); | |
| } | |
| LoadImmediate(state, target::Thread::generated_execution_state()); | |
| StoreToOffset(state, THR, target::Thread::execution_state_offset()); | |
| // Reset exit frame information in Isolate's mutator thread structure. | |
| LoadImmediate(state, 0); | |
| StoreToOffset(state, THR, target::Thread::top_exit_frame_info_offset()); | |
| StoreToOffset(state, THR, target::Thread::exit_through_ffi_offset()); | |
| } | |
| void Assembler::VerifyInGenerated(Register scratch) { | |
| #if defined(DEBUG) | |
| // Verify the thread is in generated. | |
| Comment("VerifyInGenerated"); | |
| ldr(scratch, Address(THR, target::Thread::execution_state_offset())); | |
| Label ok; | |
| CompareImmediate(scratch, target::Thread::generated_execution_state()); | |
| BranchIf(EQUAL, &ok, Assembler::kNearJump); | |
| Breakpoint(); | |
| Bind(&ok); | |
| #endif | |
| } | |
| void Assembler::VerifyNotInGenerated(Register scratch) { | |
| #if defined(DEBUG) | |
| // Verify the thread is in native or VM. | |
| Comment("VerifyNotInGenerated"); | |
| ldr(scratch, Address(THR, target::Thread::execution_state_offset())); | |
| CompareImmediate(scratch, target::Thread::generated_execution_state()); | |
| Label ok; | |
| BranchIf(NOT_EQUAL, &ok, Assembler::kNearJump); | |
| Breakpoint(); | |
| Bind(&ok); | |
| #endif | |
| } | |
| void Assembler::clrex() { | |
| int32_t encoding = (kSpecialCondition << kConditionShift) | B26 | B24 | B22 | | |
| B21 | B20 | (0xff << 12) | B4 | 0xf; | |
| Emit(encoding); | |
| } | |
| void Assembler::nop(Condition cond) { | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B25 | | |
| B24 | B21 | (0xf << 12); | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovsr(SRegister sn, Register rt, Condition cond) { | |
| ASSERT(sn != kNoSRegister); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rt != SP); | |
| ASSERT(rt != PC); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B25 | ((static_cast<int32_t>(sn) >> 1) * B16) | | |
| (static_cast<int32_t>(rt) * B12) | B11 | B9 | | |
| ((static_cast<int32_t>(sn) & 1) * B7) | B4; | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovrs(Register rt, SRegister sn, Condition cond) { | |
| ASSERT(sn != kNoSRegister); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rt != SP); | |
| ASSERT(rt != PC); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B25 | B20 | ((static_cast<int32_t>(sn) >> 1) * B16) | | |
| (static_cast<int32_t>(rt) * B12) | B11 | B9 | | |
| ((static_cast<int32_t>(sn) & 1) * B7) | B4; | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovsrr(SRegister sm, | |
| Register rt, | |
| Register rt2, | |
| Condition cond) { | |
| ASSERT(sm != kNoSRegister); | |
| ASSERT(sm != S31); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rt != SP); | |
| ASSERT(rt != PC); | |
| ASSERT(rt2 != kNoRegister); | |
| ASSERT(rt2 != SP); | |
| ASSERT(rt2 != PC); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B22 | (static_cast<int32_t>(rt2) * B16) | | |
| (static_cast<int32_t>(rt) * B12) | B11 | B9 | | |
| ((static_cast<int32_t>(sm) & 1) * B5) | B4 | | |
| (static_cast<int32_t>(sm) >> 1); | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovrrs(Register rt, | |
| Register rt2, | |
| SRegister sm, | |
| Condition cond) { | |
| ASSERT(sm != kNoSRegister); | |
| ASSERT(sm != S31); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rt != SP); | |
| ASSERT(rt != PC); | |
| ASSERT(rt2 != kNoRegister); | |
| ASSERT(rt2 != SP); | |
| ASSERT(rt2 != PC); | |
| ASSERT(rt != rt2); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B22 | B20 | (static_cast<int32_t>(rt2) * B16) | | |
| (static_cast<int32_t>(rt) * B12) | B11 | B9 | | |
| ((static_cast<int32_t>(sm) & 1) * B5) | B4 | | |
| (static_cast<int32_t>(sm) >> 1); | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovdr(DRegister dn, int i, Register rt, Condition cond) { | |
| ASSERT((i == 0) || (i == 1)); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rt != SP); | |
| ASSERT(rt != PC); | |
| ASSERT(dn != kNoDRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B25 | (i * B21) | (static_cast<int32_t>(rt) * B12) | | |
| B11 | B9 | B8 | ((static_cast<int32_t>(dn) >> 4) * B7) | | |
| ((static_cast<int32_t>(dn) & 0xf) * B16) | B4; | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovdrr(DRegister dm, | |
| Register rt, | |
| Register rt2, | |
| Condition cond) { | |
| ASSERT(dm != kNoDRegister); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rt != SP); | |
| ASSERT(rt != PC); | |
| ASSERT(rt2 != kNoRegister); | |
| ASSERT(rt2 != SP); | |
| ASSERT(rt2 != PC); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B22 | (static_cast<int32_t>(rt2) * B16) | | |
| (static_cast<int32_t>(rt) * B12) | B11 | B9 | B8 | | |
| ((static_cast<int32_t>(dm) >> 4) * B5) | B4 | | |
| (static_cast<int32_t>(dm) & 0xf); | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovrrd(Register rt, | |
| Register rt2, | |
| DRegister dm, | |
| Condition cond) { | |
| ASSERT(dm != kNoDRegister); | |
| ASSERT(rt != kNoRegister); | |
| ASSERT(rt != SP); | |
| ASSERT(rt != PC); | |
| ASSERT(rt2 != kNoRegister); | |
| ASSERT(rt2 != SP); | |
| ASSERT(rt2 != PC); | |
| ASSERT(rt != rt2); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B22 | B20 | (static_cast<int32_t>(rt2) * B16) | | |
| (static_cast<int32_t>(rt) * B12) | B11 | B9 | B8 | | |
| ((static_cast<int32_t>(dm) >> 4) * B5) | B4 | | |
| (static_cast<int32_t>(dm) & 0xf); | |
| Emit(encoding); | |
| } | |
| void Assembler::vldrs(SRegister sd, Address ad, Condition cond) { | |
| ASSERT(sd != kNoSRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B24 | B20 | ((static_cast<int32_t>(sd) & 1) * B22) | | |
| ((static_cast<int32_t>(sd) >> 1) * B12) | B11 | B9 | | |
| ad.vencoding(); | |
| Emit(encoding); | |
| } | |
| void Assembler::vstrs(SRegister sd, Address ad, Condition cond) { | |
| ASSERT(static_cast<Register>(ad.encoding_ & (0xf << kRnShift)) != PC); | |
| ASSERT(sd != kNoSRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B24 | ((static_cast<int32_t>(sd) & 1) * B22) | | |
| ((static_cast<int32_t>(sd) >> 1) * B12) | B11 | B9 | | |
| ad.vencoding(); | |
| Emit(encoding); | |
| } | |
| void Assembler::vldrd(DRegister dd, Address ad, Condition cond) { | |
| ASSERT(dd != kNoDRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B24 | B20 | ((static_cast<int32_t>(dd) >> 4) * B22) | | |
| ((static_cast<int32_t>(dd) & 0xf) * B12) | B11 | B9 | B8 | | |
| ad.vencoding(); | |
| Emit(encoding); | |
| } | |
| void Assembler::vstrd(DRegister dd, Address ad, Condition cond) { | |
| ASSERT(static_cast<Register>(ad.encoding_ & (0xf << kRnShift)) != PC); | |
| ASSERT(dd != kNoDRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B24 | ((static_cast<int32_t>(dd) >> 4) * B22) | | |
| ((static_cast<int32_t>(dd) & 0xf) * B12) | B11 | B9 | B8 | | |
| ad.vencoding(); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitMultiVSMemOp(Condition cond, | |
| BlockAddressMode am, | |
| bool load, | |
| Register base, | |
| SRegister start, | |
| uint32_t count) { | |
| ASSERT(base != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(start != kNoSRegister); | |
| ASSERT(static_cast<int32_t>(start) + count <= kNumberOfSRegisters); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B11 | B9 | am | (load ? L : 0) | | |
| ArmEncode::Rn(base) | | |
| ((static_cast<int32_t>(start) & 0x1) != 0 ? D : 0) | | |
| ((static_cast<int32_t>(start) >> 1) << 12) | count; | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitMultiVDMemOp(Condition cond, | |
| BlockAddressMode am, | |
| bool load, | |
| Register base, | |
| DRegister start, | |
| int32_t count) { | |
| ASSERT(base != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| ASSERT(start != kNoDRegister); | |
| ASSERT(static_cast<int32_t>(start) + count <= kNumberOfDRegisters); | |
| const int notArmv5te = 0; | |
| int32_t encoding = | |
| (static_cast<int32_t>(cond) << kConditionShift) | B27 | B26 | B11 | B9 | | |
| B8 | am | (load ? L : 0) | ArmEncode::Rn(base) | | |
| ((static_cast<int32_t>(start) & 0x10) != 0 ? D : 0) | | |
| ((static_cast<int32_t>(start) & 0xf) << 12) | (count << 1) | notArmv5te; | |
| Emit(encoding); | |
| } | |
| void Assembler::vldms(BlockAddressMode am, | |
| Register base, | |
| SRegister first, | |
| SRegister last, | |
| Condition cond) { | |
| ASSERT((am == IA) || (am == IA_W) || (am == DB_W)); | |
| ASSERT(last > first); | |
| EmitMultiVSMemOp(cond, am, true, base, first, last - first + 1); | |
| } | |
| void Assembler::vstms(BlockAddressMode am, | |
| Register base, | |
| SRegister first, | |
| SRegister last, | |
| Condition cond) { | |
| ASSERT((am == IA) || (am == IA_W) || (am == DB_W)); | |
| ASSERT(last > first); | |
| EmitMultiVSMemOp(cond, am, false, base, first, last - first + 1); | |
| } | |
| void Assembler::vldmd(BlockAddressMode am, | |
| Register base, | |
| DRegister first, | |
| intptr_t count, | |
| Condition cond) { | |
| ASSERT((am == IA) || (am == IA_W) || (am == DB_W)); | |
| ASSERT(count <= 16); | |
| ASSERT(first + count <= kNumberOfDRegisters); | |
| EmitMultiVDMemOp(cond, am, true, base, first, count); | |
| } | |
| void Assembler::vstmd(BlockAddressMode am, | |
| Register base, | |
| DRegister first, | |
| intptr_t count, | |
| Condition cond) { | |
| ASSERT((am == IA) || (am == IA_W) || (am == DB_W)); | |
| ASSERT(count <= 16); | |
| ASSERT(first + count <= kNumberOfDRegisters); | |
| EmitMultiVDMemOp(cond, am, false, base, first, count); | |
| } | |
| void Assembler::EmitVFPsss(Condition cond, | |
| int32_t opcode, | |
| SRegister sd, | |
| SRegister sn, | |
| SRegister sm) { | |
| ASSERT(sd != kNoSRegister); | |
| ASSERT(sn != kNoSRegister); | |
| ASSERT(sm != kNoSRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = | |
| (static_cast<int32_t>(cond) << kConditionShift) | B27 | B26 | B25 | B11 | | |
| B9 | opcode | ((static_cast<int32_t>(sd) & 1) * B22) | | |
| ((static_cast<int32_t>(sn) >> 1) * B16) | | |
| ((static_cast<int32_t>(sd) >> 1) * B12) | | |
| ((static_cast<int32_t>(sn) & 1) * B7) | | |
| ((static_cast<int32_t>(sm) & 1) * B5) | (static_cast<int32_t>(sm) >> 1); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitVFPddd(Condition cond, | |
| int32_t opcode, | |
| DRegister dd, | |
| DRegister dn, | |
| DRegister dm) { | |
| ASSERT(dd != kNoDRegister); | |
| ASSERT(dn != kNoDRegister); | |
| ASSERT(dm != kNoDRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = | |
| (static_cast<int32_t>(cond) << kConditionShift) | B27 | B26 | B25 | B11 | | |
| B9 | B8 | opcode | ((static_cast<int32_t>(dd) >> 4) * B22) | | |
| ((static_cast<int32_t>(dn) & 0xf) * B16) | | |
| ((static_cast<int32_t>(dd) & 0xf) * B12) | | |
| ((static_cast<int32_t>(dn) >> 4) * B7) | | |
| ((static_cast<int32_t>(dm) >> 4) * B5) | (static_cast<int32_t>(dm) & 0xf); | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovs(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vmovd(DRegister dd, DRegister dm, Condition cond) { | |
| EmitVFPddd(cond, B23 | B21 | B20 | B6, dd, D0, dm); | |
| } | |
| bool Assembler::vmovs(SRegister sd, float s_imm, Condition cond) { | |
| uint32_t imm32 = bit_cast<uint32_t, float>(s_imm); | |
| if (((imm32 & ((1 << 19) - 1)) == 0) && | |
| ((((imm32 >> 25) & ((1 << 6) - 1)) == (1 << 5)) || | |
| (((imm32 >> 25) & ((1 << 6) - 1)) == ((1 << 5) - 1)))) { | |
| uint8_t imm8 = ((imm32 >> 31) << 7) | (((imm32 >> 29) & 1) << 6) | | |
| ((imm32 >> 19) & ((1 << 6) - 1)); | |
| EmitVFPsss(cond, B23 | B21 | B20 | ((imm8 >> 4) * B16) | (imm8 & 0xf), sd, | |
| S0, S0); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool Assembler::vmovd(DRegister dd, double d_imm, Condition cond) { | |
| uint64_t imm64 = bit_cast<uint64_t, double>(d_imm); | |
| if (((imm64 & ((1LL << 48) - 1)) == 0) && | |
| ((((imm64 >> 54) & ((1 << 9) - 1)) == (1 << 8)) || | |
| (((imm64 >> 54) & ((1 << 9) - 1)) == ((1 << 8) - 1)))) { | |
| uint8_t imm8 = ((imm64 >> 63) << 7) | (((imm64 >> 61) & 1) << 6) | | |
| ((imm64 >> 48) & ((1 << 6) - 1)); | |
| EmitVFPddd(cond, B23 | B21 | B20 | ((imm8 >> 4) * B16) | B8 | (imm8 & 0xf), | |
| dd, D0, D0); | |
| return true; | |
| } | |
| return false; | |
| } | |
| void Assembler::vadds(SRegister sd, | |
| SRegister sn, | |
| SRegister sm, | |
| Condition cond) { | |
| EmitVFPsss(cond, B21 | B20, sd, sn, sm); | |
| } | |
| void Assembler::vaddd(DRegister dd, | |
| DRegister dn, | |
| DRegister dm, | |
| Condition cond) { | |
| EmitVFPddd(cond, B21 | B20, dd, dn, dm); | |
| } | |
| void Assembler::vsubs(SRegister sd, | |
| SRegister sn, | |
| SRegister sm, | |
| Condition cond) { | |
| EmitVFPsss(cond, B21 | B20 | B6, sd, sn, sm); | |
| } | |
| void Assembler::vsubd(DRegister dd, | |
| DRegister dn, | |
| DRegister dm, | |
| Condition cond) { | |
| EmitVFPddd(cond, B21 | B20 | B6, dd, dn, dm); | |
| } | |
| void Assembler::vmuls(SRegister sd, | |
| SRegister sn, | |
| SRegister sm, | |
| Condition cond) { | |
| EmitVFPsss(cond, B21, sd, sn, sm); | |
| } | |
| void Assembler::vmuld(DRegister dd, | |
| DRegister dn, | |
| DRegister dm, | |
| Condition cond) { | |
| EmitVFPddd(cond, B21, dd, dn, dm); | |
| } | |
| void Assembler::vmlas(SRegister sd, | |
| SRegister sn, | |
| SRegister sm, | |
| Condition cond) { | |
| EmitVFPsss(cond, 0, sd, sn, sm); | |
| } | |
| void Assembler::vmlad(DRegister dd, | |
| DRegister dn, | |
| DRegister dm, | |
| Condition cond) { | |
| EmitVFPddd(cond, 0, dd, dn, dm); | |
| } | |
| void Assembler::vmlss(SRegister sd, | |
| SRegister sn, | |
| SRegister sm, | |
| Condition cond) { | |
| EmitVFPsss(cond, B6, sd, sn, sm); | |
| } | |
| void Assembler::vmlsd(DRegister dd, | |
| DRegister dn, | |
| DRegister dm, | |
| Condition cond) { | |
| EmitVFPddd(cond, B6, dd, dn, dm); | |
| } | |
| void Assembler::vdivs(SRegister sd, | |
| SRegister sn, | |
| SRegister sm, | |
| Condition cond) { | |
| EmitVFPsss(cond, B23, sd, sn, sm); | |
| } | |
| void Assembler::vdivd(DRegister dd, | |
| DRegister dn, | |
| DRegister dm, | |
| Condition cond) { | |
| EmitVFPddd(cond, B23, dd, dn, dm); | |
| } | |
| void Assembler::vabss(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B7 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vabsd(DRegister dd, DRegister dm, Condition cond) { | |
| EmitVFPddd(cond, B23 | B21 | B20 | B7 | B6, dd, D0, dm); | |
| } | |
| void Assembler::vnegs(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B16 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vnegd(DRegister dd, DRegister dm, Condition cond) { | |
| EmitVFPddd(cond, B23 | B21 | B20 | B16 | B6, dd, D0, dm); | |
| } | |
| void Assembler::vsqrts(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B16 | B7 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vsqrtd(DRegister dd, DRegister dm, Condition cond) { | |
| EmitVFPddd(cond, B23 | B21 | B20 | B16 | B7 | B6, dd, D0, dm); | |
| } | |
| void Assembler::EmitVFPsd(Condition cond, | |
| int32_t opcode, | |
| SRegister sd, | |
| DRegister dm) { | |
| ASSERT(sd != kNoSRegister); | |
| ASSERT(dm != kNoDRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = | |
| (static_cast<int32_t>(cond) << kConditionShift) | B27 | B26 | B25 | B11 | | |
| B9 | opcode | ((static_cast<int32_t>(sd) & 1) * B22) | | |
| ((static_cast<int32_t>(sd) >> 1) * B12) | | |
| ((static_cast<int32_t>(dm) >> 4) * B5) | (static_cast<int32_t>(dm) & 0xf); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitVFPds(Condition cond, | |
| int32_t opcode, | |
| DRegister dd, | |
| SRegister sm) { | |
| ASSERT(dd != kNoDRegister); | |
| ASSERT(sm != kNoSRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = | |
| (static_cast<int32_t>(cond) << kConditionShift) | B27 | B26 | B25 | B11 | | |
| B9 | opcode | ((static_cast<int32_t>(dd) >> 4) * B22) | | |
| ((static_cast<int32_t>(dd) & 0xf) * B12) | | |
| ((static_cast<int32_t>(sm) & 1) * B5) | (static_cast<int32_t>(sm) >> 1); | |
| Emit(encoding); | |
| } | |
| void Assembler::vcvtsd(SRegister sd, DRegister dm, Condition cond) { | |
| EmitVFPsd(cond, B23 | B21 | B20 | B18 | B17 | B16 | B8 | B7 | B6, sd, dm); | |
| } | |
| void Assembler::vcvtds(DRegister dd, SRegister sm, Condition cond) { | |
| EmitVFPds(cond, B23 | B21 | B20 | B18 | B17 | B16 | B7 | B6, dd, sm); | |
| } | |
| void Assembler::vcvtis(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B19 | B18 | B16 | B7 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vcvtid(SRegister sd, DRegister dm, Condition cond) { | |
| EmitVFPsd(cond, B23 | B21 | B20 | B19 | B18 | B16 | B8 | B7 | B6, sd, dm); | |
| } | |
| void Assembler::vcvtsi(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B19 | B7 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vcvtdi(DRegister dd, SRegister sm, Condition cond) { | |
| EmitVFPds(cond, B23 | B21 | B20 | B19 | B8 | B7 | B6, dd, sm); | |
| } | |
| void Assembler::vcvtus(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B19 | B18 | B7 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vcvtud(SRegister sd, DRegister dm, Condition cond) { | |
| EmitVFPsd(cond, B23 | B21 | B20 | B19 | B18 | B8 | B7 | B6, sd, dm); | |
| } | |
| void Assembler::vcvtsu(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B19 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vcvtdu(DRegister dd, SRegister sm, Condition cond) { | |
| EmitVFPds(cond, B23 | B21 | B20 | B19 | B8 | B6, dd, sm); | |
| } | |
| void Assembler::vcmps(SRegister sd, SRegister sm, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B18 | B6, sd, S0, sm); | |
| } | |
| void Assembler::vcmpd(DRegister dd, DRegister dm, Condition cond) { | |
| EmitVFPddd(cond, B23 | B21 | B20 | B18 | B6, dd, D0, dm); | |
| } | |
| void Assembler::vcmpsz(SRegister sd, Condition cond) { | |
| EmitVFPsss(cond, B23 | B21 | B20 | B18 | B16 | B6, sd, S0, S0); | |
| } | |
| void Assembler::vcmpdz(DRegister dd, Condition cond) { | |
| EmitVFPddd(cond, B23 | B21 | B20 | B18 | B16 | B6, dd, D0, D0); | |
| } | |
| void Assembler::vmrs(Register rd, Condition cond) { | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B27 | | |
| B26 | B25 | B23 | B22 | B21 | B20 | B16 | | |
| (static_cast<int32_t>(rd) * B12) | B11 | B9 | B4; | |
| Emit(encoding); | |
| } | |
| void Assembler::vmstat(Condition cond) { | |
| vmrs(APSR, cond); | |
| } | |
| static inline int ShiftOfOperandSize(OperandSize size) { | |
| switch (size) { | |
| case kByte: | |
| case kUnsignedByte: | |
| return 0; | |
| case kTwoBytes: | |
| case kUnsignedTwoBytes: | |
| return 1; | |
| case kFourBytes: | |
| case kUnsignedFourBytes: | |
| return 2; | |
| case kWordPair: | |
| return 3; | |
| case kSWord: | |
| case kDWord: | |
| return 0; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| UNREACHABLE(); | |
| return -1; | |
| } | |
| void Assembler::EmitSIMDqqq(int32_t opcode, | |
| OperandSize size, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| ASSERT(TargetCPUFeatures::neon_supported()); | |
| int sz = ShiftOfOperandSize(size); | |
| int32_t encoding = | |
| (static_cast<int32_t>(kSpecialCondition) << kConditionShift) | B25 | B6 | | |
| opcode | ((sz & 0x3) * B20) | | |
| ((static_cast<int32_t>(qd * 2) >> 4) * B22) | | |
| ((static_cast<int32_t>(qn * 2) & 0xf) * B16) | | |
| ((static_cast<int32_t>(qd * 2) & 0xf) * B12) | | |
| ((static_cast<int32_t>(qn * 2) >> 4) * B7) | | |
| ((static_cast<int32_t>(qm * 2) >> 4) * B5) | | |
| (static_cast<int32_t>(qm * 2) & 0xf); | |
| Emit(encoding); | |
| } | |
| void Assembler::EmitSIMDddd(int32_t opcode, | |
| OperandSize size, | |
| DRegister dd, | |
| DRegister dn, | |
| DRegister dm) { | |
| ASSERT(TargetCPUFeatures::neon_supported()); | |
| int sz = ShiftOfOperandSize(size); | |
| int32_t encoding = | |
| (static_cast<int32_t>(kSpecialCondition) << kConditionShift) | B25 | | |
| opcode | ((sz & 0x3) * B20) | ((static_cast<int32_t>(dd) >> 4) * B22) | | |
| ((static_cast<int32_t>(dn) & 0xf) * B16) | | |
| ((static_cast<int32_t>(dd) & 0xf) * B12) | | |
| ((static_cast<int32_t>(dn) >> 4) * B7) | | |
| ((static_cast<int32_t>(dm) >> 4) * B5) | (static_cast<int32_t>(dm) & 0xf); | |
| Emit(encoding); | |
| } | |
| void Assembler::vmovq(QRegister qd, QRegister qm) { | |
| EmitSIMDqqq(B21 | B8 | B4, kByte, qd, qm, qm); | |
| } | |
| void Assembler::vaddqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B11, sz, qd, qn, qm); | |
| } | |
| void Assembler::vaddqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B11 | B10 | B8, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vsubqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B24 | B11, sz, qd, qn, qm); | |
| } | |
| void Assembler::vsubqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B21 | B11 | B10 | B8, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vmulqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B11 | B8 | B4, sz, qd, qn, qm); | |
| } | |
| void Assembler::vmulqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B24 | B11 | B10 | B8 | B4, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vshlqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qm, | |
| QRegister qn) { | |
| EmitSIMDqqq(B25 | B10, sz, qd, qn, qm); | |
| } | |
| void Assembler::vshlqu(OperandSize sz, | |
| QRegister qd, | |
| QRegister qm, | |
| QRegister qn) { | |
| EmitSIMDqqq(B25 | B24 | B10, sz, qd, qn, qm); | |
| } | |
| void Assembler::veorq(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B24 | B8 | B4, kByte, qd, qn, qm); | |
| } | |
| void Assembler::vorrq(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B21 | B8 | B4, kByte, qd, qn, qm); | |
| } | |
| void Assembler::vornq(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B21 | B20 | B8 | B4, kByte, qd, qn, qm); | |
| } | |
| void Assembler::vandq(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B8 | B4, kByte, qd, qn, qm); | |
| } | |
| void Assembler::vbicq(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B20 | B8 | B4, kByte, qd, qn, qm); | |
| } | |
| void Assembler::vmvnq(QRegister qd, QRegister qm) { | |
| EmitSIMDqqq(B25 | B24 | B23 | B10 | B8 | B7, kWordPair, qd, Q0, qm); | |
| } | |
| void Assembler::vcnt(DRegister dd, DRegister dm) { | |
| EmitSIMDddd(B24 | B23 | B21 | B20 | B10 | B8, kByte, dd, D0, dm); | |
| } | |
| void Assembler::vpaddlu(OperandSize sz, DRegister dd, DRegister dm) { | |
| int32_t size_bits = 0; | |
| switch (sz) { | |
| case kByte: | |
| size_bits = 0; | |
| break; | |
| case kTwoBytes: | |
| size_bits = B18; | |
| break; | |
| case kFourBytes: | |
| size_bits = B19; | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| EmitSIMDddd(B24 | B23 | B21 | B20 | size_bits | B9 | B7, kByte, dd, D0, dm); | |
| } | |
| void Assembler::vpmaxu(OperandSize sz, | |
| DRegister dd, | |
| DRegister dn, | |
| DRegister dm) { | |
| EmitSIMDddd(B24 | B11 | B9, sz, dd, dn, dm); | |
| } | |
| void Assembler::vpminu(OperandSize sz, | |
| DRegister dd, | |
| DRegister dn, | |
| DRegister dm) { | |
| EmitSIMDddd(B24 | B11 | B9 | B4, sz, dd, dn, dm); | |
| } | |
| void Assembler::vminqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B21 | B11 | B10 | B9 | B8, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vmaxqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B11 | B10 | B9 | B8, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vabsqs(QRegister qd, QRegister qm) { | |
| EmitSIMDqqq(B24 | B23 | B21 | B20 | B19 | B16 | B10 | B9 | B8, kSWord, qd, Q0, | |
| qm); | |
| } | |
| void Assembler::vnegqs(QRegister qd, QRegister qm) { | |
| EmitSIMDqqq(B24 | B23 | B21 | B20 | B19 | B16 | B10 | B9 | B8 | B7, kSWord, | |
| qd, Q0, qm); | |
| } | |
| void Assembler::vrecpeqs(QRegister qd, QRegister qm) { | |
| EmitSIMDqqq(B24 | B23 | B21 | B20 | B19 | B17 | B16 | B10 | B8, kSWord, qd, | |
| Q0, qm); | |
| } | |
| void Assembler::vrecpsqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B11 | B10 | B9 | B8 | B4, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vrsqrteqs(QRegister qd, QRegister qm) { | |
| EmitSIMDqqq(B24 | B23 | B21 | B20 | B19 | B17 | B16 | B10 | B8 | B7, kSWord, | |
| qd, Q0, qm); | |
| } | |
| void Assembler::vrsqrtsqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B21 | B11 | B10 | B9 | B8 | B4, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vdup(OperandSize sz, QRegister qd, DRegister dm, int idx) { | |
| ASSERT((sz != kDWord) && (sz != kSWord) && (sz != kWordPair)); | |
| int code = 0; | |
| switch (sz) { | |
| case kByte: | |
| case kUnsignedByte: { | |
| ASSERT((idx >= 0) && (idx < 8)); | |
| code = 1 | (idx << 1); | |
| break; | |
| } | |
| case kTwoBytes: | |
| case kUnsignedTwoBytes: { | |
| ASSERT((idx >= 0) && (idx < 4)); | |
| code = 2 | (idx << 2); | |
| break; | |
| } | |
| case kFourBytes: | |
| case kUnsignedFourBytes: { | |
| ASSERT((idx >= 0) && (idx < 2)); | |
| code = 4 | (idx << 3); | |
| break; | |
| } | |
| default: { | |
| break; | |
| } | |
| } | |
| EmitSIMDddd(B24 | B23 | B11 | B10 | B6, kWordPair, | |
| static_cast<DRegister>(qd * 2), | |
| static_cast<DRegister>(code & 0xf), dm); | |
| } | |
| void Assembler::vtbl(DRegister dd, DRegister dn, int len, DRegister dm) { | |
| ASSERT((len >= 1) && (len <= 4)); | |
| EmitSIMDddd(B24 | B23 | B11 | ((len - 1) * B8), kWordPair, dd, dn, dm); | |
| } | |
| void Assembler::vzipqw(QRegister qd, QRegister qm) { | |
| EmitSIMDqqq(B24 | B23 | B21 | B20 | B19 | B17 | B8 | B7, kByte, qd, Q0, qm); | |
| } | |
| void Assembler::vceqqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B24 | B11 | B4, sz, qd, qn, qm); | |
| } | |
| void Assembler::vceqqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B11 | B10 | B9, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vcgeqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B9 | B8 | B4, sz, qd, qn, qm); | |
| } | |
| void Assembler::vcugeqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B24 | B9 | B8 | B4, sz, qd, qn, qm); | |
| } | |
| void Assembler::vcgeqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B24 | B11 | B10 | B9, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::vcgtqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B9 | B8, sz, qd, qn, qm); | |
| } | |
| void Assembler::vcugtqi(OperandSize sz, | |
| QRegister qd, | |
| QRegister qn, | |
| QRegister qm) { | |
| EmitSIMDqqq(B24 | B9 | B8, sz, qd, qn, qm); | |
| } | |
| void Assembler::vcgtqs(QRegister qd, QRegister qn, QRegister qm) { | |
| EmitSIMDqqq(B24 | B21 | B11 | B10 | B9, kSWord, qd, qn, qm); | |
| } | |
| void Assembler::bkpt(uint16_t imm16) { | |
| Emit(BkptEncoding(imm16)); | |
| } | |
| void Assembler::b(Label* label, Condition cond) { | |
| EmitBranch(cond, label, false); | |
| } | |
| void Assembler::bl(Label* label, Condition cond) { | |
| EmitBranch(cond, label, true); | |
| } | |
| void Assembler::bx(Register rm, Condition cond) { | |
| ASSERT(rm != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B24 | | |
| B21 | (0xfff << 8) | B4 | ArmEncode::Rm(rm); | |
| Emit(encoding); | |
| } | |
| void Assembler::blx(Register rm, Condition cond) { | |
| ASSERT(rm != kNoRegister); | |
| ASSERT(cond != kNoCondition); | |
| int32_t encoding = (static_cast<int32_t>(cond) << kConditionShift) | B24 | | |
| B21 | (0xfff << 8) | B5 | B4 | ArmEncode::Rm(rm); | |
| Emit(encoding); | |
| } | |
| void Assembler::MarkExceptionHandler(Label* label) { | |
| EmitType01(AL, 1, TST, 1, PC, R0, Operand(0)); | |
| Label l; | |
| b(&l); | |
| EmitBranch(AL, label, false); | |
| Bind(&l); | |
| } | |
| void Assembler::Drop(intptr_t stack_elements) { | |
| ASSERT(stack_elements >= 0); | |
| if (stack_elements > 0) { | |
| AddImmediate(SP, stack_elements * target::kWordSize); | |
| } | |
| } | |
| // Uses a code sequence that can easily be decoded. | |
| void Assembler::LoadWordFromPoolIndex(Register rd, | |
| intptr_t index, | |
| Register pp, | |
| Condition cond) { | |
| ASSERT((pp != PP) || constant_pool_allowed()); | |
| ASSERT(rd != pp); | |
| // PP is tagged on ARM. | |
| const int32_t offset = | |
| target::ObjectPool::element_offset(index) - kHeapObjectTag; | |
| int32_t offset_mask = 0; | |
| if (Address::CanHoldLoadOffset(kFourBytes, offset, &offset_mask)) { | |
| ldr(rd, Address(pp, offset), cond); | |
| } else { | |
| int32_t offset_hi = offset & ~offset_mask; // signed | |
| uint32_t offset_lo = offset & offset_mask; // unsigned | |
| // Inline a simplified version of AddImmediate(rd, pp, offset_hi). | |
| Operand o; | |
| if (Operand::CanHold(offset_hi, &o)) { | |
| add(rd, pp, o, cond); | |
| } else { | |
| LoadImmediate(rd, offset_hi, cond); | |
| add(rd, pp, Operand(rd), cond); | |
| } | |
| ldr(rd, Address(rd, offset_lo), cond); | |
| } | |
| } | |
| void Assembler::StoreWordToPoolIndex(Register value, | |
| intptr_t index, | |
| Register pp, | |
| Condition cond) { | |
| ASSERT((pp != PP) || constant_pool_allowed()); | |
| ASSERT(value != pp); | |
| // PP is tagged on ARM. | |
| const int32_t offset = | |
| target::ObjectPool::element_offset(index) - kHeapObjectTag; | |
| int32_t offset_mask = 0; | |
| if (Address::CanHoldLoadOffset(kFourBytes, offset, &offset_mask)) { | |
| str(value, Address(pp, offset), cond); | |
| } else { | |
| int32_t offset_hi = offset & ~offset_mask; // signed | |
| uint32_t offset_lo = offset & offset_mask; // unsigned | |
| // Inline a simplified version of AddImmediate(rd, pp, offset_hi). | |
| Operand o; | |
| if (Operand::CanHold(offset_hi, &o)) { | |
| add(TMP, pp, o, cond); | |
| } else { | |
| LoadImmediate(TMP, offset_hi, cond); | |
| add(TMP, pp, Operand(TMP), cond); | |
| } | |
| str(value, Address(TMP, offset_lo), cond); | |
| } | |
| } | |
| void Assembler::CheckCodePointer() { | |
| #ifdef DEBUG | |
| if (!FLAG_check_code_pointer) { | |
| return; | |
| } | |
| Comment("CheckCodePointer"); | |
| Label cid_ok, instructions_ok; | |
| Push(R0); | |
| Push(IP); | |
| CompareClassId(CODE_REG, kCodeCid, R0); | |
| b(&cid_ok, EQ); | |
| bkpt(0); | |
| Bind(&cid_ok); | |
| const intptr_t offset = CodeSize() + Instr::kPCReadOffset + | |
| target::Instructions::HeaderSize() - kHeapObjectTag; | |
| mov(R0, Operand(PC)); | |
| AddImmediate(R0, -offset); | |
| ldr(IP, FieldAddress(CODE_REG, target::Code::instructions_offset())); | |
| cmp(R0, Operand(IP)); | |
| b(&instructions_ok, EQ); | |
| bkpt(1); | |
| Bind(&instructions_ok); | |
| Pop(IP); | |
| Pop(R0); | |
| #endif | |
| } | |
| void Assembler::RestoreCodePointer() { | |
| ldr(CODE_REG, | |
| Address(FP, target::frame_layout.code_from_fp * target::kWordSize)); | |
| CheckCodePointer(); | |
| } | |
| void Assembler::LoadPoolPointer(Register reg) { | |
| // Load new pool pointer. | |
| CheckCodePointer(); | |
| ldr(reg, FieldAddress(CODE_REG, target::Code::object_pool_offset())); | |
| set_constant_pool_allowed(reg == PP); | |
| } | |
| void Assembler::SetupGlobalPoolAndDispatchTable() { | |
| ASSERT(FLAG_precompiled_mode); | |
| ldr(PP, Address(THR, target::Thread::global_object_pool_offset())); | |
| ldr(DISPATCH_TABLE_REG, | |
| Address(THR, target::Thread::dispatch_table_array_offset())); | |
| } | |
| void Assembler::LoadIsolate(Register rd) { | |
| ldr(rd, Address(THR, target::Thread::isolate_offset())); | |
| } | |
| void Assembler::LoadIsolateGroup(Register rd) { | |
| ldr(rd, Address(THR, target::Thread::isolate_group_offset())); | |
| } | |
| bool Assembler::CanLoadFromObjectPool(const Object& object) const { | |
| ASSERT(IsOriginalObject(object)); | |
| if (!constant_pool_allowed()) { | |
| return false; | |
| } | |
| DEBUG_ASSERT(IsNotTemporaryScopedHandle(object)); | |
| ASSERT(IsInOldSpace(object)); | |
| return true; | |
| } | |
| void Assembler::LoadObjectHelper( | |
| Register rd, | |
| const Object& object, | |
| Condition cond, | |
| bool is_unique, | |
| Register pp, | |
| ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior) { | |
| ASSERT(IsOriginalObject(object)); | |
| // `is_unique == true` effectively means object has to be patchable. | |
| if (!is_unique) { | |
| intptr_t offset = 0; | |
| if (target::CanLoadFromThread(object, &offset)) { | |
| // Load common VM constants from the thread. This works also in places | |
| // where no constant pool is set up (e.g. intrinsic code). | |
| ldr(rd, Address(THR, offset), cond); | |
| return; | |
| } | |
| if (target::IsSmi(object)) { | |
| // Relocation doesn't apply to Smis. | |
| LoadImmediate(rd, target::ToRawSmi(object), cond); | |
| return; | |
| } | |
| } | |
| RELEASE_ASSERT(CanLoadFromObjectPool(object)); | |
| // Make sure that class CallPattern is able to decode this load from the | |
| // object pool. | |
| const auto index = | |
| is_unique | |
| ? object_pool_builder().AddObject( | |
| object, ObjectPoolBuilderEntry::kPatchable, snapshot_behavior) | |
| : object_pool_builder().FindObject( | |
| object, ObjectPoolBuilderEntry::kNotPatchable, | |
| snapshot_behavior); | |
| LoadWordFromPoolIndex(rd, index, pp, cond); | |
| } | |
| void Assembler::LoadObject(Register rd, const Object& object, Condition cond) { | |
| LoadObjectHelper(rd, object, cond, /* is_unique = */ false, PP); | |
| } | |
| void Assembler::LoadUniqueObject( | |
| Register rd, | |
| const Object& object, | |
| Condition cond, | |
| ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior) { | |
| LoadObjectHelper(rd, object, cond, /* is_unique = */ true, PP, | |
| snapshot_behavior); | |
| } | |
| void Assembler::LoadNativeEntry(Register rd, | |
| const ExternalLabel* label, | |
| ObjectPoolBuilderEntry::Patchability patchable, | |
| Condition cond) { | |
| const intptr_t index = | |
| object_pool_builder().FindNativeFunction(label, patchable); | |
| LoadWordFromPoolIndex(rd, index, PP, cond); | |
| } | |
| void Assembler::PushObject(const Object& object) { | |
| ASSERT(IsOriginalObject(object)); | |
| LoadObject(IP, object); | |
| Push(IP); | |
| } | |
| void Assembler::CompareObject(Register rn, const Object& object) { | |
| ASSERT(IsOriginalObject(object)); | |
| ASSERT(rn != IP); | |
| if (target::IsSmi(object)) { | |
| CompareImmediate(rn, target::ToRawSmi(object)); | |
| } else { | |
| LoadObject(IP, object); | |
| cmp(rn, Operand(IP)); | |
| } | |
| } | |
| Register UseRegister(Register reg, RegList* used) { | |
| ASSERT(reg != THR); | |
| ASSERT(reg != SP); | |
| ASSERT(reg != FP); | |
| ASSERT(reg != PC); | |
| ASSERT((*used & (1 << reg)) == 0); | |
| *used |= (1 << reg); | |
| return reg; | |
| } | |
| Register AllocateRegister(RegList* used) { | |
| const RegList free = ~*used; | |
| return (free == 0) | |
| ? kNoRegister | |
| : UseRegister( | |
| static_cast<Register>(Utils::CountTrailingZerosWord(free)), | |
| used); | |
| } | |
| void Assembler::StoreBarrier(Register object, | |
| Register value, | |
| CanBeSmi can_be_smi, | |
| Register scratch) { | |
| // x.slot = x. Barrier should have be removed at the IL level. | |
| ASSERT(object != value); | |
| ASSERT(object != LINK_REGISTER); | |
| ASSERT(value != LINK_REGISTER); | |
| ASSERT(object != scratch); | |
| ASSERT(value != scratch); | |
| ASSERT(scratch != kNoRegister); | |
| // In parallel, test whether | |
| // - object is old and not remembered and value is new, or | |
| // - object is old and value is old and not marked and concurrent marking is | |
| // in progress | |
| // If so, call the WriteBarrier stub, which will either add object to the | |
| // store buffer (case 1) or add value to the marking stack (case 2). | |
| // Compare UntaggedObject::StorePointer. | |
| Label done; | |
| if (can_be_smi == kValueCanBeSmi) { | |
| BranchIfSmi(value, &done, kNearJump); | |
| } else { | |
| #if defined(DEBUG) | |
| Label passed_check; | |
| BranchIfNotSmi(value, &passed_check, kNearJump); | |
| Breakpoint(); | |
| Bind(&passed_check); | |
| #endif | |
| } | |
| const bool preserve_lr = lr_state().LRContainsReturnAddress(); | |
| if (preserve_lr) { | |
| SPILLS_LR_TO_FRAME(Push(LR)); | |
| } | |
| CLOBBERS_LR({ | |
| ldrb(scratch, FieldAddress(object, target::Object::tags_offset())); | |
| ldrb(LR, FieldAddress(value, target::Object::tags_offset())); | |
| and_(scratch, LR, | |
| Operand(scratch, LSR, target::UntaggedObject::kBarrierOverlapShift)); | |
| ldr(LR, Address(THR, target::Thread::write_barrier_mask_offset())); | |
| tst(scratch, Operand(LR)); | |
| }); | |
| if (value != kWriteBarrierValueReg) { | |
| // Unlikely. Only non-graph intrinsics. | |
| // TODO(rmacnak): Shuffle registers in intrinsics. | |
| Label restore_and_done; | |
| b(&restore_and_done, ZERO); | |
| Register objectForCall = object; | |
| if (object != kWriteBarrierValueReg) { | |
| Push(kWriteBarrierValueReg); | |
| } else { | |
| COMPILE_ASSERT(R2 != kWriteBarrierValueReg); | |
| COMPILE_ASSERT(R3 != kWriteBarrierValueReg); | |
| objectForCall = (value == R2) ? R3 : R2; | |
| PushList((1 << kWriteBarrierValueReg) | (1 << objectForCall)); | |
| mov(objectForCall, Operand(object)); | |
| } | |
| mov(kWriteBarrierValueReg, Operand(value)); | |
| generate_invoke_write_barrier_wrapper_(AL, objectForCall); | |
| if (object != kWriteBarrierValueReg) { | |
| Pop(kWriteBarrierValueReg); | |
| } else { | |
| PopList((1 << kWriteBarrierValueReg) | (1 << objectForCall)); | |
| } | |
| Bind(&restore_and_done); | |
| } else { | |
| generate_invoke_write_barrier_wrapper_(NE, object); | |
| } | |
| if (preserve_lr) { | |
| RESTORES_LR_FROM_FRAME(Pop(LR)); | |
| } | |
| Bind(&done); | |
| } | |
| void Assembler::ArrayStoreBarrier(Register object, | |
| Register slot, | |
| Register value, | |
| CanBeSmi can_be_smi, | |
| Register scratch) { | |
| ASSERT(object != LINK_REGISTER); | |
| ASSERT(value != LINK_REGISTER); | |
| ASSERT(slot != LINK_REGISTER); | |
| ASSERT(object != scratch); | |
| ASSERT(value != scratch); | |
| ASSERT(slot != scratch); | |
| ASSERT(scratch != kNoRegister); | |
| // In parallel, test whether | |
| // - object is old and not remembered and value is new, or | |
| // - object is old and value is old and not marked and concurrent marking is | |
| // in progress | |
| // If so, call the WriteBarrier stub, which will either add object to the | |
| // store buffer (case 1) or add value to the marking stack (case 2). | |
| // Compare UntaggedObject::StorePointer. | |
| Label done; | |
| if (can_be_smi == kValueCanBeSmi) { | |
| BranchIfSmi(value, &done, kNearJump); | |
| } else { | |
| #if defined(DEBUG) | |
| Label passed_check; | |
| BranchIfNotSmi(value, &passed_check, kNearJump); | |
| Breakpoint(); | |
| Bind(&passed_check); | |
| #endif | |
| } | |
| const bool preserve_lr = lr_state().LRContainsReturnAddress(); | |
| if (preserve_lr) { | |
| SPILLS_LR_TO_FRAME(Push(LR)); | |
| } | |
| CLOBBERS_LR({ | |
| ldrb(scratch, FieldAddress(object, target::Object::tags_offset())); | |
| ldrb(LR, FieldAddress(value, target::Object::tags_offset())); | |
| and_(scratch, LR, | |
| Operand(scratch, LSR, target::UntaggedObject::kBarrierOverlapShift)); | |
| ldr(LR, Address(THR, target::Thread::write_barrier_mask_offset())); | |
| tst(scratch, Operand(LR)); | |
| }); | |
| if ((object != kWriteBarrierObjectReg) || (value != kWriteBarrierValueReg) || | |
| (slot != kWriteBarrierSlotReg)) { | |
| // Spill and shuffle unimplemented. Currently StoreIntoArray is only used | |
| // from StoreIndexInstr, which gets these exact registers from the register | |
| // allocator. | |
| UNIMPLEMENTED(); | |
| } | |
| generate_invoke_array_write_barrier_(NE); | |
| if (preserve_lr) { | |
| RESTORES_LR_FROM_FRAME(Pop(LR)); | |
| } | |
| Bind(&done); | |
| } | |
| void Assembler::StoreObjectIntoObjectNoBarrier(Register object, | |
| const Address& dest, | |
| const Object& value, | |
| MemoryOrder memory_order, | |
| OperandSize size) { | |
| ASSERT_EQUAL(size, kFourBytes); | |
| ASSERT_EQUAL(dest.mode(), Address::Mode::Offset); | |
| ASSERT_EQUAL(dest.kind(), Address::OffsetKind::Immediate); | |
| int32_t ignored = 0; | |
| Register scratch = TMP; | |
| if (!Address::CanHoldStoreOffset(size, dest.offset(), &ignored)) { | |
| // As there is no TMP2 on ARM7, Store uses TMP when the instruction cannot | |
| // contain the offset, so we need to use a different scratch register | |
| // for loading the object. | |
| scratch = dest.base() == R9 ? R8 : R9; | |
| Push(scratch); | |
| } | |
| ASSERT(IsOriginalObject(value)); | |
| DEBUG_ASSERT(IsNotTemporaryScopedHandle(value)); | |
| // No store buffer update. | |
| LoadObject(scratch, value); | |
| if (memory_order == kRelease) { | |
| StoreRelease(scratch, dest); | |
| } else { | |
| Store(scratch, dest); | |
| } | |
| if (scratch != TMP) { | |
| Pop(scratch); | |
| } | |
| } | |
| void Assembler::VerifyStoreNeedsNoWriteBarrier(Register object, | |
| Register value) { | |
| // We can't assert the incremental barrier is not needed here, only the | |
| // generational barrier. We sometimes omit the write barrier when 'value' is | |
| // a constant, but we don't eagerly mark 'value' and instead assume it is also | |
| // reachable via a constant pool, so it doesn't matter if it is not traced via | |
| // 'object'. | |
| Label done; | |
| BranchIfSmi(value, &done, kNearJump); | |
| ldrb(TMP, FieldAddress(value, target::Object::tags_offset())); | |
| tst(TMP, Operand(1 << target::UntaggedObject::kNewOrEvacuationCandidateBit)); | |
| b(&done, ZERO); | |
| ldrb(TMP, FieldAddress(object, target::Object::tags_offset())); | |
| tst(TMP, Operand(1 << target::UntaggedObject::kOldAndNotRememberedBit)); | |
| b(&done, ZERO); | |
| Stop("Write barrier is required"); | |
| Bind(&done); | |
| } | |
| void Assembler::StoreInternalPointer(Register object, | |
| const Address& dest, | |
| Register value) { | |
| str(value, dest); | |
| } | |
| void Assembler::InitializeFieldsNoBarrier(Register object, | |
| Register begin, | |
| Register end, | |
| Register value_even, | |
| Register value_odd) { | |
| ASSERT(value_odd == value_even + 1); | |
| Label init_loop; | |
| Bind(&init_loop); | |
| AddImmediate(begin, 2 * target::kWordSize); | |
| cmp(begin, Operand(end)); | |
| strd(value_even, value_odd, begin, -2 * target::kWordSize, LS); | |
| b(&init_loop, CC); | |
| str(value_even, Address(begin, -2 * target::kWordSize), HI); | |
| } | |
| void Assembler::InitializeFieldsNoBarrierUnrolled(Register object, | |
| Register base, | |
| intptr_t begin_offset, | |
| intptr_t end_offset, | |
| Register value_even, | |
| Register value_odd) { | |
| ASSERT(value_odd == value_even + 1); | |
| intptr_t current_offset = begin_offset; | |
| while (current_offset + target::kWordSize < end_offset) { | |
| strd(value_even, value_odd, base, current_offset); | |
| current_offset += 2 * target::kWordSize; | |
| } | |
| while (current_offset < end_offset) { | |
| str(value_even, Address(base, current_offset)); | |
| current_offset += target::kWordSize; | |
| } | |
| } | |
| void Assembler::StoreIntoSmiField(const Address& dest, Register value) { | |
| #if defined(DEBUG) | |
| Label done; | |
| tst(value, Operand(kHeapObjectTag)); | |
| b(&done, EQ); | |
| Stop("New value must be Smi."); | |
| Bind(&done); | |
| #endif // defined(DEBUG) | |
| Store(value, dest); | |
| } | |
| void Assembler::ExtractClassIdFromTags(Register result, | |
| Register tags, | |
| Condition cond) { | |
| ASSERT(target::UntaggedObject::kClassIdTagPos == 12); | |
| ASSERT(target::UntaggedObject::kClassIdTagSize == 20); | |
| ubfx(result, tags, target::UntaggedObject::kClassIdTagPos, | |
| target::UntaggedObject::kClassIdTagSize, cond); | |
| } | |
| void Assembler::ExtractInstanceSizeFromTags(Register result, Register tags) { | |
| ASSERT(target::UntaggedObject::kSizeTagPos == 8); | |
| ASSERT(target::UntaggedObject::kSizeTagSize == 4); | |
| Lsr(result, tags, | |
| Operand(target::UntaggedObject::kSizeTagPos - | |
| target::ObjectAlignment::kObjectAlignmentLog2), | |
| AL); | |
| AndImmediate(result, result, | |
| (Utils::NBitMask(target::UntaggedObject::kSizeTagSize) | |
| << target::ObjectAlignment::kObjectAlignmentLog2)); | |
| } | |
| void Assembler::LoadClassId(Register result, Register object, Condition cond) { | |
| ldr(result, FieldAddress(object, target::Object::tags_offset()), cond); | |
| ExtractClassIdFromTags(result, result, cond); | |
| } | |
| void Assembler::LoadClassById(Register result, Register class_id) { | |
| ASSERT(result != class_id); | |
| const intptr_t table_offset = | |
| target::IsolateGroup::cached_class_table_table_offset(); | |
| LoadIsolateGroup(result); | |
| LoadFromOffset(result, result, table_offset); | |
| ldr(result, Address(result, class_id, LSL, target::kWordSizeLog2)); | |
| } | |
| void Assembler::CompareClassId(Register object, | |
| intptr_t class_id, | |
| Register scratch) { | |
| LoadClassId(scratch, object); | |
| CompareImmediate(scratch, class_id); | |
| } | |
| void Assembler::LoadClassIdMayBeSmi(Register result, Register object) { | |
| tst(object, Operand(kSmiTagMask)); | |
| LoadClassId(result, object, NE); | |
| LoadImmediate(result, kSmiCid, EQ); | |
| } | |
| void Assembler::LoadTaggedClassIdMayBeSmi(Register result, Register object) { | |
| LoadClassIdMayBeSmi(result, object); | |
| SmiTag(result); | |
| } | |
| void Assembler::EnsureHasClassIdInDEBUG(intptr_t cid, | |
| Register src, | |
| Register scratch, | |
| bool can_be_null) { | |
| #if defined(DEBUG) | |
| Comment("Check that object in register has cid %" Pd "", cid); | |
| Label matches; | |
| LoadClassIdMayBeSmi(scratch, src); | |
| CompareImmediate(scratch, cid); | |
| BranchIf(EQUAL, &matches, Assembler::kNearJump); | |
| if (can_be_null) { | |
| CompareImmediate(scratch, kNullCid); | |
| BranchIf(EQUAL, &matches, Assembler::kNearJump); | |
| } | |
| Breakpoint(); | |
| Bind(&matches); | |
| #endif | |
| } | |
| void Assembler::BailoutIfInvalidBranchOffset(int32_t offset) { | |
| if (!CanEncodeBranchDistance(offset)) { | |
| ASSERT(!use_far_branches()); | |
| BailoutWithBranchOffsetError(); | |
| } | |
| } | |
| int32_t Assembler::EncodeBranchOffset(int32_t offset, int32_t inst) { | |
| // The offset is off by 8 due to the way the ARM CPUs read PC. | |
| offset -= Instr::kPCReadOffset; | |
| // Properly preserve only the bits supported in the instruction. | |
| offset >>= 2; | |
| offset &= kBranchOffsetMask; | |
| return (inst & ~kBranchOffsetMask) | offset; | |
| } | |
| int Assembler::DecodeBranchOffset(int32_t inst) { | |
| // Sign-extend, left-shift by 2, then add 8. | |
| return ((((inst & kBranchOffsetMask) << 8) >> 6) + Instr::kPCReadOffset); | |
| } | |
| static int32_t DecodeARMv7LoadImmediate(int32_t movt, int32_t movw) { | |
| int32_t offset = 0; | |
| offset |= (movt & 0xf0000) << 12; | |
| offset |= (movt & 0xfff) << 16; | |
| offset |= (movw & 0xf0000) >> 4; | |
| offset |= movw & 0xfff; | |
| return offset; | |
| } | |
| class PatchFarBranch : public AssemblerFixup { | |
| public: | |
| PatchFarBranch() {} | |
| void Process(const MemoryRegion& region, intptr_t position) { | |
| ProcessARMv7(region, position); | |
| } | |
| private: | |
| void ProcessARMv7(const MemoryRegion& region, intptr_t position) { | |
| const int32_t movw = region.Load<int32_t>(position); | |
| const int32_t movt = region.Load<int32_t>(position + Instr::kInstrSize); | |
| const int32_t bx = region.Load<int32_t>(position + 2 * Instr::kInstrSize); | |
| if (((movt & 0xfff0f000) == 0xe340c000) && // movt IP, high | |
| ((movw & 0xfff0f000) == 0xe300c000)) { // movw IP, low | |
| const int32_t offset = DecodeARMv7LoadImmediate(movt, movw); | |
| const int32_t dest = region.start() + offset; | |
| const uint16_t dest_high = Utils::High16Bits(dest); | |
| const uint16_t dest_low = Utils::Low16Bits(dest); | |
| const int32_t patched_movt = | |
| 0xe340c000 | ((dest_high >> 12) << 16) | (dest_high & 0xfff); | |
| const int32_t patched_movw = | |
| 0xe300c000 | ((dest_low >> 12) << 16) | (dest_low & 0xfff); | |
| region.Store<int32_t>(position, patched_movw); | |
| region.Store<int32_t>(position + Instr::kInstrSize, patched_movt); | |
| return; | |
| } | |
| // If the offset loading instructions aren't there, we must have replaced | |
| // the far branch with a near one, and so these instructions | |
| // should be NOPs. | |
| ASSERT((movt == Instr::kNopInstruction) && (bx == Instr::kNopInstruction)); | |
| } | |
| virtual bool IsPointerOffset() const { return false; } | |
| }; | |
| void Assembler::EmitFarBranch(Condition cond, int32_t offset, bool link) { | |
| buffer_.EmitFixup(new PatchFarBranch()); | |
| LoadPatchableImmediate(IP, offset); | |
| if (link) { | |
| blx(IP, cond); | |
| } else { | |
| bx(IP, cond); | |
| } | |
| } | |
| void Assembler::EmitBranch(Condition cond, Label* label, bool link) { | |
| if (label->IsBound()) { | |
| const int32_t dest = label->Position() - buffer_.Size(); | |
| if (use_far_branches() && !CanEncodeBranchDistance(dest)) { | |
| EmitFarBranch(cond, label->Position(), link); | |
| } else { | |
| EmitType5(cond, dest, link); | |
| } | |
| label->UpdateLRState(lr_state()); | |
| } else { | |
| const intptr_t position = buffer_.Size(); | |
| if (use_far_branches()) { | |
| const int32_t dest = label->position_; | |
| EmitFarBranch(cond, dest, link); | |
| } else { | |
| // Use the offset field of the branch instruction for linking the sites. | |
| EmitType5(cond, label->position_, link); | |
| } | |
| label->LinkTo(position, lr_state()); | |
| } | |
| } | |
| void Assembler::BindARMv7(Label* label) { | |
| ASSERT(!label->IsBound()); | |
| intptr_t bound_pc = buffer_.Size(); | |
| while (label->IsLinked()) { | |
| const int32_t position = label->Position(); | |
| int32_t dest = bound_pc - position; | |
| if (use_far_branches() && !CanEncodeBranchDistance(dest)) { | |
| // Far branches are enabled and we can't encode the branch offset. | |
| // Grab instructions that load the offset. | |
| const int32_t movw = | |
| buffer_.Load<int32_t>(position + 0 * Instr::kInstrSize); | |
| const int32_t movt = | |
| buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); | |
| // Change from relative to the branch to relative to the assembler | |
| // buffer. | |
| dest = buffer_.Size(); | |
| const uint16_t dest_high = Utils::High16Bits(dest); | |
| const uint16_t dest_low = Utils::Low16Bits(dest); | |
| const int32_t patched_movt = | |
| 0xe340c000 | ((dest_high >> 12) << 16) | (dest_high & 0xfff); | |
| const int32_t patched_movw = | |
| 0xe300c000 | ((dest_low >> 12) << 16) | (dest_low & 0xfff); | |
| // Rewrite the instructions. | |
| buffer_.Store<int32_t>(position + 0 * Instr::kInstrSize, patched_movw); | |
| buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, patched_movt); | |
| label->position_ = DecodeARMv7LoadImmediate(movt, movw); | |
| } else if (use_far_branches() && CanEncodeBranchDistance(dest)) { | |
| // Far branches are enabled, but we can encode the branch offset. | |
| // Grab instructions that load the offset, and the branch. | |
| const int32_t movw = | |
| buffer_.Load<int32_t>(position + 0 * Instr::kInstrSize); | |
| const int32_t movt = | |
| buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); | |
| const int32_t branch = | |
| buffer_.Load<int32_t>(position + 2 * Instr::kInstrSize); | |
| // Grab the branch condition, and encode the link bit. | |
| const int32_t cond = branch & 0xf0000000; | |
| const int32_t link = (branch & 0x20) << 19; | |
| // Encode the branch and the offset. | |
| const int32_t new_branch = cond | link | 0x0a000000; | |
| const int32_t encoded = EncodeBranchOffset(dest, new_branch); | |
| // Write the encoded branch instruction followed by two nops. | |
| buffer_.Store<int32_t>(position + 0 * Instr::kInstrSize, encoded); | |
| buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, | |
| Instr::kNopInstruction); | |
| buffer_.Store<int32_t>(position + 2 * Instr::kInstrSize, | |
| Instr::kNopInstruction); | |
| label->position_ = DecodeARMv7LoadImmediate(movt, movw); | |
| } else { | |
| BailoutIfInvalidBranchOffset(dest); | |
| int32_t next = buffer_.Load<int32_t>(position); | |
| int32_t encoded = Assembler::EncodeBranchOffset(dest, next); | |
| buffer_.Store<int32_t>(position, encoded); | |
| label->position_ = Assembler::DecodeBranchOffset(next); | |
| } | |
| } | |
| label->BindTo(bound_pc, lr_state()); | |
| } | |
| void Assembler::Bind(Label* label) { | |
| BindARMv7(label); | |
| } | |
| OperandSize Address::OperandSizeFor(intptr_t cid) { | |
| auto const rep = RepresentationUtils::RepresentationOfArrayElement(cid); | |
| switch (rep) { | |
| case kUnboxedInt64: | |
| return kDWord; | |
| case kUnboxedFloat: | |
| return kSWord; | |
| case kUnboxedDouble: | |
| return kDWord; | |
| case kUnboxedInt32x4: | |
| case kUnboxedFloat32x4: | |
| case kUnboxedFloat64x2: | |
| return kRegList; | |
| default: | |
| return RepresentationUtils::OperandSize(rep); | |
| } | |
| } | |
| bool Address::CanHoldLoadOffset(OperandSize size, | |
| int32_t offset, | |
| int32_t* offset_mask) { | |
| switch (size) { | |
| case kByte: | |
| case kTwoBytes: | |
| case kUnsignedTwoBytes: | |
| case kWordPair: { | |
| *offset_mask = 0xff; | |
| return Utils::MagnitudeIsUint(8, offset); // Addressing mode 3. | |
| } | |
| case kUnsignedByte: | |
| case kFourBytes: | |
| case kUnsignedFourBytes: { | |
| *offset_mask = 0xfff; | |
| return Utils::MagnitudeIsUint(12, offset); // Addressing mode 2. | |
| } | |
| case kSWord: | |
| case kDWord: { | |
| *offset_mask = 0x3fc; // Multiple of 4. | |
| // VFP addressing mode. | |
| return (Utils::MagnitudeIsUint(10, offset) && | |
| Utils::IsAligned(offset, 4)); | |
| } | |
| case kRegList: { | |
| *offset_mask = 0x0; | |
| return offset == 0; | |
| } | |
| default: { | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| } | |
| } | |
| bool Address::CanHoldStoreOffset(OperandSize size, | |
| int32_t offset, | |
| int32_t* offset_mask) { | |
| switch (size) { | |
| case kTwoBytes: | |
| case kUnsignedTwoBytes: | |
| case kWordPair: { | |
| *offset_mask = 0xff; | |
| return Utils::MagnitudeIsUint(8, offset); // Addressing mode 3. | |
| } | |
| case kByte: | |
| case kUnsignedByte: | |
| case kFourBytes: | |
| case kUnsignedFourBytes: { | |
| *offset_mask = 0xfff; | |
| return Utils::MagnitudeIsUint(12, offset); // Addressing mode 2. | |
| } | |
| case kSWord: | |
| case kDWord: { | |
| *offset_mask = 0x3fc; // Multiple of 4. | |
| // VFP addressing mode. | |
| return (Utils::MagnitudeIsUint(10, offset) && | |
| Utils::IsAligned(offset, 4)); | |
| } | |
| case kRegList: { | |
| *offset_mask = 0x0; | |
| return offset == 0; | |
| } | |
| default: { | |
| UNREACHABLE(); | |
| return false; | |
| } | |
| } | |
| } | |
| bool Address::CanHoldImmediateOffset(bool is_load, | |
| intptr_t cid, | |
| int64_t offset) { | |
| int32_t offset_mask = 0; | |
| if (is_load) { | |
| return CanHoldLoadOffset(OperandSizeFor(cid), offset, &offset_mask); | |
| } else { | |
| return CanHoldStoreOffset(OperandSizeFor(cid), offset, &offset_mask); | |
| } | |
| } | |
| void Assembler::Push(Register rd, Condition cond) { | |
| str(rd, Address(SP, -target::kWordSize, Address::PreIndex), cond); | |
| } | |
| void Assembler::Pop(Register rd, Condition cond) { | |
| ldr(rd, Address(SP, target::kWordSize, Address::PostIndex), cond); | |
| } | |
| void Assembler::PushList(RegList regs, Condition cond) { | |
| stm(DB_W, SP, regs, cond); | |
| } | |
| void Assembler::PopList(RegList regs, Condition cond) { | |
| ldm(IA_W, SP, regs, cond); | |
| } | |
| void Assembler::PushQuad(FpuRegister reg, Condition cond) { | |
| DRegister dreg = EvenDRegisterOf(reg); | |
| vstmd(DB_W, SP, dreg, 2, cond); // 2 D registers per Q register. | |
| } | |
| void Assembler::PopQuad(FpuRegister reg, Condition cond) { | |
| DRegister dreg = EvenDRegisterOf(reg); | |
| vldmd(IA_W, SP, dreg, 2, cond); // 2 D registers per Q register. | |
| } | |
| void Assembler::PushRegisters(const RegisterSet& regs) { | |
| // Store fpu registers with the lowest register number at the lowest | |
| // address. | |
| for (intptr_t i = kNumberOfFpuRegisters - 1; i >= 0; i--) { | |
| QRegister fpu_reg = static_cast<QRegister>(i); | |
| if (regs.ContainsFpuRegister(fpu_reg)) { | |
| PushQuad(fpu_reg); | |
| } | |
| } | |
| // The order in which the registers are pushed must match the order | |
| // in which the registers are encoded in the safe point's stack map. | |
| // NOTE: This matches the order of ARM's multi-register push. | |
| RegList reg_list = 0; | |
| for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { | |
| Register reg = static_cast<Register>(i); | |
| if (regs.ContainsRegister(reg)) { | |
| reg_list |= (1 << reg); | |
| } | |
| } | |
| if (reg_list != 0) { | |
| PushList(reg_list); | |
| } | |
| } | |
| void Assembler::PopRegisters(const RegisterSet& regs) { | |
| RegList reg_list = 0; | |
| for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { | |
| Register reg = static_cast<Register>(i); | |
| if (regs.ContainsRegister(reg)) { | |
| reg_list |= (1 << reg); | |
| } | |
| } | |
| if (reg_list != 0) { | |
| PopList(reg_list); | |
| } | |
| // Fpu registers have the lowest register number at the lowest address. | |
| for (intptr_t i = 0; i < kNumberOfFpuRegisters; ++i) { | |
| QRegister fpu_reg = static_cast<QRegister>(i); | |
| if (regs.ContainsFpuRegister(fpu_reg)) { | |
| PopQuad(fpu_reg); | |
| } | |
| } | |
| } | |
| void Assembler::PushRegistersAligned(const RegisterSet& register_set, | |
| intptr_t space) { | |
| PushRegisters(register_set); | |
| intptr_t aligned_space = Utils::RoundUp(register_set.SpillSize() + space, | |
| OS::ActivationFrameAlignment()) - | |
| register_set.SpillSize(); | |
| if (aligned_space != 0) { | |
| sub(SP, SP, Operand(aligned_space)); | |
| } | |
| } | |
| void Assembler::PopRegistersAligned(const RegisterSet& register_set, | |
| intptr_t space) { | |
| intptr_t aligned_space = Utils::RoundUp(register_set.SpillSize() + space, | |
| OS::ActivationFrameAlignment()) - | |
| register_set.SpillSize(); | |
| if (aligned_space != 0) { | |
| add(SP, SP, Operand(aligned_space)); | |
| } | |
| PopRegisters(register_set); | |
| } | |
| void Assembler::PushRegistersInOrder(std::initializer_list<Register> regs) { | |
| // Collect the longest descending sequences of registers and | |
| // push them with a single STMDB instruction. | |
| RegList pending_regs = 0; | |
| Register lowest_pending_reg = kNumberOfCpuRegisters; | |
| intptr_t num_pending_regs = 0; | |
| for (Register reg : regs) { | |
| if (reg >= lowest_pending_reg) { | |
| ASSERT(pending_regs != 0); | |
| if (num_pending_regs > 1) { | |
| PushList(pending_regs); | |
| } else { | |
| Push(lowest_pending_reg); | |
| } | |
| pending_regs = 0; | |
| num_pending_regs = 0; | |
| } | |
| pending_regs |= (1 << reg); | |
| lowest_pending_reg = reg; | |
| ++num_pending_regs; | |
| } | |
| if (pending_regs != 0) { | |
| if (num_pending_regs > 1) { | |
| PushList(pending_regs); | |
| } else { | |
| Push(lowest_pending_reg); | |
| } | |
| } | |
| } | |
| void Assembler::PushNativeCalleeSavedRegisters() { | |
| // Save new context and C++ ABI callee-saved registers. | |
| PushList(kAbiPreservedCpuRegs); | |
| const DRegister firstd = EvenDRegisterOf(kAbiFirstPreservedFpuReg); | |
| ASSERT(2 * kAbiPreservedFpuRegCount < 16); | |
| // Save FPU registers. 2 D registers per Q register. | |
| vstmd(DB_W, SP, firstd, 2 * kAbiPreservedFpuRegCount); | |
| } | |
| void Assembler::PopNativeCalleeSavedRegisters() { | |
| const DRegister firstd = EvenDRegisterOf(kAbiFirstPreservedFpuReg); | |
| // Restore C++ ABI callee-saved registers. | |
| // Restore FPU registers. 2 D registers per Q register. | |
| vldmd(IA_W, SP, firstd, 2 * kAbiPreservedFpuRegCount); | |
| // Restore CPU registers. | |
| PopList(kAbiPreservedCpuRegs); | |
| } | |
| void Assembler::ExtendValue(Register rd, | |
| Register rm, | |
| OperandSize sz, | |
| Condition cond) { | |
| switch (sz) { | |
| case kUnsignedFourBytes: | |
| case kFourBytes: | |
| if (rd == rm) return; | |
| return mov(rd, Operand(rm), cond); | |
| case kUnsignedTwoBytes: | |
| return ubfx(rd, rm, 0, kBitsPerInt16, cond); | |
| case kTwoBytes: | |
| return sbfx(rd, rm, 0, kBitsPerInt16, cond); | |
| case kUnsignedByte: | |
| return ubfx(rd, rm, 0, kBitsPerInt8, cond); | |
| case kByte: | |
| return sbfx(rd, rm, 0, kBitsPerInt8, cond); | |
| default: | |
| UNIMPLEMENTED(); | |
| break; | |
| } | |
| } | |
| void Assembler::Lsl(Register rd, | |
| Register rm, | |
| const Operand& shift_imm, | |
| Condition cond) { | |
| ASSERT(shift_imm.type() == 1); | |
| ASSERT(shift_imm.encoding() != 0); // Do not use Lsl if no shift is wanted. | |
| mov(rd, Operand(rm, LSL, shift_imm.encoding()), cond); | |
| } | |
| void Assembler::Lsl(Register rd, Register rm, Register rs, Condition cond) { | |
| mov(rd, Operand(rm, LSL, rs), cond); | |
| } | |
| void Assembler::Lsr(Register rd, | |
| Register rm, | |
| const Operand& shift_imm, | |
| Condition cond) { | |
| ASSERT(shift_imm.type() == 1); | |
| uint32_t shift = shift_imm.encoding(); | |
| ASSERT(shift != 0); // Do not use Lsr if no shift is wanted. | |
| if (shift == 32) { | |
| shift = 0; // Comply to UAL syntax. | |
| } | |
| mov(rd, Operand(rm, LSR, shift), cond); | |
| } | |
| void Assembler::Lsr(Register rd, Register rm, Register rs, Condition cond) { | |
| mov(rd, Operand(rm, LSR, rs), cond); | |
| } | |
| void Assembler::Asr(Register rd, | |
| Register rm, | |
| const Operand& shift_imm, | |
| Condition cond) { | |
| ASSERT(shift_imm.type() == 1); | |
| uint32_t shift = shift_imm.encoding(); | |
| ASSERT(shift != 0); // Do not use Asr if no shift is wanted. | |
| if (shift == 32) { | |
| shift = 0; // Comply to UAL syntax. | |
| } | |
| mov(rd, Operand(rm, ASR, shift), cond); | |
| } | |
| void Assembler::Asrs(Register rd, | |
| Register rm, | |
| const Operand& shift_imm, | |
| Condition cond) { | |
| ASSERT(shift_imm.type() == 1); | |
| uint32_t shift = shift_imm.encoding(); | |
| ASSERT(shift != 0); // Do not use Asr if no shift is wanted. | |
| if (shift == 32) { | |
| shift = 0; // Comply to UAL syntax. | |
| } | |
| movs(rd, Operand(rm, ASR, shift), cond); | |
| } | |
| void Assembler::Asr(Register rd, Register rm, Register rs, Condition cond) { | |
| mov(rd, Operand(rm, ASR, rs), cond); | |
| } | |
| void Assembler::Ror(Register rd, | |
| Register rm, | |
| const Operand& shift_imm, | |
| Condition cond) { | |
| ASSERT(shift_imm.type() == 1); | |
| ASSERT(shift_imm.encoding() != 0); // Use Rrx instruction. | |
| mov(rd, Operand(rm, ROR, shift_imm.encoding()), cond); | |
| } | |
| void Assembler::Ror(Register rd, Register rm, Register rs, Condition cond) { | |
| mov(rd, Operand(rm, ROR, rs), cond); | |
| } | |
| void Assembler::Rrx(Register rd, Register rm, Condition cond) { | |
| mov(rd, Operand(rm, ROR, 0), cond); | |
| } | |
| void Assembler::SignFill(Register rd, Register rm, Condition cond) { | |
| Asr(rd, rm, Operand(31), cond); | |
| } | |
| void Assembler::Vreciprocalqs(QRegister qd, QRegister qm) { | |
| ASSERT(qm != QTMP); | |
| ASSERT(qd != QTMP); | |
| // Reciprocal estimate. | |
| vrecpeqs(qd, qm); | |
| // 2 Newton-Raphson steps. | |
| vrecpsqs(QTMP, qm, qd); | |
| vmulqs(qd, qd, QTMP); | |
| vrecpsqs(QTMP, qm, qd); | |
| vmulqs(qd, qd, QTMP); | |
| } | |
| void Assembler::VreciprocalSqrtqs(QRegister qd, QRegister qm) { | |
| ASSERT(qm != QTMP); | |
| ASSERT(qd != QTMP); | |
| // Reciprocal square root estimate. | |
| vrsqrteqs(qd, qm); | |
| // 2 Newton-Raphson steps. xn+1 = xn * (3 - Q1*xn^2) / 2. | |
| // First step. | |
| vmulqs(QTMP, qd, qd); // QTMP <- xn^2 | |
| vrsqrtsqs(QTMP, qm, QTMP); // QTMP <- (3 - Q1*QTMP) / 2. | |
| vmulqs(qd, qd, QTMP); // xn+1 <- xn * QTMP | |
| // Second step. | |
| vmulqs(QTMP, qd, qd); | |
| vrsqrtsqs(QTMP, qm, QTMP); | |
| vmulqs(qd, qd, QTMP); | |
| } | |
| void Assembler::Vsqrtqs(QRegister qd, QRegister qm, QRegister temp) { | |
| ASSERT(temp != QTMP); | |
| ASSERT(qm != QTMP); | |
| ASSERT(qd != QTMP); | |
| if (temp != kNoQRegister) { | |
| vmovq(temp, qm); | |
| qm = temp; | |
| } | |
| VreciprocalSqrtqs(qd, qm); | |
| vmovq(qm, qd); | |
| Vreciprocalqs(qd, qm); | |
| } | |
| void Assembler::Vdivqs(QRegister qd, QRegister qn, QRegister qm) { | |
| ASSERT(qd != QTMP); | |
| ASSERT(qn != QTMP); | |
| ASSERT(qm != QTMP); | |
| Vreciprocalqs(qd, qm); | |
| vmulqs(qd, qn, qd); | |
| } | |
| void Assembler::Branch(const Address& address, Condition cond) { | |
| ldr(PC, address, cond); | |
| } | |
| void Assembler::BranchLink(intptr_t target_code_pool_index, | |
| CodeEntryKind entry_kind) { | |
| CLOBBERS_LR({ | |
| // Avoid clobbering CODE_REG when invoking code in precompiled mode. | |
| // We don't actually use CODE_REG in the callee and caller might | |
| // be using CODE_REG for a live value (e.g. a value that is alive | |
| // across invocation of a shared stub like the one we use for | |
| // allocating Mint boxes). | |
| const Register code_reg = FLAG_precompiled_mode ? LR : CODE_REG; | |
| LoadWordFromPoolIndex(code_reg, target_code_pool_index, PP, AL); | |
| Call(FieldAddress(code_reg, target::Code::entry_point_offset(entry_kind))); | |
| }); | |
| } | |
| void Assembler::BranchLink( | |
| const Code& target, | |
| ObjectPoolBuilderEntry::Patchability patchable, | |
| CodeEntryKind entry_kind, | |
| ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior) { | |
| // Make sure that class CallPattern is able to patch the label referred | |
| // to by this code sequence. | |
| // For added code robustness, use 'blx lr' in a patchable sequence and | |
| // use 'blx ip' in a non-patchable sequence (see other BranchLink flavors). | |
| const intptr_t index = object_pool_builder().FindObject( | |
| ToObject(target), patchable, snapshot_behavior); | |
| BranchLink(index, entry_kind); | |
| } | |
| void Assembler::BranchLinkPatchable( | |
| const Code& target, | |
| CodeEntryKind entry_kind, | |
| ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior) { | |
| BranchLink(target, ObjectPoolBuilderEntry::kPatchable, entry_kind, | |
| snapshot_behavior); | |
| } | |
| void Assembler::BranchLinkWithEquivalence(const Code& target, | |
| const Object& equivalence, | |
| CodeEntryKind entry_kind) { | |
| // Make sure that class CallPattern is able to patch the label referred | |
| // to by this code sequence. | |
| // For added code robustness, use 'blx lr' in a patchable sequence and | |
| // use 'blx ip' in a non-patchable sequence (see other BranchLink flavors). | |
| const intptr_t index = | |
| object_pool_builder().FindObject(ToObject(target), equivalence); | |
| BranchLink(index, entry_kind); | |
| } | |
| void Assembler::BranchLink(const ExternalLabel* label) { | |
| CLOBBERS_LR({ | |
| LoadImmediate(LR, label->address()); // Target address is never patched. | |
| blx(LR); // Use blx instruction so that the return branch prediction works. | |
| }); | |
| } | |
| void Assembler::BranchLinkOffset(Register base, int32_t offset) { | |
| ASSERT(base != PC); | |
| ASSERT(base != IP); | |
| LoadFromOffset(IP, base, offset); | |
| blx(IP); // Use blx instruction so that the return branch prediction works. | |
| } | |
| void Assembler::LoadPatchableImmediate(Register rd, | |
| int32_t value, | |
| Condition cond) { | |
| const uint16_t value_low = Utils::Low16Bits(value); | |
| const uint16_t value_high = Utils::High16Bits(value); | |
| movw(rd, value_low, cond); | |
| movt(rd, value_high, cond); | |
| } | |
| void Assembler::LoadDecodableImmediate(Register rd, | |
| int32_t value, | |
| Condition cond) { | |
| movw(rd, Utils::Low16Bits(value), cond); | |
| const uint16_t value_high = Utils::High16Bits(value); | |
| if (value_high != 0) { | |
| movt(rd, value_high, cond); | |
| } | |
| } | |
| void Assembler::LoadImmediate(Register rd, Immediate value, Condition cond) { | |
| LoadImmediate(rd, value.value(), cond); | |
| } | |
| void Assembler::LoadImmediate(Register rd, int32_t value, Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(value, &o)) { | |
| mov(rd, o, cond); | |
| } else if (Operand::CanHold(~value, &o)) { | |
| mvn_(rd, o, cond); | |
| } else { | |
| LoadDecodableImmediate(rd, value, cond); | |
| } | |
| } | |
| void Assembler::LoadSImmediate(SRegister sd, float value, Condition cond) { | |
| if (!vmovs(sd, value, cond)) { | |
| const DRegister dd = static_cast<DRegister>(sd >> 1); | |
| const int index = sd & 1; | |
| LoadImmediate(IP, bit_cast<int32_t, float>(value), cond); | |
| vmovdr(dd, index, IP, cond); | |
| } | |
| } | |
| void Assembler::LoadDImmediate(DRegister dd, | |
| double value, | |
| Register scratch, | |
| Condition cond) { | |
| ASSERT(scratch != PC); | |
| ASSERT(scratch != IP); | |
| if (vmovd(dd, value, cond)) return; | |
| int64_t imm64 = bit_cast<int64_t, double>(value); | |
| if (constant_pool_allowed()) { | |
| intptr_t index = object_pool_builder().FindImmediate64(imm64); | |
| intptr_t offset = | |
| target::ObjectPool::element_offset(index) - kHeapObjectTag; | |
| LoadDFromOffset(dd, PP, offset, cond); | |
| } else { | |
| // A scratch register and IP are needed to load an arbitrary double. | |
| ASSERT(scratch != kNoRegister); | |
| int64_t imm64 = bit_cast<int64_t, double>(value); | |
| LoadImmediate(IP, Utils::Low32Bits(imm64), cond); | |
| LoadImmediate(scratch, Utils::High32Bits(imm64), cond); | |
| vmovdrr(dd, IP, scratch, cond); | |
| } | |
| } | |
| void Assembler::LoadQImmediate(QRegister qd, simd128_value_t value) { | |
| ASSERT(constant_pool_allowed()); | |
| intptr_t index = object_pool_builder().FindImmediate128(value); | |
| intptr_t offset = target::ObjectPool::element_offset(index) - kHeapObjectTag; | |
| LoadMultipleDFromOffset(EvenDRegisterOf(qd), 2, PP, offset); | |
| } | |
| Address Assembler::PrepareLargeLoadOffset(const Address& address, | |
| OperandSize size, | |
| Condition cond) { | |
| ASSERT(size != kWordPair); | |
| if (address.kind() != Address::Immediate) { | |
| return address; | |
| } | |
| int32_t offset = address.offset(); | |
| int32_t offset_mask = 0; | |
| if (Address::CanHoldLoadOffset(size, offset, &offset_mask)) { | |
| return address; | |
| } | |
| auto mode = address.mode(); | |
| // If the retrieved offset is negative, then the U bit was flipped during | |
| // encoding, so re-flip it. | |
| if (offset < 0) { | |
| mode = static_cast<Address::Mode>(mode ^ U); | |
| } | |
| // If writing back post-indexing, we can't separate the instruction into | |
| // two parts and the offset must fit. | |
| ASSERT((mode | U) != Address::PostIndex); | |
| // If we're writing back pre-indexing, we must add directly to the base, | |
| // otherwise we use TMP. | |
| Register base = address.base(); | |
| ASSERT(base != TMP || address.has_writeback()); | |
| Register temp = address.has_writeback() ? base : TMP; | |
| AddImmediate(temp, base, offset & ~offset_mask, cond); | |
| base = temp; | |
| offset = offset & offset_mask; | |
| return Address(base, offset, mode); | |
| } | |
| Address Assembler::PrepareLargeStoreOffset(const Address& address, | |
| OperandSize size, | |
| Condition cond) { | |
| ASSERT(size != kWordPair); | |
| if (address.kind() != Address::Immediate) { | |
| return address; | |
| } | |
| int32_t offset = address.offset(); | |
| int32_t offset_mask = 0; | |
| if (Address::CanHoldStoreOffset(size, offset, &offset_mask)) { | |
| return address; | |
| } | |
| auto mode = address.mode(); | |
| // If the retrieved offset is negative, then the U bit was flipped during | |
| // encoding, so re-flip it. | |
| if (offset < 0) { | |
| mode = static_cast<Address::Mode>(mode ^ U); | |
| } | |
| // If writing back post-indexing, we can't separate the instruction into | |
| // two parts and the offset must fit. | |
| ASSERT((mode | U) != Address::PostIndex); | |
| // If we're writing back pre-indexing, we must add directly to the base, | |
| // otherwise we use TMP. | |
| Register base = address.base(); | |
| ASSERT(base != TMP || address.has_writeback()); | |
| Register temp = address.has_writeback() ? base : TMP; | |
| AddImmediate(temp, base, offset & ~offset_mask, cond); | |
| base = temp; | |
| offset = offset & offset_mask; | |
| return Address(base, offset, mode); | |
| } | |
| void Assembler::Load(Register reg, | |
| const Address& address, | |
| OperandSize size, | |
| Condition cond) { | |
| const Address& addr = PrepareLargeLoadOffset(address, size, cond); | |
| switch (size) { | |
| case kByte: | |
| ldrsb(reg, addr, cond); | |
| break; | |
| case kUnsignedByte: | |
| ldrb(reg, addr, cond); | |
| break; | |
| case kTwoBytes: | |
| ldrsh(reg, addr, cond); | |
| break; | |
| case kUnsignedTwoBytes: | |
| ldrh(reg, addr, cond); | |
| break; | |
| case kUnsignedFourBytes: | |
| case kFourBytes: | |
| ldr(reg, addr, cond); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| void Assembler::LoadFromStack(Register dst, intptr_t depth) { | |
| ASSERT(depth >= 0); | |
| LoadFromOffset(dst, SPREG, depth * target::kWordSize); | |
| } | |
| void Assembler::StoreToStack(Register src, intptr_t depth) { | |
| ASSERT(depth >= 0); | |
| StoreToOffset(src, SPREG, depth * target::kWordSize); | |
| } | |
| void Assembler::CompareToStack(Register src, intptr_t depth) { | |
| LoadFromStack(TMP, depth); | |
| CompareRegisters(src, TMP); | |
| } | |
| void Assembler::Store(Register reg, | |
| const Address& address, | |
| OperandSize size, | |
| Condition cond) { | |
| const Address& addr = PrepareLargeStoreOffset(address, size, cond); | |
| switch (size) { | |
| case kUnsignedByte: | |
| case kByte: | |
| strb(reg, addr, cond); | |
| break; | |
| case kUnsignedTwoBytes: | |
| case kTwoBytes: | |
| strh(reg, addr, cond); | |
| break; | |
| case kUnsignedFourBytes: | |
| case kFourBytes: | |
| str(reg, addr, cond); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| void Assembler::LoadSFromOffset(SRegister reg, | |
| Register base, | |
| int32_t offset, | |
| Condition cond) { | |
| vldrs(reg, PrepareLargeLoadOffset(Address(base, offset), kSWord, cond), cond); | |
| } | |
| void Assembler::StoreSToOffset(SRegister reg, | |
| Register base, | |
| int32_t offset, | |
| Condition cond) { | |
| vstrs(reg, PrepareLargeStoreOffset(Address(base, offset), kSWord, cond), | |
| cond); | |
| } | |
| void Assembler::LoadDFromOffset(DRegister reg, | |
| Register base, | |
| int32_t offset, | |
| Condition cond) { | |
| vldrd(reg, PrepareLargeLoadOffset(Address(base, offset), kDWord, cond), cond); | |
| } | |
| void Assembler::StoreDToOffset(DRegister reg, | |
| Register base, | |
| int32_t offset, | |
| Condition cond) { | |
| vstrd(reg, PrepareLargeStoreOffset(Address(base, offset), kDWord, cond), | |
| cond); | |
| } | |
| void Assembler::LoadMultipleDFromOffset(DRegister first, | |
| intptr_t count, | |
| Register base, | |
| int32_t offset) { | |
| ASSERT(base != IP); | |
| AddImmediate(IP, base, offset); | |
| vldmd(IA, IP, first, count); | |
| } | |
| void Assembler::StoreMultipleDToOffset(DRegister first, | |
| intptr_t count, | |
| Register base, | |
| int32_t offset) { | |
| ASSERT(base != IP); | |
| AddImmediate(IP, base, offset); | |
| vstmd(IA, IP, first, count); | |
| } | |
| void Assembler::AddImmediate(Register rd, | |
| Register rn, | |
| int32_t value, | |
| Condition cond) { | |
| if (value == 0) { | |
| if (rd != rn) { | |
| mov(rd, Operand(rn), cond); | |
| } | |
| return; | |
| } | |
| // We prefer to select the shorter code sequence rather than selecting add for | |
| // positive values and sub for negatives ones, which would slightly improve | |
| // the readability of generated code for some constants. | |
| Operand o; | |
| if (Operand::CanHold(value, &o)) { | |
| add(rd, rn, o, cond); | |
| } else if (Operand::CanHold(-value, &o)) { | |
| sub(rd, rn, o, cond); | |
| } else { | |
| ASSERT(rn != IP); | |
| if (Operand::CanHold(~value, &o)) { | |
| mvn_(IP, o, cond); | |
| add(rd, rn, Operand(IP), cond); | |
| } else if (Operand::CanHold(~(-value), &o)) { | |
| mvn_(IP, o, cond); | |
| sub(rd, rn, Operand(IP), cond); | |
| } else if (value > 0) { | |
| LoadDecodableImmediate(IP, value, cond); | |
| add(rd, rn, Operand(IP), cond); | |
| } else { | |
| LoadDecodableImmediate(IP, -value, cond); | |
| sub(rd, rn, Operand(IP), cond); | |
| } | |
| } | |
| } | |
| void Assembler::AddImmediateSetFlags(Register rd, | |
| Register rn, | |
| int32_t value, | |
| Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(value, &o)) { | |
| // Handles value == kMinInt32. | |
| adds(rd, rn, o, cond); | |
| } else if (Operand::CanHold(-value, &o)) { | |
| ASSERT(value != kMinInt32); // Would cause erroneous overflow detection. | |
| subs(rd, rn, o, cond); | |
| } else { | |
| ASSERT(rn != IP); | |
| if (Operand::CanHold(~value, &o)) { | |
| mvn_(IP, o, cond); | |
| adds(rd, rn, Operand(IP), cond); | |
| } else if (Operand::CanHold(~(-value), &o)) { | |
| ASSERT(value != kMinInt32); // Would cause erroneous overflow detection. | |
| mvn_(IP, o, cond); | |
| subs(rd, rn, Operand(IP), cond); | |
| } else { | |
| LoadDecodableImmediate(IP, value, cond); | |
| adds(rd, rn, Operand(IP), cond); | |
| } | |
| } | |
| } | |
| void Assembler::SubImmediate(Register rd, | |
| Register rn, | |
| int32_t value, | |
| Condition cond) { | |
| AddImmediate(rd, rn, -value, cond); | |
| } | |
| void Assembler::SubImmediateSetFlags(Register rd, | |
| Register rn, | |
| int32_t value, | |
| Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(value, &o)) { | |
| // Handles value == kMinInt32. | |
| subs(rd, rn, o, cond); | |
| } else if (Operand::CanHold(-value, &o)) { | |
| ASSERT(value != kMinInt32); // Would cause erroneous overflow detection. | |
| adds(rd, rn, o, cond); | |
| } else { | |
| ASSERT(rn != IP); | |
| if (Operand::CanHold(~value, &o)) { | |
| mvn_(IP, o, cond); | |
| subs(rd, rn, Operand(IP), cond); | |
| } else if (Operand::CanHold(~(-value), &o)) { | |
| ASSERT(value != kMinInt32); // Would cause erroneous overflow detection. | |
| mvn_(IP, o, cond); | |
| adds(rd, rn, Operand(IP), cond); | |
| } else { | |
| LoadDecodableImmediate(IP, value, cond); | |
| subs(rd, rn, Operand(IP), cond); | |
| } | |
| } | |
| } | |
| void Assembler::AndImmediate(Register rd, | |
| Register rs, | |
| int32_t imm, | |
| OperandSize sz, | |
| Condition cond) { | |
| ASSERT(sz == kFourBytes || sz == kUnsignedFourBytes); | |
| Operand o; | |
| // Avoid generating a load + and_ pair for all bits set, since | |
| // Operand::CanHold returns false for that case. This also allows the | |
| // instruction to be a no-op if rd == rs. | |
| if (imm == -1) { | |
| MoveRegister(rd, rs); | |
| } else if (Operand::CanHold(imm, &o)) { | |
| and_(rd, rs, Operand(o), cond); | |
| } else { | |
| LoadImmediate(TMP, imm, cond); | |
| and_(rd, rs, Operand(TMP), cond); | |
| } | |
| } | |
| void Assembler::AndImmediateSetFlags(Register rd, | |
| Register rs, | |
| int32_t imm, | |
| Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(imm, &o)) { | |
| ands(rd, rs, Operand(o), cond); | |
| } else { | |
| LoadImmediate(TMP, imm, cond); | |
| ands(rd, rs, Operand(TMP), cond); | |
| } | |
| } | |
| void Assembler::OrImmediate(Register rd, | |
| Register rs, | |
| int32_t imm, | |
| Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(imm, &o)) { | |
| orr(rd, rs, Operand(o), cond); | |
| } else { | |
| LoadImmediate(TMP, imm, cond); | |
| orr(rd, rs, Operand(TMP), cond); | |
| } | |
| } | |
| void Assembler::XorImmediate(Register rd, | |
| Register rs, | |
| int32_t imm, | |
| Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(imm, &o)) { | |
| eor(rd, rs, Operand(o), cond); | |
| } else { | |
| LoadImmediate(TMP, imm, cond); | |
| eor(rd, rs, Operand(TMP), cond); | |
| } | |
| } | |
| void Assembler::CompareImmediate(Register rn, int32_t value, Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(value, &o)) { | |
| cmp(rn, o, cond); | |
| } else { | |
| ASSERT(rn != IP); | |
| LoadImmediate(IP, value, cond); | |
| cmp(rn, Operand(IP), cond); | |
| } | |
| } | |
| void Assembler::TestImmediate(Register rn, int32_t imm, Condition cond) { | |
| Operand o; | |
| if (Operand::CanHold(imm, &o)) { | |
| tst(rn, o, cond); | |
| } else { | |
| LoadImmediate(IP, imm); | |
| tst(rn, Operand(IP), cond); | |
| } | |
| } | |
| void Assembler::IntegerDivide(Register result, | |
| Register left, | |
| Register right, | |
| DRegister tmpl, | |
| DRegister tmpr) { | |
| ASSERT(tmpl != tmpr); | |
| if (TargetCPUFeatures::integer_division_supported()) { | |
| sdiv(result, left, right); | |
| } else { | |
| SRegister stmpl = EvenSRegisterOf(tmpl); | |
| SRegister stmpr = EvenSRegisterOf(tmpr); | |
| vmovsr(stmpl, left); | |
| vcvtdi(tmpl, stmpl); // left is in tmpl. | |
| vmovsr(stmpr, right); | |
| vcvtdi(tmpr, stmpr); // right is in tmpr. | |
| vdivd(tmpr, tmpl, tmpr); | |
| vcvtid(stmpr, tmpr); | |
| vmovrs(result, stmpr); | |
| } | |
| } | |
| static int NumRegsBelowFP(RegList regs) { | |
| int count = 0; | |
| for (int i = 0; i < FP; i++) { | |
| if ((regs & (1 << i)) != 0) { | |
| count++; | |
| } | |
| } | |
| return count; | |
| } | |
| void Assembler::ArithmeticShiftRightImmediate(Register dst, | |
| Register src, | |
| int32_t shift, | |
| OperandSize sz) { | |
| ASSERT(sz == kFourBytes); | |
| ASSERT((shift >= 0) && (shift < OperandSizeInBits(sz))); | |
| if (shift != 0) { | |
| Asr(dst, src, Operand(shift)); | |
| } else { | |
| MoveRegister(dst, src); | |
| } | |
| } | |
| void Assembler::CompareWords(Register reg1, | |
| Register reg2, | |
| intptr_t offset, | |
| Register count, | |
| Register temp, | |
| Label* equals) { | |
| Label loop; | |
| AddImmediate(reg1, offset - kHeapObjectTag); | |
| AddImmediate(reg2, offset - kHeapObjectTag); | |
| COMPILE_ASSERT(target::kWordSize == 4); | |
| Bind(&loop); | |
| BranchIfZero(count, equals, Assembler::kNearJump); | |
| AddImmediate(count, -1); | |
| ldr(temp, Address(reg1, 4, Address::PostIndex)); | |
| ldr(TMP, Address(reg2, 4, Address::PostIndex)); | |
| cmp(temp, Operand(TMP)); | |
| BranchIf(EQUAL, &loop, Assembler::kNearJump); | |
| } | |
| void Assembler::EnterFrame(RegList regs, intptr_t frame_size) { | |
| if (prologue_offset_ == -1) { | |
| prologue_offset_ = CodeSize(); | |
| } | |
| PushList(regs); | |
| if ((regs & (1 << FP)) != 0) { | |
| // Set FP to the saved previous FP. | |
| add(FP, SP, Operand(4 * NumRegsBelowFP(regs))); | |
| } | |
| if (frame_size != 0) { | |
| AddImmediate(SP, -frame_size); | |
| } | |
| } | |
| void Assembler::LeaveFrame(RegList regs, bool allow_pop_pc) { | |
| ASSERT(allow_pop_pc || (regs & (1 << PC)) == 0); // Must not pop PC. | |
| if ((regs & (1 << FP)) != 0) { | |
| // Use FP to set SP. | |
| sub(SP, FP, Operand(4 * NumRegsBelowFP(regs))); | |
| } | |
| PopList(regs); | |
| } | |
| void Assembler::Ret(Condition cond /* = AL */) { | |
| READS_RETURN_ADDRESS_FROM_LR(bx(LR, cond)); | |
| } | |
| void Assembler::SetReturnAddress(Register value) { | |
| RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(MoveRegister(LR, value)); | |
| } | |
| void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) { | |
| // Reserve space for arguments and align frame before entering | |
| // the C++ world. | |
| AddImmediate(SP, -frame_space); | |
| if (OS::ActivationFrameAlignment() > 1) { | |
| bic(SP, SP, Operand(OS::ActivationFrameAlignment() - 1)); | |
| } | |
| } | |
| void Assembler::EmitEntryFrameVerification(Register scratch) { | |
| #if defined(DEBUG) | |
| Label done; | |
| ASSERT(!constant_pool_allowed()); | |
| LoadImmediate(scratch, target::frame_layout.exit_link_slot_from_entry_fp * | |
| target::kWordSize); | |
| add(scratch, scratch, Operand(FPREG)); | |
| cmp(scratch, Operand(SPREG)); | |
| b(&done, EQ); | |
| Breakpoint(); | |
| Bind(&done); | |
| #endif | |
| } | |
| void Assembler::CallRuntime(const RuntimeEntry& entry, | |
| intptr_t argument_count, | |
| bool tsan_enter_exit) { | |
| ASSERT(!entry.is_leaf()); | |
| // Argument count is not checked here, but in the runtime entry for a more | |
| // informative error message. | |
| LoadFromOffset(R9, THR, entry.OffsetFromThread()); | |
| LoadImmediate(R4, argument_count); | |
| ldr(IP, Address(THR, target::Thread::call_to_runtime_entry_point_offset())); | |
| Comment("Runtime call: %s", entry.name()); | |
| blx(IP); | |
| } | |
| // For use by LR related macros (e.g. CLOBBERS_LR). | |
| #undef __ | |
| #define __ assembler_-> | |
| #if defined(VFPv3_D32) | |
| static const RegisterSet kVolatileFpuRegisters(0, 0xFF0F); // Q0-Q3, Q8-Q15 | |
| #else | |
| static const RegisterSet kVolatileFpuRegisters(0, 0x000F); // Q0-Q3 | |
| #endif | |
| LeafRuntimeScope::LeafRuntimeScope(Assembler* assembler, | |
| intptr_t frame_size, | |
| bool preserve_registers) | |
| : assembler_(assembler), preserve_registers_(preserve_registers) { | |
| if (preserve_registers) { | |
| // Preserve volatile CPU registers and PP. | |
| SPILLS_LR_TO_FRAME(__ EnterFrame( | |
| kDartVolatileCpuRegs | (1 << PP) | (1 << FP) | (1 << LR), 0)); | |
| COMPILE_ASSERT((kDartVolatileCpuRegs & (1 << PP)) == 0); | |
| __ PushRegisters(kVolatileFpuRegisters); | |
| } else { | |
| SPILLS_LR_TO_FRAME(__ EnterFrame((1 << FP) | (1 << LR), 0)); | |
| // These registers must always be preserved. | |
| COMPILE_ASSERT(IsCalleeSavedRegister(THR)); | |
| COMPILE_ASSERT(IsCalleeSavedRegister(PP)); | |
| COMPILE_ASSERT(IsCalleeSavedRegister(CODE_REG)); | |
| } | |
| __ ReserveAlignedFrameSpace(frame_size); | |
| } | |
| void LeafRuntimeScope::Call(const RuntimeEntry& entry, | |
| intptr_t argument_count) { | |
| ASSERT(argument_count == entry.argument_count()); | |
| __ LoadFromOffset(TMP, THR, entry.OffsetFromThread()); | |
| __ str(TMP, | |
| compiler::Address(THR, compiler::target::Thread::vm_tag_offset())); | |
| __ Comment("Leaf runtime call: %s", entry.name()); | |
| __ blx(TMP); | |
| __ LoadImmediate(TMP, VMTag::kDartTagId); | |
| __ str(TMP, | |
| compiler::Address(THR, compiler::target::Thread::vm_tag_offset())); | |
| } | |
| LeafRuntimeScope::~LeafRuntimeScope() { | |
| if (preserve_registers_) { | |
| // SP might have been modified to reserve space for arguments | |
| // and ensure proper alignment of the stack frame. | |
| // We need to restore it before restoring registers. | |
| const intptr_t kPushedFpuRegisterSize = | |
| kVolatileFpuRegisters.FpuRegisterCount() * kFpuRegisterSize; | |
| COMPILE_ASSERT(PP < FP); | |
| COMPILE_ASSERT((kDartVolatileCpuRegs & (1 << PP)) == 0); | |
| // kVolatileCpuRegCount +1 for PP, -1 because even though LR is volatile, | |
| // it is pushed ahead of FP. | |
| const intptr_t kPushedRegistersSize = | |
| kDartVolatileCpuRegCount * target::kWordSize + kPushedFpuRegisterSize; | |
| __ AddImmediate(SP, FP, -kPushedRegistersSize); | |
| __ PopRegisters(kVolatileFpuRegisters); | |
| // Restore volatile CPU registers. | |
| RESTORES_LR_FROM_FRAME(__ LeaveFrame(kDartVolatileCpuRegs | (1 << PP) | | |
| (1 << FP) | (1 << LR))); | |
| } else { | |
| RESTORES_LR_FROM_FRAME(__ LeaveFrame((1 << FP) | (1 << LR))); | |
| } | |
| } | |
| // For use by LR related macros (e.g. CLOBBERS_LR). | |
| #undef __ | |
| #define __ this-> | |
| void Assembler::EnterDartFrame(intptr_t frame_size, bool load_pool_pointer) { | |
| ASSERT(!constant_pool_allowed()); | |
| // Registers are pushed in descending order: R5 | R6 | R7/R11 | R14. | |
| COMPILE_ASSERT(PP < CODE_REG); | |
| COMPILE_ASSERT(CODE_REG < FP); | |
| COMPILE_ASSERT(FP < LINK_REGISTER.code); | |
| if (!FLAG_precompiled_mode) { | |
| SPILLS_LR_TO_FRAME( | |
| EnterFrame((1 << PP) | (1 << CODE_REG) | (1 << FP) | (1 << LR), 0)); | |
| // Setup pool pointer for this dart function. | |
| if (load_pool_pointer) LoadPoolPointer(); | |
| } else { | |
| SPILLS_LR_TO_FRAME(EnterFrame((1 << FP) | (1 << LR), 0)); | |
| } | |
| set_constant_pool_allowed(true); | |
| // Reserve space for locals. | |
| AddImmediate(SP, -frame_size); | |
| } | |
| // On entry to a function compiled for OSR, the caller's frame pointer, the | |
| // stack locals, and any copied parameters are already in place. The frame | |
| // pointer is already set up. The PC marker is not correct for the | |
| // optimized function and there may be extra space for spill slots to | |
| // allocate. We must also set up the pool pointer for the function. | |
| void Assembler::EnterOsrFrame(intptr_t extra_size) { | |
| ASSERT(!constant_pool_allowed()); | |
| Comment("EnterOsrFrame"); | |
| RestoreCodePointer(); | |
| LoadPoolPointer(); | |
| AddImmediate(SP, -extra_size); | |
| } | |
| void Assembler::LeaveDartFrame() { | |
| if (!FLAG_precompiled_mode) { | |
| ldr(PP, Address(FP, target::frame_layout.saved_caller_pp_from_fp * | |
| target::kWordSize)); | |
| } | |
| set_constant_pool_allowed(false); | |
| // This will implicitly drop saved PP, PC marker due to restoring SP from FP | |
| // first. | |
| RESTORES_LR_FROM_FRAME(LeaveFrame((1 << FP) | (1 << LR))); | |
| } | |
| void Assembler::LeaveDartFrameAndReturn() { | |
| if (!FLAG_precompiled_mode) { | |
| ldr(PP, Address(FP, target::frame_layout.saved_caller_pp_from_fp * | |
| target::kWordSize)); | |
| } | |
| set_constant_pool_allowed(false); | |
| // This will implicitly drop saved PP, PC marker due to restoring SP from FP | |
| // first. | |
| LeaveFrame((1 << FP) | (1 << PC), /*allow_pop_pc=*/true); | |
| } | |
| void Assembler::EnterStubFrame() { | |
| EnterDartFrame(0); | |
| } | |
| void Assembler::LeaveStubFrame() { | |
| LeaveDartFrame(); | |
| } | |
| void Assembler::EnterCFrame(intptr_t frame_space) { | |
| // Already saved. | |
| COMPILE_ASSERT(IsCalleeSavedRegister(THR)); | |
| COMPILE_ASSERT(IsCalleeSavedRegister(PP)); | |
| EnterFrame(1 << FP, 0); | |
| ReserveAlignedFrameSpace(frame_space); | |
| } | |
| void Assembler::LeaveCFrame() { | |
| LeaveFrame(1 << FP); | |
| } | |
| // R0 receiver, R9 ICData entries array | |
| // Preserve R4 (ARGS_DESC_REG), not required today, but maybe later. | |
| void Assembler::MonomorphicCheckedEntryJIT() { | |
| has_monomorphic_entry_ = true; | |
| #if defined(TESTING) || defined(DEBUG) | |
| bool saved_use_far_branches = use_far_branches(); | |
| set_use_far_branches(false); | |
| #endif | |
| intptr_t start = CodeSize(); | |
| Comment("MonomorphicCheckedEntry"); | |
| ASSERT_EQUAL(CodeSize() - start, | |
| target::Instructions::kMonomorphicEntryOffsetJIT); | |
| const intptr_t cid_offset = target::Array::element_offset(0); | |
| const intptr_t count_offset = target::Array::element_offset(1); | |
| // Sadly this cannot use ldm because ldm takes no offset. | |
| ldr(R1, FieldAddress(R9, cid_offset)); | |
| ldr(R2, FieldAddress(R9, count_offset)); | |
| LoadClassIdMayBeSmi(IP, R0); | |
| add(R2, R2, Operand(target::ToRawSmi(1))); | |
| cmp(R1, Operand(IP, LSL, 1)); | |
| Branch(Address(THR, target::Thread::switchable_call_miss_entry_offset()), NE); | |
| str(R2, FieldAddress(R9, count_offset)); | |
| LoadImmediate(R4, 0); // GC-safe for OptimizeInvokedFunction. | |
| // Fall through to unchecked entry. | |
| ASSERT_EQUAL(CodeSize() - start, | |
| target::Instructions::kPolymorphicEntryOffsetJIT); | |
| #if defined(TESTING) || defined(DEBUG) | |
| set_use_far_branches(saved_use_far_branches); | |
| #endif | |
| } | |
| // R0 receiver, R9 guarded cid as Smi. | |
| // Preserve R4 (ARGS_DESC_REG), not required today, but maybe later. | |
| void Assembler::MonomorphicCheckedEntryAOT() { | |
| has_monomorphic_entry_ = true; | |
| #if defined(TESTING) || defined(DEBUG) | |
| bool saved_use_far_branches = use_far_branches(); | |
| set_use_far_branches(false); | |
| #endif | |
| intptr_t start = CodeSize(); | |
| Comment("MonomorphicCheckedEntry"); | |
| ASSERT_EQUAL(CodeSize() - start, | |
| target::Instructions::kMonomorphicEntryOffsetAOT); | |
| LoadClassId(IP, R0); | |
| cmp(R9, Operand(IP, LSL, 1)); | |
| Branch(Address(THR, target::Thread::switchable_call_miss_entry_offset()), NE); | |
| // Fall through to unchecked entry. | |
| ASSERT_EQUAL(CodeSize() - start, | |
| target::Instructions::kPolymorphicEntryOffsetAOT); | |
| #if defined(TESTING) || defined(DEBUG) | |
| set_use_far_branches(saved_use_far_branches); | |
| #endif | |
| } | |
| void Assembler::BranchOnMonomorphicCheckedEntryJIT(Label* label) { | |
| has_monomorphic_entry_ = true; | |
| while (CodeSize() < target::Instructions::kMonomorphicEntryOffsetJIT) { | |
| bkpt(0); | |
| } | |
| b(label); | |
| while (CodeSize() < target::Instructions::kPolymorphicEntryOffsetJIT) { | |
| bkpt(0); | |
| } | |
| } | |
| void Assembler::CombineHashes(Register hash, Register other) { | |
| // hash += other_hash | |
| add(hash, hash, Operand(other)); | |
| // hash += hash << 10 | |
| add(hash, hash, Operand(hash, LSL, 10)); | |
| // hash ^= hash >> 6 | |
| eor(hash, hash, Operand(hash, LSR, 6)); | |
| } | |
| void Assembler::FinalizeHashForSize(intptr_t bit_size, | |
| Register hash, | |
| Register scratch) { | |
| ASSERT(bit_size > 0); // Can't avoid returning 0 if there are no hash bits! | |
| // While any 32-bit hash value fits in X bits, where X > 32, the caller may | |
| // reasonably expect that the returned values fill the entire bit space. | |
| ASSERT(bit_size <= kBitsPerInt32); | |
| // hash += hash << 3; | |
| add(hash, hash, Operand(hash, LSL, 3)); | |
| // hash ^= hash >> 11; // Logical shift, unsigned hash. | |
| eor(hash, hash, Operand(hash, LSR, 11)); | |
| // hash += hash << 15; | |
| adds(hash, hash, Operand(hash, LSL, 15)); | |
| if (bit_size < kBitsPerInt32) { | |
| // Size to fit. | |
| AndImmediateSetFlags(hash, hash, Utils::NBitMask(bit_size), NOT_ZERO); | |
| } | |
| // return (hash == 0) ? 1 : hash; | |
| LoadImmediate(hash, 1, ZERO); | |
| } | |
| #ifndef PRODUCT | |
| void Assembler::MaybeTraceAllocation(Register stats_addr_reg, Label* trace) { | |
| ASSERT(stats_addr_reg != kNoRegister); | |
| ASSERT(stats_addr_reg != TMP); | |
| ldrb(TMP, Address(stats_addr_reg, 0)); | |
| cmp(TMP, Operand(0)); | |
| b(trace, NE); | |
| } | |
| void Assembler::MaybeTraceAllocation(intptr_t cid, | |
| Label* trace, | |
| Register temp_reg, | |
| JumpDistance distance) { | |
| LoadAllocationTracingStateAddress(temp_reg, cid); | |
| MaybeTraceAllocation(temp_reg, trace); | |
| } | |
| void Assembler::MaybeTraceAllocation(Register cid, | |
| Label* trace, | |
| Register temp_reg, | |
| JumpDistance distance) { | |
| LoadAllocationTracingStateAddress(temp_reg, cid); | |
| MaybeTraceAllocation(temp_reg, trace); | |
| } | |
| void Assembler::LoadAllocationTracingStateAddress(Register dest, Register cid) { | |
| ASSERT(dest != kNoRegister); | |
| ASSERT(dest != TMP); | |
| LoadIsolateGroup(dest); | |
| ldr(dest, Address(dest, target::IsolateGroup::class_table_offset())); | |
| ldr(dest, | |
| Address(dest, | |
| target::ClassTable::allocation_tracing_state_table_offset())); | |
| AddScaled(dest, dest, cid, TIMES_1, | |
| target::ClassTable::AllocationTracingStateSlotOffsetFor(0)); | |
| } | |
| void Assembler::LoadAllocationTracingStateAddress(Register dest, intptr_t cid) { | |
| ASSERT(dest != kNoRegister); | |
| ASSERT(dest != TMP); | |
| ASSERT(cid > 0); | |
| LoadIsolateGroup(dest); | |
| ldr(dest, Address(dest, target::IsolateGroup::class_table_offset())); | |
| ldr(dest, | |
| Address(dest, | |
| target::ClassTable::allocation_tracing_state_table_offset())); | |
| AddImmediate(dest, | |
| target::ClassTable::AllocationTracingStateSlotOffsetFor(cid)); | |
| } | |
| #endif // !PRODUCT | |
| void Assembler::TryAllocateObject(intptr_t cid, | |
| intptr_t instance_size, | |
| Label* failure, | |
| JumpDistance distance, | |
| Register instance_reg, | |
| Register temp_reg) { | |
| ASSERT(failure != nullptr); | |
| ASSERT(instance_reg != kNoRegister); | |
| ASSERT(instance_reg != temp_reg); | |
| ASSERT(instance_reg != IP); | |
| ASSERT(temp_reg != kNoRegister); | |
| ASSERT(temp_reg != IP); | |
| ASSERT(instance_size != 0); | |
| ASSERT(Utils::IsAligned(instance_size, | |
| target::ObjectAlignment::kObjectAlignment)); | |
| if (UseInlineAllocation() && | |
| target::Heap::IsAllocatableInNewSpace(instance_size)) { | |
| ldr(instance_reg, Address(THR, target::Thread::top_offset())); | |
| // TODO(koda): Protect against unsigned overflow here. | |
| AddImmediate(instance_reg, instance_size); | |
| // instance_reg: potential top (next object start). | |
| ldr(IP, Address(THR, target::Thread::end_offset())); | |
| cmp(IP, Operand(instance_reg)); | |
| // fail if heap end unsigned less than or equal to new heap top. | |
| b(failure, LS); | |
| // If this allocation is traced, program will jump to failure path | |
| // (i.e. the allocation stub) which will allocate the object and trace the | |
| // allocation call site. | |
| NOT_IN_PRODUCT(LoadAllocationTracingStateAddress(temp_reg, cid)); | |
| NOT_IN_PRODUCT(MaybeTraceAllocation(temp_reg, failure)); | |
| // Successfully allocated the object, now update top to point to | |
| // next object start and store the class in the class field of object. | |
| str(instance_reg, Address(THR, target::Thread::top_offset())); | |
| // Move instance_reg back to the start of the object and tag it. | |
| AddImmediate(instance_reg, -instance_size + kHeapObjectTag); | |
| const uword tags = target::MakeTagWordForNewSpaceObject(cid, instance_size); | |
| LoadImmediate(temp_reg, tags); | |
| InitializeHeader(temp_reg, instance_reg); | |
| } else { | |
| b(failure); | |
| } | |
| } | |
| void Assembler::TryAllocateArray(intptr_t cid, | |
| intptr_t instance_size, | |
| Label* failure, | |
| Register instance, | |
| Register end_address, | |
| Register temp1, | |
| Register temp2) { | |
| if (UseInlineAllocation() && | |
| target::Heap::IsAllocatableInNewSpace(instance_size)) { | |
| NOT_IN_PRODUCT(LoadAllocationTracingStateAddress(temp1, cid)); | |
| // Potential new object start. | |
| ldr(instance, Address(THR, target::Thread::top_offset())); | |
| AddImmediateSetFlags(end_address, instance, instance_size); | |
| b(failure, CS); // Branch if unsigned overflow. | |
| // Check if the allocation fits into the remaining space. | |
| // instance: potential new object start. | |
| // end_address: potential next object start. | |
| ldr(temp2, Address(THR, target::Thread::end_offset())); | |
| cmp(end_address, Operand(temp2)); | |
| b(failure, CS); | |
| // If this allocation is traced, program will jump to failure path | |
| // (i.e. the allocation stub) which will allocate the object and trace the | |
| // allocation call site. | |
| NOT_IN_PRODUCT(MaybeTraceAllocation(temp1, failure)); | |
| // Successfully allocated the object(s), now update top to point to | |
| // next object start and initialize the object. | |
| str(end_address, Address(THR, target::Thread::top_offset())); | |
| add(instance, instance, Operand(kHeapObjectTag)); | |
| // Initialize the tags. | |
| // instance: new object start as a tagged pointer. | |
| const uword tags = target::MakeTagWordForNewSpaceObject(cid, instance_size); | |
| LoadImmediate(temp2, tags); | |
| InitializeHeader(temp2, instance); | |
| } else { | |
| b(failure); | |
| } | |
| } | |
| void Assembler::CopyMemoryWords(Register src, | |
| Register dst, | |
| Register size, | |
| Register temp) { | |
| Label loop, done; | |
| __ cmp(size, Operand(0)); | |
| __ b(&done, EQUAL); | |
| __ Bind(&loop); | |
| __ ldr(temp, Address(src, target::kWordSize, Address::PostIndex)); | |
| __ str(temp, Address(dst, target::kWordSize, Address::PostIndex)); | |
| __ subs(size, size, Operand(target::kWordSize)); | |
| __ b(&loop, NOT_ZERO); | |
| __ Bind(&done); | |
| } | |
| void Assembler::GenerateUnRelocatedPcRelativeCall(Condition cond, | |
| intptr_t offset_into_target) { | |
| // Emit "blr.cond <offset>". | |
| EmitType5(cond, 0x686868, /*link=*/true); | |
| PcRelativeCallPattern pattern(buffer_.contents() + buffer_.Size() - | |
| PcRelativeCallPattern::kLengthInBytes); | |
| pattern.set_distance(offset_into_target); | |
| } | |
| void Assembler::GenerateUnRelocatedPcRelativeTailCall( | |
| Condition cond, | |
| intptr_t offset_into_target) { | |
| // Emit "b <offset>". | |
| EmitType5(cond, 0x686868, /*link=*/false); | |
| PcRelativeTailCallPattern pattern(buffer_.contents() + buffer_.Size() - | |
| PcRelativeTailCallPattern::kLengthInBytes); | |
| pattern.set_distance(offset_into_target); | |
| } | |
| bool Assembler::AddressCanHoldConstantIndex(const Object& constant, | |
| bool is_load, | |
| bool is_external, | |
| intptr_t cid, | |
| intptr_t index_scale, | |
| bool* needs_base) { | |
| ASSERT(needs_base != nullptr); | |
| auto const rep = RepresentationUtils::RepresentationOfArrayElement(cid); | |
| if ((rep == kUnboxedInt32x4) || (rep == kUnboxedFloat32x4) || | |
| (rep == kUnboxedFloat64x2)) { | |
| // We are using vldmd/vstmd which do not support offset. | |
| return false; | |
| } | |
| if (!IsSafeSmi(constant)) return false; | |
| const int64_t index = target::SmiValue(constant); | |
| const intptr_t offset_base = | |
| (is_external ? 0 | |
| : (target::Instance::DataOffsetFor(cid) - kHeapObjectTag)); | |
| const int64_t offset = index * index_scale + offset_base; | |
| if (!Utils::IsInt(32, offset)) return false; | |
| if (Address::CanHoldImmediateOffset(is_load, cid, offset)) { | |
| *needs_base = false; | |
| return true; | |
| } | |
| if (Address::CanHoldImmediateOffset(is_load, cid, offset - offset_base)) { | |
| *needs_base = true; | |
| return true; | |
| } | |
| return false; | |
| } | |
| Address Assembler::ElementAddressForIntIndex(bool is_load, | |
| bool is_external, | |
| intptr_t cid, | |
| intptr_t index_scale, | |
| Register array, | |
| intptr_t index, | |
| Register temp) { | |
| const int64_t offset_base = | |
| (is_external ? 0 | |
| : (target::Instance::DataOffsetFor(cid) - kHeapObjectTag)); | |
| const int64_t offset = | |
| offset_base + static_cast<int64_t>(index) * index_scale; | |
| ASSERT(Utils::IsInt(32, offset)); | |
| if (Address::CanHoldImmediateOffset(is_load, cid, offset)) { | |
| return Address(array, static_cast<int32_t>(offset)); | |
| } else { | |
| ASSERT(Address::CanHoldImmediateOffset(is_load, cid, offset - offset_base)); | |
| AddImmediate(temp, array, static_cast<int32_t>(offset_base)); | |
| return Address(temp, static_cast<int32_t>(offset - offset_base)); | |
| } | |
| } | |
| void Assembler::LoadElementAddressForIntIndex(Register address, | |
| bool is_load, | |
| bool is_external, | |
| intptr_t cid, | |
| intptr_t index_scale, | |
| Register array, | |
| intptr_t index) { | |
| const int64_t offset_base = | |
| (is_external ? 0 | |
| : (target::Instance::DataOffsetFor(cid) - kHeapObjectTag)); | |
| const int64_t offset = | |
| offset_base + static_cast<int64_t>(index) * index_scale; | |
| ASSERT(Utils::IsInt(32, offset)); | |
| AddImmediate(address, array, offset); | |
| } | |
| Address Assembler::ElementAddressForRegIndex(bool is_load, | |
| bool is_external, | |
| intptr_t cid, | |
| intptr_t index_scale, | |
| bool index_unboxed, | |
| Register array, | |
| Register index) { | |
| // If unboxed, index is expected smi-tagged, (i.e, LSL 1) for all arrays. | |
| const intptr_t boxing_shift = index_unboxed ? 0 : -kSmiTagShift; | |
| const intptr_t shift = Utils::ShiftForPowerOfTwo(index_scale) + boxing_shift; | |
| int32_t offset = | |
| is_external ? 0 : (target::Instance::DataOffsetFor(cid) - kHeapObjectTag); | |
| const OperandSize size = Address::OperandSizeFor(cid); | |
| ASSERT(array != IP); | |
| ASSERT(index != IP); | |
| const Register base = is_load ? IP : index; | |
| if ((offset != 0) || (is_load && (size == kByte || size == kUnsignedByte)) || | |
| (size == kTwoBytes) || (size == kUnsignedTwoBytes) || (size == kSWord) || | |
| (size == kDWord) || (size == kRegList)) { | |
| if (shift < 0) { | |
| ASSERT(shift == -1); | |
| add(base, array, Operand(index, ASR, 1)); | |
| } else { | |
| add(base, array, Operand(index, LSL, shift)); | |
| } | |
| } else { | |
| if (shift < 0) { | |
| ASSERT(shift == -1); | |
| return Address(array, index, ASR, 1); | |
| } else { | |
| return Address(array, index, LSL, shift); | |
| } | |
| } | |
| int32_t offset_mask = 0; | |
| if ((is_load && !Address::CanHoldLoadOffset(size, offset, &offset_mask)) || | |
| (!is_load && !Address::CanHoldStoreOffset(size, offset, &offset_mask))) { | |
| AddImmediate(base, offset & ~offset_mask); | |
| offset = offset & offset_mask; | |
| } | |
| return Address(base, offset); | |
| } | |
| void Assembler::LoadElementAddressForRegIndex(Register address, | |
| bool is_load, | |
| bool is_external, | |
| intptr_t cid, | |
| intptr_t index_scale, | |
| bool index_unboxed, | |
| Register array, | |
| Register index) { | |
| // If unboxed, index is expected smi-tagged, (i.e, LSL 1) for all arrays. | |
| const intptr_t boxing_shift = index_unboxed ? 0 : -kSmiTagShift; | |
| const intptr_t shift = Utils::ShiftForPowerOfTwo(index_scale) + boxing_shift; | |
| int32_t offset = | |
| is_external ? 0 : (target::Instance::DataOffsetFor(cid) - kHeapObjectTag); | |
| if (shift < 0) { | |
| ASSERT(shift == -1); | |
| add(address, array, Operand(index, ASR, 1)); | |
| } else { | |
| add(address, array, Operand(index, LSL, shift)); | |
| } | |
| if (offset != 0) { | |
| AddImmediate(address, offset); | |
| } | |
| } | |
| void Assembler::LoadStaticFieldAddress(Register address, | |
| Register field, | |
| Register scratch, | |
| bool is_shared) { | |
| LoadFieldFromOffset(scratch, field, | |
| target::Field::host_offset_or_field_id_offset()); | |
| const intptr_t field_table_offset = | |
| is_shared ? compiler::target::Thread::shared_field_table_values_offset() | |
| : compiler::target::Thread::field_table_values_offset(); | |
| LoadMemoryValue(address, THR, static_cast<int32_t>(field_table_offset)); | |
| add(address, address, | |
| Operand(scratch, LSL, target::kWordSizeLog2 - kSmiTagShift)); | |
| } | |
| void Assembler::LoadFieldAddressForRegOffset(Register address, | |
| Register instance, | |
| Register offset_in_words_as_smi) { | |
| add(address, instance, | |
| Operand(offset_in_words_as_smi, LSL, | |
| target::kWordSizeLog2 - kSmiTagShift)); | |
| AddImmediate(address, -kHeapObjectTag); | |
| } | |
| void Assembler::LoadHalfWordUnaligned(Register dst, | |
| Register addr, | |
| Register tmp) { | |
| ASSERT(dst != addr); | |
| ldrb(dst, Address(addr, 0)); | |
| ldrsb(tmp, Address(addr, 1)); | |
| orr(dst, dst, Operand(tmp, LSL, 8)); | |
| } | |
| void Assembler::LoadHalfWordUnsignedUnaligned(Register dst, | |
| Register addr, | |
| Register tmp) { | |
| ASSERT(dst != addr); | |
| ldrb(dst, Address(addr, 0)); | |
| ldrb(tmp, Address(addr, 1)); | |
| orr(dst, dst, Operand(tmp, LSL, 8)); | |
| } | |
| void Assembler::StoreHalfWordUnaligned(Register src, | |
| Register addr, | |
| Register tmp) { | |
| strb(src, Address(addr, 0)); | |
| Lsr(tmp, src, Operand(8)); | |
| strb(tmp, Address(addr, 1)); | |
| } | |
| void Assembler::LoadWordUnaligned(Register dst, Register addr, Register tmp) { | |
| ASSERT(dst != addr); | |
| ldrb(dst, Address(addr, 0)); | |
| ldrb(tmp, Address(addr, 1)); | |
| orr(dst, dst, Operand(tmp, LSL, 8)); | |
| ldrb(tmp, Address(addr, 2)); | |
| orr(dst, dst, Operand(tmp, LSL, 16)); | |
| ldrb(tmp, Address(addr, 3)); | |
| orr(dst, dst, Operand(tmp, LSL, 24)); | |
| } | |
| void Assembler::StoreWordUnaligned(Register src, Register addr, Register tmp) { | |
| strb(src, Address(addr, 0)); | |
| Lsr(tmp, src, Operand(8)); | |
| strb(tmp, Address(addr, 1)); | |
| Lsr(tmp, src, Operand(16)); | |
| strb(tmp, Address(addr, 2)); | |
| Lsr(tmp, src, Operand(24)); | |
| strb(tmp, Address(addr, 3)); | |
| } | |
| void Assembler::RangeCheck(Register value, | |
| Register temp, | |
| intptr_t low, | |
| intptr_t high, | |
| RangeCheckCondition condition, | |
| Label* target) { | |
| auto cc = condition == kIfInRange ? LS : HI; | |
| Register to_check = temp != kNoRegister ? temp : value; | |
| AddImmediate(to_check, value, -low); | |
| CompareImmediate(to_check, high - low); | |
| b(target, cc); | |
| } | |
| } // namespace compiler | |
| } // namespace dart | |
| #endif // defined(TARGET_ARCH_ARM) |