| // Copyright (c) 2014, 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_ARM64) |
| |
| #define SHOULD_NOT_INCLUDE_RUNTIME |
| |
| #include "vm/compiler/assembler/assembler.h" |
| #include "vm/compiler/backend/locations.h" |
| #include "vm/cpu.h" |
| #include "vm/instructions.h" |
| #include "vm/simulator.h" |
| |
| namespace dart { |
| |
| DECLARE_FLAG(bool, check_code_pointer); |
| DECLARE_FLAG(bool, precompiled_mode); |
| |
| DEFINE_FLAG(bool, use_far_branches, false, "Always use far branches"); |
| |
| // For use by LR related macros (e.g. CLOBBERS_LR). |
| #define __ this-> |
| |
| namespace compiler { |
| |
| Assembler::Assembler(ObjectPoolBuilder* object_pool_builder, |
| bool use_far_branches) |
| : AssemblerBase(object_pool_builder), |
| use_far_branches_(use_far_branches), |
| constant_pool_allowed_(false) { |
| generate_invoke_write_barrier_wrapper_ = [&](Register reg) { |
| Call(Address(THR, |
| target::Thread::write_barrier_wrappers_thread_offset(reg))); |
| }; |
| generate_invoke_array_write_barrier_ = [&]() { |
| Call( |
| Address(THR, target::Thread::array_write_barrier_entry_point_offset())); |
| }; |
| } |
| |
| void Assembler::Emit(int32_t value) { |
| AssemblerBuffer::EnsureCapacity ensured(&buffer_); |
| buffer_.Emit<int32_t>(value); |
| } |
| |
| void Assembler::Emit64(int64_t value) { |
| AssemblerBuffer::EnsureCapacity ensured(&buffer_); |
| buffer_.Emit<int64_t>(value); |
| } |
| |
| int32_t Assembler::BindImm19Branch(int64_t position, int64_t dest) { |
| if (use_far_branches() && !CanEncodeImm19BranchOffset(dest)) { |
| // Far branches are enabled, and we can't encode the branch offset in |
| // 19 bits. |
| |
| // Grab the guarding branch instruction. |
| const int32_t guard_branch = |
| buffer_.Load<int32_t>(position + 0 * Instr::kInstrSize); |
| |
| // Grab the far branch instruction. |
| const int32_t far_branch = |
| buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); |
| const Condition c = DecodeImm19BranchCondition(guard_branch); |
| |
| // Grab the link to the next branch. |
| const int32_t next = DecodeImm26BranchOffset(far_branch); |
| |
| // dest is the offset is from the guarding branch instruction. |
| // Correct it to be from the following instruction. |
| const int64_t offset = dest - Instr::kInstrSize; |
| |
| // Encode the branch. |
| const int32_t encoded_branch = EncodeImm26BranchOffset(offset, far_branch); |
| |
| // If the guard branch is conditioned on NV, replace it with a nop. |
| if (c == NV) { |
| buffer_.Store<int32_t>(position + 0 * Instr::kInstrSize, |
| Instr::kNopInstruction); |
| } |
| |
| // Write the far branch into the buffer and link to the next branch. |
| buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, encoded_branch); |
| return next; |
| } else if (use_far_branches() && CanEncodeImm19BranchOffset(dest)) { |
| // We assembled a far branch, but we don't need it. Replace it with a near |
| // branch. |
| |
| // Grab the guarding branch instruction. |
| const int32_t guard_branch = |
| buffer_.Load<int32_t>(position + 0 * Instr::kInstrSize); |
| |
| // Grab the far branch instruction. |
| const int32_t far_branch = |
| buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); |
| |
| // Grab the link to the next branch. |
| const int32_t next = DecodeImm26BranchOffset(far_branch); |
| |
| // Re-target the guarding branch and flip the conditional sense. |
| int32_t encoded_guard_branch = EncodeImm19BranchOffset(dest, guard_branch); |
| const Condition c = DecodeImm19BranchCondition(encoded_guard_branch); |
| encoded_guard_branch = |
| EncodeImm19BranchCondition(InvertCondition(c), encoded_guard_branch); |
| |
| // Write back the re-encoded instructions. The far branch becomes a nop. |
| buffer_.Store<int32_t>(position + 0 * Instr::kInstrSize, |
| encoded_guard_branch); |
| buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, |
| Instr::kNopInstruction); |
| return next; |
| } else { |
| const int32_t next = buffer_.Load<int32_t>(position); |
| const int32_t encoded = EncodeImm19BranchOffset(dest, next); |
| buffer_.Store<int32_t>(position, encoded); |
| return DecodeImm19BranchOffset(next); |
| } |
| } |
| |
| int32_t Assembler::BindImm14Branch(int64_t position, int64_t dest) { |
| if (use_far_branches() && !CanEncodeImm14BranchOffset(dest)) { |
| // Far branches are enabled, and we can't encode the branch offset in |
| // 14 bits. |
| |
| // Grab the guarding branch instruction. |
| const int32_t guard_branch = |
| buffer_.Load<int32_t>(position + 0 * Instr::kInstrSize); |
| |
| // Grab the far branch instruction. |
| const int32_t far_branch = |
| buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); |
| const Condition c = DecodeImm14BranchCondition(guard_branch); |
| |
| // Grab the link to the next branch. |
| const int32_t next = DecodeImm26BranchOffset(far_branch); |
| |
| // dest is the offset is from the guarding branch instruction. |
| // Correct it to be from the following instruction. |
| const int64_t offset = dest - Instr::kInstrSize; |
| |
| // Encode the branch. |
| const int32_t encoded_branch = EncodeImm26BranchOffset(offset, far_branch); |
| |
| // If the guard branch is conditioned on NV, replace it with a nop. |
| if (c == NV) { |
| buffer_.Store<int32_t>(position + 0 * Instr::kInstrSize, |
| Instr::kNopInstruction); |
| } |
| |
| // Write the far branch into the buffer and link to the next branch. |
| buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, encoded_branch); |
| return next; |
| } else if (use_far_branches() && CanEncodeImm14BranchOffset(dest)) { |
| // We assembled a far branch, but we don't need it. Replace it with a near |
| // branch. |
| |
| // Grab the guarding branch instruction. |
| const int32_t guard_branch = |
| buffer_.Load<int32_t>(position + 0 * Instr::kInstrSize); |
| |
| // Grab the far branch instruction. |
| const int32_t far_branch = |
| buffer_.Load<int32_t>(position + 1 * Instr::kInstrSize); |
| |
| // Grab the link to the next branch. |
| const int32_t next = DecodeImm26BranchOffset(far_branch); |
| |
| // Re-target the guarding branch and flip the conditional sense. |
| int32_t encoded_guard_branch = EncodeImm14BranchOffset(dest, guard_branch); |
| const Condition c = DecodeImm14BranchCondition(encoded_guard_branch); |
| encoded_guard_branch = |
| EncodeImm14BranchCondition(InvertCondition(c), encoded_guard_branch); |
| |
| // Write back the re-encoded instructions. The far branch becomes a nop. |
| buffer_.Store<int32_t>(position + 0 * Instr::kInstrSize, |
| encoded_guard_branch); |
| buffer_.Store<int32_t>(position + 1 * Instr::kInstrSize, |
| Instr::kNopInstruction); |
| return next; |
| } else { |
| const int32_t next = buffer_.Load<int32_t>(position); |
| const int32_t encoded = EncodeImm14BranchOffset(dest, next); |
| buffer_.Store<int32_t>(position, encoded); |
| return DecodeImm14BranchOffset(next); |
| } |
| } |
| |
| void Assembler::ExtendValue(Register rd, Register rn, OperandSize sz) { |
| switch (sz) { |
| case kEightBytes: |
| if (rd == rn) return; // No operation needed. |
| return mov(rd, rn); |
| case kUnsignedFourBytes: |
| return uxtw(rd, rn); |
| case kFourBytes: |
| return sxtw(rd, rn); |
| case kUnsignedTwoBytes: |
| return uxth(rd, rn); |
| case kTwoBytes: |
| return sxth(rd, rn); |
| case kUnsignedByte: |
| return uxtb(rd, rn); |
| case kByte: |
| return sxtb(rd, rn); |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| } |
| |
| // Equivalent to left rotate of kSmiTagSize. |
| static constexpr intptr_t kBFMTagRotate = kBitsPerInt64 - kSmiTagSize; |
| |
| void Assembler::ExtendAndSmiTagValue(Register rd, Register rn, OperandSize sz) { |
| switch (sz) { |
| case kEightBytes: |
| return sbfm(rd, rn, kBFMTagRotate, target::kSmiBits + 1); |
| case kUnsignedFourBytes: |
| return ubfm(rd, rn, kBFMTagRotate, kBitsPerInt32 - 1); |
| case kFourBytes: |
| return sbfm(rd, rn, kBFMTagRotate, kBitsPerInt32 - 1); |
| case kUnsignedTwoBytes: |
| return ubfm(rd, rn, kBFMTagRotate, kBitsPerInt16 - 1); |
| case kTwoBytes: |
| return sbfm(rd, rn, kBFMTagRotate, kBitsPerInt16 - 1); |
| case kUnsignedByte: |
| return ubfm(rd, rn, kBFMTagRotate, kBitsPerInt8 - 1); |
| case kByte: |
| return sbfm(rd, rn, kBFMTagRotate, kBitsPerInt8 - 1); |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| } |
| |
| void Assembler::Bind(Label* label) { |
| ASSERT(!label->IsBound()); |
| const intptr_t bound_pc = buffer_.Size(); |
| |
| while (label->IsLinked()) { |
| const int64_t position = label->Position(); |
| const int64_t dest = bound_pc - position; |
| if (IsTestAndBranch(buffer_.Load<int32_t>(position))) { |
| label->position_ = BindImm14Branch(position, dest); |
| } else { |
| label->position_ = BindImm19Branch(position, dest); |
| } |
| } |
| label->BindTo(bound_pc, lr_state()); |
| } |
| |
| #if defined(USING_THREAD_SANITIZER) |
| void Assembler::TsanLoadAcquire(Register addr) { |
| EnterCallRuntimeFrame(/*frame_size=*/0, /*is_leaf=*/true); |
| ASSERT(kTsanLoadAcquireRuntimeEntry.is_leaf()); |
| CallRuntime(kTsanLoadAcquireRuntimeEntry, /*argument_count=*/1); |
| LeaveCallRuntimeFrame(/*is_leaf=*/true); |
| } |
| |
| void Assembler::TsanStoreRelease(Register addr) { |
| EnterCallRuntimeFrame(/*frame_size=*/0, /*is_leaf=*/true); |
| ASSERT(kTsanStoreReleaseRuntimeEntry.is_leaf()); |
| CallRuntime(kTsanStoreReleaseRuntimeEntry, /*argument_count=*/1); |
| LeaveCallRuntimeFrame(/*is_leaf=*/true); |
| } |
| #endif |
| |
| static int CountLeadingZeros(uint64_t value, int width) { |
| if (width == 64) return Utils::CountLeadingZeros64(value); |
| if (width == 32) return Utils::CountLeadingZeros32(value); |
| UNREACHABLE(); |
| return 0; |
| } |
| |
| static int CountOneBits(uint64_t value, int width) { |
| // Mask out unused bits to ensure that they are not counted. |
| value &= (0xffffffffffffffffULL >> (64 - width)); |
| |
| value = ((value >> 1) & 0x5555555555555555) + (value & 0x5555555555555555); |
| value = ((value >> 2) & 0x3333333333333333) + (value & 0x3333333333333333); |
| value = ((value >> 4) & 0x0f0f0f0f0f0f0f0f) + (value & 0x0f0f0f0f0f0f0f0f); |
| value = ((value >> 8) & 0x00ff00ff00ff00ff) + (value & 0x00ff00ff00ff00ff); |
| value = ((value >> 16) & 0x0000ffff0000ffff) + (value & 0x0000ffff0000ffff); |
| value = ((value >> 32) & 0x00000000ffffffff) + (value & 0x00000000ffffffff); |
| |
| return value; |
| } |
| |
| // Test if a given value can be encoded in the immediate field of a logical |
| // instruction. |
| // If it can be encoded, the function returns true, and values pointed to by n, |
| // imm_s and imm_r are updated with immediates encoded in the format required |
| // by the corresponding fields in the logical instruction. |
| // If it can't be encoded, the function returns false, and the operand is |
| // undefined. |
| bool Operand::IsImmLogical(uint64_t value, uint8_t width, Operand* imm_op) { |
| ASSERT(imm_op != NULL); |
| ASSERT((width == kWRegSizeInBits) || (width == kXRegSizeInBits)); |
| if (width == kWRegSizeInBits) { |
| value &= 0xffffffffUL; |
| } |
| uint8_t n = 0; |
| uint8_t imm_s = 0; |
| uint8_t imm_r = 0; |
| |
| // Logical immediates are encoded using parameters n, imm_s and imm_r using |
| // the following table: |
| // |
| // N imms immr size S R |
| // 1 ssssss rrrrrr 64 UInt(ssssss) UInt(rrrrrr) |
| // 0 0sssss xrrrrr 32 UInt(sssss) UInt(rrrrr) |
| // 0 10ssss xxrrrr 16 UInt(ssss) UInt(rrrr) |
| // 0 110sss xxxrrr 8 UInt(sss) UInt(rrr) |
| // 0 1110ss xxxxrr 4 UInt(ss) UInt(rr) |
| // 0 11110s xxxxxr 2 UInt(s) UInt(r) |
| // (s bits must not be all set) |
| // |
| // A pattern is constructed of size bits, where the least significant S+1 |
| // bits are set. The pattern is rotated right by R, and repeated across a |
| // 32 or 64-bit value, depending on destination register width. |
| // |
| // To test if an arbitrary immediate can be encoded using this scheme, an |
| // iterative algorithm is used. |
| |
| // 1. If the value has all set or all clear bits, it can't be encoded. |
| if ((value == 0) || (value == 0xffffffffffffffffULL) || |
| ((width == kWRegSizeInBits) && (value == 0xffffffff))) { |
| return false; |
| } |
| |
| int lead_zero = CountLeadingZeros(value, width); |
| int lead_one = CountLeadingZeros(~value, width); |
| int trail_zero = Utils::CountTrailingZerosWord(value); |
| int trail_one = Utils::CountTrailingZerosWord(~value); |
| int set_bits = CountOneBits(value, width); |
| |
| // The fixed bits in the immediate s field. |
| // If width == 64 (X reg), start at 0xFFFFFF80. |
| // If width == 32 (W reg), start at 0xFFFFFFC0, as the iteration for 64-bit |
| // widths won't be executed. |
| int imm_s_fixed = (width == kXRegSizeInBits) ? -128 : -64; |
| int imm_s_mask = 0x3F; |
| |
| for (;;) { |
| // 2. If the value is two bits wide, it can be encoded. |
| if (width == 2) { |
| n = 0; |
| imm_s = 0x3C; |
| imm_r = (value & 3) - 1; |
| *imm_op = Operand(n, imm_s, imm_r); |
| return true; |
| } |
| |
| n = (width == 64) ? 1 : 0; |
| imm_s = ((imm_s_fixed | (set_bits - 1)) & imm_s_mask); |
| if ((lead_zero + set_bits) == width) { |
| imm_r = 0; |
| } else { |
| imm_r = (lead_zero > 0) ? (width - trail_zero) : lead_one; |
| } |
| |
| // 3. If the sum of leading zeros, trailing zeros and set bits is equal to |
| // the bit width of the value, it can be encoded. |
| if (lead_zero + trail_zero + set_bits == width) { |
| *imm_op = Operand(n, imm_s, imm_r); |
| return true; |
| } |
| |
| // 4. If the sum of leading ones, trailing ones and unset bits in the |
| // value is equal to the bit width of the value, it can be encoded. |
| if (lead_one + trail_one + (width - set_bits) == width) { |
| *imm_op = Operand(n, imm_s, imm_r); |
| return true; |
| } |
| |
| // 5. If the most-significant half of the bitwise value is equal to the |
| // least-significant half, return to step 2 using the least-significant |
| // half of the value. |
| uint64_t mask = (1ULL << (width >> 1)) - 1; |
| if ((value & mask) == ((value >> (width >> 1)) & mask)) { |
| width >>= 1; |
| set_bits >>= 1; |
| imm_s_fixed >>= 1; |
| continue; |
| } |
| |
| // 6. Otherwise, the value can't be encoded. |
| return false; |
| } |
| } |
| |
| void Assembler::LoadPoolPointer(Register pp) { |
| CheckCodePointer(); |
| ldr(pp, FieldAddress(CODE_REG, target::Code::object_pool_offset())); |
| |
| // When in the PP register, the pool pointer is untagged. When we |
| // push it on the stack with TagAndPushPP it is tagged again. PopAndUntagPP |
| // then untags when restoring from the stack. This will make loading from the |
| // object pool only one instruction for the first 4096 entries. Otherwise, |
| // because the offset wouldn't be aligned, it would be only one instruction |
| // for the first 64 entries. |
| sub(pp, pp, Operand(kHeapObjectTag)); |
| set_constant_pool_allowed(pp == PP); |
| } |
| |
| void Assembler::LoadWordFromPoolIndex(Register dst, |
| intptr_t index, |
| Register pp) { |
| ASSERT((pp != PP) || constant_pool_allowed()); |
| ASSERT(dst != pp); |
| Operand op; |
| // PP is _un_tagged on ARM64. |
| const uint32_t offset = target::ObjectPool::element_offset(index); |
| const uint32_t upper20 = offset & 0xfffff000; |
| if (Address::CanHoldOffset(offset)) { |
| ldr(dst, Address(pp, offset)); |
| } else if (Operand::CanHold(upper20, kXRegSizeInBits, &op) == |
| Operand::Immediate) { |
| const uint32_t lower12 = offset & 0x00000fff; |
| ASSERT(Address::CanHoldOffset(lower12)); |
| add(dst, pp, op); |
| ldr(dst, Address(dst, lower12)); |
| } else { |
| const uint16_t offset_low = Utils::Low16Bits(offset); |
| const uint16_t offset_high = Utils::High16Bits(offset); |
| movz(dst, Immediate(offset_low), 0); |
| if (offset_high != 0) { |
| movk(dst, Immediate(offset_high), 1); |
| } |
| ldr(dst, Address(pp, dst)); |
| } |
| } |
| |
| void Assembler::LoadWordFromPoolIndexFixed(Register dst, intptr_t index) { |
| ASSERT(constant_pool_allowed()); |
| ASSERT(dst != PP); |
| Operand op; |
| // PP is _un_tagged on ARM64. |
| const uint32_t offset = target::ObjectPool::element_offset(index); |
| const uint32_t upper20 = offset & 0xfffff000; |
| const uint32_t lower12 = offset & 0x00000fff; |
| const Operand::OperandType ot = |
| Operand::CanHold(upper20, kXRegSizeInBits, &op); |
| ASSERT(ot == Operand::Immediate); |
| ASSERT(Address::CanHoldOffset(lower12)); |
| add(dst, PP, op); |
| ldr(dst, Address(dst, lower12)); |
| } |
| |
| void Assembler::LoadDoubleWordFromPoolIndex(Register lower, |
| Register upper, |
| intptr_t index) { |
| // This implementation needs to be kept in sync with |
| // [InstructionPattern::DecodeLoadDoubleWordFromPool]. |
| ASSERT(constant_pool_allowed()); |
| ASSERT(lower != PP && upper != PP); |
| |
| Operand op; |
| // PP is _un_tagged on ARM64. |
| const uint32_t offset = target::ObjectPool::element_offset(index); |
| ASSERT(offset < (1 << 24)); |
| const uint32_t upper20 = offset & 0xfffff000; |
| const uint32_t lower12 = offset & 0x00000fff; |
| if (Address::CanHoldOffset(offset, Address::PairOffset)) { |
| ldp(lower, upper, Address(PP, offset, Address::PairOffset)); |
| } else if (Operand::CanHold(offset, kXRegSizeInBits, &op) == |
| Operand::Immediate) { |
| add(TMP, PP, op); |
| ldp(lower, upper, Address(TMP, 0, Address::PairOffset)); |
| } else if (Operand::CanHold(upper20, kXRegSizeInBits, &op) == |
| Operand::Immediate && |
| Address::CanHoldOffset(lower12, Address::PairOffset)) { |
| add(TMP, PP, op); |
| ldp(lower, upper, Address(TMP, lower12, Address::PairOffset)); |
| } else { |
| const uint32_t lower12 = offset & 0xfff; |
| const uint32_t higher12 = offset & 0xfff000; |
| |
| Operand op_high, op_low; |
| bool ok = Operand::CanHold(higher12, kXRegSizeInBits, &op_high) == |
| Operand::Immediate && |
| Operand::CanHold(lower12, kXRegSizeInBits, &op_low) == |
| Operand::Immediate; |
| RELEASE_ASSERT(ok); |
| |
| add(TMP, PP, op_high); |
| add(TMP, TMP, op_low); |
| ldp(lower, upper, Address(TMP, 0, Address::PairOffset)); |
| } |
| } |
| |
| intptr_t Assembler::FindImmediate(int64_t imm) { |
| return object_pool_builder().FindImmediate(imm); |
| } |
| |
| bool Assembler::CanLoadFromObjectPool(const Object& object) const { |
| ASSERT(IsOriginalObject(object)); |
| if (!constant_pool_allowed()) { |
| return false; |
| } |
| |
| // TODO(zra, kmillikin): Also load other large immediates from the object |
| // pool |
| if (target::IsSmi(object)) { |
| // If the raw smi does not fit into a 32-bit signed int, then we'll keep |
| // the raw value in the object pool. |
| return !Utils::IsInt(32, target::ToRawSmi(object)); |
| } |
| ASSERT(IsNotTemporaryScopedHandle(object)); |
| ASSERT(IsInOldSpace(object)); |
| return true; |
| } |
| |
| void Assembler::LoadNativeEntry( |
| Register dst, |
| const ExternalLabel* label, |
| ObjectPoolBuilderEntry::Patchability patchable) { |
| const intptr_t index = |
| object_pool_builder().FindNativeFunction(label, patchable); |
| LoadWordFromPoolIndex(dst, index); |
| } |
| |
| void Assembler::LoadIsolate(Register dst) { |
| ldr(dst, Address(THR, target::Thread::isolate_offset())); |
| } |
| |
| void Assembler::LoadIsolateGroup(Register rd) { |
| ldr(rd, Address(THR, target::Thread::isolate_group_offset())); |
| } |
| |
| void Assembler::LoadObjectHelper(Register dst, |
| const Object& object, |
| bool is_unique) { |
| ASSERT(IsOriginalObject(object)); |
| // `is_unique == true` effectively means object has to be patchable. |
| // (even if the object is null) |
| if (!is_unique) { |
| if (IsSameObject(compiler::NullObject(), object)) { |
| mov(dst, NULL_REG); |
| return; |
| } |
| if (IsSameObject(CastHandle<Object>(compiler::TrueObject()), object)) { |
| AddImmediate(dst, NULL_REG, kTrueOffsetFromNull); |
| return; |
| } |
| if (IsSameObject(CastHandle<Object>(compiler::FalseObject()), object)) { |
| AddImmediate(dst, NULL_REG, kFalseOffsetFromNull); |
| return; |
| } |
| word offset = 0; |
| if (target::CanLoadFromThread(object, &offset)) { |
| ldr(dst, Address(THR, offset)); |
| return; |
| } |
| } |
| if (CanLoadFromObjectPool(object)) { |
| const intptr_t index = is_unique ? object_pool_builder().AddObject(object) |
| : object_pool_builder().FindObject(object); |
| LoadWordFromPoolIndex(dst, index); |
| return; |
| } |
| ASSERT(target::IsSmi(object)); |
| LoadImmediate(dst, target::ToRawSmi(object)); |
| } |
| |
| void Assembler::LoadObject(Register dst, const Object& object) { |
| LoadObjectHelper(dst, object, false); |
| } |
| |
| void Assembler::LoadUniqueObject(Register dst, const Object& object) { |
| LoadObjectHelper(dst, object, true); |
| } |
| |
| 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::CompareObject(Register reg, const Object& object) { |
| ASSERT(IsOriginalObject(object)); |
| word offset = 0; |
| if (IsSameObject(compiler::NullObject(), object)) { |
| CompareObjectRegisters(reg, NULL_REG); |
| } else if (target::CanLoadFromThread(object, &offset)) { |
| ldr(TMP, Address(THR, offset)); |
| CompareObjectRegisters(reg, TMP); |
| } else if (CanLoadFromObjectPool(object)) { |
| LoadObject(TMP, object); |
| CompareObjectRegisters(reg, TMP); |
| } else { |
| ASSERT(target::IsSmi(object)); |
| CompareImmediate(reg, target::ToRawSmi(object), kObjectBytes); |
| } |
| } |
| |
| void Assembler::LoadImmediate(Register reg, int64_t imm) { |
| // Is it 0? |
| if (imm == 0) { |
| movz(reg, Immediate(0), 0); |
| return; |
| } |
| |
| // Can we use one orri operation? |
| Operand op; |
| Operand::OperandType ot; |
| ot = Operand::CanHold(imm, kXRegSizeInBits, &op); |
| if (ot == Operand::BitfieldImm) { |
| orri(reg, ZR, Immediate(imm)); |
| return; |
| } |
| |
| // We may fall back on movz, movk, movn. |
| const uint32_t w0 = Utils::Low32Bits(imm); |
| const uint32_t w1 = Utils::High32Bits(imm); |
| const uint16_t h0 = Utils::Low16Bits(w0); |
| const uint16_t h1 = Utils::High16Bits(w0); |
| const uint16_t h2 = Utils::Low16Bits(w1); |
| const uint16_t h3 = Utils::High16Bits(w1); |
| |
| // Special case for w1 == 0xffffffff |
| if (w1 == 0xffffffff) { |
| if (h1 == 0xffff) { |
| movn(reg, Immediate(~h0), 0); |
| } else { |
| movn(reg, Immediate(~h1), 1); |
| movk(reg, Immediate(h0), 0); |
| } |
| return; |
| } |
| |
| // Special case for h3 == 0xffff |
| if (h3 == 0xffff) { |
| // We know h2 != 0xffff. |
| movn(reg, Immediate(~h2), 2); |
| if (h1 != 0xffff) { |
| movk(reg, Immediate(h1), 1); |
| } |
| if (h0 != 0xffff) { |
| movk(reg, Immediate(h0), 0); |
| } |
| return; |
| } |
| |
| // Use constant pool if allowed, unless we can load imm with 2 instructions. |
| if ((w1 != 0) && constant_pool_allowed()) { |
| const intptr_t index = FindImmediate(imm); |
| LoadWordFromPoolIndex(reg, index); |
| return; |
| } |
| |
| bool initialized = false; |
| if (h0 != 0) { |
| movz(reg, Immediate(h0), 0); |
| initialized = true; |
| } |
| if (h1 != 0) { |
| if (initialized) { |
| movk(reg, Immediate(h1), 1); |
| } else { |
| movz(reg, Immediate(h1), 1); |
| initialized = true; |
| } |
| } |
| if (h2 != 0) { |
| if (initialized) { |
| movk(reg, Immediate(h2), 2); |
| } else { |
| movz(reg, Immediate(h2), 2); |
| initialized = true; |
| } |
| } |
| if (h3 != 0) { |
| if (initialized) { |
| movk(reg, Immediate(h3), 3); |
| } else { |
| movz(reg, Immediate(h3), 3); |
| } |
| } |
| } |
| |
| void Assembler::LoadDImmediate(VRegister vd, double immd) { |
| if (!fmovdi(vd, immd)) { |
| int64_t imm = bit_cast<int64_t, double>(immd); |
| LoadImmediate(TMP, imm); |
| fmovdr(vd, TMP); |
| } |
| } |
| |
| void Assembler::Branch(const Code& target, |
| Register pp, |
| ObjectPoolBuilderEntry::Patchability patchable) { |
| const intptr_t index = |
| object_pool_builder().FindObject(ToObject(target), patchable); |
| LoadWordFromPoolIndex(CODE_REG, index, pp); |
| ldr(TMP, FieldAddress(CODE_REG, target::Code::entry_point_offset())); |
| br(TMP); |
| } |
| |
| void Assembler::BranchLink(const Code& target, |
| ObjectPoolBuilderEntry::Patchability patchable, |
| CodeEntryKind entry_kind) { |
| const intptr_t index = |
| object_pool_builder().FindObject(ToObject(target), patchable); |
| LoadWordFromPoolIndex(CODE_REG, index); |
| Call(FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind))); |
| } |
| |
| void Assembler::BranchLinkToRuntime() { |
| Call(Address(THR, target::Thread::call_to_runtime_entry_point_offset())); |
| } |
| |
| void Assembler::BranchLinkWithEquivalence(const Code& target, |
| const Object& equivalence, |
| CodeEntryKind entry_kind) { |
| const intptr_t index = |
| object_pool_builder().FindObject(ToObject(target), equivalence); |
| LoadWordFromPoolIndex(CODE_REG, index); |
| Call(FieldAddress(CODE_REG, target::Code::entry_point_offset(entry_kind))); |
| } |
| |
| void Assembler::AddImmediate(Register dest, |
| Register rn, |
| int64_t imm, |
| OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand op; |
| if (imm == 0) { |
| if (dest != rn) { |
| mov(dest, rn); |
| } |
| return; |
| } |
| if (Operand::CanHold(imm, width, &op) == Operand::Immediate) { |
| add(dest, rn, op, sz); |
| } else if (Operand::CanHold(-imm, width, &op) == Operand::Immediate) { |
| sub(dest, rn, op, sz); |
| } else { |
| // TODO(zra): Try adding top 12 bits, then bottom 12 bits. |
| ASSERT(rn != TMP2); |
| LoadImmediate(TMP2, imm); |
| add(dest, rn, Operand(TMP2), sz); |
| } |
| } |
| |
| void Assembler::AddImmediateSetFlags(Register dest, |
| Register rn, |
| int64_t imm, |
| OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand op; |
| if (Operand::CanHold(imm, width, &op) == Operand::Immediate) { |
| // Handles imm == kMinInt64. |
| adds(dest, rn, op, sz); |
| } else if (Operand::CanHold(-imm, width, &op) == Operand::Immediate) { |
| ASSERT(imm != kMinInt64); // Would cause erroneous overflow detection. |
| subs(dest, rn, op, sz); |
| } else { |
| // TODO(zra): Try adding top 12 bits, then bottom 12 bits. |
| ASSERT(rn != TMP2); |
| LoadImmediate(TMP2, imm); |
| adds(dest, rn, Operand(TMP2), sz); |
| } |
| } |
| |
| void Assembler::SubImmediateSetFlags(Register dest, |
| Register rn, |
| int64_t imm, |
| OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand op; |
| if (Operand::CanHold(imm, width, &op) == Operand::Immediate) { |
| // Handles imm == kMinInt64. |
| subs(dest, rn, op, sz); |
| } else if (Operand::CanHold(-imm, width, &op) == Operand::Immediate) { |
| ASSERT(imm != kMinInt64); // Would cause erroneous overflow detection. |
| adds(dest, rn, op, sz); |
| } else { |
| // TODO(zra): Try subtracting top 12 bits, then bottom 12 bits. |
| ASSERT(rn != TMP2); |
| LoadImmediate(TMP2, imm); |
| subs(dest, rn, Operand(TMP2), sz); |
| } |
| } |
| |
| void Assembler::AndImmediate(Register rd, |
| Register rn, |
| int64_t imm, |
| OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand imm_op; |
| if (Operand::IsImmLogical(imm, width, &imm_op)) { |
| andi(rd, rn, Immediate(imm), sz); |
| } else { |
| LoadImmediate(TMP, imm); |
| and_(rd, rn, Operand(TMP), sz); |
| } |
| } |
| |
| void Assembler::OrImmediate(Register rd, |
| Register rn, |
| int64_t imm, |
| OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand imm_op; |
| if (Operand::IsImmLogical(imm, width, &imm_op)) { |
| orri(rd, rn, Immediate(imm), sz); |
| } else { |
| LoadImmediate(TMP, imm); |
| orr(rd, rn, Operand(TMP), sz); |
| } |
| } |
| |
| void Assembler::XorImmediate(Register rd, |
| Register rn, |
| int64_t imm, |
| OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand imm_op; |
| if (Operand::IsImmLogical(imm, width, &imm_op)) { |
| eori(rd, rn, Immediate(imm), sz); |
| } else { |
| LoadImmediate(TMP, imm); |
| eor(rd, rn, Operand(TMP), sz); |
| } |
| } |
| |
| void Assembler::TestImmediate(Register rn, int64_t imm, OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand imm_op; |
| if (Operand::IsImmLogical(imm, width, &imm_op)) { |
| tsti(rn, Immediate(imm), sz); |
| } else { |
| LoadImmediate(TMP, imm); |
| tst(rn, Operand(TMP), sz); |
| } |
| } |
| |
| void Assembler::CompareImmediate(Register rn, int64_t imm, OperandSize sz) { |
| ASSERT(sz == kEightBytes || sz == kFourBytes); |
| int width = sz == kEightBytes ? kXRegSizeInBits : kWRegSizeInBits; |
| Operand op; |
| if (Operand::CanHold(imm, width, &op) == Operand::Immediate) { |
| cmp(rn, op, sz); |
| } else if (Operand::CanHold(-static_cast<uint64_t>(imm), width, &op) == |
| Operand::Immediate) { |
| cmn(rn, op, sz); |
| } else { |
| ASSERT(rn != TMP2); |
| LoadImmediate(TMP2, imm); |
| cmp(rn, Operand(TMP2), sz); |
| } |
| } |
| |
| void Assembler::LoadFromOffset(Register dest, |
| Register base, |
| int32_t offset, |
| OperandSize sz) { |
| if (Address::CanHoldOffset(offset, Address::Offset, sz)) { |
| LoadFromOffset(dest, Address(base, offset, Address::Offset, sz), sz); |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| LoadFromOffset(dest, Address(TMP2), sz); |
| } |
| } |
| |
| void Assembler::LoadSFromOffset(VRegister dest, Register base, int32_t offset) { |
| if (Address::CanHoldOffset(offset, Address::Offset, kSWord)) { |
| fldrs(dest, Address(base, offset, Address::Offset, kSWord)); |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| fldrs(dest, Address(TMP2)); |
| } |
| } |
| |
| void Assembler::LoadDFromOffset(VRegister dest, Register base, int32_t offset) { |
| if (Address::CanHoldOffset(offset, Address::Offset, kDWord)) { |
| fldrd(dest, Address(base, offset, Address::Offset, kDWord)); |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| fldrd(dest, Address(TMP2)); |
| } |
| } |
| |
| void Assembler::LoadQFromOffset(VRegister dest, Register base, int32_t offset) { |
| if (Address::CanHoldOffset(offset, Address::Offset, kQWord)) { |
| fldrq(dest, Address(base, offset, Address::Offset, kQWord)); |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| fldrq(dest, Address(TMP2)); |
| } |
| } |
| |
| void Assembler::StoreToOffset(Register src, |
| Register base, |
| int32_t offset, |
| OperandSize sz) { |
| ASSERT(base != TMP2); |
| if (Address::CanHoldOffset(offset, Address::Offset, sz)) { |
| StoreToOffset(src, Address(base, offset, Address::Offset, sz), sz); |
| } else { |
| ASSERT(src != TMP2); |
| AddImmediate(TMP2, base, offset); |
| StoreToOffset(src, Address(TMP2), sz); |
| } |
| } |
| |
| void Assembler::StoreSToOffset(VRegister src, Register base, int32_t offset) { |
| if (Address::CanHoldOffset(offset, Address::Offset, kSWord)) { |
| fstrs(src, Address(base, offset, Address::Offset, kSWord)); |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| fstrs(src, Address(TMP2)); |
| } |
| } |
| |
| void Assembler::StoreDToOffset(VRegister src, Register base, int32_t offset) { |
| if (Address::CanHoldOffset(offset, Address::Offset, kDWord)) { |
| fstrd(src, Address(base, offset, Address::Offset, kDWord)); |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| fstrd(src, Address(TMP2)); |
| } |
| } |
| |
| void Assembler::StoreQToOffset(VRegister src, Register base, int32_t offset) { |
| if (Address::CanHoldOffset(offset, Address::Offset, kQWord)) { |
| fstrq(src, Address(base, offset, Address::Offset, kQWord)); |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| fstrq(src, Address(TMP2)); |
| } |
| } |
| |
| void Assembler::VRecps(VRegister vd, VRegister vn) { |
| ASSERT(vn != VTMP); |
| ASSERT(vd != VTMP); |
| |
| // Reciprocal estimate. |
| vrecpes(vd, vn); |
| // 2 Newton-Raphson steps. |
| vrecpss(VTMP, vn, vd); |
| vmuls(vd, vd, VTMP); |
| vrecpss(VTMP, vn, vd); |
| vmuls(vd, vd, VTMP); |
| } |
| |
| void Assembler::VRSqrts(VRegister vd, VRegister vn) { |
| ASSERT(vd != VTMP); |
| ASSERT(vn != VTMP); |
| |
| // Reciprocal square root estimate. |
| vrsqrtes(vd, vn); |
| // 2 Newton-Raphson steps. xn+1 = xn * (3 - V1*xn^2) / 2. |
| // First step. |
| vmuls(VTMP, vd, vd); // VTMP <- xn^2 |
| vrsqrtss(VTMP, vn, VTMP); // VTMP <- (3 - V1*VTMP) / 2. |
| vmuls(vd, vd, VTMP); // xn+1 <- xn * VTMP |
| // Second step. |
| vmuls(VTMP, vd, vd); |
| vrsqrtss(VTMP, vn, VTMP); |
| vmuls(vd, vd, VTMP); |
| } |
| |
| void Assembler::LoadCompressed(Register dest, const Address& slot) { |
| #if !defined(DART_COMPRESSED_POINTERS) |
| ldr(dest, slot); |
| #else |
| ldr(dest, slot, kUnsignedFourBytes); // Zero-extension. |
| add(dest, dest, Operand(HEAP_BITS, LSL, 32)); |
| #endif |
| } |
| |
| void Assembler::LoadCompressedFromOffset(Register dest, |
| Register base, |
| int32_t offset) { |
| #if !defined(DART_COMPRESSED_POINTERS) |
| LoadFromOffset(dest, base, offset); |
| #else |
| if (Address::CanHoldOffset(offset, Address::Offset, kObjectBytes)) { |
| ldr(dest, Address(base, offset, Address::Offset, kObjectBytes), |
| kUnsignedFourBytes); // Zero-extension. |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| ldr(dest, Address(TMP2, 0, Address::Offset, kObjectBytes), |
| kUnsignedFourBytes); // Zero-extension. |
| } |
| add(dest, dest, Operand(HEAP_BITS, LSL, 32)); |
| #endif |
| } |
| |
| void Assembler::LoadCompressedSmi(Register dest, const Address& slot) { |
| #if !defined(DART_COMPRESSED_POINTERS) |
| ldr(dest, slot); |
| #else |
| ldr(dest, slot, kUnsignedFourBytes); // Zero-extension. |
| #endif |
| #if defined(DEBUG) |
| Label done; |
| BranchIfSmi(dest, &done); |
| Stop("Expected Smi"); |
| Bind(&done); |
| #endif |
| } |
| |
| void Assembler::LoadCompressedSmiFromOffset(Register dest, |
| Register base, |
| int32_t offset) { |
| #if !defined(DART_COMPRESSED_POINTERS) |
| LoadFromOffset(dest, base, offset); |
| #else |
| if (Address::CanHoldOffset(offset, Address::Offset, kObjectBytes)) { |
| ldr(dest, Address(base, offset, Address::Offset, kObjectBytes), |
| kUnsignedFourBytes); // Zero-extension. |
| } else { |
| ASSERT(base != TMP2); |
| AddImmediate(TMP2, base, offset); |
| ldr(dest, Address(TMP2, 0, Address::Offset, kObjectBytes), |
| kUnsignedFourBytes); // Zero-extension. |
| } |
| #endif |
| #if defined(DEBUG) |
| Label done; |
| BranchIfSmi(dest, &done); |
| Stop("Expected Smi"); |
| Bind(&done); |
| #endif |
| } |
| |
| // Preserves object and value registers. |
| void Assembler::StoreIntoObjectFilter(Register object, |
| Register value, |
| Label* label, |
| CanBeSmi value_can_be_smi, |
| BarrierFilterMode how_to_jump) { |
| COMPILE_ASSERT((target::ObjectAlignment::kNewObjectAlignmentOffset == |
| target::kWordSize) && |
| (target::ObjectAlignment::kOldObjectAlignmentOffset == 0)); |
| |
| // Write-barrier triggers if the value is in the new space (has bit set) and |
| // the object is in the old space (has bit cleared). |
| if (value_can_be_smi == kValueIsNotSmi) { |
| #if defined(DEBUG) |
| Label okay; |
| BranchIfNotSmi(value, &okay); |
| Stop("Unexpected Smi!"); |
| Bind(&okay); |
| #endif |
| // To check that, we compute value & ~object and skip the write barrier |
| // if the bit is not set. We can't destroy the object. |
| bic(TMP, value, Operand(object)); |
| } else { |
| // For the value we are only interested in the new/old bit and the tag bit. |
| // And the new bit with the tag bit. The resulting bit will be 0 for a Smi. |
| and_(TMP, value, |
| Operand(value, LSL, target::ObjectAlignment::kNewObjectBitPosition)); |
| // And the result with the negated space bit of the object. |
| bic(TMP, TMP, Operand(object)); |
| } |
| if (how_to_jump == kJumpToNoUpdate) { |
| tbz(label, TMP, target::ObjectAlignment::kNewObjectBitPosition); |
| } else { |
| tbnz(label, TMP, target::ObjectAlignment::kNewObjectBitPosition); |
| } |
| } |
| |
| void Assembler::StoreIntoObjectOffset(Register object, |
| int32_t offset, |
| Register value, |
| CanBeSmi value_can_be_smi) { |
| if (Address::CanHoldOffset(offset - kHeapObjectTag)) { |
| StoreIntoObject(object, FieldAddress(object, offset), value, |
| value_can_be_smi); |
| } else { |
| AddImmediate(TMP, object, offset - kHeapObjectTag); |
| StoreIntoObject(object, Address(TMP), value, value_can_be_smi); |
| } |
| } |
| |
| void Assembler::StoreCompressedIntoObjectOffset(Register object, |
| int32_t offset, |
| Register value, |
| CanBeSmi value_can_be_smi) { |
| if (Address::CanHoldOffset(offset - kHeapObjectTag)) { |
| StoreCompressedIntoObject(object, FieldAddress(object, offset), value, |
| value_can_be_smi); |
| } else { |
| AddImmediate(TMP, object, offset - kHeapObjectTag); |
| StoreCompressedIntoObject(object, Address(TMP), value, value_can_be_smi); |
| } |
| } |
| |
| void Assembler::StoreIntoObject(Register object, |
| const Address& dest, |
| Register value, |
| CanBeSmi can_be_smi) { |
| str(value, dest); |
| StoreBarrier(object, value, can_be_smi); |
| } |
| |
| void Assembler::StoreCompressedIntoObject(Register object, |
| const Address& dest, |
| Register value, |
| CanBeSmi can_be_smi) { |
| str(value, dest, kObjectBytes); |
| StoreBarrier(object, value, can_be_smi); |
| } |
| |
| void Assembler::StoreBarrier(Register object, |
| Register value, |
| CanBeSmi can_be_smi) { |
| const bool spill_lr = lr_state().LRContainsReturnAddress(); |
| // 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 != TMP); |
| ASSERT(object != TMP2); |
| ASSERT(value != TMP); |
| ASSERT(value != TMP2); |
| |
| // 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); |
| } |
| ldr(TMP, FieldAddress(object, target::Object::tags_offset(), kByte), |
| kUnsignedByte); |
| ldr(TMP2, FieldAddress(value, target::Object::tags_offset(), kByte), |
| kUnsignedByte); |
| and_(TMP, TMP2, |
| Operand(TMP, LSR, target::UntaggedObject::kBarrierOverlapShift)); |
| tst(TMP, Operand(HEAP_BITS, LSR, 32)); |
| b(&done, ZERO); |
| |
| if (spill_lr) { |
| SPILLS_LR_TO_FRAME(Push(LR)); |
| } |
| Register objectForCall = object; |
| if (value != kWriteBarrierValueReg) { |
| // Unlikely. Only non-graph intrinsics. |
| // TODO(rmacnak): Shuffle registers in intrinsics. |
| if (object != kWriteBarrierValueReg) { |
| Push(kWriteBarrierValueReg); |
| } else { |
| COMPILE_ASSERT(R2 != kWriteBarrierValueReg); |
| COMPILE_ASSERT(R3 != kWriteBarrierValueReg); |
| objectForCall = (value == R2) ? R3 : R2; |
| PushPair(kWriteBarrierValueReg, objectForCall); |
| mov(objectForCall, object); |
| } |
| mov(kWriteBarrierValueReg, value); |
| } |
| |
| generate_invoke_write_barrier_wrapper_(objectForCall); |
| |
| if (value != kWriteBarrierValueReg) { |
| if (object != kWriteBarrierValueReg) { |
| Pop(kWriteBarrierValueReg); |
| } else { |
| PopPair(kWriteBarrierValueReg, objectForCall); |
| } |
| } |
| if (spill_lr) { |
| RESTORES_LR_FROM_FRAME(Pop(LR)); |
| } |
| Bind(&done); |
| } |
| |
| void Assembler::StoreIntoArray(Register object, |
| Register slot, |
| Register value, |
| CanBeSmi can_be_smi) { |
| str(value, Address(slot, 0)); |
| StoreIntoArrayBarrier(object, slot, value, can_be_smi); |
| } |
| |
| void Assembler::StoreCompressedIntoArray(Register object, |
| Register slot, |
| Register value, |
| CanBeSmi can_be_smi) { |
| str(value, Address(slot, 0, Address::Offset, kObjectBytes), kObjectBytes); |
| StoreIntoArrayBarrier(object, slot, value, can_be_smi); |
| } |
| |
| void Assembler::StoreIntoArrayBarrier(Register object, |
| Register slot, |
| Register value, |
| CanBeSmi can_be_smi) { |
| const bool spill_lr = lr_state().LRContainsReturnAddress(); |
| ASSERT(object != TMP); |
| ASSERT(object != TMP2); |
| ASSERT(value != TMP); |
| ASSERT(value != TMP2); |
| ASSERT(slot != TMP); |
| ASSERT(slot != TMP2); |
| |
| // 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); |
| } |
| ldr(TMP, FieldAddress(object, target::Object::tags_offset(), kByte), |
| kUnsignedByte); |
| ldr(TMP2, FieldAddress(value, target::Object::tags_offset(), kByte), |
| kUnsignedByte); |
| and_(TMP, TMP2, |
| Operand(TMP, LSR, target::UntaggedObject::kBarrierOverlapShift)); |
| tst(TMP, Operand(HEAP_BITS, LSR, 32)); |
| b(&done, ZERO); |
| if (spill_lr) { |
| SPILLS_LR_TO_FRAME(Push(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_(); |
| if (spill_lr) { |
| RESTORES_LR_FROM_FRAME(Pop(LR)); |
| } |
| Bind(&done); |
| } |
| |
| void Assembler::StoreIntoObjectNoBarrier(Register object, |
| const Address& dest, |
| Register value) { |
| str(value, dest); |
| #if defined(DEBUG) |
| Label done; |
| StoreIntoObjectFilter(object, value, &done, kValueCanBeSmi, kJumpToNoUpdate); |
| |
| ldr(TMP, FieldAddress(object, target::Object::tags_offset(), kByte), |
| kUnsignedByte); |
| tsti(TMP, Immediate(1 << target::UntaggedObject::kOldAndNotRememberedBit)); |
| b(&done, ZERO); |
| |
| Stop("Store buffer update is required"); |
| Bind(&done); |
| #endif // defined(DEBUG) |
| // No store buffer update. |
| } |
| |
| void Assembler::StoreCompressedIntoObjectNoBarrier(Register object, |
| const Address& dest, |
| Register value) { |
| str(value, dest, kObjectBytes); |
| #if defined(DEBUG) |
| Label done; |
| StoreIntoObjectFilter(object, value, &done, kValueCanBeSmi, kJumpToNoUpdate); |
| |
| ldr(TMP, FieldAddress(object, target::Object::tags_offset(), kByte), |
| kUnsignedByte); |
| tsti(TMP, Immediate(1 << target::UntaggedObject::kOldAndNotRememberedBit)); |
| b(&done, ZERO); |
| |
| Stop("Store buffer update is required"); |
| Bind(&done); |
| #endif // defined(DEBUG) |
| // No store buffer update. |
| } |
| |
| void Assembler::StoreIntoObjectOffsetNoBarrier(Register object, |
| int32_t offset, |
| Register value) { |
| if (Address::CanHoldOffset(offset - kHeapObjectTag)) { |
| StoreIntoObjectNoBarrier(object, FieldAddress(object, offset), value); |
| } else { |
| AddImmediate(TMP, object, offset - kHeapObjectTag); |
| StoreIntoObjectNoBarrier(object, Address(TMP), value); |
| } |
| } |
| |
| void Assembler::StoreCompressedIntoObjectOffsetNoBarrier(Register object, |
| int32_t offset, |
| Register value) { |
| if (Address::CanHoldOffset(offset - kHeapObjectTag)) { |
| StoreCompressedIntoObjectNoBarrier(object, FieldAddress(object, offset), |
| value); |
| } else { |
| AddImmediate(TMP, object, offset - kHeapObjectTag); |
| StoreCompressedIntoObjectNoBarrier(object, Address(TMP), value); |
| } |
| } |
| |
| void Assembler::StoreIntoObjectNoBarrier(Register object, |
| const Address& dest, |
| const Object& value) { |
| ASSERT(IsOriginalObject(value)); |
| ASSERT(IsNotTemporaryScopedHandle(value)); |
| // No store buffer update. |
| if (IsSameObject(compiler::NullObject(), value)) { |
| str(NULL_REG, dest); |
| } else { |
| LoadObject(TMP2, value); |
| str(TMP2, dest); |
| } |
| } |
| |
| void Assembler::StoreCompressedIntoObjectNoBarrier(Register object, |
| const Address& dest, |
| const Object& value) { |
| ASSERT(IsOriginalObject(value)); |
| ASSERT(IsNotTemporaryScopedHandle(value)); |
| // No store buffer update. |
| if (IsSameObject(compiler::NullObject(), value)) { |
| str(NULL_REG, dest, kObjectBytes); |
| } else { |
| LoadObject(TMP2, value); |
| str(TMP2, dest, kObjectBytes); |
| } |
| } |
| |
| void Assembler::StoreIntoObjectOffsetNoBarrier(Register object, |
| int32_t offset, |
| const Object& value) { |
| if (Address::CanHoldOffset(offset - kHeapObjectTag)) { |
| StoreIntoObjectNoBarrier(object, FieldAddress(object, offset), value); |
| } else { |
| AddImmediate(TMP, object, offset - kHeapObjectTag); |
| StoreIntoObjectNoBarrier(object, Address(TMP), value); |
| } |
| } |
| |
| void Assembler::StoreCompressedIntoObjectOffsetNoBarrier(Register object, |
| int32_t offset, |
| const Object& value) { |
| if (Address::CanHoldOffset(offset - kHeapObjectTag)) { |
| StoreCompressedIntoObjectNoBarrier(object, FieldAddress(object, offset), |
| value); |
| } else { |
| AddImmediate(TMP, object, offset - kHeapObjectTag); |
| StoreCompressedIntoObjectNoBarrier(object, Address(TMP), value); |
| } |
| } |
| |
| void Assembler::StoreInternalPointer(Register object, |
| const Address& dest, |
| Register value) { |
| str(value, dest); |
| } |
| |
| void Assembler::ExtractClassIdFromTags(Register result, Register tags) { |
| ASSERT(target::UntaggedObject::kClassIdTagPos == 16); |
| ASSERT(target::UntaggedObject::kClassIdTagSize == 16); |
| LsrImmediate(result, tags, target::UntaggedObject::kClassIdTagPos, |
| kFourBytes); |
| } |
| |
| void Assembler::ExtractInstanceSizeFromTags(Register result, Register tags) { |
| ASSERT(target::UntaggedObject::kSizeTagPos == 8); |
| ASSERT(target::UntaggedObject::kSizeTagSize == 8); |
| ubfx(result, tags, target::UntaggedObject::kSizeTagPos, |
| target::UntaggedObject::kSizeTagSize); |
| LslImmediate(result, result, target::ObjectAlignment::kObjectAlignmentLog2); |
| } |
| |
| void Assembler::LoadClassId(Register result, Register object) { |
| ASSERT(target::UntaggedObject::kClassIdTagPos == 16); |
| ASSERT(target::UntaggedObject::kClassIdTagSize == 16); |
| const intptr_t class_id_offset = |
| target::Object::tags_offset() + |
| target::UntaggedObject::kClassIdTagPos / kBitsPerByte; |
| LoadFromOffset(result, object, class_id_offset - kHeapObjectTag, |
| kUnsignedTwoBytes); |
| } |
| |
| 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, UXTX, Address::Scaled)); |
| } |
| |
| void Assembler::CompareClassId(Register object, |
| intptr_t class_id, |
| Register scratch) { |
| LoadClassId(TMP, object); |
| CompareImmediate(TMP, class_id); |
| } |
| |
| void Assembler::LoadClassIdMayBeSmi(Register result, Register object) { |
| ASSERT(result != object); |
| Label done; |
| LoadImmediate(result, kSmiCid); |
| BranchIfSmi(object, &done); |
| LoadClassId(result, object); |
| Bind(&done); |
| } |
| |
| void Assembler::LoadTaggedClassIdMayBeSmi(Register result, Register object) { |
| if (result == object) { |
| LoadClassIdMayBeSmi(TMP, object); |
| SmiTag(result, TMP); |
| } else { |
| Label done; |
| LoadImmediate(result, target::ToRawSmi(kSmiCid)); |
| BranchIfSmi(object, &done); |
| LoadClassId(result, object); |
| SmiTag(result); |
| Bind(&done); |
| } |
| } |
| |
| 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 |
| } |
| |
| // Frame entry and exit. |
| void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) { |
| // Reserve space for arguments and align frame before entering |
| // the C++ world. |
| if (frame_space != 0) { |
| AddImmediate(SP, -frame_space); |
| } |
| if (OS::ActivationFrameAlignment() > 1) { |
| andi(SP, SP, Immediate(~(OS::ActivationFrameAlignment() - 1))); |
| } |
| } |
| |
| void Assembler::EmitEntryFrameVerification() { |
| #if defined(DEBUG) |
| Label done; |
| ASSERT(!constant_pool_allowed()); |
| LoadImmediate(TMP, target::frame_layout.exit_link_slot_from_entry_fp * |
| target::kWordSize); |
| add(TMP, TMP, Operand(FPREG)); |
| cmp(TMP, Operand(SPREG)); |
| b(&done, EQ); |
| |
| Breakpoint(); |
| |
| Bind(&done); |
| #endif |
| } |
| |
| void Assembler::RestoreCodePointer() { |
| ldr(CODE_REG, |
| Address(FP, target::frame_layout.code_from_fp * target::kWordSize)); |
| CheckCodePointer(); |
| } |
| |
| void Assembler::RestorePinnedRegisters() { |
| ldr(HEAP_BITS, |
| compiler::Address(THR, target::Thread::write_barrier_mask_offset())); |
| LslImmediate(HEAP_BITS, HEAP_BITS, 32); |
| ldr(NULL_REG, compiler::Address(THR, target::Thread::object_null_offset())); |
| #if defined(DART_COMPRESSED_POINTERS) |
| ldr(TMP, compiler::Address(THR, target::Thread::heap_base_offset())); |
| orr(HEAP_BITS, HEAP_BITS, Operand(TMP, LSR, 32)); |
| #endif |
| } |
| |
| void Assembler::SetupGlobalPoolAndDispatchTable() { |
| ASSERT(FLAG_precompiled_mode && FLAG_use_bare_instructions); |
| ldr(PP, Address(THR, target::Thread::global_object_pool_offset())); |
| sub(PP, PP, Operand(kHeapObjectTag)); // Pool in PP is untagged! |
| if (FLAG_use_table_dispatch) { |
| ldr(DISPATCH_TABLE_REG, |
| Address(THR, target::Thread::dispatch_table_array_offset())); |
| } |
| } |
| |
| void Assembler::CheckCodePointer() { |
| #ifdef DEBUG |
| if (!FLAG_check_code_pointer) { |
| return; |
| } |
| Comment("CheckCodePointer"); |
| Label cid_ok, instructions_ok; |
| Push(R0); |
| CompareClassId(CODE_REG, kCodeCid); |
| b(&cid_ok, EQ); |
| brk(0); |
| Bind(&cid_ok); |
| |
| const intptr_t entry_offset = |
| CodeSize() + target::Instructions::HeaderSize() - kHeapObjectTag; |
| adr(R0, Immediate(-entry_offset)); |
| ldr(TMP, FieldAddress(CODE_REG, target::Code::saved_instructions_offset())); |
| cmp(R0, Operand(TMP)); |
| b(&instructions_ok, EQ); |
| brk(1); |
| Bind(&instructions_ok); |
| Pop(R0); |
| #endif |
| } |
| |
| // The ARM64 ABI requires at all times |
| // - stack limit < CSP <= stack base |
| // - CSP mod 16 = 0 |
| // - we do not access stack memory below CSP |
| // Practically, this means we need to keep the C stack pointer ahead of the |
| // Dart stack pointer and 16-byte aligned for signal handlers. We set |
| // CSP to a value near the stack limit during SetupDartSP*, and use a different |
| // register within our generated code to avoid the alignment requirement. |
| // Note that Fuchsia does not have signal handlers. |
| |
| void Assembler::SetupDartSP(intptr_t reserve /* = 4096 */) { |
| mov(SP, CSP); |
| // The caller doesn't have a Thread available. Just kick CSP forward a bit. |
| AddImmediate(CSP, CSP, -Utils::RoundUp(reserve, 16)); |
| } |
| |
| void Assembler::SetupCSPFromThread(Register thr) { |
| // Thread::saved_stack_limit_ is OSThread::overflow_stack_limit(), which is |
| // OSThread::stack_limit() with some headroom. Set CSP a bit below this value |
| // so that signal handlers won't stomp on the stack of Dart code that pushs a |
| // bit past overflow_stack_limit before its next overflow check. (We build |
| // frames before doing an overflow check.) |
| ldr(TMP, Address(thr, target::Thread::saved_stack_limit_offset())); |
| AddImmediate(CSP, TMP, -4096); |
| } |
| |
| void Assembler::RestoreCSP() { |
| mov(CSP, SP); |
| } |
| |
| void Assembler::EnterFrame(intptr_t frame_size) { |
| SPILLS_LR_TO_FRAME(PushPair(FP, LR)); // low: FP, high: LR. |
| mov(FP, SP); |
| |
| if (frame_size > 0) { |
| sub(SP, SP, Operand(frame_size)); |
| } |
| } |
| |
| void Assembler::LeaveFrame() { |
| mov(SP, FP); |
| RESTORES_LR_FROM_FRAME(PopPair(FP, LR)); // low: FP, high: LR. |
| } |
| |
| void Assembler::EnterDartFrame(intptr_t frame_size, Register new_pp) { |
| ASSERT(!constant_pool_allowed()); |
| // Setup the frame. |
| EnterFrame(0); |
| |
| if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions)) { |
| TagAndPushPPAndPcMarker(); // Save PP and PC marker. |
| |
| // Load the pool pointer. |
| if (new_pp == kNoRegister) { |
| LoadPoolPointer(); |
| } else { |
| mov(PP, new_pp); |
| } |
| } |
| set_constant_pool_allowed(true); |
| |
| // Reserve space. |
| if (frame_size > 0) { |
| 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, Register new_pp) { |
| ASSERT(!constant_pool_allowed()); |
| Comment("EnterOsrFrame"); |
| RestoreCodePointer(); |
| LoadPoolPointer(); |
| |
| if (extra_size > 0) { |
| AddImmediate(SP, -extra_size); |
| } |
| } |
| |
| void Assembler::LeaveDartFrame(RestorePP restore_pp) { |
| if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions)) { |
| if (restore_pp == kRestoreCallerPP) { |
| // Restore and untag PP. |
| LoadFromOffset( |
| PP, FP, |
| target::frame_layout.saved_caller_pp_from_fp * target::kWordSize); |
| sub(PP, PP, Operand(kHeapObjectTag)); |
| } |
| } |
| set_constant_pool_allowed(false); |
| LeaveFrame(); |
| } |
| |
| void Assembler::EnterFullSafepoint(Register state) { |
| // We generate the same number of instructions whether or not the slow-path is |
| // forced. This simplifies GenerateJitCallbackTrampolines. |
| |
| Register addr = TMP2; |
| ASSERT(addr != state); |
| |
| Label slow_path, done, retry; |
| if (FLAG_use_slow_path) { |
| b(&slow_path); |
| } |
| |
| movz(addr, Immediate(target::Thread::safepoint_state_offset()), 0); |
| add(addr, THR, Operand(addr)); |
| Bind(&retry); |
| ldxr(state, addr); |
| cmp(state, Operand(target::Thread::full_safepoint_state_unacquired())); |
| b(&slow_path, NE); |
| |
| movz(state, Immediate(target::Thread::full_safepoint_state_acquired()), 0); |
| stxr(TMP, state, addr); |
| cbz(&done, TMP); // 0 means stxr was successful. |
| |
| if (!FLAG_use_slow_path) { |
| b(&retry); |
| } |
| |
| Bind(&slow_path); |
| ldr(addr, Address(THR, target::Thread::enter_safepoint_stub_offset())); |
| ldr(addr, FieldAddress(addr, target::Code::entry_point_offset())); |
| blr(addr); |
| |
| Bind(&done); |
| } |
| |
| void Assembler::TransitionGeneratedToNative(Register destination, |
| Register new_exit_frame, |
| Register new_exit_through_ffi, |
| bool enter_safepoint) { |
| // Save exit frame information to enable stack walking. |
| StoreToOffset(new_exit_frame, THR, |
| target::Thread::top_exit_frame_info_offset()); |
| |
| StoreToOffset(new_exit_through_ffi, THR, |
| target::Thread::exit_through_ffi_offset()); |
| Register tmp = new_exit_through_ffi; |
| |
| // Mark that the thread is executing native code. |
| StoreToOffset(destination, THR, target::Thread::vm_tag_offset()); |
| LoadImmediate(tmp, target::Thread::native_execution_state()); |
| StoreToOffset(tmp, THR, target::Thread::execution_state_offset()); |
| |
| if (enter_safepoint) { |
| EnterFullSafepoint(tmp); |
| } |
| } |
| |
| void Assembler::ExitFullSafepoint(Register state) { |
| // We generate the same number of instructions whether or not the slow-path is |
| // forced, for consistency with EnterFullSafepoint. |
| Register addr = TMP2; |
| ASSERT(addr != state); |
| |
| Label slow_path, done, retry; |
| if (FLAG_use_slow_path) { |
| b(&slow_path); |
| } |
| |
| movz(addr, Immediate(target::Thread::safepoint_state_offset()), 0); |
| add(addr, THR, Operand(addr)); |
| Bind(&retry); |
| ldxr(state, addr); |
| cmp(state, Operand(target::Thread::full_safepoint_state_acquired())); |
| b(&slow_path, NE); |
| |
| movz(state, Immediate(target::Thread::full_safepoint_state_unacquired()), 0); |
| stxr(TMP, state, addr); |
| cbz(&done, TMP); // 0 means stxr was successful. |
| |
| if (!FLAG_use_slow_path) { |
| b(&retry); |
| } |
| |
| Bind(&slow_path); |
| ldr(addr, Address(THR, target::Thread::exit_safepoint_stub_offset())); |
| ldr(addr, FieldAddress(addr, target::Code::entry_point_offset())); |
| blr(addr); |
| |
| Bind(&done); |
| } |
| |
| void Assembler::TransitionNativeToGenerated(Register state, |
| bool exit_safepoint) { |
| if (exit_safepoint) { |
| ExitFullSafepoint(state); |
| } else { |
| #if defined(DEBUG) |
| // Ensure we've already left the safepoint. |
| ASSERT(target::Thread::full_safepoint_state_acquired() != 0); |
| LoadImmediate(state, target::Thread::full_safepoint_state_acquired()); |
| ldr(TMP, Address(THR, target::Thread::safepoint_state_offset())); |
| and_(TMP, TMP, Operand(state)); |
| Label ok; |
| cbz(&ok, TMP); |
| Breakpoint(); |
| Bind(&ok); |
| #endif |
| } |
| |
| // Mark that the thread is executing Dart code. |
| 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. |
| StoreToOffset(ZR, THR, target::Thread::top_exit_frame_info_offset()); |
| LoadImmediate(state, 0); |
| StoreToOffset(state, THR, target::Thread::exit_through_ffi_offset()); |
| } |
| |
| void Assembler::EnterCallRuntimeFrame(intptr_t frame_size, bool is_leaf) { |
| Comment("EnterCallRuntimeFrame"); |
| EnterFrame(0); |
| if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions)) { |
| TagAndPushPPAndPcMarker(); // Save PP and PC marker. |
| } |
| |
| // Store fpu registers with the lowest register number at the lowest |
| // address. |
| for (int i = kNumberOfVRegisters - 1; i >= 0; i--) { |
| if ((i >= kAbiFirstPreservedFpuReg) && (i <= kAbiLastPreservedFpuReg)) { |
| // TODO(zra): When SIMD is added, we must also preserve the top |
| // 64-bits of the callee-saved registers. |
| continue; |
| } |
| // TODO(zra): Save the whole V register. |
| VRegister reg = static_cast<VRegister>(i); |
| PushDouble(reg); |
| } |
| |
| for (int i = kDartFirstVolatileCpuReg; i <= kDartLastVolatileCpuReg; i++) { |
| const Register reg = static_cast<Register>(i); |
| Push(reg); |
| } |
| |
| if (!is_leaf) { // Leaf calling sequence aligns the stack itself. |
| ReserveAlignedFrameSpace(frame_size); |
| } else { |
| PushPair(kCallLeafRuntimeCalleeSaveScratch1, |
| kCallLeafRuntimeCalleeSaveScratch2); |
| } |
| } |
| |
| void Assembler::LeaveCallRuntimeFrame(bool is_leaf) { |
| // 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 fixed_frame_words_without_pc_and_fp = |
| target::frame_layout.dart_fixed_frame_size - 2; |
| const intptr_t kPushedRegistersSize = |
| kDartVolatileFpuRegCount * sizeof(double) + |
| (kDartVolatileCpuRegCount + (is_leaf ? 2 : 0) + |
| fixed_frame_words_without_pc_and_fp) * |
| target::kWordSize; |
| AddImmediate(SP, FP, -kPushedRegistersSize); |
| if (is_leaf) { |
| PopPair(kCallLeafRuntimeCalleeSaveScratch1, |
| kCallLeafRuntimeCalleeSaveScratch2); |
| } |
| for (int i = kDartLastVolatileCpuReg; i >= kDartFirstVolatileCpuReg; i--) { |
| const Register reg = static_cast<Register>(i); |
| Pop(reg); |
| } |
| |
| for (int i = 0; i < kNumberOfVRegisters; i++) { |
| if ((i >= kAbiFirstPreservedFpuReg) && (i <= kAbiLastPreservedFpuReg)) { |
| // TODO(zra): When SIMD is added, we must also restore the top |
| // 64-bits of the callee-saved registers. |
| continue; |
| } |
| // TODO(zra): Restore the whole V register. |
| VRegister reg = static_cast<VRegister>(i); |
| PopDouble(reg); |
| } |
| |
| LeaveStubFrame(); |
| } |
| |
| void Assembler::CallRuntime(const RuntimeEntry& entry, |
| intptr_t argument_count) { |
| entry.Call(this, argument_count); |
| } |
| |
| void Assembler::CallRuntimeScope::Call(intptr_t argument_count) { |
| assembler_->CallRuntime(entry_, argument_count); |
| } |
| |
| Assembler::CallRuntimeScope::~CallRuntimeScope() { |
| if (preserve_registers_) { |
| assembler_->LeaveCallRuntimeFrame(entry_.is_leaf()); |
| if (restore_code_reg_) { |
| assembler_->Pop(CODE_REG); |
| } |
| } |
| } |
| |
| Assembler::CallRuntimeScope::CallRuntimeScope(Assembler* assembler, |
| const RuntimeEntry& entry, |
| intptr_t frame_size, |
| bool preserve_registers, |
| const Address* caller) |
| : assembler_(assembler), |
| entry_(entry), |
| preserve_registers_(preserve_registers), |
| restore_code_reg_(caller != nullptr) { |
| if (preserve_registers_) { |
| if (caller != nullptr) { |
| assembler_->Push(CODE_REG); |
| assembler_->ldr(CODE_REG, *caller); |
| } |
| assembler_->EnterCallRuntimeFrame(frame_size, entry.is_leaf()); |
| } |
| } |
| |
| void Assembler::EnterStubFrame() { |
| EnterDartFrame(0); |
| } |
| |
| void Assembler::LeaveStubFrame() { |
| LeaveDartFrame(); |
| } |
| |
| void Assembler::EnterCFrame(intptr_t frame_space) { |
| Push(FP); |
| mov(FP, SP); |
| ReserveAlignedFrameSpace(frame_space); |
| } |
| |
| void Assembler::LeaveCFrame() { |
| mov(SP, FP); |
| Pop(FP); |
| } |
| |
| // R0 receiver, R5 ICData entries array |
| // Preserve R4 (ARGS_DESC_REG), not required today, but maybe later. |
| void Assembler::MonomorphicCheckedEntryJIT() { |
| has_monomorphic_entry_ = true; |
| const bool saved_use_far_branches = use_far_branches(); |
| set_use_far_branches(false); |
| const intptr_t start = CodeSize(); |
| |
| Label immediate, miss; |
| Bind(&miss); |
| ldr(IP0, Address(THR, target::Thread::switchable_call_miss_entry_offset())); |
| br(IP0); |
| |
| 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 ldp because ldp requires aligned offsets. |
| ldr(R1, FieldAddress(R5, cid_offset, kObjectBytes), kObjectBytes); |
| ldr(R2, FieldAddress(R5, count_offset, kObjectBytes), kObjectBytes); |
| LoadClassIdMayBeSmi(IP0, R0); |
| add(R2, R2, Operand(target::ToRawSmi(1)), kObjectBytes); |
| cmp(R1, Operand(IP0, LSL, 1), kObjectBytes); |
| b(&miss, NE); |
| str(R2, FieldAddress(R5, count_offset, kObjectBytes), kObjectBytes); |
| LoadImmediate(R4, 0); // GC-safe for OptimizeInvokedFunction |
| |
| // Fall through to unchecked entry. |
| ASSERT_EQUAL(CodeSize() - start, |
| target::Instructions::kPolymorphicEntryOffsetJIT); |
| |
| set_use_far_branches(saved_use_far_branches); |
| } |
| |
| // R0 receiver, R5 guarded cid as Smi. |
| // Preserve R4 (ARGS_DESC_REG), not required today, but maybe later. |
| void Assembler::MonomorphicCheckedEntryAOT() { |
| has_monomorphic_entry_ = true; |
| bool saved_use_far_branches = use_far_branches(); |
| set_use_far_branches(false); |
| |
| const intptr_t start = CodeSize(); |
| |
| Label immediate, miss; |
| Bind(&miss); |
| ldr(IP0, Address(THR, target::Thread::switchable_call_miss_entry_offset())); |
| br(IP0); |
| |
| Comment("MonomorphicCheckedEntry"); |
| ASSERT_EQUAL(CodeSize() - start, |
| target::Instructions::kMonomorphicEntryOffsetAOT); |
| LoadClassId(IP0, R0); |
| cmp(R5, Operand(IP0, LSL, 1), kObjectBytes); |
| b(&miss, NE); |
| |
| // Fall through to unchecked entry. |
| ASSERT_EQUAL(CodeSize() - start, |
| target::Instructions::kPolymorphicEntryOffsetAOT); |
| |
| set_use_far_branches(saved_use_far_branches); |
| } |
| |
| void Assembler::BranchOnMonomorphicCheckedEntryJIT(Label* label) { |
| has_monomorphic_entry_ = true; |
| while (CodeSize() < target::Instructions::kMonomorphicEntryOffsetJIT) { |
| brk(0); |
| } |
| b(label); |
| while (CodeSize() < target::Instructions::kPolymorphicEntryOffsetJIT) { |
| brk(0); |
| } |
| } |
| |
| #ifndef PRODUCT |
| void Assembler::MaybeTraceAllocation(intptr_t cid, |
| Register temp_reg, |
| Label* trace) { |
| ASSERT(cid > 0); |
| |
| const intptr_t shared_table_offset = |
| target::IsolateGroup::shared_class_table_offset(); |
| const intptr_t table_offset = |
| target::SharedClassTable::class_heap_stats_table_offset(); |
| const intptr_t class_offset = target::ClassTable::ClassOffsetFor(cid); |
| |
| LoadIsolateGroup(temp_reg); |
| ldr(temp_reg, Address(temp_reg, shared_table_offset)); |
| ldr(temp_reg, Address(temp_reg, table_offset)); |
| AddImmediate(temp_reg, class_offset); |
| ldr(temp_reg, Address(temp_reg, 0), kUnsignedByte); |
| cbnz(trace, temp_reg); |
| } |
| #endif // !PRODUCT |
| |
| void Assembler::TryAllocateObject(intptr_t cid, |
| intptr_t instance_size, |
| Label* failure, |
| JumpDistance distance, |
| Register instance_reg, |
| Register temp_reg) { |
| ASSERT(failure != NULL); |
| ASSERT(instance_size != 0); |
| ASSERT(instance_reg != temp_reg); |
| ASSERT(temp_reg != kNoRegister); |
| ASSERT(Utils::IsAligned(instance_size, |
| target::ObjectAlignment::kObjectAlignment)); |
| if (FLAG_inline_alloc && |
| target::Heap::IsAllocatableInNewSpace(instance_size)) { |
| // 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(cid, temp_reg, failure)); |
| RELEASE_ASSERT((target::Thread::top_offset() + target::kWordSize) == |
| target::Thread::end_offset()); |
| ldp(instance_reg, temp_reg, |
| Address(THR, target::Thread::top_offset(), Address::PairOffset)); |
| // instance_reg: current top (next object start). |
| // temp_reg: heap end |
| |
| // TODO(koda): Protect against unsigned overflow here. |
| AddImmediate(instance_reg, instance_size); |
| // instance_reg: potential top (next object start). |
| // fail if heap end unsigned less than or equal to new heap top. |
| cmp(temp_reg, Operand(instance_reg)); |
| b(failure, LS); |
| |
| // Successfully allocated the object, now update temp 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); |
| StoreToOffset(temp_reg, |
| FieldAddress(instance_reg, target::Object::tags_offset())); |
| } 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 (FLAG_inline_alloc && |
| target::Heap::IsAllocatableInNewSpace(instance_size)) { |
| // 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(cid, temp1, failure)); |
| // Potential new object start. |
| ldr(instance, Address(THR, target::Thread::top_offset())); |
| AddImmediateSetFlags(end_address, instance, instance_size); |
| b(failure, CS); // Fail on 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); |
| |
| // 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)); |
| NOT_IN_PRODUCT(LoadImmediate(temp2, instance_size)); |
| |
| // Initialize the tags. |
| // instance: new object start as a tagged pointer. |
| const uword tags = target::MakeTagWordForNewSpaceObject(cid, instance_size); |
| LoadImmediate(temp2, tags); |
| str(temp2, FieldAddress(instance, target::Object::tags_offset())); |
| } else { |
| b(failure); |
| } |
| } |
| |
| void Assembler::GenerateUnRelocatedPcRelativeCall(intptr_t offset_into_target) { |
| // Emit "bl <offset>". |
| EmitUnconditionalBranchOp(BL, 0); |
| |
| PcRelativeCallPattern pattern(buffer_.contents() + buffer_.Size() - |
| PcRelativeCallPattern::kLengthInBytes); |
| pattern.set_distance(offset_into_target); |
| } |
| |
| void Assembler::GenerateUnRelocatedPcRelativeTailCall( |
| intptr_t offset_into_target) { |
| // Emit "b <offset>". |
| EmitUnconditionalBranchOp(B, 0); |
| PcRelativeTailCallPattern pattern(buffer_.contents() + buffer_.Size() - |
| PcRelativeTailCallPattern::kLengthInBytes); |
| pattern.set_distance(offset_into_target); |
| } |
| |
| Address Assembler::ElementAddressForIntIndex(bool is_external, |
| intptr_t cid, |
| intptr_t index_scale, |
| Register array, |
| intptr_t index) const { |
| const int64_t offset = index * index_scale + HeapDataOffset(is_external, cid); |
| ASSERT(Utils::IsInt(32, offset)); |
| const OperandSize size = Address::OperandSizeFor(cid); |
| ASSERT(Address::CanHoldOffset(offset, Address::Offset, size)); |
| return Address(array, static_cast<int32_t>(offset), Address::Offset, size); |
| } |
| |
| void Assembler::ComputeElementAddressForIntIndex(Register address, |
| bool is_external, |
| intptr_t cid, |
| intptr_t index_scale, |
| Register array, |
| intptr_t index) { |
| const int64_t offset = index * index_scale + HeapDataOffset(is_external, cid); |
| AddImmediate(address, array, offset); |
| } |
| |
| Address Assembler::ElementAddressForRegIndex(bool is_external, |
| intptr_t cid, |
| intptr_t index_scale, |
| bool index_unboxed, |
| Register array, |
| Register index, |
| Register temp) { |
| return ElementAddressForRegIndexWithSize( |
| is_external, cid, Address::OperandSizeFor(cid), index_scale, |
| index_unboxed, array, index, temp); |
| } |
| |
| Address Assembler::ElementAddressForRegIndexWithSize(bool is_external, |
| intptr_t cid, |
| OperandSize size, |
| intptr_t index_scale, |
| bool index_unboxed, |
| Register array, |
| Register index, |
| Register temp) { |
| // 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; |
| const int32_t offset = HeapDataOffset(is_external, cid); |
| #if !defined(DART_COMPRESSED_POINTERS) |
| const bool index_is_32bit = false; |
| #else |
| const bool index_is_32bit = !index_unboxed; |
| #endif |
| ASSERT(array != temp); |
| ASSERT(index != temp); |
| if ((offset == 0) && (shift == 0)) { |
| if (index_is_32bit) { |
| return Address(array, index, SXTW, Address::Unscaled); |
| } else { |
| return Address(array, index, UXTX, Address::Unscaled); |
| } |
| } else if (shift < 0) { |
| ASSERT(shift == -1); |
| if (index_is_32bit) { |
| AsrImmediate(temp, index, 1, kFourBytes); |
| add(temp, array, Operand(temp, SXTW, 0)); |
| } else { |
| add(temp, array, Operand(index, ASR, 1)); |
| } |
| } else { |
| if (index_is_32bit) { |
| add(temp, array, Operand(index, SXTW, shift)); |
| } else { |
| add(temp, array, Operand(index, LSL, shift)); |
| } |
| } |
| ASSERT(Address::CanHoldOffset(offset, Address::Offset, size)); |
| return Address(temp, offset, Address::Offset, size); |
| } |
| |
| void Assembler::ComputeElementAddressForRegIndex(Register address, |
| 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; |
| const int32_t offset = HeapDataOffset(is_external, cid); |
| #if !defined(DART_COMPRESSED_POINTERS) |
| const bool index_is_32bit = false; |
| #else |
| const bool index_is_32bit = !index_unboxed; |
| #endif |
| if (shift == 0) { |
| if (index_is_32bit) { |
| add(address, array, Operand(index, SXTW, 0)); |
| } else { |
| add(address, array, Operand(index)); |
| } |
| } else if (shift < 0) { |
| ASSERT(shift == -1); |
| if (index_is_32bit) { |
| sxtw(index, index); |
| add(address, array, Operand(index, ASR, 1)); |
| } else { |
| add(address, array, Operand(index, ASR, 1)); |
| } |
| } else { |
| if (index_is_32bit) { |
| add(address, array, Operand(index, SXTW, shift)); |
| } else { |
| add(address, array, Operand(index, LSL, shift)); |
| } |
| } |
| if (offset != 0) { |
| AddImmediate(address, offset); |
| } |
| } |
| |
| void Assembler::LoadCompressedFieldAddressForRegOffset( |
| Register address, |
| Register instance, |
| Register offset_in_compressed_words_as_smi) { |
| add(address, instance, |
| Operand(offset_in_compressed_words_as_smi, LSL, |
| target::kCompressedWordSizeLog2 - kSmiTagShift)); |
| AddImmediate(address, -kHeapObjectTag); |
| } |
| |
| 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::PushRegisters(const RegisterSet& regs) { |
| const intptr_t fpu_regs_count = regs.FpuRegisterCount(); |
| if (fpu_regs_count > 0) { |
| // Store fpu registers with the lowest register number at the lowest |
| // address. |
| for (intptr_t i = kNumberOfVRegisters - 1; i >= 0; --i) { |
| VRegister fpu_reg = static_cast<VRegister>(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. |
| Register prev = kNoRegister; |
| for (intptr_t i = kNumberOfCpuRegisters - 1; i >= 0; --i) { |
| Register reg = static_cast<Register>(i); |
| if (regs.ContainsRegister(reg)) { |
| if (prev != kNoRegister) { |
| PushPair(/*low=*/reg, /*high=*/prev); |
| prev = kNoRegister; |
| } else { |
| prev = reg; |
| } |
| } |
| } |
| if (prev != kNoRegister) { |
| Push(prev); |
| } |
| } |
| |
| void Assembler::PopRegisters(const RegisterSet& regs) { |
| bool pop_single = (regs.CpuRegisterCount() & 1) == 1; |
| Register prev = kNoRegister; |
| for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) { |
| Register reg = static_cast<Register>(i); |
| if (regs.ContainsRegister(reg)) { |
| if (pop_single) { |
| // Emit the leftover pop at the beginning instead of the end to |
| // mirror PushRegisters. |
| Pop(reg); |
| pop_single = false; |
| } else if (prev != kNoRegister) { |
| PopPair(/*low=*/prev, /*high=*/reg); |
| prev = kNoRegister; |
| } else { |
| prev = reg; |
| } |
| } |
| } |
| ASSERT(prev == kNoRegister); |
| |
| const intptr_t fpu_regs_count = regs.FpuRegisterCount(); |
| if (fpu_regs_count > 0) { |
| // Fpu registers have the lowest register number at the lowest address. |
| for (intptr_t i = 0; i < kNumberOfVRegisters; ++i) { |
| VRegister fpu_reg = static_cast<VRegister>(i); |
| if (regs.ContainsFpuRegister(fpu_reg)) { |
| PopQuad(fpu_reg); |
| } |
| } |
| } |
| } |
| |
| void Assembler::PushNativeCalleeSavedRegisters() { |
| // Save the callee-saved registers. |
| for (int i = kAbiFirstPreservedCpuReg; i <= kAbiLastPreservedCpuReg; i++) { |
| const Register r = static_cast<Register>(i); |
| // We use str instead of the Push macro because we will be pushing the PP |
| // register when it is not holding a pool-pointer since we are coming from |
| // C++ code. |
| str(r, Address(SP, -1 * target::kWordSize, Address::PreIndex)); |
| } |
| |
| // Save the bottom 64-bits of callee-saved V registers. |
| for (int i = kAbiFirstPreservedFpuReg; i <= kAbiLastPreservedFpuReg; i++) { |
| const VRegister r = static_cast<VRegister>(i); |
| PushDouble(r); |
| } |
| } |
| |
| void Assembler::PopNativeCalleeSavedRegisters() { |
| // Restore the bottom 64-bits of callee-saved V registers. |
| for (int i = kAbiLastPreservedFpuReg; i >= kAbiFirstPreservedFpuReg; i--) { |
| const VRegister r = static_cast<VRegister>(i); |
| PopDouble(r); |
| } |
| |
| // Restore C++ ABI callee-saved registers. |
| for (int i = kAbiLastPreservedCpuReg; i >= kAbiFirstPreservedCpuReg; i--) { |
| Register r = static_cast<Register>(i); |
| // We use ldr instead of the Pop macro because we will be popping the PP |
| // register when it is not holding a pool-pointer since we are returning to |
| // C++ code. We also skip the dart stack pointer SP, since we are still |
| // using it as the stack pointer. |
| ldr(r, Address(SP, 1 * target::kWordSize, Address::PostIndex)); |
| } |
| } |
| |
| bool Assembler::CanGenerateCbzTbz(Register rn, Condition cond) { |
| if (rn == CSP) { |
| return false; |
| } |
| switch (cond) { |
| case EQ: // equal |
| case NE: // not equal |
| case MI: // minus/negative |
| case LT: // signed less than |
| case PL: // plus/positive or zero |
| case GE: // signed greater than or equal |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| void Assembler::GenerateCbzTbz(Register rn, |
| Condition cond, |
| Label* label, |
| OperandSize sz) { |
| ASSERT((sz == kEightBytes) || (sz == kFourBytes)); |
| const int32_t sign_bit = sz == kEightBytes ? 63 : 31; |
| ASSERT(rn != CSP); |
| switch (cond) { |
| case EQ: // equal |
| cbz(label, rn, sz); |
| return; |
| case NE: // not equal |
| cbnz(label, rn, sz); |
| return; |
| case MI: // minus/negative |
| case LT: // signed less than |
| tbnz(label, rn, sign_bit); |
| return; |
| case PL: // plus/positive or zero |
| case GE: // signed greater than or equal |
| tbz(label, rn, sign_bit); |
| return; |
| default: |
| // Only conditions above allow single instruction emission. |
| UNREACHABLE(); |
| } |
| } |
| |
| } // namespace compiler |
| |
| } // namespace dart |
| |
| #endif // defined(TARGET_ARCH_ARM64) |