// Copyright 2017, VIXL authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of ARM Limited nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #include "aarch32/macro-assembler-aarch32.h" #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define CONTEXT_SCOPE \ ContextScope context(this, __FILE__ ":" TOSTRING(__LINE__)) namespace vixl { namespace aarch32 { ExactAssemblyScopeWithoutPoolsCheck::ExactAssemblyScopeWithoutPoolsCheck( MacroAssembler* masm, size_t size, SizePolicy size_policy) : ExactAssemblyScope(masm, size, size_policy, ExactAssemblyScope::kIgnorePools) {} void UseScratchRegisterScope::Open(MacroAssembler* masm) { VIXL_ASSERT(masm_ == NULL); VIXL_ASSERT(masm != NULL); masm_ = masm; old_available_ = masm_->GetScratchRegisterList()->GetList(); old_available_vfp_ = masm_->GetScratchVRegisterList()->GetList(); parent_ = masm->GetCurrentScratchRegisterScope(); masm->SetCurrentScratchRegisterScope(this); } void UseScratchRegisterScope::Close() { if (masm_ != NULL) { // Ensure that scopes nest perfectly, and do not outlive their parents. // This is a run-time check because the order of destruction of objects in // the _same_ scope is implementation-defined, and is likely to change in // optimised builds. VIXL_CHECK(masm_->GetCurrentScratchRegisterScope() == this); masm_->SetCurrentScratchRegisterScope(parent_); masm_->GetScratchRegisterList()->SetList(old_available_); masm_->GetScratchVRegisterList()->SetList(old_available_vfp_); masm_ = NULL; } } bool UseScratchRegisterScope::IsAvailable(const Register& reg) const { VIXL_ASSERT(masm_ != NULL); VIXL_ASSERT(reg.IsValid()); return masm_->GetScratchRegisterList()->Includes(reg); } bool UseScratchRegisterScope::IsAvailable(const VRegister& reg) const { VIXL_ASSERT(masm_ != NULL); VIXL_ASSERT(reg.IsValid()); return masm_->GetScratchVRegisterList()->IncludesAllOf(reg); } Register UseScratchRegisterScope::Acquire() { VIXL_ASSERT(masm_ != NULL); Register reg = masm_->GetScratchRegisterList()->GetFirstAvailableRegister(); VIXL_CHECK(reg.IsValid()); masm_->GetScratchRegisterList()->Remove(reg); return reg; } VRegister UseScratchRegisterScope::AcquireV(unsigned size_in_bits) { switch (size_in_bits) { case kSRegSizeInBits: return AcquireS(); case kDRegSizeInBits: return AcquireD(); case kQRegSizeInBits: return AcquireQ(); default: VIXL_UNREACHABLE(); return NoVReg; } } QRegister UseScratchRegisterScope::AcquireQ() { VIXL_ASSERT(masm_ != NULL); QRegister reg = masm_->GetScratchVRegisterList()->GetFirstAvailableQRegister(); VIXL_CHECK(reg.IsValid()); masm_->GetScratchVRegisterList()->Remove(reg); return reg; } DRegister UseScratchRegisterScope::AcquireD() { VIXL_ASSERT(masm_ != NULL); DRegister reg = masm_->GetScratchVRegisterList()->GetFirstAvailableDRegister(); VIXL_CHECK(reg.IsValid()); masm_->GetScratchVRegisterList()->Remove(reg); return reg; } SRegister UseScratchRegisterScope::AcquireS() { VIXL_ASSERT(masm_ != NULL); SRegister reg = masm_->GetScratchVRegisterList()->GetFirstAvailableSRegister(); VIXL_CHECK(reg.IsValid()); masm_->GetScratchVRegisterList()->Remove(reg); return reg; } void UseScratchRegisterScope::Release(const Register& reg) { VIXL_ASSERT(masm_ != NULL); VIXL_ASSERT(reg.IsValid()); VIXL_ASSERT(!masm_->GetScratchRegisterList()->Includes(reg)); masm_->GetScratchRegisterList()->Combine(reg); } void UseScratchRegisterScope::Release(const VRegister& reg) { VIXL_ASSERT(masm_ != NULL); VIXL_ASSERT(reg.IsValid()); VIXL_ASSERT(!masm_->GetScratchVRegisterList()->IncludesAliasOf(reg)); masm_->GetScratchVRegisterList()->Combine(reg); } void UseScratchRegisterScope::Include(const RegisterList& list) { VIXL_ASSERT(masm_ != NULL); RegisterList excluded_registers(sp, lr, pc); uint32_t mask = list.GetList() & ~excluded_registers.GetList(); RegisterList* available = masm_->GetScratchRegisterList(); available->SetList(available->GetList() | mask); } void UseScratchRegisterScope::Include(const VRegisterList& list) { VIXL_ASSERT(masm_ != NULL); VRegisterList* available = masm_->GetScratchVRegisterList(); available->SetList(available->GetList() | list.GetList()); } void UseScratchRegisterScope::Exclude(const RegisterList& list) { VIXL_ASSERT(masm_ != NULL); RegisterList* available = masm_->GetScratchRegisterList(); available->SetList(available->GetList() & ~list.GetList()); } void UseScratchRegisterScope::Exclude(const VRegisterList& list) { VIXL_ASSERT(masm_ != NULL); VRegisterList* available = masm_->GetScratchVRegisterList(); available->SetList(available->GetList() & ~list.GetList()); } void UseScratchRegisterScope::Exclude(const Operand& operand) { if (operand.IsImmediateShiftedRegister()) { Exclude(operand.GetBaseRegister()); } else if (operand.IsRegisterShiftedRegister()) { Exclude(operand.GetBaseRegister(), operand.GetShiftRegister()); } else { VIXL_ASSERT(operand.IsImmediate()); } } void UseScratchRegisterScope::ExcludeAll() { VIXL_ASSERT(masm_ != NULL); masm_->GetScratchRegisterList()->SetList(0); masm_->GetScratchVRegisterList()->SetList(0); } void MacroAssembler::EnsureEmitPoolsFor(size_t size_arg) { // We skip the check when the pools are blocked. if (ArePoolsBlocked()) return; VIXL_ASSERT(IsUint32(size_arg)); uint32_t size = static_cast(size_arg); if (pool_manager_.MustEmit(GetCursorOffset(), size)) { int32_t new_pc = pool_manager_.Emit(this, GetCursorOffset(), size); VIXL_ASSERT(new_pc == GetCursorOffset()); USE(new_pc); } } void MacroAssembler::HandleOutOfBoundsImmediate(Condition cond, Register tmp, uint32_t imm) { if (IsUintN(16, imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); mov(cond, tmp, imm & 0xffff); return; } if (IsUsingT32()) { if (ImmediateT32::IsImmediateT32(~imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); mvn(cond, tmp, ~imm); return; } } else { if (ImmediateA32::IsImmediateA32(~imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); mvn(cond, tmp, ~imm); return; } } CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, tmp, imm & 0xffff); movt(cond, tmp, imm >> 16); } MemOperand MacroAssembler::MemOperandComputationHelper( Condition cond, Register scratch, Register base, uint32_t offset, uint32_t extra_offset_mask) { VIXL_ASSERT(!AliasesAvailableScratchRegister(scratch)); VIXL_ASSERT(!AliasesAvailableScratchRegister(base)); VIXL_ASSERT(allow_macro_instructions_); VIXL_ASSERT(OutsideITBlock()); // Check for the simple pass-through case. if ((offset & extra_offset_mask) == offset) return MemOperand(base, offset); MacroEmissionCheckScope guard(this); ITScope it_scope(this, &cond, guard); uint32_t load_store_offset = offset & extra_offset_mask; uint32_t add_offset = offset & ~extra_offset_mask; if ((add_offset != 0) && (IsModifiedImmediate(offset) || IsModifiedImmediate(-offset))) { load_store_offset = 0; add_offset = offset; } if (base.IsPC()) { // Special handling for PC bases. We must read the PC in the first // instruction (and only in that instruction), and we must also take care to // keep the same address calculation as loads and stores. For T32, that // means using something like ADR, which uses AlignDown(PC, 4). // We don't handle positive offsets from PC because the intention is not // clear; does the user expect the offset from the current // GetCursorOffset(), or to allow a certain amount of space after the // instruction? VIXL_ASSERT((offset & 0x80000000) != 0); if (IsUsingT32()) { // T32: make the first instruction "SUB (immediate, from PC)" -- an alias // of ADR -- to get behaviour like loads and stores. This ADR can handle // at least as much offset as the load_store_offset so it can replace it. uint32_t sub_pc_offset = (-offset) & 0xfff; load_store_offset = (offset + sub_pc_offset) & extra_offset_mask; add_offset = (offset + sub_pc_offset) & ~extra_offset_mask; ExactAssemblyScope scope(this, k32BitT32InstructionSizeInBytes); sub(cond, scratch, base, sub_pc_offset); if (add_offset == 0) return MemOperand(scratch, load_store_offset); // The rest of the offset can be generated in the usual way. base = scratch; } // A32 can use any SUB instruction, so we don't have to do anything special // here except to ensure that we read the PC first. } add(cond, scratch, base, add_offset); return MemOperand(scratch, load_store_offset); } uint32_t MacroAssembler::GetOffsetMask(InstructionType type, AddrMode addrmode) { switch (type) { case kLdr: case kLdrb: case kStr: case kStrb: if (IsUsingA32() || (addrmode == Offset)) { return 0xfff; } else { return 0xff; } case kLdrsb: case kLdrh: case kLdrsh: case kStrh: if (IsUsingT32() && (addrmode == Offset)) { return 0xfff; } else { return 0xff; } case kVldr: case kVstr: return 0x3fc; case kLdrd: case kStrd: if (IsUsingA32()) { return 0xff; } else { return 0x3fc; } default: VIXL_UNREACHABLE(); return 0; } } HARDFLOAT void PrintfTrampolineRRRR( const char* format, uint32_t a, uint32_t b, uint32_t c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineRRRD( const char* format, uint32_t a, uint32_t b, uint32_t c, double d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineRRDR( const char* format, uint32_t a, uint32_t b, double c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineRRDD( const char* format, uint32_t a, uint32_t b, double c, double d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineRDRR( const char* format, uint32_t a, double b, uint32_t c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineRDRD( const char* format, uint32_t a, double b, uint32_t c, double d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineRDDR( const char* format, uint32_t a, double b, double c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineRDDD( const char* format, uint32_t a, double b, double c, double d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDRRR( const char* format, double a, uint32_t b, uint32_t c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDRRD( const char* format, double a, uint32_t b, uint32_t c, double d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDRDR( const char* format, double a, uint32_t b, double c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDRDD( const char* format, double a, uint32_t b, double c, double d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDDRR( const char* format, double a, double b, uint32_t c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDDRD( const char* format, double a, double b, uint32_t c, double d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDDDR( const char* format, double a, double b, double c, uint32_t d) { printf(format, a, b, c, d); } HARDFLOAT void PrintfTrampolineDDDD( const char* format, double a, double b, double c, double d) { printf(format, a, b, c, d); } void MacroAssembler::Printf(const char* format, CPURegister reg1, CPURegister reg2, CPURegister reg3, CPURegister reg4) { // Exclude all registers from the available scratch registers, so // that we are able to use ip below. // TODO: Refactor this function to use UseScratchRegisterScope // for temporary registers below. UseScratchRegisterScope scratch(this); scratch.ExcludeAll(); if (generate_simulator_code_) { PushRegister(reg4); PushRegister(reg3); PushRegister(reg2); PushRegister(reg1); Push(RegisterList(r0, r1)); StringLiteral* format_literal = new StringLiteral(format, RawLiteral::kDeletedOnPlacementByPool); Adr(r0, format_literal); uint32_t args = (reg4.GetType() << 12) | (reg3.GetType() << 8) | (reg2.GetType() << 4) | reg1.GetType(); Mov(r1, args); Hvc(kPrintfCode); Pop(RegisterList(r0, r1)); int size = reg4.GetRegSizeInBytes() + reg3.GetRegSizeInBytes() + reg2.GetRegSizeInBytes() + reg1.GetRegSizeInBytes(); Drop(size); } else { // Generate on a native platform => 32 bit environment. // Preserve core registers r0-r3, r12, r14 const uint32_t saved_registers_mask = kCallerSavedRegistersMask | (1 << r5.GetCode()); Push(RegisterList(saved_registers_mask)); // Push VFP registers. Vpush(Untyped64, DRegisterList(d0, 8)); if (Has32DRegs()) Vpush(Untyped64, DRegisterList(d16, 16)); // Search one register which has been saved and which doesn't need to be // printed. RegisterList available_registers(kCallerSavedRegistersMask); if (reg1.GetType() == CPURegister::kRRegister) { available_registers.Remove(Register(reg1.GetCode())); } if (reg2.GetType() == CPURegister::kRRegister) { available_registers.Remove(Register(reg2.GetCode())); } if (reg3.GetType() == CPURegister::kRRegister) { available_registers.Remove(Register(reg3.GetCode())); } if (reg4.GetType() == CPURegister::kRRegister) { available_registers.Remove(Register(reg4.GetCode())); } Register tmp = available_registers.GetFirstAvailableRegister(); VIXL_ASSERT(tmp.GetType() == CPURegister::kRRegister); // Push the flags. Mrs(tmp, APSR); Push(tmp); Vmrs(RegisterOrAPSR_nzcv(tmp.GetCode()), FPSCR); Push(tmp); // Push the registers to print on the stack. PushRegister(reg4); PushRegister(reg3); PushRegister(reg2); PushRegister(reg1); int core_count = 1; int vfp_count = 0; uint32_t printf_type = 0; // Pop the registers to print and store them into r1-r3 and/or d0-d3. // Reg4 may stay into the stack if all the register to print are core // registers. PreparePrintfArgument(reg1, &core_count, &vfp_count, &printf_type); PreparePrintfArgument(reg2, &core_count, &vfp_count, &printf_type); PreparePrintfArgument(reg3, &core_count, &vfp_count, &printf_type); PreparePrintfArgument(reg4, &core_count, &vfp_count, &printf_type); // Ensure that the stack is aligned on 8 bytes. And(r5, sp, 0x7); if (core_count == 5) { // One 32 bit argument (reg4) has been left on the stack => align the // stack // before the argument. Pop(r0); Sub(sp, sp, r5); Push(r0); } else { Sub(sp, sp, r5); } // Select the right trampoline depending on the arguments. uintptr_t address; switch (printf_type) { case 0: address = reinterpret_cast(PrintfTrampolineRRRR); break; case 1: address = reinterpret_cast(PrintfTrampolineDRRR); break; case 2: address = reinterpret_cast(PrintfTrampolineRDRR); break; case 3: address = reinterpret_cast(PrintfTrampolineDDRR); break; case 4: address = reinterpret_cast(PrintfTrampolineRRDR); break; case 5: address = reinterpret_cast(PrintfTrampolineDRDR); break; case 6: address = reinterpret_cast(PrintfTrampolineRDDR); break; case 7: address = reinterpret_cast(PrintfTrampolineDDDR); break; case 8: address = reinterpret_cast(PrintfTrampolineRRRD); break; case 9: address = reinterpret_cast(PrintfTrampolineDRRD); break; case 10: address = reinterpret_cast(PrintfTrampolineRDRD); break; case 11: address = reinterpret_cast(PrintfTrampolineDDRD); break; case 12: address = reinterpret_cast(PrintfTrampolineRRDD); break; case 13: address = reinterpret_cast(PrintfTrampolineDRDD); break; case 14: address = reinterpret_cast(PrintfTrampolineRDDD); break; case 15: address = reinterpret_cast(PrintfTrampolineDDDD); break; default: VIXL_UNREACHABLE(); address = reinterpret_cast(PrintfTrampolineRRRR); break; } StringLiteral* format_literal = new StringLiteral(format, RawLiteral::kDeletedOnPlacementByPool); Adr(r0, format_literal); Mov(ip, Operand::From(address)); Blx(ip); // If register reg4 was left on the stack => skip it. if (core_count == 5) Drop(kRegSizeInBytes); // Restore the stack as it was before alignment. Add(sp, sp, r5); // Restore the flags. Pop(tmp); Vmsr(FPSCR, tmp); Pop(tmp); Msr(APSR_nzcvqg, tmp); // Restore the regsisters. if (Has32DRegs()) Vpop(Untyped64, DRegisterList(d16, 16)); Vpop(Untyped64, DRegisterList(d0, 8)); Pop(RegisterList(saved_registers_mask)); } } void MacroAssembler::PushRegister(CPURegister reg) { switch (reg.GetType()) { case CPURegister::kNoRegister: break; case CPURegister::kRRegister: Push(Register(reg.GetCode())); break; case CPURegister::kSRegister: Vpush(Untyped32, SRegisterList(SRegister(reg.GetCode()))); break; case CPURegister::kDRegister: Vpush(Untyped64, DRegisterList(DRegister(reg.GetCode()))); break; case CPURegister::kQRegister: VIXL_UNIMPLEMENTED(); break; } } void MacroAssembler::PreparePrintfArgument(CPURegister reg, int* core_count, int* vfp_count, uint32_t* printf_type) { switch (reg.GetType()) { case CPURegister::kNoRegister: break; case CPURegister::kRRegister: VIXL_ASSERT(*core_count <= 4); if (*core_count < 4) Pop(Register(*core_count)); *core_count += 1; break; case CPURegister::kSRegister: VIXL_ASSERT(*vfp_count < 4); *printf_type |= 1 << (*core_count + *vfp_count - 1); Vpop(Untyped32, SRegisterList(SRegister(*vfp_count * 2))); Vcvt(F64, F32, DRegister(*vfp_count), SRegister(*vfp_count * 2)); *vfp_count += 1; break; case CPURegister::kDRegister: VIXL_ASSERT(*vfp_count < 4); *printf_type |= 1 << (*core_count + *vfp_count - 1); Vpop(Untyped64, DRegisterList(DRegister(*vfp_count))); *vfp_count += 1; break; case CPURegister::kQRegister: VIXL_UNIMPLEMENTED(); break; } } void MacroAssembler::Delegate(InstructionType type, InstructionCondROp instruction, Condition cond, Register rn, const Operand& operand) { VIXL_ASSERT((type == kMovt) || (type == kSxtb16) || (type == kTeq) || (type == kUxtb16)); if (type == kMovt) { VIXL_ABORT_WITH_MSG("`Movt` expects a 16-bit immediate.\n"); } // This delegate only supports teq with immediates. CONTEXT_SCOPE; if ((type == kTeq) && operand.IsImmediate()) { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); HandleOutOfBoundsImmediate(cond, scratch, operand.GetImmediate()); CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); teq(cond, rn, scratch); return; } Assembler::Delegate(type, instruction, cond, rn, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondSizeROp instruction, Condition cond, EncodingSize size, Register rn, const Operand& operand) { CONTEXT_SCOPE; VIXL_ASSERT(size.IsBest()); VIXL_ASSERT((type == kCmn) || (type == kCmp) || (type == kMov) || (type == kMovs) || (type == kMvn) || (type == kMvns) || (type == kSxtb) || (type == kSxth) || (type == kTst) || (type == kUxtb) || (type == kUxth)); if (IsUsingT32() && operand.IsRegisterShiftedRegister()) { VIXL_ASSERT((type != kMov) || (type != kMovs)); InstructionCondRROp shiftop = NULL; switch (operand.GetShift().GetType()) { case LSL: shiftop = &Assembler::lsl; break; case LSR: shiftop = &Assembler::lsr; break; case ASR: shiftop = &Assembler::asr; break; case RRX: // A RegisterShiftedRegister operand cannot have a shift of type RRX. VIXL_UNREACHABLE(); break; case ROR: shiftop = &Assembler::ror; break; default: VIXL_UNREACHABLE(); } if (shiftop != NULL) { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); (this->*shiftop)(cond, scratch, operand.GetBaseRegister(), operand.GetShiftRegister()); (this->*instruction)(cond, size, rn, scratch); return; } } if (operand.IsImmediate()) { uint32_t imm = operand.GetImmediate(); switch (type) { case kMov: case kMovs: if (!rn.IsPC()) { // Immediate is too large, but not using PC, so handle with mov{t}. HandleOutOfBoundsImmediate(cond, rn, imm); if (type == kMovs) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); tst(cond, rn, rn); } return; } else if (type == kMov) { VIXL_ASSERT(IsUsingA32() || cond.Is(al)); // Immediate is too large and using PC, so handle using a temporary // register. UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); HandleOutOfBoundsImmediate(al, scratch, imm); CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); bx(cond, scratch); return; } break; case kCmn: case kCmp: if (IsUsingA32() || !rn.IsPC()) { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); HandleOutOfBoundsImmediate(cond, scratch, imm); CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rn, scratch); return; } break; case kMvn: case kMvns: if (!rn.IsPC()) { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); HandleOutOfBoundsImmediate(cond, scratch, imm); CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rn, scratch); return; } break; case kTst: if (IsUsingA32() || !rn.IsPC()) { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); HandleOutOfBoundsImmediate(cond, scratch, imm); CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rn, scratch); return; } break; default: // kSxtb, Sxth, Uxtb, Uxth break; } } Assembler::Delegate(type, instruction, cond, size, rn, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondRROp instruction, Condition cond, Register rd, Register rn, const Operand& operand) { if ((type == kSxtab) || (type == kSxtab16) || (type == kSxtah) || (type == kUxtab) || (type == kUxtab16) || (type == kUxtah) || (type == kPkhbt) || (type == kPkhtb)) { UnimplementedDelegate(type); return; } // This delegate only handles the following instructions. VIXL_ASSERT((type == kOrn) || (type == kOrns) || (type == kRsc) || (type == kRscs)); CONTEXT_SCOPE; // T32 does not support register shifted register operands, emulate it. if (IsUsingT32() && operand.IsRegisterShiftedRegister()) { InstructionCondRROp shiftop = NULL; switch (operand.GetShift().GetType()) { case LSL: shiftop = &Assembler::lsl; break; case LSR: shiftop = &Assembler::lsr; break; case ASR: shiftop = &Assembler::asr; break; case RRX: // A RegisterShiftedRegister operand cannot have a shift of type RRX. VIXL_UNREACHABLE(); break; case ROR: shiftop = &Assembler::ror; break; default: VIXL_UNREACHABLE(); } if (shiftop != NULL) { UseScratchRegisterScope temps(this); Register rm = operand.GetBaseRegister(); Register rs = operand.GetShiftRegister(); // Try to use rd as a scratch register. We can do this if it aliases rs or // rm (because we read them in the first instruction), but not rn. if (!rd.Is(rn)) temps.Include(rd); Register scratch = temps.Acquire(); // TODO: The scope length was measured empirically. We should analyse the // worst-case size and add targetted tests. CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); (this->*shiftop)(cond, scratch, rm, rs); (this->*instruction)(cond, rd, rn, scratch); return; } } // T32 does not have a Rsc instruction, negate the lhs input and turn it into // an Adc. Adc and Rsc are equivalent using a bitwise NOT: // adc rd, rn, operand <-> rsc rd, NOT(rn), operand if (IsUsingT32() && ((type == kRsc) || (type == kRscs))) { // The RegisterShiftRegister case should have been handled above. VIXL_ASSERT(!operand.IsRegisterShiftedRegister()); UseScratchRegisterScope temps(this); // Try to use rd as a scratch register. We can do this if it aliases rn // (because we read it in the first instruction), but not rm. temps.Include(rd); temps.Exclude(operand); Register negated_rn = temps.Acquire(); { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); mvn(cond, negated_rn, rn); } if (type == kRsc) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); adc(cond, rd, negated_rn, operand); return; } // TODO: We shouldn't have to specify how much space the next instruction // needs. CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); adcs(cond, rd, negated_rn, operand); return; } if (operand.IsImmediate()) { // If the immediate can be encoded when inverted, turn Orn into Orr. // Otherwise rely on HandleOutOfBoundsImmediate to generate a series of // mov. int32_t imm = operand.GetSignedImmediate(); if (((type == kOrn) || (type == kOrns)) && IsModifiedImmediate(~imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); switch (type) { case kOrn: orr(cond, rd, rn, ~imm); return; case kOrns: orrs(cond, rd, rn, ~imm); return; default: VIXL_UNREACHABLE(); break; } } } // A32 does not have a Orn instruction, negate the rhs input and turn it into // a Orr. if (IsUsingA32() && ((type == kOrn) || (type == kOrns))) { // TODO: orn r0, r1, imm -> orr r0, r1, neg(imm) if doable // mvn r0, r2 // orr r0, r1, r0 Register scratch; UseScratchRegisterScope temps(this); // Try to use rd as a scratch register. We can do this if it aliases rs or // rm (because we read them in the first instruction), but not rn. if (!rd.Is(rn)) temps.Include(rd); scratch = temps.Acquire(); { // TODO: We shouldn't have to specify how much space the next instruction // needs. CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); mvn(cond, scratch, operand); } if (type == kOrns) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); orrs(cond, rd, rn, scratch); return; } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); orr(cond, rd, rn, scratch); return; } if (operand.IsImmediate()) { UseScratchRegisterScope temps(this); // Allow using the destination as a scratch register if possible. if (!rd.Is(rn)) temps.Include(rd); Register scratch = temps.Acquire(); int32_t imm = operand.GetSignedImmediate(); HandleOutOfBoundsImmediate(cond, scratch, imm); CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, rd, rn, scratch); return; } Assembler::Delegate(type, instruction, cond, rd, rn, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondSizeRL instruction, Condition cond, EncodingSize size, Register rd, Location* location) { VIXL_ASSERT((type == kLdr) || (type == kAdr)); CONTEXT_SCOPE; VIXL_ASSERT(size.IsBest()); if ((type == kLdr) && location->IsBound()) { CodeBufferCheckScope scope(this, 5 * kMaxInstructionSizeInBytes); UseScratchRegisterScope temps(this); temps.Include(rd); uint32_t mask = GetOffsetMask(type, Offset); ldr(rd, MemOperandComputationHelper(cond, temps.Acquire(), location, mask)); return; } Assembler::Delegate(type, instruction, cond, size, rd, location); } bool MacroAssembler::GenerateSplitInstruction( InstructionCondSizeRROp instruction, Condition cond, Register rd, Register rn, uint32_t imm, uint32_t mask) { uint32_t high = imm & ~mask; if (!IsModifiedImmediate(high) && !rn.IsPC()) return false; // If high is a modified immediate, we can perform the operation with // only 2 instructions. // Else, if rn is PC, we want to avoid moving PC into a temporary. // Therefore, we also use the pattern even if the second call may // generate 3 instructions. uint32_t low = imm & mask; CodeBufferCheckScope scope(this, (rn.IsPC() ? 4 : 2) * kMaxInstructionSizeInBytes); (this->*instruction)(cond, Best, rd, rn, low); (this->*instruction)(cond, Best, rd, rd, high); return true; } void MacroAssembler::Delegate(InstructionType type, InstructionCondSizeRROp instruction, Condition cond, EncodingSize size, Register rd, Register rn, const Operand& operand) { VIXL_ASSERT( (type == kAdc) || (type == kAdcs) || (type == kAdd) || (type == kAdds) || (type == kAnd) || (type == kAnds) || (type == kAsr) || (type == kAsrs) || (type == kBic) || (type == kBics) || (type == kEor) || (type == kEors) || (type == kLsl) || (type == kLsls) || (type == kLsr) || (type == kLsrs) || (type == kOrr) || (type == kOrrs) || (type == kRor) || (type == kRors) || (type == kRsb) || (type == kRsbs) || (type == kSbc) || (type == kSbcs) || (type == kSub) || (type == kSubs)); CONTEXT_SCOPE; VIXL_ASSERT(size.IsBest()); if (IsUsingT32() && operand.IsRegisterShiftedRegister()) { InstructionCondRROp shiftop = NULL; switch (operand.GetShift().GetType()) { case LSL: shiftop = &Assembler::lsl; break; case LSR: shiftop = &Assembler::lsr; break; case ASR: shiftop = &Assembler::asr; break; case RRX: // A RegisterShiftedRegister operand cannot have a shift of type RRX. VIXL_UNREACHABLE(); break; case ROR: shiftop = &Assembler::ror; break; default: VIXL_UNREACHABLE(); } if (shiftop != NULL) { UseScratchRegisterScope temps(this); Register rm = operand.GetBaseRegister(); Register rs = operand.GetShiftRegister(); // Try to use rd as a scratch register. We can do this if it aliases rs or // rm (because we read them in the first instruction), but not rn. if (!rd.Is(rn)) temps.Include(rd); Register scratch = temps.Acquire(); CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); (this->*shiftop)(cond, scratch, rm, rs); (this->*instruction)(cond, size, rd, rn, scratch); return; } } if (operand.IsImmediate()) { int32_t imm = operand.GetSignedImmediate(); if (ImmediateT32::IsImmediateT32(~imm)) { if (IsUsingT32()) { switch (type) { case kOrr: orn(cond, rd, rn, ~imm); return; case kOrrs: orns(cond, rd, rn, ~imm); return; default: break; } } } if (imm < 0) { InstructionCondSizeRROp asmcb = NULL; // Add and sub are equivalent using an arithmetic negation: // add rd, rn, #imm <-> sub rd, rn, - #imm // Add and sub with carry are equivalent using a bitwise NOT: // adc rd, rn, #imm <-> sbc rd, rn, NOT #imm switch (type) { case kAdd: asmcb = &Assembler::sub; imm = -imm; break; case kAdds: asmcb = &Assembler::subs; imm = -imm; break; case kSub: asmcb = &Assembler::add; imm = -imm; break; case kSubs: asmcb = &Assembler::adds; imm = -imm; break; case kAdc: asmcb = &Assembler::sbc; imm = ~imm; break; case kAdcs: asmcb = &Assembler::sbcs; imm = ~imm; break; case kSbc: asmcb = &Assembler::adc; imm = ~imm; break; case kSbcs: asmcb = &Assembler::adcs; imm = ~imm; break; default: break; } if (asmcb != NULL) { CodeBufferCheckScope scope(this, 4 * kMaxInstructionSizeInBytes); (this->*asmcb)(cond, size, rd, rn, Operand(imm)); return; } } // When rn is PC, only handle negative offsets. The correct way to handle // positive offsets isn't clear; does the user want the offset from the // start of the macro, or from the end (to allow a certain amount of space)? // When type is Add or Sub, imm is always positive (imm < 0 has just been // handled and imm == 0 would have been generated without the need of a // delegate). Therefore, only add to PC is forbidden here. if ((((type == kAdd) && !rn.IsPC()) || (type == kSub)) && (IsUsingA32() || (!rd.IsPC() && !rn.IsPC()))) { VIXL_ASSERT(imm > 0); // Try to break the constant into two modified immediates. // For T32 also try to break the constant into one imm12 and one modified // immediate. Count the trailing zeroes and get the biggest even value. int trailing_zeroes = CountTrailingZeros(imm) & ~1u; uint32_t mask = ((trailing_zeroes < 4) && IsUsingT32()) ? 0xfff : (0xff << trailing_zeroes); if (GenerateSplitInstruction(instruction, cond, rd, rn, imm, mask)) { return; } InstructionCondSizeRROp asmcb = NULL; switch (type) { case kAdd: asmcb = &Assembler::sub; break; case kSub: asmcb = &Assembler::add; break; default: VIXL_UNREACHABLE(); } if (GenerateSplitInstruction(asmcb, cond, rd, rn, -imm, mask)) { return; } } UseScratchRegisterScope temps(this); // Allow using the destination as a scratch register if possible. if (!rd.Is(rn)) temps.Include(rd); if (rn.IsPC()) { // If we're reading the PC, we need to do it in the first instruction, // otherwise we'll read the wrong value. We rely on this to handle the // long-range PC-relative MemOperands which can result from user-managed // literals. // Only handle negative offsets. The correct way to handle positive // offsets isn't clear; does the user want the offset from the start of // the macro, or from the end (to allow a certain amount of space)? bool offset_is_negative_or_zero = (imm <= 0); switch (type) { case kAdd: case kAdds: offset_is_negative_or_zero = (imm <= 0); break; case kSub: case kSubs: offset_is_negative_or_zero = (imm >= 0); break; case kAdc: case kAdcs: offset_is_negative_or_zero = (imm < 0); break; case kSbc: case kSbcs: offset_is_negative_or_zero = (imm > 0); break; default: break; } if (offset_is_negative_or_zero) { { rn = temps.Acquire(); CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); mov(cond, rn, pc); } // Recurse rather than falling through, to try to get the immediate into // a single instruction. CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rd, rn, operand); return; } } else { Register scratch = temps.Acquire(); // TODO: The scope length was measured empirically. We should analyse the // worst-case size and add targetted tests. CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); mov(cond, scratch, operand.GetImmediate()); (this->*instruction)(cond, size, rd, rn, scratch); return; } } Assembler::Delegate(type, instruction, cond, size, rd, rn, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionRL instruction, Register rn, Location* location) { VIXL_ASSERT((type == kCbz) || (type == kCbnz)); CONTEXT_SCOPE; CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); if (IsUsingA32()) { if (type == kCbz) { VIXL_ABORT_WITH_MSG("Cbz is only available for T32.\n"); } else { VIXL_ABORT_WITH_MSG("Cbnz is only available for T32.\n"); } } else if (rn.IsLow()) { switch (type) { case kCbnz: { Label done; cbz(rn, &done); b(location); Bind(&done); return; } case kCbz: { Label done; cbnz(rn, &done); b(location); Bind(&done); return; } default: break; } } Assembler::Delegate(type, instruction, rn, location); } template static inline bool IsI64BitPattern(T imm) { for (T mask = 0xff << ((sizeof(T) - 1) * 8); mask != 0; mask >>= 8) { if (((imm & mask) != mask) && ((imm & mask) != 0)) return false; } return true; } template static inline bool IsI8BitPattern(T imm) { uint8_t imm8 = imm & 0xff; for (unsigned rep = sizeof(T) - 1; rep > 0; rep--) { imm >>= 8; if ((imm & 0xff) != imm8) return false; } return true; } static inline bool CanBeInverted(uint32_t imm32) { uint32_t fill8 = 0; if ((imm32 & 0xffffff00) == 0xffffff00) { // 11111111 11111111 11111111 abcdefgh return true; } if (((imm32 & 0xff) == 0) || ((imm32 & 0xff) == 0xff)) { fill8 = imm32 & 0xff; imm32 >>= 8; if ((imm32 >> 8) == 0xffff) { // 11111111 11111111 abcdefgh 00000000 // or 11111111 11111111 abcdefgh 11111111 return true; } if ((imm32 & 0xff) == fill8) { imm32 >>= 8; if ((imm32 >> 8) == 0xff) { // 11111111 abcdefgh 00000000 00000000 // or 11111111 abcdefgh 11111111 11111111 return true; } if ((fill8 == 0xff) && ((imm32 & 0xff) == 0xff)) { // abcdefgh 11111111 11111111 11111111 return true; } } } return false; } template static inline RES replicate(T imm) { VIXL_ASSERT((sizeof(RES) > sizeof(T)) && (((sizeof(RES) / sizeof(T)) * sizeof(T)) == sizeof(RES))); RES res = imm; for (unsigned i = sizeof(RES) / sizeof(T) - 1; i > 0; i--) { res = (res << (sizeof(T) * 8)) | imm; } return res; } void MacroAssembler::Delegate(InstructionType type, InstructionCondDtSSop instruction, Condition cond, DataType dt, SRegister rd, const SOperand& operand) { CONTEXT_SCOPE; if (type == kVmov) { if (operand.IsImmediate() && dt.Is(F32)) { const NeonImmediate& neon_imm = operand.GetNeonImmediate(); if (neon_imm.CanConvert()) { // movw ip, imm16 // movk ip, imm16 // vmov s0, ip UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); float f = neon_imm.GetImmediate(); // TODO: The scope length was measured empirically. We should analyse // the // worst-case size and add targetted tests. CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); mov(cond, scratch, FloatToRawbits(f)); vmov(cond, rd, scratch); return; } } } Assembler::Delegate(type, instruction, cond, dt, rd, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondDtDDop instruction, Condition cond, DataType dt, DRegister rd, const DOperand& operand) { CONTEXT_SCOPE; if (type == kVmov) { if (operand.IsImmediate()) { const NeonImmediate& neon_imm = operand.GetNeonImmediate(); switch (dt.GetValue()) { case I32: if (neon_imm.CanConvert()) { uint32_t imm = neon_imm.GetImmediate(); // vmov.i32 d0, 0xabababab will translate into vmov.i8 d0, 0xab if (IsI8BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I8, rd, imm & 0xff); return; } // vmov.i32 d0, 0xff0000ff will translate into // vmov.i64 d0, 0xff0000ffff0000ff if (IsI64BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I64, rd, replicate(imm)); return; } // vmov.i32 d0, 0xffab0000 will translate into // vmvn.i32 d0, 0x0054ffff if (cond.Is(al) && CanBeInverted(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmvn(I32, rd, ~imm); return; } } break; case I16: if (neon_imm.CanConvert()) { uint16_t imm = neon_imm.GetImmediate(); // vmov.i16 d0, 0xabab will translate into vmov.i8 d0, 0xab if (IsI8BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I8, rd, imm & 0xff); return; } } break; case I64: if (neon_imm.CanConvert()) { uint64_t imm = neon_imm.GetImmediate(); // vmov.i64 d0, -1 will translate into vmov.i8 d0, 0xff if (IsI8BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I8, rd, imm & 0xff); return; } // mov ip, lo(imm64) // vdup d0, ip // vdup is prefered to 'vmov d0[0]' as d0[1] does not need to be // preserved { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); { // TODO: The scope length was measured empirically. We should // analyse the // worst-case size and add targetted tests. CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, scratch, static_cast(imm & 0xffffffff)); } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vdup(cond, Untyped32, rd, scratch); } // mov ip, hi(imm64) // vmov d0[1], ip { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); { // TODO: The scope length was measured empirically. We should // analyse the // worst-case size and add targetted tests. CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, scratch, static_cast(imm >> 32)); } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, Untyped32, DRegisterLane(rd, 1), scratch); } return; } break; default: break; } VIXL_ASSERT(!dt.Is(I8)); // I8 cases should have been handled already. if ((dt.Is(I16) || dt.Is(I32)) && neon_imm.CanConvert()) { // mov ip, imm32 // vdup.16 d0, ip UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); { CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, scratch, neon_imm.GetImmediate()); } DataTypeValue vdup_dt = Untyped32; switch (dt.GetValue()) { case I16: vdup_dt = Untyped16; break; case I32: vdup_dt = Untyped32; break; default: VIXL_UNREACHABLE(); } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vdup(cond, vdup_dt, rd, scratch); return; } if (dt.Is(F32) && neon_imm.CanConvert()) { float f = neon_imm.GetImmediate(); // Punt to vmov.i32 // TODO: The scope length was guessed based on the double case below. We // should analyse the worst-case size and add targetted tests. CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); vmov(cond, I32, rd, FloatToRawbits(f)); return; } if (dt.Is(F64) && neon_imm.CanConvert()) { // Punt to vmov.i64 double d = neon_imm.GetImmediate(); // TODO: The scope length was measured empirically. We should analyse // the // worst-case size and add targetted tests. CodeBufferCheckScope scope(this, 6 * kMaxInstructionSizeInBytes); vmov(cond, I64, rd, DoubleToRawbits(d)); return; } } } Assembler::Delegate(type, instruction, cond, dt, rd, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondDtQQop instruction, Condition cond, DataType dt, QRegister rd, const QOperand& operand) { CONTEXT_SCOPE; if (type == kVmov) { if (operand.IsImmediate()) { const NeonImmediate& neon_imm = operand.GetNeonImmediate(); switch (dt.GetValue()) { case I32: if (neon_imm.CanConvert()) { uint32_t imm = neon_imm.GetImmediate(); // vmov.i32 d0, 0xabababab will translate into vmov.i8 d0, 0xab if (IsI8BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I8, rd, imm & 0xff); return; } // vmov.i32 d0, 0xff0000ff will translate into // vmov.i64 d0, 0xff0000ffff0000ff if (IsI64BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I64, rd, replicate(imm)); return; } // vmov.i32 d0, 0xffab0000 will translate into // vmvn.i32 d0, 0x0054ffff if (CanBeInverted(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmvn(cond, I32, rd, ~imm); return; } } break; case I16: if (neon_imm.CanConvert()) { uint16_t imm = neon_imm.GetImmediate(); // vmov.i16 d0, 0xabab will translate into vmov.i8 d0, 0xab if (IsI8BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I8, rd, imm & 0xff); return; } } break; case I64: if (neon_imm.CanConvert()) { uint64_t imm = neon_imm.GetImmediate(); // vmov.i64 d0, -1 will translate into vmov.i8 d0, 0xff if (IsI8BitPattern(imm)) { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, I8, rd, imm & 0xff); return; } // mov ip, lo(imm64) // vdup q0, ip // vdup is prefered to 'vmov d0[0]' as d0[1-3] don't need to be // preserved { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); { CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, scratch, static_cast(imm & 0xffffffff)); } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vdup(cond, Untyped32, rd, scratch); } // mov ip, hi(imm64) // vmov.i32 d0[1], ip // vmov d1, d0 { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); { CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, scratch, static_cast(imm >> 32)); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, Untyped32, DRegisterLane(rd.GetLowDRegister(), 1), scratch); } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vmov(cond, F64, rd.GetHighDRegister(), rd.GetLowDRegister()); } return; } break; default: break; } VIXL_ASSERT(!dt.Is(I8)); // I8 cases should have been handled already. if ((dt.Is(I16) || dt.Is(I32)) && neon_imm.CanConvert()) { // mov ip, imm32 // vdup.16 d0, ip UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); { CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, scratch, neon_imm.GetImmediate()); } DataTypeValue vdup_dt = Untyped32; switch (dt.GetValue()) { case I16: vdup_dt = Untyped16; break; case I32: vdup_dt = Untyped32; break; default: VIXL_UNREACHABLE(); } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); vdup(cond, vdup_dt, rd, scratch); return; } if (dt.Is(F32) && neon_imm.CanConvert()) { // Punt to vmov.i64 float f = neon_imm.GetImmediate(); CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); vmov(cond, I32, rd, FloatToRawbits(f)); return; } if (dt.Is(F64) && neon_imm.CanConvert()) { // Use vmov to create the double in the low D register, then duplicate // it into the high D register. double d = neon_imm.GetImmediate(); CodeBufferCheckScope scope(this, 7 * kMaxInstructionSizeInBytes); vmov(cond, F64, rd.GetLowDRegister(), d); vmov(cond, F64, rd.GetHighDRegister(), rd.GetLowDRegister()); return; } } } Assembler::Delegate(type, instruction, cond, dt, rd, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondRL instruction, Condition cond, Register rt, Location* location) { VIXL_ASSERT((type == kLdrb) || (type == kLdrh) || (type == kLdrsb) || (type == kLdrsh)); CONTEXT_SCOPE; if (location->IsBound()) { CodeBufferCheckScope scope(this, 5 * kMaxInstructionSizeInBytes); UseScratchRegisterScope temps(this); temps.Include(rt); Register scratch = temps.Acquire(); uint32_t mask = GetOffsetMask(type, Offset); switch (type) { case kLdrb: ldrb(rt, MemOperandComputationHelper(cond, scratch, location, mask)); return; case kLdrh: ldrh(rt, MemOperandComputationHelper(cond, scratch, location, mask)); return; case kLdrsb: ldrsb(rt, MemOperandComputationHelper(cond, scratch, location, mask)); return; case kLdrsh: ldrsh(rt, MemOperandComputationHelper(cond, scratch, location, mask)); return; default: VIXL_UNREACHABLE(); } return; } Assembler::Delegate(type, instruction, cond, rt, location); } void MacroAssembler::Delegate(InstructionType type, InstructionCondRRL instruction, Condition cond, Register rt, Register rt2, Location* location) { VIXL_ASSERT(type == kLdrd); CONTEXT_SCOPE; if (location->IsBound()) { CodeBufferCheckScope scope(this, 6 * kMaxInstructionSizeInBytes); UseScratchRegisterScope temps(this); temps.Include(rt, rt2); Register scratch = temps.Acquire(); uint32_t mask = GetOffsetMask(type, Offset); ldrd(rt, rt2, MemOperandComputationHelper(cond, scratch, location, mask)); return; } Assembler::Delegate(type, instruction, cond, rt, rt2, location); } void MacroAssembler::Delegate(InstructionType type, InstructionCondSizeRMop instruction, Condition cond, EncodingSize size, Register rd, const MemOperand& operand) { CONTEXT_SCOPE; VIXL_ASSERT(size.IsBest()); VIXL_ASSERT((type == kLdr) || (type == kLdrb) || (type == kLdrh) || (type == kLdrsb) || (type == kLdrsh) || (type == kStr) || (type == kStrb) || (type == kStrh)); if (operand.IsImmediate()) { const Register& rn = operand.GetBaseRegister(); AddrMode addrmode = operand.GetAddrMode(); int32_t offset = operand.GetOffsetImmediate(); uint32_t extra_offset_mask = GetOffsetMask(type, addrmode); // Try to maximize the offset used by the MemOperand (load_store_offset). // Add the part which can't be used by the MemOperand (add_offset). uint32_t load_store_offset = offset & extra_offset_mask; uint32_t add_offset = offset & ~extra_offset_mask; if ((add_offset != 0) && (IsModifiedImmediate(offset) || IsModifiedImmediate(-offset))) { load_store_offset = 0; add_offset = offset; } switch (addrmode) { case PreIndex: // Avoid the unpredictable case 'str r0, [r0, imm]!' if (!rn.Is(rd)) { // Pre-Indexed case: // ldr r0, [r1, 12345]! will translate into // add r1, r1, 12345 // ldr r0, [r1] { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, add_offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rd, MemOperand(rn, load_store_offset, PreIndex)); } return; } break; case Offset: { UseScratchRegisterScope temps(this); // Allow using the destination as a scratch register if possible. if ((type != kStr) && (type != kStrb) && (type != kStrh) && !rd.Is(rn)) { temps.Include(rd); } Register scratch = temps.Acquire(); // Offset case: // ldr r0, [r1, 12345] will translate into // add r0, r1, 12345 // ldr r0, [r0] { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, scratch, rn, add_offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rd, MemOperand(scratch, load_store_offset)); } return; } case PostIndex: // Avoid the unpredictable case 'ldr r0, [r0], imm' if (!rn.Is(rd)) { // Post-indexed case: // ldr r0. [r1], imm32 will translate into // ldr r0, [r1] // movw ip. imm32 & 0xffffffff // movt ip, imm32 >> 16 // add r1, r1, ip { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rd, MemOperand(rn, load_store_offset, PostIndex)); } { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, add_offset); } return; } break; } } else if (operand.IsPlainRegister()) { const Register& rn = operand.GetBaseRegister(); AddrMode addrmode = operand.GetAddrMode(); const Register& rm = operand.GetOffsetRegister(); if (rm.IsPC()) { VIXL_ABORT_WITH_MSG( "The MacroAssembler does not convert loads and stores with a PC " "offset register.\n"); } if (rn.IsPC()) { if (addrmode == Offset) { if (IsUsingT32()) { VIXL_ABORT_WITH_MSG( "The MacroAssembler does not convert loads and stores with a PC " "base register for T32.\n"); } } else { VIXL_ABORT_WITH_MSG( "The MacroAssembler does not convert loads and stores with a PC " "base register in pre-index or post-index mode.\n"); } } switch (addrmode) { case PreIndex: // Avoid the unpredictable case 'str r0, [r0, imm]!' if (!rn.Is(rd)) { // Pre-Indexed case: // ldr r0, [r1, r2]! will translate into // add r1, r1, r2 // ldr r0, [r1] { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); if (operand.GetSign().IsPlus()) { add(cond, rn, rn, rm); } else { sub(cond, rn, rn, rm); } } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rd, MemOperand(rn, Offset)); } return; } break; case Offset: { UseScratchRegisterScope temps(this); // Allow using the destination as a scratch register if this is not a // store. // Avoid using PC as a temporary as this has side-effects. if ((type != kStr) && (type != kStrb) && (type != kStrh) && !rd.IsPC()) { temps.Include(rd); } Register scratch = temps.Acquire(); // Offset case: // ldr r0, [r1, r2] will translate into // add r0, r1, r2 // ldr r0, [r0] { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); if (operand.GetSign().IsPlus()) { add(cond, scratch, rn, rm); } else { sub(cond, scratch, rn, rm); } } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rd, MemOperand(scratch, Offset)); } return; } case PostIndex: // Avoid the unpredictable case 'ldr r0, [r0], imm' if (!rn.Is(rd)) { // Post-indexed case: // ldr r0. [r1], r2 will translate into // ldr r0, [r1] // add r1, r1, r2 { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, size, rd, MemOperand(rn, Offset)); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); if (operand.GetSign().IsPlus()) { add(cond, rn, rn, rm); } else { sub(cond, rn, rn, rm); } } return; } break; } } Assembler::Delegate(type, instruction, cond, size, rd, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondRRMop instruction, Condition cond, Register rt, Register rt2, const MemOperand& operand) { if ((type == kLdaexd) || (type == kLdrexd) || (type == kStlex) || (type == kStlexb) || (type == kStlexh) || (type == kStrex) || (type == kStrexb) || (type == kStrexh)) { UnimplementedDelegate(type); return; } VIXL_ASSERT((type == kLdrd) || (type == kStrd)); CONTEXT_SCOPE; // TODO: Should we allow these cases? if (IsUsingA32()) { // The first register needs to be even. if ((rt.GetCode() & 1) != 0) { UnimplementedDelegate(type); return; } // Registers need to be adjacent. if (((rt.GetCode() + 1) % kNumberOfRegisters) != rt2.GetCode()) { UnimplementedDelegate(type); return; } // LDRD lr, pc [...] is not allowed. if (rt.Is(lr)) { UnimplementedDelegate(type); return; } } if (operand.IsImmediate()) { const Register& rn = operand.GetBaseRegister(); AddrMode addrmode = operand.GetAddrMode(); int32_t offset = operand.GetOffsetImmediate(); uint32_t extra_offset_mask = GetOffsetMask(type, addrmode); // Try to maximize the offset used by the MemOperand (load_store_offset). // Add the part which can't be used by the MemOperand (add_offset). uint32_t load_store_offset = offset & extra_offset_mask; uint32_t add_offset = offset & ~extra_offset_mask; if ((add_offset != 0) && (IsModifiedImmediate(offset) || IsModifiedImmediate(-offset))) { load_store_offset = 0; add_offset = offset; } switch (addrmode) { case PreIndex: { // Allow using the destinations as a scratch registers if possible. UseScratchRegisterScope temps(this); if (type == kLdrd) { if (!rt.Is(rn)) temps.Include(rt); if (!rt2.Is(rn)) temps.Include(rt2); } // Pre-Indexed case: // ldrd r0, r1, [r2, 12345]! will translate into // add r2, 12345 // ldrd r0, r1, [r2] { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, add_offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, rt, rt2, MemOperand(rn, load_store_offset, PreIndex)); } return; } case Offset: { UseScratchRegisterScope temps(this); // Allow using the destinations as a scratch registers if possible. if (type == kLdrd) { if (!rt.Is(rn)) temps.Include(rt); if (!rt2.Is(rn)) temps.Include(rt2); } Register scratch = temps.Acquire(); // Offset case: // ldrd r0, r1, [r2, 12345] will translate into // add r0, r2, 12345 // ldrd r0, r1, [r0] { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, scratch, rn, add_offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, rt, rt2, MemOperand(scratch, load_store_offset)); } return; } case PostIndex: // Avoid the unpredictable case 'ldrd r0, r1, [r0], imm' if (!rn.Is(rt) && !rn.Is(rt2)) { // Post-indexed case: // ldrd r0, r1, [r2], imm32 will translate into // ldrd r0, r1, [r2] // movw ip. imm32 & 0xffffffff // movt ip, imm32 >> 16 // add r2, ip { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, rt, rt2, MemOperand(rn, load_store_offset, PostIndex)); } { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, add_offset); } return; } break; } } if (operand.IsPlainRegister()) { const Register& rn = operand.GetBaseRegister(); const Register& rm = operand.GetOffsetRegister(); AddrMode addrmode = operand.GetAddrMode(); switch (addrmode) { case PreIndex: // ldrd r0, r1, [r2, r3]! will translate into // add r2, r3 // ldrd r0, r1, [r2] { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); if (operand.GetSign().IsPlus()) { add(cond, rn, rn, rm); } else { sub(cond, rn, rn, rm); } } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, rt, rt2, MemOperand(rn, Offset)); } return; case PostIndex: // ldrd r0, r1, [r2], r3 will translate into // ldrd r0, r1, [r2] // add r2, r3 { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, rt, rt2, MemOperand(rn, Offset)); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); if (operand.GetSign().IsPlus()) { add(cond, rn, rn, rm); } else { sub(cond, rn, rn, rm); } } return; case Offset: { UseScratchRegisterScope temps(this); // Allow using the destinations as a scratch registers if possible. if (type == kLdrd) { if (!rt.Is(rn)) temps.Include(rt); if (!rt2.Is(rn)) temps.Include(rt2); } Register scratch = temps.Acquire(); // Offset case: // ldrd r0, r1, [r2, r3] will translate into // add r0, r2, r3 // ldrd r0, r1, [r0] { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); if (operand.GetSign().IsPlus()) { add(cond, scratch, rn, rm); } else { sub(cond, scratch, rn, rm); } } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, rt, rt2, MemOperand(scratch, Offset)); } return; } } } Assembler::Delegate(type, instruction, cond, rt, rt2, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondDtSMop instruction, Condition cond, DataType dt, SRegister rd, const MemOperand& operand) { CONTEXT_SCOPE; if (operand.IsImmediate()) { const Register& rn = operand.GetBaseRegister(); AddrMode addrmode = operand.GetAddrMode(); int32_t offset = operand.GetOffsetImmediate(); VIXL_ASSERT(((offset > 0) && operand.GetSign().IsPlus()) || ((offset < 0) && operand.GetSign().IsMinus()) || (offset == 0)); if (rn.IsPC()) { VIXL_ABORT_WITH_MSG( "The MacroAssembler does not convert vldr or vstr with a PC base " "register.\n"); } switch (addrmode) { case PreIndex: // Pre-Indexed case: // vldr.32 s0, [r1, 12345]! will translate into // add r1, 12345 // vldr.32 s0, [r1] if (offset != 0) { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, dt, rd, MemOperand(rn, Offset)); } return; case Offset: { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); // Offset case: // vldr.32 s0, [r1, 12345] will translate into // add ip, r1, 12345 // vldr.32 s0, [ip] { VIXL_ASSERT(offset != 0); CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, scratch, rn, offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, dt, rd, MemOperand(scratch, Offset)); } return; } case PostIndex: // Post-indexed case: // vldr.32 s0, [r1], imm32 will translate into // vldr.32 s0, [r1] // movw ip. imm32 & 0xffffffff // movt ip, imm32 >> 16 // add r1, ip { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, dt, rd, MemOperand(rn, Offset)); } if (offset != 0) { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, offset); } return; } } Assembler::Delegate(type, instruction, cond, dt, rd, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondDtDMop instruction, Condition cond, DataType dt, DRegister rd, const MemOperand& operand) { CONTEXT_SCOPE; if (operand.IsImmediate()) { const Register& rn = operand.GetBaseRegister(); AddrMode addrmode = operand.GetAddrMode(); int32_t offset = operand.GetOffsetImmediate(); VIXL_ASSERT(((offset > 0) && operand.GetSign().IsPlus()) || ((offset < 0) && operand.GetSign().IsMinus()) || (offset == 0)); if (rn.IsPC()) { VIXL_ABORT_WITH_MSG( "The MacroAssembler does not convert vldr or vstr with a PC base " "register.\n"); } switch (addrmode) { case PreIndex: // Pre-Indexed case: // vldr.64 d0, [r1, 12345]! will translate into // add r1, 12345 // vldr.64 d0, [r1] if (offset != 0) { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, dt, rd, MemOperand(rn, Offset)); } return; case Offset: { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); // Offset case: // vldr.64 d0, [r1, 12345] will translate into // add ip, r1, 12345 // vldr.32 s0, [ip] { VIXL_ASSERT(offset != 0); CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, scratch, rn, offset); } { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, dt, rd, MemOperand(scratch, Offset)); } return; } case PostIndex: // Post-indexed case: // vldr.64 d0. [r1], imm32 will translate into // vldr.64 d0, [r1] // movw ip. imm32 & 0xffffffff // movt ip, imm32 >> 16 // add r1, ip { CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); (this->*instruction)(cond, dt, rd, MemOperand(rn, Offset)); } if (offset != 0) { CodeBufferCheckScope scope(this, 3 * kMaxInstructionSizeInBytes); add(cond, rn, rn, offset); } return; } } Assembler::Delegate(type, instruction, cond, dt, rd, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondMsrOp instruction, Condition cond, MaskedSpecialRegister spec_reg, const Operand& operand) { USE(type); VIXL_ASSERT(type == kMsr); if (operand.IsImmediate()) { UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); { CodeBufferCheckScope scope(this, 2 * kMaxInstructionSizeInBytes); mov(cond, scratch, operand); } CodeBufferCheckScope scope(this, kMaxInstructionSizeInBytes); msr(cond, spec_reg, scratch); return; } Assembler::Delegate(type, instruction, cond, spec_reg, operand); } void MacroAssembler::Delegate(InstructionType type, InstructionCondDtDL instruction, Condition cond, DataType dt, DRegister rd, Location* location) { VIXL_ASSERT(type == kVldr); CONTEXT_SCOPE; if (location->IsBound()) { CodeBufferCheckScope scope(this, 5 * kMaxInstructionSizeInBytes); UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); uint32_t mask = GetOffsetMask(type, Offset); vldr(dt, rd, MemOperandComputationHelper(cond, scratch, location, mask)); return; } Assembler::Delegate(type, instruction, cond, dt, rd, location); } void MacroAssembler::Delegate(InstructionType type, InstructionCondDtSL instruction, Condition cond, DataType dt, SRegister rd, Location* location) { VIXL_ASSERT(type == kVldr); CONTEXT_SCOPE; if (location->IsBound()) { CodeBufferCheckScope scope(this, 5 * kMaxInstructionSizeInBytes); UseScratchRegisterScope temps(this); Register scratch = temps.Acquire(); uint32_t mask = GetOffsetMask(type, Offset); vldr(dt, rd, MemOperandComputationHelper(cond, scratch, location, mask)); return; } Assembler::Delegate(type, instruction, cond, dt, rd, location); } #undef CONTEXT_SCOPE #undef TOSTRING #undef STRINGIFY // Start of generated code. // End of generated code. } // namespace aarch32 } // namespace vixl