diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/v8/src/arm64 | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/v8/src/arm64')
42 files changed, 58070 insertions, 0 deletions
diff --git a/chromium/v8/src/arm64/OWNERS b/chromium/v8/src/arm64/OWNERS new file mode 100644 index 00000000000..906a5ce6418 --- /dev/null +++ b/chromium/v8/src/arm64/OWNERS @@ -0,0 +1 @@ +rmcilroy@chromium.org diff --git a/chromium/v8/src/arm64/assembler-arm64-inl.h b/chromium/v8/src/arm64/assembler-arm64-inl.h new file mode 100644 index 00000000000..135858d8b1c --- /dev/null +++ b/chromium/v8/src/arm64/assembler-arm64-inl.h @@ -0,0 +1,1264 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_ASSEMBLER_ARM64_INL_H_ +#define V8_ARM64_ASSEMBLER_ARM64_INL_H_ + +#include "src/arm64/assembler-arm64.h" +#include "src/cpu.h" +#include "src/debug.h" + + +namespace v8 { +namespace internal { + + +bool CpuFeatures::SupportsCrankshaft() { return true; } + + +void RelocInfo::apply(intptr_t delta, ICacheFlushMode icache_flush_mode) { + UNIMPLEMENTED(); +} + + +void RelocInfo::set_target_address(Address target, + WriteBarrierMode write_barrier_mode, + ICacheFlushMode icache_flush_mode) { + ASSERT(IsCodeTarget(rmode_) || IsRuntimeEntry(rmode_)); + Assembler::set_target_address_at(pc_, host_, target, icache_flush_mode); + if (write_barrier_mode == UPDATE_WRITE_BARRIER && host() != NULL && + IsCodeTarget(rmode_)) { + Object* target_code = Code::GetCodeFromTargetAddress(target); + host()->GetHeap()->incremental_marking()->RecordWriteIntoCode( + host(), this, HeapObject::cast(target_code)); + } +} + + +inline unsigned CPURegister::code() const { + ASSERT(IsValid()); + return reg_code; +} + + +inline CPURegister::RegisterType CPURegister::type() const { + ASSERT(IsValidOrNone()); + return reg_type; +} + + +inline RegList CPURegister::Bit() const { + ASSERT(reg_code < (sizeof(RegList) * kBitsPerByte)); + return IsValid() ? 1UL << reg_code : 0; +} + + +inline unsigned CPURegister::SizeInBits() const { + ASSERT(IsValid()); + return reg_size; +} + + +inline int CPURegister::SizeInBytes() const { + ASSERT(IsValid()); + ASSERT(SizeInBits() % 8 == 0); + return reg_size / 8; +} + + +inline bool CPURegister::Is32Bits() const { + ASSERT(IsValid()); + return reg_size == 32; +} + + +inline bool CPURegister::Is64Bits() const { + ASSERT(IsValid()); + return reg_size == 64; +} + + +inline bool CPURegister::IsValid() const { + if (IsValidRegister() || IsValidFPRegister()) { + ASSERT(!IsNone()); + return true; + } else { + ASSERT(IsNone()); + return false; + } +} + + +inline bool CPURegister::IsValidRegister() const { + return IsRegister() && + ((reg_size == kWRegSizeInBits) || (reg_size == kXRegSizeInBits)) && + ((reg_code < kNumberOfRegisters) || (reg_code == kSPRegInternalCode)); +} + + +inline bool CPURegister::IsValidFPRegister() const { + return IsFPRegister() && + ((reg_size == kSRegSizeInBits) || (reg_size == kDRegSizeInBits)) && + (reg_code < kNumberOfFPRegisters); +} + + +inline bool CPURegister::IsNone() const { + // kNoRegister types should always have size 0 and code 0. + ASSERT((reg_type != kNoRegister) || (reg_code == 0)); + ASSERT((reg_type != kNoRegister) || (reg_size == 0)); + + return reg_type == kNoRegister; +} + + +inline bool CPURegister::Is(const CPURegister& other) const { + ASSERT(IsValidOrNone() && other.IsValidOrNone()); + return Aliases(other) && (reg_size == other.reg_size); +} + + +inline bool CPURegister::Aliases(const CPURegister& other) const { + ASSERT(IsValidOrNone() && other.IsValidOrNone()); + return (reg_code == other.reg_code) && (reg_type == other.reg_type); +} + + +inline bool CPURegister::IsRegister() const { + return reg_type == kRegister; +} + + +inline bool CPURegister::IsFPRegister() const { + return reg_type == kFPRegister; +} + + +inline bool CPURegister::IsSameSizeAndType(const CPURegister& other) const { + return (reg_size == other.reg_size) && (reg_type == other.reg_type); +} + + +inline bool CPURegister::IsValidOrNone() const { + return IsValid() || IsNone(); +} + + +inline bool CPURegister::IsZero() const { + ASSERT(IsValid()); + return IsRegister() && (reg_code == kZeroRegCode); +} + + +inline bool CPURegister::IsSP() const { + ASSERT(IsValid()); + return IsRegister() && (reg_code == kSPRegInternalCode); +} + + +inline void CPURegList::Combine(const CPURegList& other) { + ASSERT(IsValid()); + ASSERT(other.type() == type_); + ASSERT(other.RegisterSizeInBits() == size_); + list_ |= other.list(); +} + + +inline void CPURegList::Remove(const CPURegList& other) { + ASSERT(IsValid()); + if (other.type() == type_) { + list_ &= ~other.list(); + } +} + + +inline void CPURegList::Combine(const CPURegister& other) { + ASSERT(other.type() == type_); + ASSERT(other.SizeInBits() == size_); + Combine(other.code()); +} + + +inline void CPURegList::Remove(const CPURegister& other1, + const CPURegister& other2, + const CPURegister& other3, + const CPURegister& other4) { + if (!other1.IsNone() && (other1.type() == type_)) Remove(other1.code()); + if (!other2.IsNone() && (other2.type() == type_)) Remove(other2.code()); + if (!other3.IsNone() && (other3.type() == type_)) Remove(other3.code()); + if (!other4.IsNone() && (other4.type() == type_)) Remove(other4.code()); +} + + +inline void CPURegList::Combine(int code) { + ASSERT(IsValid()); + ASSERT(CPURegister::Create(code, size_, type_).IsValid()); + list_ |= (1UL << code); +} + + +inline void CPURegList::Remove(int code) { + ASSERT(IsValid()); + ASSERT(CPURegister::Create(code, size_, type_).IsValid()); + list_ &= ~(1UL << code); +} + + +inline Register Register::XRegFromCode(unsigned code) { + if (code == kSPRegInternalCode) { + return csp; + } else { + ASSERT(code < kNumberOfRegisters); + return Register::Create(code, kXRegSizeInBits); + } +} + + +inline Register Register::WRegFromCode(unsigned code) { + if (code == kSPRegInternalCode) { + return wcsp; + } else { + ASSERT(code < kNumberOfRegisters); + return Register::Create(code, kWRegSizeInBits); + } +} + + +inline FPRegister FPRegister::SRegFromCode(unsigned code) { + ASSERT(code < kNumberOfFPRegisters); + return FPRegister::Create(code, kSRegSizeInBits); +} + + +inline FPRegister FPRegister::DRegFromCode(unsigned code) { + ASSERT(code < kNumberOfFPRegisters); + return FPRegister::Create(code, kDRegSizeInBits); +} + + +inline Register CPURegister::W() const { + ASSERT(IsValidRegister()); + return Register::WRegFromCode(reg_code); +} + + +inline Register CPURegister::X() const { + ASSERT(IsValidRegister()); + return Register::XRegFromCode(reg_code); +} + + +inline FPRegister CPURegister::S() const { + ASSERT(IsValidFPRegister()); + return FPRegister::SRegFromCode(reg_code); +} + + +inline FPRegister CPURegister::D() const { + ASSERT(IsValidFPRegister()); + return FPRegister::DRegFromCode(reg_code); +} + + +// Immediate. +// Default initializer is for int types +template<typename T> +struct ImmediateInitializer { + static const bool kIsIntType = true; + static inline RelocInfo::Mode rmode_for(T) { + return sizeof(T) == 8 ? RelocInfo::NONE64 : RelocInfo::NONE32; + } + static inline int64_t immediate_for(T t) { + STATIC_ASSERT(sizeof(T) <= 8); + return t; + } +}; + + +template<> +struct ImmediateInitializer<Smi*> { + static const bool kIsIntType = false; + static inline RelocInfo::Mode rmode_for(Smi* t) { + return RelocInfo::NONE64; + } + static inline int64_t immediate_for(Smi* t) {; + return reinterpret_cast<int64_t>(t); + } +}; + + +template<> +struct ImmediateInitializer<ExternalReference> { + static const bool kIsIntType = false; + static inline RelocInfo::Mode rmode_for(ExternalReference t) { + return RelocInfo::EXTERNAL_REFERENCE; + } + static inline int64_t immediate_for(ExternalReference t) {; + return reinterpret_cast<int64_t>(t.address()); + } +}; + + +template<typename T> +Immediate::Immediate(Handle<T> value) { + InitializeHandle(value); +} + + +template<typename T> +Immediate::Immediate(T t) + : value_(ImmediateInitializer<T>::immediate_for(t)), + rmode_(ImmediateInitializer<T>::rmode_for(t)) {} + + +template<typename T> +Immediate::Immediate(T t, RelocInfo::Mode rmode) + : value_(ImmediateInitializer<T>::immediate_for(t)), + rmode_(rmode) { + STATIC_ASSERT(ImmediateInitializer<T>::kIsIntType); +} + + +// Operand. +template<typename T> +Operand::Operand(Handle<T> value) : immediate_(value), reg_(NoReg) {} + + +template<typename T> +Operand::Operand(T t) : immediate_(t), reg_(NoReg) {} + + +template<typename T> +Operand::Operand(T t, RelocInfo::Mode rmode) + : immediate_(t, rmode), + reg_(NoReg) {} + + +Operand::Operand(Register reg, Shift shift, unsigned shift_amount) + : immediate_(0), + reg_(reg), + shift_(shift), + extend_(NO_EXTEND), + shift_amount_(shift_amount) { + ASSERT(reg.Is64Bits() || (shift_amount < kWRegSizeInBits)); + ASSERT(reg.Is32Bits() || (shift_amount < kXRegSizeInBits)); + ASSERT(!reg.IsSP()); +} + + +Operand::Operand(Register reg, Extend extend, unsigned shift_amount) + : immediate_(0), + reg_(reg), + shift_(NO_SHIFT), + extend_(extend), + shift_amount_(shift_amount) { + ASSERT(reg.IsValid()); + ASSERT(shift_amount <= 4); + ASSERT(!reg.IsSP()); + + // Extend modes SXTX and UXTX require a 64-bit register. + ASSERT(reg.Is64Bits() || ((extend != SXTX) && (extend != UXTX))); +} + + +bool Operand::IsImmediate() const { + return reg_.Is(NoReg); +} + + +bool Operand::IsShiftedRegister() const { + return reg_.IsValid() && (shift_ != NO_SHIFT); +} + + +bool Operand::IsExtendedRegister() const { + return reg_.IsValid() && (extend_ != NO_EXTEND); +} + + +bool Operand::IsZero() const { + if (IsImmediate()) { + return ImmediateValue() == 0; + } else { + return reg().IsZero(); + } +} + + +Operand Operand::ToExtendedRegister() const { + ASSERT(IsShiftedRegister()); + ASSERT((shift_ == LSL) && (shift_amount_ <= 4)); + return Operand(reg_, reg_.Is64Bits() ? UXTX : UXTW, shift_amount_); +} + + +Immediate Operand::immediate() const { + ASSERT(IsImmediate()); + return immediate_; +} + + +int64_t Operand::ImmediateValue() const { + ASSERT(IsImmediate()); + return immediate_.value(); +} + + +Register Operand::reg() const { + ASSERT(IsShiftedRegister() || IsExtendedRegister()); + return reg_; +} + + +Shift Operand::shift() const { + ASSERT(IsShiftedRegister()); + return shift_; +} + + +Extend Operand::extend() const { + ASSERT(IsExtendedRegister()); + return extend_; +} + + +unsigned Operand::shift_amount() const { + ASSERT(IsShiftedRegister() || IsExtendedRegister()); + return shift_amount_; +} + + +Operand Operand::UntagSmi(Register smi) { + ASSERT(smi.Is64Bits()); + return Operand(smi, ASR, kSmiShift); +} + + +Operand Operand::UntagSmiAndScale(Register smi, int scale) { + ASSERT(smi.Is64Bits()); + ASSERT((scale >= 0) && (scale <= (64 - kSmiValueSize))); + if (scale > kSmiShift) { + return Operand(smi, LSL, scale - kSmiShift); + } else if (scale < kSmiShift) { + return Operand(smi, ASR, kSmiShift - scale); + } + return Operand(smi); +} + + +MemOperand::MemOperand() + : base_(NoReg), regoffset_(NoReg), offset_(0), addrmode_(Offset), + shift_(NO_SHIFT), extend_(NO_EXTEND), shift_amount_(0) { +} + + +MemOperand::MemOperand(Register base, ptrdiff_t offset, AddrMode addrmode) + : base_(base), regoffset_(NoReg), offset_(offset), addrmode_(addrmode), + shift_(NO_SHIFT), extend_(NO_EXTEND), shift_amount_(0) { + ASSERT(base.Is64Bits() && !base.IsZero()); +} + + +MemOperand::MemOperand(Register base, + Register regoffset, + Extend extend, + unsigned shift_amount) + : base_(base), regoffset_(regoffset), offset_(0), addrmode_(Offset), + shift_(NO_SHIFT), extend_(extend), shift_amount_(shift_amount) { + ASSERT(base.Is64Bits() && !base.IsZero()); + ASSERT(!regoffset.IsSP()); + ASSERT((extend == UXTW) || (extend == SXTW) || (extend == SXTX)); + + // SXTX extend mode requires a 64-bit offset register. + ASSERT(regoffset.Is64Bits() || (extend != SXTX)); +} + + +MemOperand::MemOperand(Register base, + Register regoffset, + Shift shift, + unsigned shift_amount) + : base_(base), regoffset_(regoffset), offset_(0), addrmode_(Offset), + shift_(shift), extend_(NO_EXTEND), shift_amount_(shift_amount) { + ASSERT(base.Is64Bits() && !base.IsZero()); + ASSERT(regoffset.Is64Bits() && !regoffset.IsSP()); + ASSERT(shift == LSL); +} + + +MemOperand::MemOperand(Register base, const Operand& offset, AddrMode addrmode) + : base_(base), addrmode_(addrmode) { + ASSERT(base.Is64Bits() && !base.IsZero()); + + if (offset.IsImmediate()) { + offset_ = offset.ImmediateValue(); + + regoffset_ = NoReg; + } else if (offset.IsShiftedRegister()) { + ASSERT(addrmode == Offset); + + regoffset_ = offset.reg(); + shift_= offset.shift(); + shift_amount_ = offset.shift_amount(); + + extend_ = NO_EXTEND; + offset_ = 0; + + // These assertions match those in the shifted-register constructor. + ASSERT(regoffset_.Is64Bits() && !regoffset_.IsSP()); + ASSERT(shift_ == LSL); + } else { + ASSERT(offset.IsExtendedRegister()); + ASSERT(addrmode == Offset); + + regoffset_ = offset.reg(); + extend_ = offset.extend(); + shift_amount_ = offset.shift_amount(); + + shift_= NO_SHIFT; + offset_ = 0; + + // These assertions match those in the extended-register constructor. + ASSERT(!regoffset_.IsSP()); + ASSERT((extend_ == UXTW) || (extend_ == SXTW) || (extend_ == SXTX)); + ASSERT((regoffset_.Is64Bits() || (extend_ != SXTX))); + } +} + +bool MemOperand::IsImmediateOffset() const { + return (addrmode_ == Offset) && regoffset_.Is(NoReg); +} + + +bool MemOperand::IsRegisterOffset() const { + return (addrmode_ == Offset) && !regoffset_.Is(NoReg); +} + + +bool MemOperand::IsPreIndex() const { + return addrmode_ == PreIndex; +} + + +bool MemOperand::IsPostIndex() const { + return addrmode_ == PostIndex; +} + +Operand MemOperand::OffsetAsOperand() const { + if (IsImmediateOffset()) { + return offset(); + } else { + ASSERT(IsRegisterOffset()); + if (extend() == NO_EXTEND) { + return Operand(regoffset(), shift(), shift_amount()); + } else { + return Operand(regoffset(), extend(), shift_amount()); + } + } +} + + +void Assembler::Unreachable() { +#ifdef USE_SIMULATOR + debug("UNREACHABLE", __LINE__, BREAK); +#else + // Crash by branching to 0. lr now points near the fault. + Emit(BLR | Rn(xzr)); +#endif +} + + +Address Assembler::target_pointer_address_at(Address pc) { + Instruction* instr = reinterpret_cast<Instruction*>(pc); + ASSERT(instr->IsLdrLiteralX()); + return reinterpret_cast<Address>(instr->ImmPCOffsetTarget()); +} + + +// Read/Modify the code target address in the branch/call instruction at pc. +Address Assembler::target_address_at(Address pc, + ConstantPoolArray* constant_pool) { + return Memory::Address_at(target_pointer_address_at(pc)); +} + + +Address Assembler::target_address_at(Address pc, Code* code) { + ConstantPoolArray* constant_pool = code ? code->constant_pool() : NULL; + return target_address_at(pc, constant_pool); +} + + +Address Assembler::target_address_from_return_address(Address pc) { + // Returns the address of the call target from the return address that will + // be returned to after a call. + // Call sequence on ARM64 is: + // ldr ip0, #... @ load from literal pool + // blr ip0 + Address candidate = pc - 2 * kInstructionSize; + Instruction* instr = reinterpret_cast<Instruction*>(candidate); + USE(instr); + ASSERT(instr->IsLdrLiteralX()); + return candidate; +} + + +Address Assembler::return_address_from_call_start(Address pc) { + // The call, generated by MacroAssembler::Call, is one of two possible + // sequences: + // + // Without relocation: + // movz temp, #(target & 0x000000000000ffff) + // movk temp, #(target & 0x00000000ffff0000) + // movk temp, #(target & 0x0000ffff00000000) + // blr temp + // + // With relocation: + // ldr temp, =target + // blr temp + // + // The return address is immediately after the blr instruction in both cases, + // so it can be found by adding the call size to the address at the start of + // the call sequence. + STATIC_ASSERT(Assembler::kCallSizeWithoutRelocation == 4 * kInstructionSize); + STATIC_ASSERT(Assembler::kCallSizeWithRelocation == 2 * kInstructionSize); + + Instruction* instr = reinterpret_cast<Instruction*>(pc); + if (instr->IsMovz()) { + // Verify the instruction sequence. + ASSERT(instr->following(1)->IsMovk()); + ASSERT(instr->following(2)->IsMovk()); + ASSERT(instr->following(3)->IsBranchAndLinkToRegister()); + return pc + Assembler::kCallSizeWithoutRelocation; + } else { + // Verify the instruction sequence. + ASSERT(instr->IsLdrLiteralX()); + ASSERT(instr->following(1)->IsBranchAndLinkToRegister()); + return pc + Assembler::kCallSizeWithRelocation; + } +} + + +void Assembler::deserialization_set_special_target_at( + Address constant_pool_entry, Code* code, Address target) { + Memory::Address_at(constant_pool_entry) = target; +} + + +void Assembler::set_target_address_at(Address pc, + ConstantPoolArray* constant_pool, + Address target, + ICacheFlushMode icache_flush_mode) { + Memory::Address_at(target_pointer_address_at(pc)) = target; + // Intuitively, we would think it is necessary to always flush the + // instruction cache after patching a target address in the code as follows: + // CPU::FlushICache(pc, sizeof(target)); + // However, on ARM, an instruction is actually patched in the case of + // embedded constants of the form: + // ldr ip, [pc, #...] + // since the instruction accessing this address in the constant pool remains + // unchanged, a flush is not required. +} + + +void Assembler::set_target_address_at(Address pc, + Code* code, + Address target, + ICacheFlushMode icache_flush_mode) { + ConstantPoolArray* constant_pool = code ? code->constant_pool() : NULL; + set_target_address_at(pc, constant_pool, target, icache_flush_mode); +} + + +int RelocInfo::target_address_size() { + return kPointerSize; +} + + +Address RelocInfo::target_address() { + ASSERT(IsCodeTarget(rmode_) || IsRuntimeEntry(rmode_)); + return Assembler::target_address_at(pc_, host_); +} + + +Address RelocInfo::target_address_address() { + ASSERT(IsCodeTarget(rmode_) || IsRuntimeEntry(rmode_) + || rmode_ == EMBEDDED_OBJECT + || rmode_ == EXTERNAL_REFERENCE); + return Assembler::target_pointer_address_at(pc_); +} + + +Address RelocInfo::constant_pool_entry_address() { + ASSERT(IsInConstantPool()); + return Assembler::target_pointer_address_at(pc_); +} + + +Object* RelocInfo::target_object() { + ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); + return reinterpret_cast<Object*>(Assembler::target_address_at(pc_, host_)); +} + + +Handle<Object> RelocInfo::target_object_handle(Assembler* origin) { + ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); + return Handle<Object>(reinterpret_cast<Object**>( + Assembler::target_address_at(pc_, host_))); +} + + +void RelocInfo::set_target_object(Object* target, + WriteBarrierMode write_barrier_mode, + ICacheFlushMode icache_flush_mode) { + ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); + ASSERT(!target->IsConsString()); + Assembler::set_target_address_at(pc_, host_, + reinterpret_cast<Address>(target), + icache_flush_mode); + if (write_barrier_mode == UPDATE_WRITE_BARRIER && + host() != NULL && + target->IsHeapObject()) { + host()->GetHeap()->incremental_marking()->RecordWrite( + host(), &Memory::Object_at(pc_), HeapObject::cast(target)); + } +} + + +Address RelocInfo::target_reference() { + ASSERT(rmode_ == EXTERNAL_REFERENCE); + return Assembler::target_address_at(pc_, host_); +} + + +Address RelocInfo::target_runtime_entry(Assembler* origin) { + ASSERT(IsRuntimeEntry(rmode_)); + return target_address(); +} + + +void RelocInfo::set_target_runtime_entry(Address target, + WriteBarrierMode write_barrier_mode, + ICacheFlushMode icache_flush_mode) { + ASSERT(IsRuntimeEntry(rmode_)); + if (target_address() != target) { + set_target_address(target, write_barrier_mode, icache_flush_mode); + } +} + + +Handle<Cell> RelocInfo::target_cell_handle() { + UNIMPLEMENTED(); + Cell *null_cell = NULL; + return Handle<Cell>(null_cell); +} + + +Cell* RelocInfo::target_cell() { + ASSERT(rmode_ == RelocInfo::CELL); + return Cell::FromValueAddress(Memory::Address_at(pc_)); +} + + +void RelocInfo::set_target_cell(Cell* cell, + WriteBarrierMode write_barrier_mode, + ICacheFlushMode icache_flush_mode) { + UNIMPLEMENTED(); +} + + +static const int kNoCodeAgeSequenceLength = 5 * kInstructionSize; +static const int kCodeAgeStubEntryOffset = 3 * kInstructionSize; + + +Handle<Object> RelocInfo::code_age_stub_handle(Assembler* origin) { + UNREACHABLE(); // This should never be reached on ARM64. + return Handle<Object>(); +} + + +Code* RelocInfo::code_age_stub() { + ASSERT(rmode_ == RelocInfo::CODE_AGE_SEQUENCE); + // Read the stub entry point from the code age sequence. + Address stub_entry_address = pc_ + kCodeAgeStubEntryOffset; + return Code::GetCodeFromTargetAddress(Memory::Address_at(stub_entry_address)); +} + + +void RelocInfo::set_code_age_stub(Code* stub, + ICacheFlushMode icache_flush_mode) { + ASSERT(rmode_ == RelocInfo::CODE_AGE_SEQUENCE); + ASSERT(!Code::IsYoungSequence(stub->GetIsolate(), pc_)); + // Overwrite the stub entry point in the code age sequence. This is loaded as + // a literal so there is no need to call FlushICache here. + Address stub_entry_address = pc_ + kCodeAgeStubEntryOffset; + Memory::Address_at(stub_entry_address) = stub->instruction_start(); +} + + +Address RelocInfo::call_address() { + ASSERT((IsJSReturn(rmode()) && IsPatchedReturnSequence()) || + (IsDebugBreakSlot(rmode()) && IsPatchedDebugBreakSlotSequence())); + // For the above sequences the Relocinfo points to the load literal loading + // the call address. + return Assembler::target_address_at(pc_, host_); +} + + +void RelocInfo::set_call_address(Address target) { + ASSERT((IsJSReturn(rmode()) && IsPatchedReturnSequence()) || + (IsDebugBreakSlot(rmode()) && IsPatchedDebugBreakSlotSequence())); + Assembler::set_target_address_at(pc_, host_, target); + if (host() != NULL) { + Object* target_code = Code::GetCodeFromTargetAddress(target); + host()->GetHeap()->incremental_marking()->RecordWriteIntoCode( + host(), this, HeapObject::cast(target_code)); + } +} + + +void RelocInfo::WipeOut() { + ASSERT(IsEmbeddedObject(rmode_) || + IsCodeTarget(rmode_) || + IsRuntimeEntry(rmode_) || + IsExternalReference(rmode_)); + Assembler::set_target_address_at(pc_, host_, NULL); +} + + +bool RelocInfo::IsPatchedReturnSequence() { + // The sequence must be: + // ldr ip0, [pc, #offset] + // blr ip0 + // See arm64/debug-arm64.cc BreakLocationIterator::SetDebugBreakAtReturn(). + Instruction* i1 = reinterpret_cast<Instruction*>(pc_); + Instruction* i2 = i1->following(); + return i1->IsLdrLiteralX() && (i1->Rt() == ip0.code()) && + i2->IsBranchAndLinkToRegister() && (i2->Rn() == ip0.code()); +} + + +bool RelocInfo::IsPatchedDebugBreakSlotSequence() { + Instruction* current_instr = reinterpret_cast<Instruction*>(pc_); + return !current_instr->IsNop(Assembler::DEBUG_BREAK_NOP); +} + + +void RelocInfo::Visit(Isolate* isolate, ObjectVisitor* visitor) { + RelocInfo::Mode mode = rmode(); + if (mode == RelocInfo::EMBEDDED_OBJECT) { + visitor->VisitEmbeddedPointer(this); + } else if (RelocInfo::IsCodeTarget(mode)) { + visitor->VisitCodeTarget(this); + } else if (mode == RelocInfo::CELL) { + visitor->VisitCell(this); + } else if (mode == RelocInfo::EXTERNAL_REFERENCE) { + visitor->VisitExternalReference(this); + } else if (((RelocInfo::IsJSReturn(mode) && + IsPatchedReturnSequence()) || + (RelocInfo::IsDebugBreakSlot(mode) && + IsPatchedDebugBreakSlotSequence())) && + isolate->debug()->has_break_points()) { + visitor->VisitDebugTarget(this); + } else if (RelocInfo::IsRuntimeEntry(mode)) { + visitor->VisitRuntimeEntry(this); + } +} + + +template<typename StaticVisitor> +void RelocInfo::Visit(Heap* heap) { + RelocInfo::Mode mode = rmode(); + if (mode == RelocInfo::EMBEDDED_OBJECT) { + StaticVisitor::VisitEmbeddedPointer(heap, this); + } else if (RelocInfo::IsCodeTarget(mode)) { + StaticVisitor::VisitCodeTarget(heap, this); + } else if (mode == RelocInfo::CELL) { + StaticVisitor::VisitCell(heap, this); + } else if (mode == RelocInfo::EXTERNAL_REFERENCE) { + StaticVisitor::VisitExternalReference(this); + } else if (heap->isolate()->debug()->has_break_points() && + ((RelocInfo::IsJSReturn(mode) && + IsPatchedReturnSequence()) || + (RelocInfo::IsDebugBreakSlot(mode) && + IsPatchedDebugBreakSlotSequence()))) { + StaticVisitor::VisitDebugTarget(heap, this); + } else if (RelocInfo::IsRuntimeEntry(mode)) { + StaticVisitor::VisitRuntimeEntry(this); + } +} + + +LoadStoreOp Assembler::LoadOpFor(const CPURegister& rt) { + ASSERT(rt.IsValid()); + if (rt.IsRegister()) { + return rt.Is64Bits() ? LDR_x : LDR_w; + } else { + ASSERT(rt.IsFPRegister()); + return rt.Is64Bits() ? LDR_d : LDR_s; + } +} + + +LoadStorePairOp Assembler::LoadPairOpFor(const CPURegister& rt, + const CPURegister& rt2) { + ASSERT(AreSameSizeAndType(rt, rt2)); + USE(rt2); + if (rt.IsRegister()) { + return rt.Is64Bits() ? LDP_x : LDP_w; + } else { + ASSERT(rt.IsFPRegister()); + return rt.Is64Bits() ? LDP_d : LDP_s; + } +} + + +LoadStoreOp Assembler::StoreOpFor(const CPURegister& rt) { + ASSERT(rt.IsValid()); + if (rt.IsRegister()) { + return rt.Is64Bits() ? STR_x : STR_w; + } else { + ASSERT(rt.IsFPRegister()); + return rt.Is64Bits() ? STR_d : STR_s; + } +} + + +LoadStorePairOp Assembler::StorePairOpFor(const CPURegister& rt, + const CPURegister& rt2) { + ASSERT(AreSameSizeAndType(rt, rt2)); + USE(rt2); + if (rt.IsRegister()) { + return rt.Is64Bits() ? STP_x : STP_w; + } else { + ASSERT(rt.IsFPRegister()); + return rt.Is64Bits() ? STP_d : STP_s; + } +} + + +LoadStorePairNonTemporalOp Assembler::LoadPairNonTemporalOpFor( + const CPURegister& rt, const CPURegister& rt2) { + ASSERT(AreSameSizeAndType(rt, rt2)); + USE(rt2); + if (rt.IsRegister()) { + return rt.Is64Bits() ? LDNP_x : LDNP_w; + } else { + ASSERT(rt.IsFPRegister()); + return rt.Is64Bits() ? LDNP_d : LDNP_s; + } +} + + +LoadStorePairNonTemporalOp Assembler::StorePairNonTemporalOpFor( + const CPURegister& rt, const CPURegister& rt2) { + ASSERT(AreSameSizeAndType(rt, rt2)); + USE(rt2); + if (rt.IsRegister()) { + return rt.Is64Bits() ? STNP_x : STNP_w; + } else { + ASSERT(rt.IsFPRegister()); + return rt.Is64Bits() ? STNP_d : STNP_s; + } +} + + +LoadLiteralOp Assembler::LoadLiteralOpFor(const CPURegister& rt) { + if (rt.IsRegister()) { + return rt.Is64Bits() ? LDR_x_lit : LDR_w_lit; + } else { + ASSERT(rt.IsFPRegister()); + return rt.Is64Bits() ? LDR_d_lit : LDR_s_lit; + } +} + + +int Assembler::LinkAndGetInstructionOffsetTo(Label* label) { + ASSERT(kStartOfLabelLinkChain == 0); + int offset = LinkAndGetByteOffsetTo(label); + ASSERT(IsAligned(offset, kInstructionSize)); + return offset >> kInstructionSizeLog2; +} + + +Instr Assembler::Flags(FlagsUpdate S) { + if (S == SetFlags) { + return 1 << FlagsUpdate_offset; + } else if (S == LeaveFlags) { + return 0 << FlagsUpdate_offset; + } + UNREACHABLE(); + return 0; +} + + +Instr Assembler::Cond(Condition cond) { + return cond << Condition_offset; +} + + +Instr Assembler::ImmPCRelAddress(int imm21) { + CHECK(is_int21(imm21)); + Instr imm = static_cast<Instr>(truncate_to_int21(imm21)); + Instr immhi = (imm >> ImmPCRelLo_width) << ImmPCRelHi_offset; + Instr immlo = imm << ImmPCRelLo_offset; + return (immhi & ImmPCRelHi_mask) | (immlo & ImmPCRelLo_mask); +} + + +Instr Assembler::ImmUncondBranch(int imm26) { + CHECK(is_int26(imm26)); + return truncate_to_int26(imm26) << ImmUncondBranch_offset; +} + + +Instr Assembler::ImmCondBranch(int imm19) { + CHECK(is_int19(imm19)); + return truncate_to_int19(imm19) << ImmCondBranch_offset; +} + + +Instr Assembler::ImmCmpBranch(int imm19) { + CHECK(is_int19(imm19)); + return truncate_to_int19(imm19) << ImmCmpBranch_offset; +} + + +Instr Assembler::ImmTestBranch(int imm14) { + CHECK(is_int14(imm14)); + return truncate_to_int14(imm14) << ImmTestBranch_offset; +} + + +Instr Assembler::ImmTestBranchBit(unsigned bit_pos) { + ASSERT(is_uint6(bit_pos)); + // Subtract five from the shift offset, as we need bit 5 from bit_pos. + unsigned b5 = bit_pos << (ImmTestBranchBit5_offset - 5); + unsigned b40 = bit_pos << ImmTestBranchBit40_offset; + b5 &= ImmTestBranchBit5_mask; + b40 &= ImmTestBranchBit40_mask; + return b5 | b40; +} + + +Instr Assembler::SF(Register rd) { + return rd.Is64Bits() ? SixtyFourBits : ThirtyTwoBits; +} + + +Instr Assembler::ImmAddSub(int64_t imm) { + ASSERT(IsImmAddSub(imm)); + if (is_uint12(imm)) { // No shift required. + return imm << ImmAddSub_offset; + } else { + return ((imm >> 12) << ImmAddSub_offset) | (1 << ShiftAddSub_offset); + } +} + + +Instr Assembler::ImmS(unsigned imms, unsigned reg_size) { + ASSERT(((reg_size == kXRegSizeInBits) && is_uint6(imms)) || + ((reg_size == kWRegSizeInBits) && is_uint5(imms))); + USE(reg_size); + return imms << ImmS_offset; +} + + +Instr Assembler::ImmR(unsigned immr, unsigned reg_size) { + ASSERT(((reg_size == kXRegSizeInBits) && is_uint6(immr)) || + ((reg_size == kWRegSizeInBits) && is_uint5(immr))); + USE(reg_size); + ASSERT(is_uint6(immr)); + return immr << ImmR_offset; +} + + +Instr Assembler::ImmSetBits(unsigned imms, unsigned reg_size) { + ASSERT((reg_size == kWRegSizeInBits) || (reg_size == kXRegSizeInBits)); + ASSERT(is_uint6(imms)); + ASSERT((reg_size == kXRegSizeInBits) || is_uint6(imms + 3)); + USE(reg_size); + return imms << ImmSetBits_offset; +} + + +Instr Assembler::ImmRotate(unsigned immr, unsigned reg_size) { + ASSERT((reg_size == kWRegSizeInBits) || (reg_size == kXRegSizeInBits)); + ASSERT(((reg_size == kXRegSizeInBits) && is_uint6(immr)) || + ((reg_size == kWRegSizeInBits) && is_uint5(immr))); + USE(reg_size); + return immr << ImmRotate_offset; +} + + +Instr Assembler::ImmLLiteral(int imm19) { + CHECK(is_int19(imm19)); + return truncate_to_int19(imm19) << ImmLLiteral_offset; +} + + +Instr Assembler::BitN(unsigned bitn, unsigned reg_size) { + ASSERT((reg_size == kWRegSizeInBits) || (reg_size == kXRegSizeInBits)); + ASSERT((reg_size == kXRegSizeInBits) || (bitn == 0)); + USE(reg_size); + return bitn << BitN_offset; +} + + +Instr Assembler::ShiftDP(Shift shift) { + ASSERT(shift == LSL || shift == LSR || shift == ASR || shift == ROR); + return shift << ShiftDP_offset; +} + + +Instr Assembler::ImmDPShift(unsigned amount) { + ASSERT(is_uint6(amount)); + return amount << ImmDPShift_offset; +} + + +Instr Assembler::ExtendMode(Extend extend) { + return extend << ExtendMode_offset; +} + + +Instr Assembler::ImmExtendShift(unsigned left_shift) { + ASSERT(left_shift <= 4); + return left_shift << ImmExtendShift_offset; +} + + +Instr Assembler::ImmCondCmp(unsigned imm) { + ASSERT(is_uint5(imm)); + return imm << ImmCondCmp_offset; +} + + +Instr Assembler::Nzcv(StatusFlags nzcv) { + return ((nzcv >> Flags_offset) & 0xf) << Nzcv_offset; +} + + +Instr Assembler::ImmLSUnsigned(int imm12) { + ASSERT(is_uint12(imm12)); + return imm12 << ImmLSUnsigned_offset; +} + + +Instr Assembler::ImmLS(int imm9) { + ASSERT(is_int9(imm9)); + return truncate_to_int9(imm9) << ImmLS_offset; +} + + +Instr Assembler::ImmLSPair(int imm7, LSDataSize size) { + ASSERT(((imm7 >> size) << size) == imm7); + int scaled_imm7 = imm7 >> size; + ASSERT(is_int7(scaled_imm7)); + return truncate_to_int7(scaled_imm7) << ImmLSPair_offset; +} + + +Instr Assembler::ImmShiftLS(unsigned shift_amount) { + ASSERT(is_uint1(shift_amount)); + return shift_amount << ImmShiftLS_offset; +} + + +Instr Assembler::ImmException(int imm16) { + ASSERT(is_uint16(imm16)); + return imm16 << ImmException_offset; +} + + +Instr Assembler::ImmSystemRegister(int imm15) { + ASSERT(is_uint15(imm15)); + return imm15 << ImmSystemRegister_offset; +} + + +Instr Assembler::ImmHint(int imm7) { + ASSERT(is_uint7(imm7)); + return imm7 << ImmHint_offset; +} + + +Instr Assembler::ImmBarrierDomain(int imm2) { + ASSERT(is_uint2(imm2)); + return imm2 << ImmBarrierDomain_offset; +} + + +Instr Assembler::ImmBarrierType(int imm2) { + ASSERT(is_uint2(imm2)); + return imm2 << ImmBarrierType_offset; +} + + +LSDataSize Assembler::CalcLSDataSize(LoadStoreOp op) { + ASSERT((SizeLS_offset + SizeLS_width) == (kInstructionSize * 8)); + return static_cast<LSDataSize>(op >> SizeLS_offset); +} + + +Instr Assembler::ImmMoveWide(uint64_t imm) { + ASSERT(is_uint16(imm)); + return imm << ImmMoveWide_offset; +} + + +Instr Assembler::ShiftMoveWide(int64_t shift) { + ASSERT(is_uint2(shift)); + return shift << ShiftMoveWide_offset; +} + + +Instr Assembler::FPType(FPRegister fd) { + return fd.Is64Bits() ? FP64 : FP32; +} + + +Instr Assembler::FPScale(unsigned scale) { + ASSERT(is_uint6(scale)); + return scale << FPScale_offset; +} + + +const Register& Assembler::AppropriateZeroRegFor(const CPURegister& reg) const { + return reg.Is64Bits() ? xzr : wzr; +} + + +inline void Assembler::CheckBufferSpace() { + ASSERT(pc_ < (buffer_ + buffer_size_)); + if (buffer_space() < kGap) { + GrowBuffer(); + } +} + + +inline void Assembler::CheckBuffer() { + CheckBufferSpace(); + if (pc_offset() >= next_veneer_pool_check_) { + CheckVeneerPool(false, true); + } + if (pc_offset() >= next_constant_pool_check_) { + CheckConstPool(false, true); + } +} + + +TypeFeedbackId Assembler::RecordedAstId() { + ASSERT(!recorded_ast_id_.IsNone()); + return recorded_ast_id_; +} + + +void Assembler::ClearRecordedAstId() { + recorded_ast_id_ = TypeFeedbackId::None(); +} + + +} } // namespace v8::internal + +#endif // V8_ARM64_ASSEMBLER_ARM64_INL_H_ diff --git a/chromium/v8/src/arm64/assembler-arm64.cc b/chromium/v8/src/arm64/assembler-arm64.cc new file mode 100644 index 00000000000..90cff59620e --- /dev/null +++ b/chromium/v8/src/arm64/assembler-arm64.cc @@ -0,0 +1,2892 @@ +// Copyright 2013 the V8 project 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 Google Inc. 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 AND 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 "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#define ARM64_DEFINE_REG_STATICS + +#include "src/arm64/assembler-arm64-inl.h" + +namespace v8 { +namespace internal { + + +// ----------------------------------------------------------------------------- +// CpuFeatures implementation. + +void CpuFeatures::ProbeImpl(bool cross_compile) { + if (cross_compile) { + // Always align csp in cross compiled code - this is safe and ensures that + // csp will always be aligned if it is enabled by probing at runtime. + if (FLAG_enable_always_align_csp) supported_ |= 1u << ALWAYS_ALIGN_CSP; + } else { + CPU cpu; + if (FLAG_enable_always_align_csp && (cpu.implementer() == CPU::NVIDIA || + FLAG_debug_code)) { + supported_ |= 1u << ALWAYS_ALIGN_CSP; + } + } +} + + +void CpuFeatures::PrintTarget() { } +void CpuFeatures::PrintFeatures() { } + + +// ----------------------------------------------------------------------------- +// CPURegList utilities. + +CPURegister CPURegList::PopLowestIndex() { + ASSERT(IsValid()); + if (IsEmpty()) { + return NoCPUReg; + } + int index = CountTrailingZeros(list_, kRegListSizeInBits); + ASSERT((1 << index) & list_); + Remove(index); + return CPURegister::Create(index, size_, type_); +} + + +CPURegister CPURegList::PopHighestIndex() { + ASSERT(IsValid()); + if (IsEmpty()) { + return NoCPUReg; + } + int index = CountLeadingZeros(list_, kRegListSizeInBits); + index = kRegListSizeInBits - 1 - index; + ASSERT((1 << index) & list_); + Remove(index); + return CPURegister::Create(index, size_, type_); +} + + +void CPURegList::RemoveCalleeSaved() { + if (type() == CPURegister::kRegister) { + Remove(GetCalleeSaved(RegisterSizeInBits())); + } else if (type() == CPURegister::kFPRegister) { + Remove(GetCalleeSavedFP(RegisterSizeInBits())); + } else { + ASSERT(type() == CPURegister::kNoRegister); + ASSERT(IsEmpty()); + // The list must already be empty, so do nothing. + } +} + + +CPURegList CPURegList::GetCalleeSaved(unsigned size) { + return CPURegList(CPURegister::kRegister, size, 19, 29); +} + + +CPURegList CPURegList::GetCalleeSavedFP(unsigned size) { + return CPURegList(CPURegister::kFPRegister, size, 8, 15); +} + + +CPURegList CPURegList::GetCallerSaved(unsigned size) { + // Registers x0-x18 and lr (x30) are caller-saved. + CPURegList list = CPURegList(CPURegister::kRegister, size, 0, 18); + list.Combine(lr); + return list; +} + + +CPURegList CPURegList::GetCallerSavedFP(unsigned size) { + // Registers d0-d7 and d16-d31 are caller-saved. + CPURegList list = CPURegList(CPURegister::kFPRegister, size, 0, 7); + list.Combine(CPURegList(CPURegister::kFPRegister, size, 16, 31)); + return list; +} + + +// This function defines the list of registers which are associated with a +// safepoint slot. Safepoint register slots are saved contiguously on the stack. +// MacroAssembler::SafepointRegisterStackIndex handles mapping from register +// code to index in the safepoint register slots. Any change here can affect +// this mapping. +CPURegList CPURegList::GetSafepointSavedRegisters() { + CPURegList list = CPURegList::GetCalleeSaved(); + list.Combine( + CPURegList(CPURegister::kRegister, kXRegSizeInBits, kJSCallerSaved)); + + // Note that unfortunately we can't use symbolic names for registers and have + // to directly use register codes. This is because this function is used to + // initialize some static variables and we can't rely on register variables + // to be initialized due to static initialization order issues in C++. + + // Drop ip0 and ip1 (i.e. x16 and x17), as they should not be expected to be + // preserved outside of the macro assembler. + list.Remove(16); + list.Remove(17); + + // Add x18 to the safepoint list, as although it's not in kJSCallerSaved, it + // is a caller-saved register according to the procedure call standard. + list.Combine(18); + + // Drop jssp as the stack pointer doesn't need to be included. + list.Remove(28); + + // Add the link register (x30) to the safepoint list. + list.Combine(30); + + return list; +} + + +// ----------------------------------------------------------------------------- +// Implementation of RelocInfo + +const int RelocInfo::kApplyMask = 0; + + +bool RelocInfo::IsCodedSpecially() { + // The deserializer needs to know whether a pointer is specially coded. Being + // specially coded on ARM64 means that it is a movz/movk sequence. We don't + // generate those for relocatable pointers. + return false; +} + + +bool RelocInfo::IsInConstantPool() { + Instruction* instr = reinterpret_cast<Instruction*>(pc_); + return instr->IsLdrLiteralX(); +} + + +void RelocInfo::PatchCode(byte* instructions, int instruction_count) { + // Patch the code at the current address with the supplied instructions. + Instr* pc = reinterpret_cast<Instr*>(pc_); + Instr* instr = reinterpret_cast<Instr*>(instructions); + for (int i = 0; i < instruction_count; i++) { + *(pc + i) = *(instr + i); + } + + // Indicate that code has changed. + CPU::FlushICache(pc_, instruction_count * kInstructionSize); +} + + +// Patch the code at the current PC with a call to the target address. +// Additional guard instructions can be added if required. +void RelocInfo::PatchCodeWithCall(Address target, int guard_bytes) { + UNIMPLEMENTED(); +} + + +Register GetAllocatableRegisterThatIsNotOneOf(Register reg1, Register reg2, + Register reg3, Register reg4) { + CPURegList regs(reg1, reg2, reg3, reg4); + for (int i = 0; i < Register::NumAllocatableRegisters(); i++) { + Register candidate = Register::FromAllocationIndex(i); + if (regs.IncludesAliasOf(candidate)) continue; + return candidate; + } + UNREACHABLE(); + return NoReg; +} + + +bool AreAliased(const CPURegister& reg1, const CPURegister& reg2, + const CPURegister& reg3, const CPURegister& reg4, + const CPURegister& reg5, const CPURegister& reg6, + const CPURegister& reg7, const CPURegister& reg8) { + int number_of_valid_regs = 0; + int number_of_valid_fpregs = 0; + + RegList unique_regs = 0; + RegList unique_fpregs = 0; + + const CPURegister regs[] = {reg1, reg2, reg3, reg4, reg5, reg6, reg7, reg8}; + + for (unsigned i = 0; i < sizeof(regs) / sizeof(regs[0]); i++) { + if (regs[i].IsRegister()) { + number_of_valid_regs++; + unique_regs |= regs[i].Bit(); + } else if (regs[i].IsFPRegister()) { + number_of_valid_fpregs++; + unique_fpregs |= regs[i].Bit(); + } else { + ASSERT(!regs[i].IsValid()); + } + } + + int number_of_unique_regs = + CountSetBits(unique_regs, sizeof(unique_regs) * kBitsPerByte); + int number_of_unique_fpregs = + CountSetBits(unique_fpregs, sizeof(unique_fpregs) * kBitsPerByte); + + ASSERT(number_of_valid_regs >= number_of_unique_regs); + ASSERT(number_of_valid_fpregs >= number_of_unique_fpregs); + + return (number_of_valid_regs != number_of_unique_regs) || + (number_of_valid_fpregs != number_of_unique_fpregs); +} + + +bool AreSameSizeAndType(const CPURegister& reg1, const CPURegister& reg2, + const CPURegister& reg3, const CPURegister& reg4, + const CPURegister& reg5, const CPURegister& reg6, + const CPURegister& reg7, const CPURegister& reg8) { + ASSERT(reg1.IsValid()); + bool match = true; + match &= !reg2.IsValid() || reg2.IsSameSizeAndType(reg1); + match &= !reg3.IsValid() || reg3.IsSameSizeAndType(reg1); + match &= !reg4.IsValid() || reg4.IsSameSizeAndType(reg1); + match &= !reg5.IsValid() || reg5.IsSameSizeAndType(reg1); + match &= !reg6.IsValid() || reg6.IsSameSizeAndType(reg1); + match &= !reg7.IsValid() || reg7.IsSameSizeAndType(reg1); + match &= !reg8.IsValid() || reg8.IsSameSizeAndType(reg1); + return match; +} + + +void Immediate::InitializeHandle(Handle<Object> handle) { + AllowDeferredHandleDereference using_raw_address; + + // Verify all Objects referred by code are NOT in new space. + Object* obj = *handle; + if (obj->IsHeapObject()) { + ASSERT(!HeapObject::cast(obj)->GetHeap()->InNewSpace(obj)); + value_ = reinterpret_cast<intptr_t>(handle.location()); + rmode_ = RelocInfo::EMBEDDED_OBJECT; + } else { + STATIC_ASSERT(sizeof(intptr_t) == sizeof(int64_t)); + value_ = reinterpret_cast<intptr_t>(obj); + rmode_ = RelocInfo::NONE64; + } +} + + +bool Operand::NeedsRelocation(const Assembler* assembler) const { + RelocInfo::Mode rmode = immediate_.rmode(); + + if (rmode == RelocInfo::EXTERNAL_REFERENCE) { + return assembler->serializer_enabled(); + } + + return !RelocInfo::IsNone(rmode); +} + + +// Assembler + +Assembler::Assembler(Isolate* isolate, void* buffer, int buffer_size) + : AssemblerBase(isolate, buffer, buffer_size), + recorded_ast_id_(TypeFeedbackId::None()), + unresolved_branches_(), + positions_recorder_(this) { + const_pool_blocked_nesting_ = 0; + veneer_pool_blocked_nesting_ = 0; + Reset(); +} + + +Assembler::~Assembler() { + ASSERT(num_pending_reloc_info_ == 0); + ASSERT(const_pool_blocked_nesting_ == 0); + ASSERT(veneer_pool_blocked_nesting_ == 0); +} + + +void Assembler::Reset() { +#ifdef DEBUG + ASSERT((pc_ >= buffer_) && (pc_ < buffer_ + buffer_size_)); + ASSERT(const_pool_blocked_nesting_ == 0); + ASSERT(veneer_pool_blocked_nesting_ == 0); + ASSERT(unresolved_branches_.empty()); + memset(buffer_, 0, pc_ - buffer_); +#endif + pc_ = buffer_; + reloc_info_writer.Reposition(reinterpret_cast<byte*>(buffer_ + buffer_size_), + reinterpret_cast<byte*>(pc_)); + num_pending_reloc_info_ = 0; + next_constant_pool_check_ = 0; + next_veneer_pool_check_ = kMaxInt; + no_const_pool_before_ = 0; + first_const_pool_use_ = -1; + ClearRecordedAstId(); +} + + +void Assembler::GetCode(CodeDesc* desc) { + // Emit constant pool if necessary. + CheckConstPool(true, false); + ASSERT(num_pending_reloc_info_ == 0); + + // Set up code descriptor. + if (desc) { + desc->buffer = reinterpret_cast<byte*>(buffer_); + desc->buffer_size = buffer_size_; + desc->instr_size = pc_offset(); + desc->reloc_size = (reinterpret_cast<byte*>(buffer_) + buffer_size_) - + reloc_info_writer.pos(); + desc->origin = this; + } +} + + +void Assembler::Align(int m) { + ASSERT(m >= 4 && IsPowerOf2(m)); + while ((pc_offset() & (m - 1)) != 0) { + nop(); + } +} + + +void Assembler::CheckLabelLinkChain(Label const * label) { +#ifdef DEBUG + if (label->is_linked()) { + int linkoffset = label->pos(); + bool end_of_chain = false; + while (!end_of_chain) { + Instruction * link = InstructionAt(linkoffset); + int linkpcoffset = link->ImmPCOffset(); + int prevlinkoffset = linkoffset + linkpcoffset; + + end_of_chain = (linkoffset == prevlinkoffset); + linkoffset = linkoffset + linkpcoffset; + } + } +#endif +} + + +void Assembler::RemoveBranchFromLabelLinkChain(Instruction* branch, + Label* label, + Instruction* label_veneer) { + ASSERT(label->is_linked()); + + CheckLabelLinkChain(label); + + Instruction* link = InstructionAt(label->pos()); + Instruction* prev_link = link; + Instruction* next_link; + bool end_of_chain = false; + + while (link != branch && !end_of_chain) { + next_link = link->ImmPCOffsetTarget(); + end_of_chain = (link == next_link); + prev_link = link; + link = next_link; + } + + ASSERT(branch == link); + next_link = branch->ImmPCOffsetTarget(); + + if (branch == prev_link) { + // The branch is the first instruction in the chain. + if (branch == next_link) { + // It is also the last instruction in the chain, so it is the only branch + // currently referring to this label. + label->Unuse(); + } else { + label->link_to(reinterpret_cast<byte*>(next_link) - buffer_); + } + + } else if (branch == next_link) { + // The branch is the last (but not also the first) instruction in the chain. + prev_link->SetImmPCOffsetTarget(prev_link); + + } else { + // The branch is in the middle of the chain. + if (prev_link->IsTargetInImmPCOffsetRange(next_link)) { + prev_link->SetImmPCOffsetTarget(next_link); + } else if (label_veneer != NULL) { + // Use the veneer for all previous links in the chain. + prev_link->SetImmPCOffsetTarget(prev_link); + + end_of_chain = false; + link = next_link; + while (!end_of_chain) { + next_link = link->ImmPCOffsetTarget(); + end_of_chain = (link == next_link); + link->SetImmPCOffsetTarget(label_veneer); + link = next_link; + } + } else { + // The assert below will fire. + // Some other work could be attempted to fix up the chain, but it would be + // rather complicated. If we crash here, we may want to consider using an + // other mechanism than a chain of branches. + // + // Note that this situation currently should not happen, as we always call + // this function with a veneer to the target label. + // However this could happen with a MacroAssembler in the following state: + // [previous code] + // B(label); + // [20KB code] + // Tbz(label); // First tbz. Pointing to unconditional branch. + // [20KB code] + // Tbz(label); // Second tbz. Pointing to the first tbz. + // [more code] + // and this function is called to remove the first tbz from the label link + // chain. Since tbz has a range of +-32KB, the second tbz cannot point to + // the unconditional branch. + CHECK(prev_link->IsTargetInImmPCOffsetRange(next_link)); + UNREACHABLE(); + } + } + + CheckLabelLinkChain(label); +} + + +void Assembler::bind(Label* label) { + // Bind label to the address at pc_. All instructions (most likely branches) + // that are linked to this label will be updated to point to the newly-bound + // label. + + ASSERT(!label->is_near_linked()); + ASSERT(!label->is_bound()); + + DeleteUnresolvedBranchInfoForLabel(label); + + // If the label is linked, the link chain looks something like this: + // + // |--I----I-------I-------L + // |---------------------->| pc_offset + // |-------------->| linkoffset = label->pos() + // |<------| link->ImmPCOffset() + // |------>| prevlinkoffset = linkoffset + link->ImmPCOffset() + // + // On each iteration, the last link is updated and then removed from the + // chain until only one remains. At that point, the label is bound. + // + // If the label is not linked, no preparation is required before binding. + while (label->is_linked()) { + int linkoffset = label->pos(); + Instruction* link = InstructionAt(linkoffset); + int prevlinkoffset = linkoffset + link->ImmPCOffset(); + + CheckLabelLinkChain(label); + + ASSERT(linkoffset >= 0); + ASSERT(linkoffset < pc_offset()); + ASSERT((linkoffset > prevlinkoffset) || + (linkoffset - prevlinkoffset == kStartOfLabelLinkChain)); + ASSERT(prevlinkoffset >= 0); + + // Update the link to point to the label. + link->SetImmPCOffsetTarget(reinterpret_cast<Instruction*>(pc_)); + + // Link the label to the previous link in the chain. + if (linkoffset - prevlinkoffset == kStartOfLabelLinkChain) { + // We hit kStartOfLabelLinkChain, so the chain is fully processed. + label->Unuse(); + } else { + // Update the label for the next iteration. + label->link_to(prevlinkoffset); + } + } + label->bind_to(pc_offset()); + + ASSERT(label->is_bound()); + ASSERT(!label->is_linked()); +} + + +int Assembler::LinkAndGetByteOffsetTo(Label* label) { + ASSERT(sizeof(*pc_) == 1); + CheckLabelLinkChain(label); + + int offset; + if (label->is_bound()) { + // The label is bound, so it does not need to be updated. Referring + // instructions must link directly to the label as they will not be + // updated. + // + // In this case, label->pos() returns the offset of the label from the + // start of the buffer. + // + // Note that offset can be zero for self-referential instructions. (This + // could be useful for ADR, for example.) + offset = label->pos() - pc_offset(); + ASSERT(offset <= 0); + } else { + if (label->is_linked()) { + // The label is linked, so the referring instruction should be added onto + // the end of the label's link chain. + // + // In this case, label->pos() returns the offset of the last linked + // instruction from the start of the buffer. + offset = label->pos() - pc_offset(); + ASSERT(offset != kStartOfLabelLinkChain); + // Note that the offset here needs to be PC-relative only so that the + // first instruction in a buffer can link to an unbound label. Otherwise, + // the offset would be 0 for this case, and 0 is reserved for + // kStartOfLabelLinkChain. + } else { + // The label is unused, so it now becomes linked and the referring + // instruction is at the start of the new link chain. + offset = kStartOfLabelLinkChain; + } + // The instruction at pc is now the last link in the label's chain. + label->link_to(pc_offset()); + } + + return offset; +} + + +void Assembler::DeleteUnresolvedBranchInfoForLabelTraverse(Label* label) { + ASSERT(label->is_linked()); + CheckLabelLinkChain(label); + + int link_offset = label->pos(); + int link_pcoffset; + bool end_of_chain = false; + + while (!end_of_chain) { + Instruction * link = InstructionAt(link_offset); + link_pcoffset = link->ImmPCOffset(); + + // ADR instructions are not handled by veneers. + if (link->IsImmBranch()) { + int max_reachable_pc = InstructionOffset(link) + + Instruction::ImmBranchRange(link->BranchType()); + typedef std::multimap<int, FarBranchInfo>::iterator unresolved_info_it; + std::pair<unresolved_info_it, unresolved_info_it> range; + range = unresolved_branches_.equal_range(max_reachable_pc); + unresolved_info_it it; + for (it = range.first; it != range.second; ++it) { + if (it->second.pc_offset_ == link_offset) { + unresolved_branches_.erase(it); + break; + } + } + } + + end_of_chain = (link_pcoffset == 0); + link_offset = link_offset + link_pcoffset; + } +} + + +void Assembler::DeleteUnresolvedBranchInfoForLabel(Label* label) { + if (unresolved_branches_.empty()) { + ASSERT(next_veneer_pool_check_ == kMaxInt); + return; + } + + if (label->is_linked()) { + // Branches to this label will be resolved when the label is bound, normally + // just after all the associated info has been deleted. + DeleteUnresolvedBranchInfoForLabelTraverse(label); + } + if (unresolved_branches_.empty()) { + next_veneer_pool_check_ = kMaxInt; + } else { + next_veneer_pool_check_ = + unresolved_branches_first_limit() - kVeneerDistanceCheckMargin; + } +} + + +void Assembler::StartBlockConstPool() { + if (const_pool_blocked_nesting_++ == 0) { + // Prevent constant pool checks happening by setting the next check to + // the biggest possible offset. + next_constant_pool_check_ = kMaxInt; + } +} + + +void Assembler::EndBlockConstPool() { + if (--const_pool_blocked_nesting_ == 0) { + // Check the constant pool hasn't been blocked for too long. + ASSERT((num_pending_reloc_info_ == 0) || + (pc_offset() < (first_const_pool_use_ + kMaxDistToConstPool))); + // Two cases: + // * no_const_pool_before_ >= next_constant_pool_check_ and the emission is + // still blocked + // * no_const_pool_before_ < next_constant_pool_check_ and the next emit + // will trigger a check. + next_constant_pool_check_ = no_const_pool_before_; + } +} + + +bool Assembler::is_const_pool_blocked() const { + return (const_pool_blocked_nesting_ > 0) || + (pc_offset() < no_const_pool_before_); +} + + +bool Assembler::IsConstantPoolAt(Instruction* instr) { + // The constant pool marker is made of two instructions. These instructions + // will never be emitted by the JIT, so checking for the first one is enough: + // 0: ldr xzr, #<size of pool> + bool result = instr->IsLdrLiteralX() && (instr->Rt() == xzr.code()); + + // It is still worth asserting the marker is complete. + // 4: blr xzr + ASSERT(!result || (instr->following()->IsBranchAndLinkToRegister() && + instr->following()->Rn() == xzr.code())); + + return result; +} + + +int Assembler::ConstantPoolSizeAt(Instruction* instr) { +#ifdef USE_SIMULATOR + // Assembler::debug() embeds constants directly into the instruction stream. + // Although this is not a genuine constant pool, treat it like one to avoid + // disassembling the constants. + if ((instr->Mask(ExceptionMask) == HLT) && + (instr->ImmException() == kImmExceptionIsDebug)) { + const char* message = + reinterpret_cast<const char*>( + instr->InstructionAtOffset(kDebugMessageOffset)); + int size = kDebugMessageOffset + strlen(message) + 1; + return RoundUp(size, kInstructionSize) / kInstructionSize; + } + // Same for printf support, see MacroAssembler::CallPrintf(). + if ((instr->Mask(ExceptionMask) == HLT) && + (instr->ImmException() == kImmExceptionIsPrintf)) { + return kPrintfLength / kInstructionSize; + } +#endif + if (IsConstantPoolAt(instr)) { + return instr->ImmLLiteral(); + } else { + return -1; + } +} + + +void Assembler::ConstantPoolMarker(uint32_t size) { + ASSERT(is_const_pool_blocked()); + // + 1 is for the crash guard. + Emit(LDR_x_lit | ImmLLiteral(size + 1) | Rt(xzr)); +} + + +void Assembler::EmitPoolGuard() { + // We must generate only one instruction as this is used in scopes that + // control the size of the code generated. + Emit(BLR | Rn(xzr)); +} + + +void Assembler::ConstantPoolGuard() { +#ifdef DEBUG + // Currently this is only used after a constant pool marker. + ASSERT(is_const_pool_blocked()); + Instruction* instr = reinterpret_cast<Instruction*>(pc_); + ASSERT(instr->preceding()->IsLdrLiteralX() && + instr->preceding()->Rt() == xzr.code()); +#endif + EmitPoolGuard(); +} + + +void Assembler::StartBlockVeneerPool() { + ++veneer_pool_blocked_nesting_; +} + + +void Assembler::EndBlockVeneerPool() { + if (--veneer_pool_blocked_nesting_ == 0) { + // Check the veneer pool hasn't been blocked for too long. + ASSERT(unresolved_branches_.empty() || + (pc_offset() < unresolved_branches_first_limit())); + } +} + + +void Assembler::br(const Register& xn) { + positions_recorder()->WriteRecordedPositions(); + ASSERT(xn.Is64Bits()); + Emit(BR | Rn(xn)); +} + + +void Assembler::blr(const Register& xn) { + positions_recorder()->WriteRecordedPositions(); + ASSERT(xn.Is64Bits()); + // The pattern 'blr xzr' is used as a guard to detect when execution falls + // through the constant pool. It should not be emitted. + ASSERT(!xn.Is(xzr)); + Emit(BLR | Rn(xn)); +} + + +void Assembler::ret(const Register& xn) { + positions_recorder()->WriteRecordedPositions(); + ASSERT(xn.Is64Bits()); + Emit(RET | Rn(xn)); +} + + +void Assembler::b(int imm26) { + Emit(B | ImmUncondBranch(imm26)); +} + + +void Assembler::b(Label* label) { + positions_recorder()->WriteRecordedPositions(); + b(LinkAndGetInstructionOffsetTo(label)); +} + + +void Assembler::b(int imm19, Condition cond) { + Emit(B_cond | ImmCondBranch(imm19) | cond); +} + + +void Assembler::b(Label* label, Condition cond) { + positions_recorder()->WriteRecordedPositions(); + b(LinkAndGetInstructionOffsetTo(label), cond); +} + + +void Assembler::bl(int imm26) { + positions_recorder()->WriteRecordedPositions(); + Emit(BL | ImmUncondBranch(imm26)); +} + + +void Assembler::bl(Label* label) { + positions_recorder()->WriteRecordedPositions(); + bl(LinkAndGetInstructionOffsetTo(label)); +} + + +void Assembler::cbz(const Register& rt, + int imm19) { + positions_recorder()->WriteRecordedPositions(); + Emit(SF(rt) | CBZ | ImmCmpBranch(imm19) | Rt(rt)); +} + + +void Assembler::cbz(const Register& rt, + Label* label) { + positions_recorder()->WriteRecordedPositions(); + cbz(rt, LinkAndGetInstructionOffsetTo(label)); +} + + +void Assembler::cbnz(const Register& rt, + int imm19) { + positions_recorder()->WriteRecordedPositions(); + Emit(SF(rt) | CBNZ | ImmCmpBranch(imm19) | Rt(rt)); +} + + +void Assembler::cbnz(const Register& rt, + Label* label) { + positions_recorder()->WriteRecordedPositions(); + cbnz(rt, LinkAndGetInstructionOffsetTo(label)); +} + + +void Assembler::tbz(const Register& rt, + unsigned bit_pos, + int imm14) { + positions_recorder()->WriteRecordedPositions(); + ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSizeInBits))); + Emit(TBZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt)); +} + + +void Assembler::tbz(const Register& rt, + unsigned bit_pos, + Label* label) { + positions_recorder()->WriteRecordedPositions(); + tbz(rt, bit_pos, LinkAndGetInstructionOffsetTo(label)); +} + + +void Assembler::tbnz(const Register& rt, + unsigned bit_pos, + int imm14) { + positions_recorder()->WriteRecordedPositions(); + ASSERT(rt.Is64Bits() || (rt.Is32Bits() && (bit_pos < kWRegSizeInBits))); + Emit(TBNZ | ImmTestBranchBit(bit_pos) | ImmTestBranch(imm14) | Rt(rt)); +} + + +void Assembler::tbnz(const Register& rt, + unsigned bit_pos, + Label* label) { + positions_recorder()->WriteRecordedPositions(); + tbnz(rt, bit_pos, LinkAndGetInstructionOffsetTo(label)); +} + + +void Assembler::adr(const Register& rd, int imm21) { + ASSERT(rd.Is64Bits()); + Emit(ADR | ImmPCRelAddress(imm21) | Rd(rd)); +} + + +void Assembler::adr(const Register& rd, Label* label) { + adr(rd, LinkAndGetByteOffsetTo(label)); +} + + +void Assembler::add(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSub(rd, rn, operand, LeaveFlags, ADD); +} + + +void Assembler::adds(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSub(rd, rn, operand, SetFlags, ADD); +} + + +void Assembler::cmn(const Register& rn, + const Operand& operand) { + Register zr = AppropriateZeroRegFor(rn); + adds(zr, rn, operand); +} + + +void Assembler::sub(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSub(rd, rn, operand, LeaveFlags, SUB); +} + + +void Assembler::subs(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSub(rd, rn, operand, SetFlags, SUB); +} + + +void Assembler::cmp(const Register& rn, const Operand& operand) { + Register zr = AppropriateZeroRegFor(rn); + subs(zr, rn, operand); +} + + +void Assembler::neg(const Register& rd, const Operand& operand) { + Register zr = AppropriateZeroRegFor(rd); + sub(rd, zr, operand); +} + + +void Assembler::negs(const Register& rd, const Operand& operand) { + Register zr = AppropriateZeroRegFor(rd); + subs(rd, zr, operand); +} + + +void Assembler::adc(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSubWithCarry(rd, rn, operand, LeaveFlags, ADC); +} + + +void Assembler::adcs(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSubWithCarry(rd, rn, operand, SetFlags, ADC); +} + + +void Assembler::sbc(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSubWithCarry(rd, rn, operand, LeaveFlags, SBC); +} + + +void Assembler::sbcs(const Register& rd, + const Register& rn, + const Operand& operand) { + AddSubWithCarry(rd, rn, operand, SetFlags, SBC); +} + + +void Assembler::ngc(const Register& rd, const Operand& operand) { + Register zr = AppropriateZeroRegFor(rd); + sbc(rd, zr, operand); +} + + +void Assembler::ngcs(const Register& rd, const Operand& operand) { + Register zr = AppropriateZeroRegFor(rd); + sbcs(rd, zr, operand); +} + + +// Logical instructions. +void Assembler::and_(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, AND); +} + + +void Assembler::ands(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, ANDS); +} + + +void Assembler::tst(const Register& rn, + const Operand& operand) { + ands(AppropriateZeroRegFor(rn), rn, operand); +} + + +void Assembler::bic(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, BIC); +} + + +void Assembler::bics(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, BICS); +} + + +void Assembler::orr(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, ORR); +} + + +void Assembler::orn(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, ORN); +} + + +void Assembler::eor(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, EOR); +} + + +void Assembler::eon(const Register& rd, + const Register& rn, + const Operand& operand) { + Logical(rd, rn, operand, EON); +} + + +void Assembler::lslv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Emit(SF(rd) | LSLV | Rm(rm) | Rn(rn) | Rd(rd)); +} + + +void Assembler::lsrv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Emit(SF(rd) | LSRV | Rm(rm) | Rn(rn) | Rd(rd)); +} + + +void Assembler::asrv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Emit(SF(rd) | ASRV | Rm(rm) | Rn(rn) | Rd(rd)); +} + + +void Assembler::rorv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Emit(SF(rd) | RORV | Rm(rm) | Rn(rn) | Rd(rd)); +} + + +// Bitfield operations. +void Assembler::bfm(const Register& rd, + const Register& rn, + unsigned immr, + unsigned imms) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + Instr N = SF(rd) >> (kSFOffset - kBitfieldNOffset); + Emit(SF(rd) | BFM | N | + ImmR(immr, rd.SizeInBits()) | + ImmS(imms, rn.SizeInBits()) | + Rn(rn) | Rd(rd)); +} + + +void Assembler::sbfm(const Register& rd, + const Register& rn, + unsigned immr, + unsigned imms) { + ASSERT(rd.Is64Bits() || rn.Is32Bits()); + Instr N = SF(rd) >> (kSFOffset - kBitfieldNOffset); + Emit(SF(rd) | SBFM | N | + ImmR(immr, rd.SizeInBits()) | + ImmS(imms, rn.SizeInBits()) | + Rn(rn) | Rd(rd)); +} + + +void Assembler::ubfm(const Register& rd, + const Register& rn, + unsigned immr, + unsigned imms) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + Instr N = SF(rd) >> (kSFOffset - kBitfieldNOffset); + Emit(SF(rd) | UBFM | N | + ImmR(immr, rd.SizeInBits()) | + ImmS(imms, rn.SizeInBits()) | + Rn(rn) | Rd(rd)); +} + + +void Assembler::extr(const Register& rd, + const Register& rn, + const Register& rm, + unsigned lsb) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Instr N = SF(rd) >> (kSFOffset - kBitfieldNOffset); + Emit(SF(rd) | EXTR | N | Rm(rm) | + ImmS(lsb, rn.SizeInBits()) | Rn(rn) | Rd(rd)); +} + + +void Assembler::csel(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond) { + ConditionalSelect(rd, rn, rm, cond, CSEL); +} + + +void Assembler::csinc(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond) { + ConditionalSelect(rd, rn, rm, cond, CSINC); +} + + +void Assembler::csinv(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond) { + ConditionalSelect(rd, rn, rm, cond, CSINV); +} + + +void Assembler::csneg(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond) { + ConditionalSelect(rd, rn, rm, cond, CSNEG); +} + + +void Assembler::cset(const Register &rd, Condition cond) { + ASSERT((cond != al) && (cond != nv)); + Register zr = AppropriateZeroRegFor(rd); + csinc(rd, zr, zr, NegateCondition(cond)); +} + + +void Assembler::csetm(const Register &rd, Condition cond) { + ASSERT((cond != al) && (cond != nv)); + Register zr = AppropriateZeroRegFor(rd); + csinv(rd, zr, zr, NegateCondition(cond)); +} + + +void Assembler::cinc(const Register &rd, const Register &rn, Condition cond) { + ASSERT((cond != al) && (cond != nv)); + csinc(rd, rn, rn, NegateCondition(cond)); +} + + +void Assembler::cinv(const Register &rd, const Register &rn, Condition cond) { + ASSERT((cond != al) && (cond != nv)); + csinv(rd, rn, rn, NegateCondition(cond)); +} + + +void Assembler::cneg(const Register &rd, const Register &rn, Condition cond) { + ASSERT((cond != al) && (cond != nv)); + csneg(rd, rn, rn, NegateCondition(cond)); +} + + +void Assembler::ConditionalSelect(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond, + ConditionalSelectOp op) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Emit(SF(rd) | op | Rm(rm) | Cond(cond) | Rn(rn) | Rd(rd)); +} + + +void Assembler::ccmn(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond) { + ConditionalCompare(rn, operand, nzcv, cond, CCMN); +} + + +void Assembler::ccmp(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond) { + ConditionalCompare(rn, operand, nzcv, cond, CCMP); +} + + +void Assembler::DataProcessing3Source(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra, + DataProcessing3SourceOp op) { + Emit(SF(rd) | op | Rm(rm) | Ra(ra) | Rn(rn) | Rd(rd)); +} + + +void Assembler::mul(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(AreSameSizeAndType(rd, rn, rm)); + Register zr = AppropriateZeroRegFor(rn); + DataProcessing3Source(rd, rn, rm, zr, MADD); +} + + +void Assembler::madd(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(AreSameSizeAndType(rd, rn, rm, ra)); + DataProcessing3Source(rd, rn, rm, ra, MADD); +} + + +void Assembler::mneg(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(AreSameSizeAndType(rd, rn, rm)); + Register zr = AppropriateZeroRegFor(rn); + DataProcessing3Source(rd, rn, rm, zr, MSUB); +} + + +void Assembler::msub(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(AreSameSizeAndType(rd, rn, rm, ra)); + DataProcessing3Source(rd, rn, rm, ra, MSUB); +} + + +void Assembler::smaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(rd.Is64Bits() && ra.Is64Bits()); + ASSERT(rn.Is32Bits() && rm.Is32Bits()); + DataProcessing3Source(rd, rn, rm, ra, SMADDL_x); +} + + +void Assembler::smsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(rd.Is64Bits() && ra.Is64Bits()); + ASSERT(rn.Is32Bits() && rm.Is32Bits()); + DataProcessing3Source(rd, rn, rm, ra, SMSUBL_x); +} + + +void Assembler::umaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(rd.Is64Bits() && ra.Is64Bits()); + ASSERT(rn.Is32Bits() && rm.Is32Bits()); + DataProcessing3Source(rd, rn, rm, ra, UMADDL_x); +} + + +void Assembler::umsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(rd.Is64Bits() && ra.Is64Bits()); + ASSERT(rn.Is32Bits() && rm.Is32Bits()); + DataProcessing3Source(rd, rn, rm, ra, UMSUBL_x); +} + + +void Assembler::smull(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(rd.Is64Bits()); + ASSERT(rn.Is32Bits() && rm.Is32Bits()); + DataProcessing3Source(rd, rn, rm, xzr, SMADDL_x); +} + + +void Assembler::smulh(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(AreSameSizeAndType(rd, rn, rm)); + DataProcessing3Source(rd, rn, rm, xzr, SMULH_x); +} + + +void Assembler::sdiv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Emit(SF(rd) | SDIV | Rm(rm) | Rn(rn) | Rd(rd)); +} + + +void Assembler::udiv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == rm.SizeInBits()); + Emit(SF(rd) | UDIV | Rm(rm) | Rn(rn) | Rd(rd)); +} + + +void Assembler::rbit(const Register& rd, + const Register& rn) { + DataProcessing1Source(rd, rn, RBIT); +} + + +void Assembler::rev16(const Register& rd, + const Register& rn) { + DataProcessing1Source(rd, rn, REV16); +} + + +void Assembler::rev32(const Register& rd, + const Register& rn) { + ASSERT(rd.Is64Bits()); + DataProcessing1Source(rd, rn, REV); +} + + +void Assembler::rev(const Register& rd, + const Register& rn) { + DataProcessing1Source(rd, rn, rd.Is64Bits() ? REV_x : REV_w); +} + + +void Assembler::clz(const Register& rd, + const Register& rn) { + DataProcessing1Source(rd, rn, CLZ); +} + + +void Assembler::cls(const Register& rd, + const Register& rn) { + DataProcessing1Source(rd, rn, CLS); +} + + +void Assembler::ldp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& src) { + LoadStorePair(rt, rt2, src, LoadPairOpFor(rt, rt2)); +} + + +void Assembler::stp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& dst) { + LoadStorePair(rt, rt2, dst, StorePairOpFor(rt, rt2)); +} + + +void Assembler::ldpsw(const Register& rt, + const Register& rt2, + const MemOperand& src) { + ASSERT(rt.Is64Bits()); + LoadStorePair(rt, rt2, src, LDPSW_x); +} + + +void Assembler::LoadStorePair(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& addr, + LoadStorePairOp op) { + // 'rt' and 'rt2' can only be aliased for stores. + ASSERT(((op & LoadStorePairLBit) == 0) || !rt.Is(rt2)); + ASSERT(AreSameSizeAndType(rt, rt2)); + + Instr memop = op | Rt(rt) | Rt2(rt2) | RnSP(addr.base()) | + ImmLSPair(addr.offset(), CalcLSPairDataSize(op)); + + Instr addrmodeop; + if (addr.IsImmediateOffset()) { + addrmodeop = LoadStorePairOffsetFixed; + } else { + // Pre-index and post-index modes. + ASSERT(!rt.Is(addr.base())); + ASSERT(!rt2.Is(addr.base())); + ASSERT(addr.offset() != 0); + if (addr.IsPreIndex()) { + addrmodeop = LoadStorePairPreIndexFixed; + } else { + ASSERT(addr.IsPostIndex()); + addrmodeop = LoadStorePairPostIndexFixed; + } + } + Emit(addrmodeop | memop); +} + + +void Assembler::ldnp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& src) { + LoadStorePairNonTemporal(rt, rt2, src, + LoadPairNonTemporalOpFor(rt, rt2)); +} + + +void Assembler::stnp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& dst) { + LoadStorePairNonTemporal(rt, rt2, dst, + StorePairNonTemporalOpFor(rt, rt2)); +} + + +void Assembler::LoadStorePairNonTemporal(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& addr, + LoadStorePairNonTemporalOp op) { + ASSERT(!rt.Is(rt2)); + ASSERT(AreSameSizeAndType(rt, rt2)); + ASSERT(addr.IsImmediateOffset()); + + LSDataSize size = CalcLSPairDataSize( + static_cast<LoadStorePairOp>(op & LoadStorePairMask)); + Emit(op | Rt(rt) | Rt2(rt2) | RnSP(addr.base()) | + ImmLSPair(addr.offset(), size)); +} + + +// Memory instructions. +void Assembler::ldrb(const Register& rt, const MemOperand& src) { + LoadStore(rt, src, LDRB_w); +} + + +void Assembler::strb(const Register& rt, const MemOperand& dst) { + LoadStore(rt, dst, STRB_w); +} + + +void Assembler::ldrsb(const Register& rt, const MemOperand& src) { + LoadStore(rt, src, rt.Is64Bits() ? LDRSB_x : LDRSB_w); +} + + +void Assembler::ldrh(const Register& rt, const MemOperand& src) { + LoadStore(rt, src, LDRH_w); +} + + +void Assembler::strh(const Register& rt, const MemOperand& dst) { + LoadStore(rt, dst, STRH_w); +} + + +void Assembler::ldrsh(const Register& rt, const MemOperand& src) { + LoadStore(rt, src, rt.Is64Bits() ? LDRSH_x : LDRSH_w); +} + + +void Assembler::ldr(const CPURegister& rt, const MemOperand& src) { + LoadStore(rt, src, LoadOpFor(rt)); +} + + +void Assembler::str(const CPURegister& rt, const MemOperand& src) { + LoadStore(rt, src, StoreOpFor(rt)); +} + + +void Assembler::ldrsw(const Register& rt, const MemOperand& src) { + ASSERT(rt.Is64Bits()); + LoadStore(rt, src, LDRSW_x); +} + + +void Assembler::ldr_pcrel(const CPURegister& rt, int imm19) { + // The pattern 'ldr xzr, #offset' is used to indicate the beginning of a + // constant pool. It should not be emitted. + ASSERT(!rt.IsZero()); + Emit(LoadLiteralOpFor(rt) | ImmLLiteral(imm19) | Rt(rt)); +} + + +void Assembler::ldr(const CPURegister& rt, const Immediate& imm) { + // Currently we only support 64-bit literals. + ASSERT(rt.Is64Bits()); + + RecordRelocInfo(imm.rmode(), imm.value()); + BlockConstPoolFor(1); + // The load will be patched when the constpool is emitted, patching code + // expect a load literal with offset 0. + ldr_pcrel(rt, 0); +} + + +void Assembler::mov(const Register& rd, const Register& rm) { + // Moves involving the stack pointer are encoded as add immediate with + // second operand of zero. Otherwise, orr with first operand zr is + // used. + if (rd.IsSP() || rm.IsSP()) { + add(rd, rm, 0); + } else { + orr(rd, AppropriateZeroRegFor(rd), rm); + } +} + + +void Assembler::mvn(const Register& rd, const Operand& operand) { + orn(rd, AppropriateZeroRegFor(rd), operand); +} + + +void Assembler::mrs(const Register& rt, SystemRegister sysreg) { + ASSERT(rt.Is64Bits()); + Emit(MRS | ImmSystemRegister(sysreg) | Rt(rt)); +} + + +void Assembler::msr(SystemRegister sysreg, const Register& rt) { + ASSERT(rt.Is64Bits()); + Emit(MSR | Rt(rt) | ImmSystemRegister(sysreg)); +} + + +void Assembler::hint(SystemHint code) { + Emit(HINT | ImmHint(code) | Rt(xzr)); +} + + +void Assembler::dmb(BarrierDomain domain, BarrierType type) { + Emit(DMB | ImmBarrierDomain(domain) | ImmBarrierType(type)); +} + + +void Assembler::dsb(BarrierDomain domain, BarrierType type) { + Emit(DSB | ImmBarrierDomain(domain) | ImmBarrierType(type)); +} + + +void Assembler::isb() { + Emit(ISB | ImmBarrierDomain(FullSystem) | ImmBarrierType(BarrierAll)); +} + + +void Assembler::fmov(FPRegister fd, double imm) { + ASSERT(fd.Is64Bits()); + ASSERT(IsImmFP64(imm)); + Emit(FMOV_d_imm | Rd(fd) | ImmFP64(imm)); +} + + +void Assembler::fmov(FPRegister fd, float imm) { + ASSERT(fd.Is32Bits()); + ASSERT(IsImmFP32(imm)); + Emit(FMOV_s_imm | Rd(fd) | ImmFP32(imm)); +} + + +void Assembler::fmov(Register rd, FPRegister fn) { + ASSERT(rd.SizeInBits() == fn.SizeInBits()); + FPIntegerConvertOp op = rd.Is32Bits() ? FMOV_ws : FMOV_xd; + Emit(op | Rd(rd) | Rn(fn)); +} + + +void Assembler::fmov(FPRegister fd, Register rn) { + ASSERT(fd.SizeInBits() == rn.SizeInBits()); + FPIntegerConvertOp op = fd.Is32Bits() ? FMOV_sw : FMOV_dx; + Emit(op | Rd(fd) | Rn(rn)); +} + + +void Assembler::fmov(FPRegister fd, FPRegister fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + Emit(FPType(fd) | FMOV | Rd(fd) | Rn(fn)); +} + + +void Assembler::fadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FADD); +} + + +void Assembler::fsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FSUB); +} + + +void Assembler::fmul(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FMUL); +} + + +void Assembler::fmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + FPDataProcessing3Source(fd, fn, fm, fa, fd.Is32Bits() ? FMADD_s : FMADD_d); +} + + +void Assembler::fmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + FPDataProcessing3Source(fd, fn, fm, fa, fd.Is32Bits() ? FMSUB_s : FMSUB_d); +} + + +void Assembler::fnmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + FPDataProcessing3Source(fd, fn, fm, fa, fd.Is32Bits() ? FNMADD_s : FNMADD_d); +} + + +void Assembler::fnmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + FPDataProcessing3Source(fd, fn, fm, fa, fd.Is32Bits() ? FNMSUB_s : FNMSUB_d); +} + + +void Assembler::fdiv(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FDIV); +} + + +void Assembler::fmax(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FMAX); +} + + +void Assembler::fmaxnm(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FMAXNM); +} + + +void Assembler::fmin(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FMIN); +} + + +void Assembler::fminnm(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + FPDataProcessing2Source(fd, fn, fm, FMINNM); +} + + +void Assembler::fabs(const FPRegister& fd, + const FPRegister& fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + FPDataProcessing1Source(fd, fn, FABS); +} + + +void Assembler::fneg(const FPRegister& fd, + const FPRegister& fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + FPDataProcessing1Source(fd, fn, FNEG); +} + + +void Assembler::fsqrt(const FPRegister& fd, + const FPRegister& fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + FPDataProcessing1Source(fd, fn, FSQRT); +} + + +void Assembler::frinta(const FPRegister& fd, + const FPRegister& fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + FPDataProcessing1Source(fd, fn, FRINTA); +} + + +void Assembler::frintm(const FPRegister& fd, + const FPRegister& fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + FPDataProcessing1Source(fd, fn, FRINTM); +} + + +void Assembler::frintn(const FPRegister& fd, + const FPRegister& fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + FPDataProcessing1Source(fd, fn, FRINTN); +} + + +void Assembler::frintz(const FPRegister& fd, + const FPRegister& fn) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + FPDataProcessing1Source(fd, fn, FRINTZ); +} + + +void Assembler::fcmp(const FPRegister& fn, + const FPRegister& fm) { + ASSERT(fn.SizeInBits() == fm.SizeInBits()); + Emit(FPType(fn) | FCMP | Rm(fm) | Rn(fn)); +} + + +void Assembler::fcmp(const FPRegister& fn, + double value) { + USE(value); + // Although the fcmp instruction can strictly only take an immediate value of + // +0.0, we don't need to check for -0.0 because the sign of 0.0 doesn't + // affect the result of the comparison. + ASSERT(value == 0.0); + Emit(FPType(fn) | FCMP_zero | Rn(fn)); +} + + +void Assembler::fccmp(const FPRegister& fn, + const FPRegister& fm, + StatusFlags nzcv, + Condition cond) { + ASSERT(fn.SizeInBits() == fm.SizeInBits()); + Emit(FPType(fn) | FCCMP | Rm(fm) | Cond(cond) | Rn(fn) | Nzcv(nzcv)); +} + + +void Assembler::fcsel(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + Condition cond) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + ASSERT(fd.SizeInBits() == fm.SizeInBits()); + Emit(FPType(fd) | FCSEL | Rm(fm) | Cond(cond) | Rn(fn) | Rd(fd)); +} + + +void Assembler::FPConvertToInt(const Register& rd, + const FPRegister& fn, + FPIntegerConvertOp op) { + Emit(SF(rd) | FPType(fn) | op | Rn(fn) | Rd(rd)); +} + + +void Assembler::fcvt(const FPRegister& fd, + const FPRegister& fn) { + if (fd.Is64Bits()) { + // Convert float to double. + ASSERT(fn.Is32Bits()); + FPDataProcessing1Source(fd, fn, FCVT_ds); + } else { + // Convert double to float. + ASSERT(fn.Is64Bits()); + FPDataProcessing1Source(fd, fn, FCVT_sd); + } +} + + +void Assembler::fcvtau(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTAU); +} + + +void Assembler::fcvtas(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTAS); +} + + +void Assembler::fcvtmu(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTMU); +} + + +void Assembler::fcvtms(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTMS); +} + + +void Assembler::fcvtnu(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTNU); +} + + +void Assembler::fcvtns(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTNS); +} + + +void Assembler::fcvtzu(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTZU); +} + + +void Assembler::fcvtzs(const Register& rd, const FPRegister& fn) { + FPConvertToInt(rd, fn, FCVTZS); +} + + +void Assembler::scvtf(const FPRegister& fd, + const Register& rn, + unsigned fbits) { + if (fbits == 0) { + Emit(SF(rn) | FPType(fd) | SCVTF | Rn(rn) | Rd(fd)); + } else { + Emit(SF(rn) | FPType(fd) | SCVTF_fixed | FPScale(64 - fbits) | Rn(rn) | + Rd(fd)); + } +} + + +void Assembler::ucvtf(const FPRegister& fd, + const Register& rn, + unsigned fbits) { + if (fbits == 0) { + Emit(SF(rn) | FPType(fd) | UCVTF | Rn(rn) | Rd(fd)); + } else { + Emit(SF(rn) | FPType(fd) | UCVTF_fixed | FPScale(64 - fbits) | Rn(rn) | + Rd(fd)); + } +} + + +// Note: +// Below, a difference in case for the same letter indicates a +// negated bit. +// If b is 1, then B is 0. +Instr Assembler::ImmFP32(float imm) { + ASSERT(IsImmFP32(imm)); + // bits: aBbb.bbbc.defg.h000.0000.0000.0000.0000 + uint32_t bits = float_to_rawbits(imm); + // bit7: a000.0000 + uint32_t bit7 = ((bits >> 31) & 0x1) << 7; + // bit6: 0b00.0000 + uint32_t bit6 = ((bits >> 29) & 0x1) << 6; + // bit5_to_0: 00cd.efgh + uint32_t bit5_to_0 = (bits >> 19) & 0x3f; + + return (bit7 | bit6 | bit5_to_0) << ImmFP_offset; +} + + +Instr Assembler::ImmFP64(double imm) { + ASSERT(IsImmFP64(imm)); + // bits: aBbb.bbbb.bbcd.efgh.0000.0000.0000.0000 + // 0000.0000.0000.0000.0000.0000.0000.0000 + uint64_t bits = double_to_rawbits(imm); + // bit7: a000.0000 + uint32_t bit7 = ((bits >> 63) & 0x1) << 7; + // bit6: 0b00.0000 + uint32_t bit6 = ((bits >> 61) & 0x1) << 6; + // bit5_to_0: 00cd.efgh + uint32_t bit5_to_0 = (bits >> 48) & 0x3f; + + return (bit7 | bit6 | bit5_to_0) << ImmFP_offset; +} + + +// Code generation helpers. +void Assembler::MoveWide(const Register& rd, + uint64_t imm, + int shift, + MoveWideImmediateOp mov_op) { + if (shift >= 0) { + // Explicit shift specified. + ASSERT((shift == 0) || (shift == 16) || (shift == 32) || (shift == 48)); + ASSERT(rd.Is64Bits() || (shift == 0) || (shift == 16)); + shift /= 16; + } else { + // Calculate a new immediate and shift combination to encode the immediate + // argument. + shift = 0; + if ((imm & ~0xffffUL) == 0) { + // Nothing to do. + } else if ((imm & ~(0xffffUL << 16)) == 0) { + imm >>= 16; + shift = 1; + } else if ((imm & ~(0xffffUL << 32)) == 0) { + ASSERT(rd.Is64Bits()); + imm >>= 32; + shift = 2; + } else if ((imm & ~(0xffffUL << 48)) == 0) { + ASSERT(rd.Is64Bits()); + imm >>= 48; + shift = 3; + } + } + + ASSERT(is_uint16(imm)); + + Emit(SF(rd) | MoveWideImmediateFixed | mov_op | + Rd(rd) | ImmMoveWide(imm) | ShiftMoveWide(shift)); +} + + +void Assembler::AddSub(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubOp op) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(!operand.NeedsRelocation(this)); + if (operand.IsImmediate()) { + int64_t immediate = operand.ImmediateValue(); + ASSERT(IsImmAddSub(immediate)); + Instr dest_reg = (S == SetFlags) ? Rd(rd) : RdSP(rd); + Emit(SF(rd) | AddSubImmediateFixed | op | Flags(S) | + ImmAddSub(immediate) | dest_reg | RnSP(rn)); + } else if (operand.IsShiftedRegister()) { + ASSERT(operand.reg().SizeInBits() == rd.SizeInBits()); + ASSERT(operand.shift() != ROR); + + // For instructions of the form: + // add/sub wsp, <Wn>, <Wm> [, LSL #0-3 ] + // add/sub <Wd>, wsp, <Wm> [, LSL #0-3 ] + // add/sub wsp, wsp, <Wm> [, LSL #0-3 ] + // adds/subs <Wd>, wsp, <Wm> [, LSL #0-3 ] + // or their 64-bit register equivalents, convert the operand from shifted to + // extended register mode, and emit an add/sub extended instruction. + if (rn.IsSP() || rd.IsSP()) { + ASSERT(!(rd.IsSP() && (S == SetFlags))); + DataProcExtendedRegister(rd, rn, operand.ToExtendedRegister(), S, + AddSubExtendedFixed | op); + } else { + DataProcShiftedRegister(rd, rn, operand, S, AddSubShiftedFixed | op); + } + } else { + ASSERT(operand.IsExtendedRegister()); + DataProcExtendedRegister(rd, rn, operand, S, AddSubExtendedFixed | op); + } +} + + +void Assembler::AddSubWithCarry(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubWithCarryOp op) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(rd.SizeInBits() == operand.reg().SizeInBits()); + ASSERT(operand.IsShiftedRegister() && (operand.shift_amount() == 0)); + ASSERT(!operand.NeedsRelocation(this)); + Emit(SF(rd) | op | Flags(S) | Rm(operand.reg()) | Rn(rn) | Rd(rd)); +} + + +void Assembler::hlt(int code) { + ASSERT(is_uint16(code)); + Emit(HLT | ImmException(code)); +} + + +void Assembler::brk(int code) { + ASSERT(is_uint16(code)); + Emit(BRK | ImmException(code)); +} + + +void Assembler::debug(const char* message, uint32_t code, Instr params) { +#ifdef USE_SIMULATOR + // Don't generate simulator specific code if we are building a snapshot, which + // might be run on real hardware. + if (!serializer_enabled()) { + // The arguments to the debug marker need to be contiguous in memory, so + // make sure we don't try to emit pools. + BlockPoolsScope scope(this); + + Label start; + bind(&start); + + // Refer to instructions-arm64.h for a description of the marker and its + // arguments. + hlt(kImmExceptionIsDebug); + ASSERT(SizeOfCodeGeneratedSince(&start) == kDebugCodeOffset); + dc32(code); + ASSERT(SizeOfCodeGeneratedSince(&start) == kDebugParamsOffset); + dc32(params); + ASSERT(SizeOfCodeGeneratedSince(&start) == kDebugMessageOffset); + EmitStringData(message); + hlt(kImmExceptionIsUnreachable); + + return; + } + // Fall through if Serializer is enabled. +#endif + + if (params & BREAK) { + hlt(kImmExceptionIsDebug); + } +} + + +void Assembler::Logical(const Register& rd, + const Register& rn, + const Operand& operand, + LogicalOp op) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + ASSERT(!operand.NeedsRelocation(this)); + if (operand.IsImmediate()) { + int64_t immediate = operand.ImmediateValue(); + unsigned reg_size = rd.SizeInBits(); + + ASSERT(immediate != 0); + ASSERT(immediate != -1); + ASSERT(rd.Is64Bits() || is_uint32(immediate)); + + // If the operation is NOT, invert the operation and immediate. + if ((op & NOT) == NOT) { + op = static_cast<LogicalOp>(op & ~NOT); + immediate = rd.Is64Bits() ? ~immediate : (~immediate & kWRegMask); + } + + unsigned n, imm_s, imm_r; + if (IsImmLogical(immediate, reg_size, &n, &imm_s, &imm_r)) { + // Immediate can be encoded in the instruction. + LogicalImmediate(rd, rn, n, imm_s, imm_r, op); + } else { + // This case is handled in the macro assembler. + UNREACHABLE(); + } + } else { + ASSERT(operand.IsShiftedRegister()); + ASSERT(operand.reg().SizeInBits() == rd.SizeInBits()); + Instr dp_op = static_cast<Instr>(op | LogicalShiftedFixed); + DataProcShiftedRegister(rd, rn, operand, LeaveFlags, dp_op); + } +} + + +void Assembler::LogicalImmediate(const Register& rd, + const Register& rn, + unsigned n, + unsigned imm_s, + unsigned imm_r, + LogicalOp op) { + unsigned reg_size = rd.SizeInBits(); + Instr dest_reg = (op == ANDS) ? Rd(rd) : RdSP(rd); + Emit(SF(rd) | LogicalImmediateFixed | op | BitN(n, reg_size) | + ImmSetBits(imm_s, reg_size) | ImmRotate(imm_r, reg_size) | dest_reg | + Rn(rn)); +} + + +void Assembler::ConditionalCompare(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond, + ConditionalCompareOp op) { + Instr ccmpop; + ASSERT(!operand.NeedsRelocation(this)); + if (operand.IsImmediate()) { + int64_t immediate = operand.ImmediateValue(); + ASSERT(IsImmConditionalCompare(immediate)); + ccmpop = ConditionalCompareImmediateFixed | op | ImmCondCmp(immediate); + } else { + ASSERT(operand.IsShiftedRegister() && (operand.shift_amount() == 0)); + ccmpop = ConditionalCompareRegisterFixed | op | Rm(operand.reg()); + } + Emit(SF(rn) | ccmpop | Cond(cond) | Rn(rn) | Nzcv(nzcv)); +} + + +void Assembler::DataProcessing1Source(const Register& rd, + const Register& rn, + DataProcessing1SourceOp op) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + Emit(SF(rn) | op | Rn(rn) | Rd(rd)); +} + + +void Assembler::FPDataProcessing1Source(const FPRegister& fd, + const FPRegister& fn, + FPDataProcessing1SourceOp op) { + Emit(FPType(fn) | op | Rn(fn) | Rd(fd)); +} + + +void Assembler::FPDataProcessing2Source(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + FPDataProcessing2SourceOp op) { + ASSERT(fd.SizeInBits() == fn.SizeInBits()); + ASSERT(fd.SizeInBits() == fm.SizeInBits()); + Emit(FPType(fd) | op | Rm(fm) | Rn(fn) | Rd(fd)); +} + + +void Assembler::FPDataProcessing3Source(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa, + FPDataProcessing3SourceOp op) { + ASSERT(AreSameSizeAndType(fd, fn, fm, fa)); + Emit(FPType(fd) | op | Rm(fm) | Rn(fn) | Rd(fd) | Ra(fa)); +} + + +void Assembler::EmitShift(const Register& rd, + const Register& rn, + Shift shift, + unsigned shift_amount) { + switch (shift) { + case LSL: + lsl(rd, rn, shift_amount); + break; + case LSR: + lsr(rd, rn, shift_amount); + break; + case ASR: + asr(rd, rn, shift_amount); + break; + case ROR: + ror(rd, rn, shift_amount); + break; + default: + UNREACHABLE(); + } +} + + +void Assembler::EmitExtendShift(const Register& rd, + const Register& rn, + Extend extend, + unsigned left_shift) { + ASSERT(rd.SizeInBits() >= rn.SizeInBits()); + unsigned reg_size = rd.SizeInBits(); + // Use the correct size of register. + Register rn_ = Register::Create(rn.code(), rd.SizeInBits()); + // Bits extracted are high_bit:0. + unsigned high_bit = (8 << (extend & 0x3)) - 1; + // Number of bits left in the result that are not introduced by the shift. + unsigned non_shift_bits = (reg_size - left_shift) & (reg_size - 1); + + if ((non_shift_bits > high_bit) || (non_shift_bits == 0)) { + switch (extend) { + case UXTB: + case UXTH: + case UXTW: ubfm(rd, rn_, non_shift_bits, high_bit); break; + case SXTB: + case SXTH: + case SXTW: sbfm(rd, rn_, non_shift_bits, high_bit); break; + case UXTX: + case SXTX: { + ASSERT(rn.SizeInBits() == kXRegSizeInBits); + // Nothing to extend. Just shift. + lsl(rd, rn_, left_shift); + break; + } + default: UNREACHABLE(); + } + } else { + // No need to extend as the extended bits would be shifted away. + lsl(rd, rn_, left_shift); + } +} + + +void Assembler::DataProcShiftedRegister(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + Instr op) { + ASSERT(operand.IsShiftedRegister()); + ASSERT(rn.Is64Bits() || (rn.Is32Bits() && is_uint5(operand.shift_amount()))); + ASSERT(!operand.NeedsRelocation(this)); + Emit(SF(rd) | op | Flags(S) | + ShiftDP(operand.shift()) | ImmDPShift(operand.shift_amount()) | + Rm(operand.reg()) | Rn(rn) | Rd(rd)); +} + + +void Assembler::DataProcExtendedRegister(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + Instr op) { + ASSERT(!operand.NeedsRelocation(this)); + Instr dest_reg = (S == SetFlags) ? Rd(rd) : RdSP(rd); + Emit(SF(rd) | op | Flags(S) | Rm(operand.reg()) | + ExtendMode(operand.extend()) | ImmExtendShift(operand.shift_amount()) | + dest_reg | RnSP(rn)); +} + + +bool Assembler::IsImmAddSub(int64_t immediate) { + return is_uint12(immediate) || + (is_uint12(immediate >> 12) && ((immediate & 0xfff) == 0)); +} + +void Assembler::LoadStore(const CPURegister& rt, + const MemOperand& addr, + LoadStoreOp op) { + Instr memop = op | Rt(rt) | RnSP(addr.base()); + ptrdiff_t offset = addr.offset(); + + if (addr.IsImmediateOffset()) { + LSDataSize size = CalcLSDataSize(op); + if (IsImmLSScaled(offset, size)) { + // Use the scaled addressing mode. + Emit(LoadStoreUnsignedOffsetFixed | memop | + ImmLSUnsigned(offset >> size)); + } else if (IsImmLSUnscaled(offset)) { + // Use the unscaled addressing mode. + Emit(LoadStoreUnscaledOffsetFixed | memop | ImmLS(offset)); + } else { + // This case is handled in the macro assembler. + UNREACHABLE(); + } + } else if (addr.IsRegisterOffset()) { + Extend ext = addr.extend(); + Shift shift = addr.shift(); + unsigned shift_amount = addr.shift_amount(); + + // LSL is encoded in the option field as UXTX. + if (shift == LSL) { + ext = UXTX; + } + + // Shifts are encoded in one bit, indicating a left shift by the memory + // access size. + ASSERT((shift_amount == 0) || + (shift_amount == static_cast<unsigned>(CalcLSDataSize(op)))); + Emit(LoadStoreRegisterOffsetFixed | memop | Rm(addr.regoffset()) | + ExtendMode(ext) | ImmShiftLS((shift_amount > 0) ? 1 : 0)); + } else { + // Pre-index and post-index modes. + ASSERT(!rt.Is(addr.base())); + if (IsImmLSUnscaled(offset)) { + if (addr.IsPreIndex()) { + Emit(LoadStorePreIndexFixed | memop | ImmLS(offset)); + } else { + ASSERT(addr.IsPostIndex()); + Emit(LoadStorePostIndexFixed | memop | ImmLS(offset)); + } + } else { + // This case is handled in the macro assembler. + UNREACHABLE(); + } + } +} + + +bool Assembler::IsImmLSUnscaled(ptrdiff_t offset) { + return is_int9(offset); +} + + +bool Assembler::IsImmLSScaled(ptrdiff_t offset, LSDataSize size) { + bool offset_is_size_multiple = (((offset >> size) << size) == offset); + return offset_is_size_multiple && is_uint12(offset >> size); +} + + +// 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 not be encoded, the function returns false, and the values pointed +// to by n, imm_s and imm_r are undefined. +bool Assembler::IsImmLogical(uint64_t value, + unsigned width, + unsigned* n, + unsigned* imm_s, + unsigned* imm_r) { + ASSERT((n != NULL) && (imm_s != NULL) && (imm_r != NULL)); + ASSERT((width == kWRegSizeInBits) || (width == kXRegSizeInBits)); + + // 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 arbitary immediate can be encoded using this scheme, an + // iterative algorithm is used. + // + // TODO(mcapewel) This code does not consider using X/W register overlap to + // support 64-bit immediates where the top 32-bits are zero, and the bottom + // 32-bits are an encodable logical immediate. + + // 1. If the value has all set or all clear bits, it can't be encoded. + if ((value == 0) || (value == 0xffffffffffffffffUL) || + ((width == kWRegSizeInBits) && (value == 0xffffffff))) { + return false; + } + + unsigned lead_zero = CountLeadingZeros(value, width); + unsigned lead_one = CountLeadingZeros(~value, width); + unsigned trail_zero = CountTrailingZeros(value, width); + unsigned trail_one = CountTrailingZeros(~value, width); + unsigned set_bits = CountSetBits(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; + 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) { + 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) { + 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 = (1UL << (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; + } +} + + +bool Assembler::IsImmConditionalCompare(int64_t immediate) { + return is_uint5(immediate); +} + + +bool Assembler::IsImmFP32(float imm) { + // Valid values will have the form: + // aBbb.bbbc.defg.h000.0000.0000.0000.0000 + uint32_t bits = float_to_rawbits(imm); + // bits[19..0] are cleared. + if ((bits & 0x7ffff) != 0) { + return false; + } + + // bits[29..25] are all set or all cleared. + uint32_t b_pattern = (bits >> 16) & 0x3e00; + if (b_pattern != 0 && b_pattern != 0x3e00) { + return false; + } + + // bit[30] and bit[29] are opposite. + if (((bits ^ (bits << 1)) & 0x40000000) == 0) { + return false; + } + + return true; +} + + +bool Assembler::IsImmFP64(double imm) { + // Valid values will have the form: + // aBbb.bbbb.bbcd.efgh.0000.0000.0000.0000 + // 0000.0000.0000.0000.0000.0000.0000.0000 + uint64_t bits = double_to_rawbits(imm); + // bits[47..0] are cleared. + if ((bits & 0xffffffffffffL) != 0) { + return false; + } + + // bits[61..54] are all set or all cleared. + uint32_t b_pattern = (bits >> 48) & 0x3fc0; + if (b_pattern != 0 && b_pattern != 0x3fc0) { + return false; + } + + // bit[62] and bit[61] are opposite. + if (((bits ^ (bits << 1)) & 0x4000000000000000L) == 0) { + return false; + } + + return true; +} + + +void Assembler::GrowBuffer() { + if (!own_buffer_) FATAL("external code buffer is too small"); + + // Compute new buffer size. + CodeDesc desc; // the new buffer + if (buffer_size_ < 4 * KB) { + desc.buffer_size = 4 * KB; + } else if (buffer_size_ < 1 * MB) { + desc.buffer_size = 2 * buffer_size_; + } else { + desc.buffer_size = buffer_size_ + 1 * MB; + } + CHECK_GT(desc.buffer_size, 0); // No overflow. + + byte* buffer = reinterpret_cast<byte*>(buffer_); + + // Set up new buffer. + desc.buffer = NewArray<byte>(desc.buffer_size); + + desc.instr_size = pc_offset(); + desc.reloc_size = (buffer + buffer_size_) - reloc_info_writer.pos(); + + // Copy the data. + intptr_t pc_delta = desc.buffer - buffer; + intptr_t rc_delta = (desc.buffer + desc.buffer_size) - + (buffer + buffer_size_); + memmove(desc.buffer, buffer, desc.instr_size); + memmove(reloc_info_writer.pos() + rc_delta, + reloc_info_writer.pos(), desc.reloc_size); + + // Switch buffers. + DeleteArray(buffer_); + buffer_ = desc.buffer; + buffer_size_ = desc.buffer_size; + pc_ = reinterpret_cast<byte*>(pc_) + pc_delta; + reloc_info_writer.Reposition(reloc_info_writer.pos() + rc_delta, + reloc_info_writer.last_pc() + pc_delta); + + // None of our relocation types are pc relative pointing outside the code + // buffer nor pc absolute pointing inside the code buffer, so there is no need + // to relocate any emitted relocation entries. + + // Relocate pending relocation entries. + for (int i = 0; i < num_pending_reloc_info_; i++) { + RelocInfo& rinfo = pending_reloc_info_[i]; + ASSERT(rinfo.rmode() != RelocInfo::COMMENT && + rinfo.rmode() != RelocInfo::POSITION); + if (rinfo.rmode() != RelocInfo::JS_RETURN) { + rinfo.set_pc(rinfo.pc() + pc_delta); + } + } +} + + +void Assembler::RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data) { + // We do not try to reuse pool constants. + RelocInfo rinfo(reinterpret_cast<byte*>(pc_), rmode, data, NULL); + if (((rmode >= RelocInfo::JS_RETURN) && + (rmode <= RelocInfo::DEBUG_BREAK_SLOT)) || + (rmode == RelocInfo::CONST_POOL) || + (rmode == RelocInfo::VENEER_POOL)) { + // Adjust code for new modes. + ASSERT(RelocInfo::IsDebugBreakSlot(rmode) + || RelocInfo::IsJSReturn(rmode) + || RelocInfo::IsComment(rmode) + || RelocInfo::IsPosition(rmode) + || RelocInfo::IsConstPool(rmode) + || RelocInfo::IsVeneerPool(rmode)); + // These modes do not need an entry in the constant pool. + } else { + ASSERT(num_pending_reloc_info_ < kMaxNumPendingRelocInfo); + if (num_pending_reloc_info_ == 0) { + first_const_pool_use_ = pc_offset(); + } + pending_reloc_info_[num_pending_reloc_info_++] = rinfo; + // Make sure the constant pool is not emitted in place of the next + // instruction for which we just recorded relocation info. + BlockConstPoolFor(1); + } + + if (!RelocInfo::IsNone(rmode)) { + // Don't record external references unless the heap will be serialized. + if (rmode == RelocInfo::EXTERNAL_REFERENCE && + !serializer_enabled() && !emit_debug_code()) { + return; + } + ASSERT(buffer_space() >= kMaxRelocSize); // too late to grow buffer here + if (rmode == RelocInfo::CODE_TARGET_WITH_ID) { + RelocInfo reloc_info_with_ast_id( + reinterpret_cast<byte*>(pc_), rmode, RecordedAstId().ToInt(), NULL); + ClearRecordedAstId(); + reloc_info_writer.Write(&reloc_info_with_ast_id); + } else { + reloc_info_writer.Write(&rinfo); + } + } +} + + +void Assembler::BlockConstPoolFor(int instructions) { + int pc_limit = pc_offset() + instructions * kInstructionSize; + if (no_const_pool_before_ < pc_limit) { + // If there are some pending entries, the constant pool cannot be blocked + // further than first_const_pool_use_ + kMaxDistToConstPool + ASSERT((num_pending_reloc_info_ == 0) || + (pc_limit < (first_const_pool_use_ + kMaxDistToConstPool))); + no_const_pool_before_ = pc_limit; + } + + if (next_constant_pool_check_ < no_const_pool_before_) { + next_constant_pool_check_ = no_const_pool_before_; + } +} + + +void Assembler::CheckConstPool(bool force_emit, bool require_jump) { + // Some short sequence of instruction mustn't be broken up by constant pool + // emission, such sequences are protected by calls to BlockConstPoolFor and + // BlockConstPoolScope. + if (is_const_pool_blocked()) { + // Something is wrong if emission is forced and blocked at the same time. + ASSERT(!force_emit); + return; + } + + // There is nothing to do if there are no pending constant pool entries. + if (num_pending_reloc_info_ == 0) { + // Calculate the offset of the next check. + next_constant_pool_check_ = pc_offset() + kCheckConstPoolInterval; + return; + } + + // We emit a constant pool when: + // * requested to do so by parameter force_emit (e.g. after each function). + // * the distance to the first instruction accessing the constant pool is + // kAvgDistToConstPool or more. + // * no jump is required and the distance to the first instruction accessing + // the constant pool is at least kMaxDistToPConstool / 2. + ASSERT(first_const_pool_use_ >= 0); + int dist = pc_offset() - first_const_pool_use_; + if (!force_emit && dist < kAvgDistToConstPool && + (require_jump || (dist < (kMaxDistToConstPool / 2)))) { + return; + } + + int jump_instr = require_jump ? kInstructionSize : 0; + int size_pool_marker = kInstructionSize; + int size_pool_guard = kInstructionSize; + int pool_size = jump_instr + size_pool_marker + size_pool_guard + + num_pending_reloc_info_ * kPointerSize; + int needed_space = pool_size + kGap; + + // Emit veneers for branches that would go out of range during emission of the + // constant pool. + CheckVeneerPool(false, require_jump, kVeneerDistanceMargin + pool_size); + + Label size_check; + bind(&size_check); + + // Check that the code buffer is large enough before emitting the constant + // pool (include the jump over the pool, the constant pool marker, the + // constant pool guard, and the gap to the relocation information). + while (buffer_space() <= needed_space) { + GrowBuffer(); + } + + { + // Block recursive calls to CheckConstPool and protect from veneer pools. + BlockPoolsScope block_pools(this); + RecordConstPool(pool_size); + + // Emit jump over constant pool if necessary. + Label after_pool; + if (require_jump) { + b(&after_pool); + } + + // Emit a constant pool header. The header has two goals: + // 1) Encode the size of the constant pool, for use by the disassembler. + // 2) Terminate the program, to try to prevent execution from accidentally + // flowing into the constant pool. + // The header is therefore made of two arm64 instructions: + // ldr xzr, #<size of the constant pool in 32-bit words> + // blr xzr + // If executed the code will likely segfault and lr will point to the + // beginning of the constant pool. + // TODO(all): currently each relocated constant is 64 bits, consider adding + // support for 32-bit entries. + RecordComment("[ Constant Pool"); + ConstantPoolMarker(2 * num_pending_reloc_info_); + ConstantPoolGuard(); + + // Emit constant pool entries. + for (int i = 0; i < num_pending_reloc_info_; i++) { + RelocInfo& rinfo = pending_reloc_info_[i]; + ASSERT(rinfo.rmode() != RelocInfo::COMMENT && + rinfo.rmode() != RelocInfo::POSITION && + rinfo.rmode() != RelocInfo::STATEMENT_POSITION && + rinfo.rmode() != RelocInfo::CONST_POOL && + rinfo.rmode() != RelocInfo::VENEER_POOL); + + Instruction* instr = reinterpret_cast<Instruction*>(rinfo.pc()); + // Instruction to patch must be 'ldr rd, [pc, #offset]' with offset == 0. + ASSERT(instr->IsLdrLiteral() && + instr->ImmLLiteral() == 0); + + instr->SetImmPCOffsetTarget(reinterpret_cast<Instruction*>(pc_)); + dc64(rinfo.data()); + } + + num_pending_reloc_info_ = 0; + first_const_pool_use_ = -1; + + RecordComment("]"); + + if (after_pool.is_linked()) { + bind(&after_pool); + } + } + + // Since a constant pool was just emitted, move the check offset forward by + // the standard interval. + next_constant_pool_check_ = pc_offset() + kCheckConstPoolInterval; + + ASSERT(SizeOfCodeGeneratedSince(&size_check) == + static_cast<unsigned>(pool_size)); +} + + +bool Assembler::ShouldEmitVeneer(int max_reachable_pc, int margin) { + // Account for the branch around the veneers and the guard. + int protection_offset = 2 * kInstructionSize; + return pc_offset() > max_reachable_pc - margin - protection_offset - + static_cast<int>(unresolved_branches_.size() * kMaxVeneerCodeSize); +} + + +void Assembler::RecordVeneerPool(int location_offset, int size) { + RelocInfo rinfo(buffer_ + location_offset, + RelocInfo::VENEER_POOL, static_cast<intptr_t>(size), + NULL); + reloc_info_writer.Write(&rinfo); +} + + +void Assembler::EmitVeneers(bool force_emit, bool need_protection, int margin) { + BlockPoolsScope scope(this); + RecordComment("[ Veneers"); + + // The exact size of the veneer pool must be recorded (see the comment at the + // declaration site of RecordConstPool()), but computing the number of + // veneers that will be generated is not obvious. So instead we remember the + // current position and will record the size after the pool has been + // generated. + Label size_check; + bind(&size_check); + int veneer_pool_relocinfo_loc = pc_offset(); + + Label end; + if (need_protection) { + b(&end); + } + + EmitVeneersGuard(); + + Label veneer_size_check; + + std::multimap<int, FarBranchInfo>::iterator it, it_to_delete; + + it = unresolved_branches_.begin(); + while (it != unresolved_branches_.end()) { + if (force_emit || ShouldEmitVeneer(it->first, margin)) { + Instruction* branch = InstructionAt(it->second.pc_offset_); + Label* label = it->second.label_; + +#ifdef DEBUG + bind(&veneer_size_check); +#endif + // Patch the branch to point to the current position, and emit a branch + // to the label. + Instruction* veneer = reinterpret_cast<Instruction*>(pc_); + RemoveBranchFromLabelLinkChain(branch, label, veneer); + branch->SetImmPCOffsetTarget(veneer); + b(label); +#ifdef DEBUG + ASSERT(SizeOfCodeGeneratedSince(&veneer_size_check) <= + static_cast<uint64_t>(kMaxVeneerCodeSize)); + veneer_size_check.Unuse(); +#endif + + it_to_delete = it++; + unresolved_branches_.erase(it_to_delete); + } else { + ++it; + } + } + + // Record the veneer pool size. + int pool_size = SizeOfCodeGeneratedSince(&size_check); + RecordVeneerPool(veneer_pool_relocinfo_loc, pool_size); + + if (unresolved_branches_.empty()) { + next_veneer_pool_check_ = kMaxInt; + } else { + next_veneer_pool_check_ = + unresolved_branches_first_limit() - kVeneerDistanceCheckMargin; + } + + bind(&end); + + RecordComment("]"); +} + + +void Assembler::CheckVeneerPool(bool force_emit, bool require_jump, + int margin) { + // There is nothing to do if there are no pending veneer pool entries. + if (unresolved_branches_.empty()) { + ASSERT(next_veneer_pool_check_ == kMaxInt); + return; + } + + ASSERT(pc_offset() < unresolved_branches_first_limit()); + + // Some short sequence of instruction mustn't be broken up by veneer pool + // emission, such sequences are protected by calls to BlockVeneerPoolFor and + // BlockVeneerPoolScope. + if (is_veneer_pool_blocked()) { + ASSERT(!force_emit); + return; + } + + if (!require_jump) { + // Prefer emitting veneers protected by an existing instruction. + margin *= kVeneerNoProtectionFactor; + } + if (force_emit || ShouldEmitVeneers(margin)) { + EmitVeneers(force_emit, require_jump, margin); + } else { + next_veneer_pool_check_ = + unresolved_branches_first_limit() - kVeneerDistanceCheckMargin; + } +} + + +void Assembler::RecordComment(const char* msg) { + if (FLAG_code_comments) { + CheckBuffer(); + RecordRelocInfo(RelocInfo::COMMENT, reinterpret_cast<intptr_t>(msg)); + } +} + + +int Assembler::buffer_space() const { + return reloc_info_writer.pos() - reinterpret_cast<byte*>(pc_); +} + + +void Assembler::RecordJSReturn() { + positions_recorder()->WriteRecordedPositions(); + CheckBuffer(); + RecordRelocInfo(RelocInfo::JS_RETURN); +} + + +void Assembler::RecordDebugBreakSlot() { + positions_recorder()->WriteRecordedPositions(); + CheckBuffer(); + RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT); +} + + +void Assembler::RecordConstPool(int size) { + // We only need this for debugger support, to correctly compute offsets in the + // code. + RecordRelocInfo(RelocInfo::CONST_POOL, static_cast<intptr_t>(size)); +} + + +Handle<ConstantPoolArray> Assembler::NewConstantPool(Isolate* isolate) { + // No out-of-line constant pool support. + ASSERT(!FLAG_enable_ool_constant_pool); + return isolate->factory()->empty_constant_pool_array(); +} + + +void Assembler::PopulateConstantPool(ConstantPoolArray* constant_pool) { + // No out-of-line constant pool support. + ASSERT(!FLAG_enable_ool_constant_pool); + return; +} + + +void PatchingAssembler::MovInt64(const Register& rd, int64_t imm) { + Label start; + bind(&start); + + ASSERT(rd.Is64Bits()); + ASSERT(!rd.IsSP()); + + for (unsigned i = 0; i < (rd.SizeInBits() / 16); i++) { + uint64_t imm16 = (imm >> (16 * i)) & 0xffffL; + movk(rd, imm16, 16 * i); + } + + ASSERT(SizeOfCodeGeneratedSince(&start) == + kMovInt64NInstrs * kInstructionSize); +} + + +void PatchingAssembler::PatchAdrFar(Instruction* target) { + // The code at the current instruction should be: + // adr rd, 0 + // nop (adr_far) + // nop (adr_far) + // nop (adr_far) + // movz scratch, 0 + // add rd, rd, scratch + + // Verify the expected code. + Instruction* expected_adr = InstructionAt(0); + CHECK(expected_adr->IsAdr() && (expected_adr->ImmPCRel() == 0)); + int rd_code = expected_adr->Rd(); + for (int i = 0; i < kAdrFarPatchableNNops; ++i) { + CHECK(InstructionAt((i + 1) * kInstructionSize)->IsNop(ADR_FAR_NOP)); + } + Instruction* expected_movz = + InstructionAt((kAdrFarPatchableNInstrs - 2) * kInstructionSize); + CHECK(expected_movz->IsMovz() && + (expected_movz->ImmMoveWide() == 0) && + (expected_movz->ShiftMoveWide() == 0)); + int scratch_code = expected_movz->Rd(); + Instruction* expected_add = + InstructionAt((kAdrFarPatchableNInstrs - 1) * kInstructionSize); + CHECK(expected_add->IsAddSubShifted() && + (expected_add->Mask(AddSubOpMask) == ADD) && + expected_add->SixtyFourBits() && + (expected_add->Rd() == rd_code) && (expected_add->Rn() == rd_code) && + (expected_add->Rm() == scratch_code) && + (static_cast<Shift>(expected_add->ShiftDP()) == LSL) && + (expected_add->ImmDPShift() == 0)); + + // Patch to load the correct address. + Label start; + bind(&start); + Register rd = Register::XRegFromCode(rd_code); + // If the target is in range, we only patch the adr. Otherwise we patch the + // nops with fixup instructions. + int target_offset = expected_adr->DistanceTo(target); + if (Instruction::IsValidPCRelOffset(target_offset)) { + adr(rd, target_offset); + for (int i = 0; i < kAdrFarPatchableNInstrs - 2; ++i) { + nop(ADR_FAR_NOP); + } + } else { + Register scratch = Register::XRegFromCode(scratch_code); + adr(rd, 0); + MovInt64(scratch, target_offset); + add(rd, rd, scratch); + } +} + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/assembler-arm64.h b/chromium/v8/src/arm64/assembler-arm64.h new file mode 100644 index 00000000000..c0ad4d053b1 --- /dev/null +++ b/chromium/v8/src/arm64/assembler-arm64.h @@ -0,0 +1,2256 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_ASSEMBLER_ARM64_H_ +#define V8_ARM64_ASSEMBLER_ARM64_H_ + +#include <list> +#include <map> + +#include "src/cpu.h" +#include "src/globals.h" +#include "src/utils.h" +#include "src/assembler.h" +#include "src/serialize.h" +#include "src/arm64/instructions-arm64.h" + + +namespace v8 { +namespace internal { + + +// ----------------------------------------------------------------------------- +// Registers. +#define REGISTER_CODE_LIST(R) \ +R(0) R(1) R(2) R(3) R(4) R(5) R(6) R(7) \ +R(8) R(9) R(10) R(11) R(12) R(13) R(14) R(15) \ +R(16) R(17) R(18) R(19) R(20) R(21) R(22) R(23) \ +R(24) R(25) R(26) R(27) R(28) R(29) R(30) R(31) + + +static const int kRegListSizeInBits = sizeof(RegList) * kBitsPerByte; + + +// Some CPURegister methods can return Register and FPRegister types, so we +// need to declare them in advance. +struct Register; +struct FPRegister; + + +struct CPURegister { + enum RegisterType { + // The kInvalid value is used to detect uninitialized static instances, + // which are always zero-initialized before any constructors are called. + kInvalid = 0, + kRegister, + kFPRegister, + kNoRegister + }; + + static CPURegister Create(unsigned code, unsigned size, RegisterType type) { + CPURegister r = {code, size, type}; + return r; + } + + unsigned code() const; + RegisterType type() const; + RegList Bit() const; + unsigned SizeInBits() const; + int SizeInBytes() const; + bool Is32Bits() const; + bool Is64Bits() const; + bool IsValid() const; + bool IsValidOrNone() const; + bool IsValidRegister() const; + bool IsValidFPRegister() const; + bool IsNone() const; + bool Is(const CPURegister& other) const; + bool Aliases(const CPURegister& other) const; + + bool IsZero() const; + bool IsSP() const; + + bool IsRegister() const; + bool IsFPRegister() const; + + Register X() const; + Register W() const; + FPRegister D() const; + FPRegister S() const; + + bool IsSameSizeAndType(const CPURegister& other) const; + + // V8 compatibility. + bool is(const CPURegister& other) const { return Is(other); } + bool is_valid() const { return IsValid(); } + + unsigned reg_code; + unsigned reg_size; + RegisterType reg_type; +}; + + +struct Register : public CPURegister { + static Register Create(unsigned code, unsigned size) { + return Register(CPURegister::Create(code, size, CPURegister::kRegister)); + } + + Register() { + reg_code = 0; + reg_size = 0; + reg_type = CPURegister::kNoRegister; + } + + explicit Register(const CPURegister& r) { + reg_code = r.reg_code; + reg_size = r.reg_size; + reg_type = r.reg_type; + ASSERT(IsValidOrNone()); + } + + Register(const Register& r) { // NOLINT(runtime/explicit) + reg_code = r.reg_code; + reg_size = r.reg_size; + reg_type = r.reg_type; + ASSERT(IsValidOrNone()); + } + + bool IsValid() const { + ASSERT(IsRegister() || IsNone()); + return IsValidRegister(); + } + + static Register XRegFromCode(unsigned code); + static Register WRegFromCode(unsigned code); + + // Start of V8 compatibility section --------------------- + // These memebers are necessary for compilation. + // A few of them may be unused for now. + + static const int kNumRegisters = kNumberOfRegisters; + static int NumRegisters() { return kNumRegisters; } + + // We allow crankshaft to use the following registers: + // - x0 to x15 + // - x18 to x24 + // - x27 (also context) + // + // TODO(all): Register x25 is currently free and could be available for + // crankshaft, but we don't use it as we might use it as a per function + // literal pool pointer in the future. + // + // TODO(all): Consider storing cp in x25 to have only two ranges. + // We split allocatable registers in three ranges called + // - "low range" + // - "high range" + // - "context" + static const unsigned kAllocatableLowRangeBegin = 0; + static const unsigned kAllocatableLowRangeEnd = 15; + static const unsigned kAllocatableHighRangeBegin = 18; + static const unsigned kAllocatableHighRangeEnd = 24; + static const unsigned kAllocatableContext = 27; + + // Gap between low and high ranges. + static const int kAllocatableRangeGapSize = + (kAllocatableHighRangeBegin - kAllocatableLowRangeEnd) - 1; + + static const int kMaxNumAllocatableRegisters = + (kAllocatableLowRangeEnd - kAllocatableLowRangeBegin + 1) + + (kAllocatableHighRangeEnd - kAllocatableHighRangeBegin + 1) + 1; // cp + static int NumAllocatableRegisters() { return kMaxNumAllocatableRegisters; } + + // Return true if the register is one that crankshaft can allocate. + bool IsAllocatable() const { + return ((reg_code == kAllocatableContext) || + (reg_code <= kAllocatableLowRangeEnd) || + ((reg_code >= kAllocatableHighRangeBegin) && + (reg_code <= kAllocatableHighRangeEnd))); + } + + static Register FromAllocationIndex(unsigned index) { + ASSERT(index < static_cast<unsigned>(NumAllocatableRegisters())); + // cp is the last allocatable register. + if (index == (static_cast<unsigned>(NumAllocatableRegisters() - 1))) { + return from_code(kAllocatableContext); + } + + // Handle low and high ranges. + return (index <= kAllocatableLowRangeEnd) + ? from_code(index) + : from_code(index + kAllocatableRangeGapSize); + } + + static const char* AllocationIndexToString(int index) { + ASSERT((index >= 0) && (index < NumAllocatableRegisters())); + ASSERT((kAllocatableLowRangeBegin == 0) && + (kAllocatableLowRangeEnd == 15) && + (kAllocatableHighRangeBegin == 18) && + (kAllocatableHighRangeEnd == 24) && + (kAllocatableContext == 27)); + const char* const names[] = { + "x0", "x1", "x2", "x3", "x4", + "x5", "x6", "x7", "x8", "x9", + "x10", "x11", "x12", "x13", "x14", + "x15", "x18", "x19", "x20", "x21", + "x22", "x23", "x24", "x27", + }; + return names[index]; + } + + static int ToAllocationIndex(Register reg) { + ASSERT(reg.IsAllocatable()); + unsigned code = reg.code(); + if (code == kAllocatableContext) { + return NumAllocatableRegisters() - 1; + } + + return (code <= kAllocatableLowRangeEnd) + ? code + : code - kAllocatableRangeGapSize; + } + + static Register from_code(int code) { + // Always return an X register. + return Register::Create(code, kXRegSizeInBits); + } + + // End of V8 compatibility section ----------------------- +}; + + +struct FPRegister : public CPURegister { + static FPRegister Create(unsigned code, unsigned size) { + return FPRegister( + CPURegister::Create(code, size, CPURegister::kFPRegister)); + } + + FPRegister() { + reg_code = 0; + reg_size = 0; + reg_type = CPURegister::kNoRegister; + } + + explicit FPRegister(const CPURegister& r) { + reg_code = r.reg_code; + reg_size = r.reg_size; + reg_type = r.reg_type; + ASSERT(IsValidOrNone()); + } + + FPRegister(const FPRegister& r) { // NOLINT(runtime/explicit) + reg_code = r.reg_code; + reg_size = r.reg_size; + reg_type = r.reg_type; + ASSERT(IsValidOrNone()); + } + + bool IsValid() const { + ASSERT(IsFPRegister() || IsNone()); + return IsValidFPRegister(); + } + + static FPRegister SRegFromCode(unsigned code); + static FPRegister DRegFromCode(unsigned code); + + // Start of V8 compatibility section --------------------- + static const int kMaxNumRegisters = kNumberOfFPRegisters; + + // Crankshaft can use all the FP registers except: + // - d15 which is used to keep the 0 double value + // - d30 which is used in crankshaft as a double scratch register + // - d31 which is used in the MacroAssembler as a double scratch register + static const unsigned kAllocatableLowRangeBegin = 0; + static const unsigned kAllocatableLowRangeEnd = 14; + static const unsigned kAllocatableHighRangeBegin = 16; + static const unsigned kAllocatableHighRangeEnd = 28; + + static const RegList kAllocatableFPRegisters = 0x1fff7fff; + + // Gap between low and high ranges. + static const int kAllocatableRangeGapSize = + (kAllocatableHighRangeBegin - kAllocatableLowRangeEnd) - 1; + + static const int kMaxNumAllocatableRegisters = + (kAllocatableLowRangeEnd - kAllocatableLowRangeBegin + 1) + + (kAllocatableHighRangeEnd - kAllocatableHighRangeBegin + 1); + static int NumAllocatableRegisters() { return kMaxNumAllocatableRegisters; } + + // Return true if the register is one that crankshaft can allocate. + bool IsAllocatable() const { + return (Bit() & kAllocatableFPRegisters) != 0; + } + + static FPRegister FromAllocationIndex(unsigned int index) { + ASSERT(index < static_cast<unsigned>(NumAllocatableRegisters())); + + return (index <= kAllocatableLowRangeEnd) + ? from_code(index) + : from_code(index + kAllocatableRangeGapSize); + } + + static const char* AllocationIndexToString(int index) { + ASSERT((index >= 0) && (index < NumAllocatableRegisters())); + ASSERT((kAllocatableLowRangeBegin == 0) && + (kAllocatableLowRangeEnd == 14) && + (kAllocatableHighRangeBegin == 16) && + (kAllocatableHighRangeEnd == 28)); + const char* const names[] = { + "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", + "d8", "d9", "d10", "d11", "d12", "d13", "d14", + "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", + "d24", "d25", "d26", "d27", "d28" + }; + return names[index]; + } + + static int ToAllocationIndex(FPRegister reg) { + ASSERT(reg.IsAllocatable()); + unsigned code = reg.code(); + + return (code <= kAllocatableLowRangeEnd) + ? code + : code - kAllocatableRangeGapSize; + } + + static FPRegister from_code(int code) { + // Always return a D register. + return FPRegister::Create(code, kDRegSizeInBits); + } + // End of V8 compatibility section ----------------------- +}; + + +STATIC_ASSERT(sizeof(CPURegister) == sizeof(Register)); +STATIC_ASSERT(sizeof(CPURegister) == sizeof(FPRegister)); + + +#if defined(ARM64_DEFINE_REG_STATICS) +#define INITIALIZE_REGISTER(register_class, name, code, size, type) \ + const CPURegister init_##register_class##_##name = {code, size, type}; \ + const register_class& name = *reinterpret_cast<const register_class*>( \ + &init_##register_class##_##name) +#define ALIAS_REGISTER(register_class, alias, name) \ + const register_class& alias = *reinterpret_cast<const register_class*>( \ + &init_##register_class##_##name) +#else +#define INITIALIZE_REGISTER(register_class, name, code, size, type) \ + extern const register_class& name +#define ALIAS_REGISTER(register_class, alias, name) \ + extern const register_class& alias +#endif // defined(ARM64_DEFINE_REG_STATICS) + +// No*Reg is used to indicate an unused argument, or an error case. Note that +// these all compare equal (using the Is() method). The Register and FPRegister +// variants are provided for convenience. +INITIALIZE_REGISTER(Register, NoReg, 0, 0, CPURegister::kNoRegister); +INITIALIZE_REGISTER(FPRegister, NoFPReg, 0, 0, CPURegister::kNoRegister); +INITIALIZE_REGISTER(CPURegister, NoCPUReg, 0, 0, CPURegister::kNoRegister); + +// v8 compatibility. +INITIALIZE_REGISTER(Register, no_reg, 0, 0, CPURegister::kNoRegister); + +#define DEFINE_REGISTERS(N) \ + INITIALIZE_REGISTER(Register, w##N, N, \ + kWRegSizeInBits, CPURegister::kRegister); \ + INITIALIZE_REGISTER(Register, x##N, N, \ + kXRegSizeInBits, CPURegister::kRegister); +REGISTER_CODE_LIST(DEFINE_REGISTERS) +#undef DEFINE_REGISTERS + +INITIALIZE_REGISTER(Register, wcsp, kSPRegInternalCode, kWRegSizeInBits, + CPURegister::kRegister); +INITIALIZE_REGISTER(Register, csp, kSPRegInternalCode, kXRegSizeInBits, + CPURegister::kRegister); + +#define DEFINE_FPREGISTERS(N) \ + INITIALIZE_REGISTER(FPRegister, s##N, N, \ + kSRegSizeInBits, CPURegister::kFPRegister); \ + INITIALIZE_REGISTER(FPRegister, d##N, N, \ + kDRegSizeInBits, CPURegister::kFPRegister); +REGISTER_CODE_LIST(DEFINE_FPREGISTERS) +#undef DEFINE_FPREGISTERS + +#undef INITIALIZE_REGISTER + +// Registers aliases. +ALIAS_REGISTER(Register, ip0, x16); +ALIAS_REGISTER(Register, ip1, x17); +ALIAS_REGISTER(Register, wip0, w16); +ALIAS_REGISTER(Register, wip1, w17); +// Root register. +ALIAS_REGISTER(Register, root, x26); +ALIAS_REGISTER(Register, rr, x26); +// Context pointer register. +ALIAS_REGISTER(Register, cp, x27); +// We use a register as a JS stack pointer to overcome the restriction on the +// architectural SP alignment. +// We chose x28 because it is contiguous with the other specific purpose +// registers. +STATIC_ASSERT(kJSSPCode == 28); +ALIAS_REGISTER(Register, jssp, x28); +ALIAS_REGISTER(Register, wjssp, w28); +ALIAS_REGISTER(Register, fp, x29); +ALIAS_REGISTER(Register, lr, x30); +ALIAS_REGISTER(Register, xzr, x31); +ALIAS_REGISTER(Register, wzr, w31); + +// Keeps the 0 double value. +ALIAS_REGISTER(FPRegister, fp_zero, d15); +// Crankshaft double scratch register. +ALIAS_REGISTER(FPRegister, crankshaft_fp_scratch, d29); +// MacroAssembler double scratch registers. +ALIAS_REGISTER(FPRegister, fp_scratch, d30); +ALIAS_REGISTER(FPRegister, fp_scratch1, d30); +ALIAS_REGISTER(FPRegister, fp_scratch2, d31); + +#undef ALIAS_REGISTER + + +Register GetAllocatableRegisterThatIsNotOneOf(Register reg1, + Register reg2 = NoReg, + Register reg3 = NoReg, + Register reg4 = NoReg); + + +// AreAliased returns true if any of the named registers overlap. Arguments set +// to NoReg are ignored. The system stack pointer may be specified. +bool AreAliased(const CPURegister& reg1, + const CPURegister& reg2, + const CPURegister& reg3 = NoReg, + const CPURegister& reg4 = NoReg, + const CPURegister& reg5 = NoReg, + const CPURegister& reg6 = NoReg, + const CPURegister& reg7 = NoReg, + const CPURegister& reg8 = NoReg); + +// AreSameSizeAndType returns true if all of the specified registers have the +// same size, and are of the same type. The system stack pointer may be +// specified. Arguments set to NoReg are ignored, as are any subsequent +// arguments. At least one argument (reg1) must be valid (not NoCPUReg). +bool AreSameSizeAndType(const CPURegister& reg1, + const CPURegister& reg2, + const CPURegister& reg3 = NoCPUReg, + const CPURegister& reg4 = NoCPUReg, + const CPURegister& reg5 = NoCPUReg, + const CPURegister& reg6 = NoCPUReg, + const CPURegister& reg7 = NoCPUReg, + const CPURegister& reg8 = NoCPUReg); + + +typedef FPRegister DoubleRegister; + + +// ----------------------------------------------------------------------------- +// Lists of registers. +class CPURegList { + public: + explicit CPURegList(CPURegister reg1, + CPURegister reg2 = NoCPUReg, + CPURegister reg3 = NoCPUReg, + CPURegister reg4 = NoCPUReg) + : list_(reg1.Bit() | reg2.Bit() | reg3.Bit() | reg4.Bit()), + size_(reg1.SizeInBits()), type_(reg1.type()) { + ASSERT(AreSameSizeAndType(reg1, reg2, reg3, reg4)); + ASSERT(IsValid()); + } + + CPURegList(CPURegister::RegisterType type, unsigned size, RegList list) + : list_(list), size_(size), type_(type) { + ASSERT(IsValid()); + } + + CPURegList(CPURegister::RegisterType type, unsigned size, + unsigned first_reg, unsigned last_reg) + : size_(size), type_(type) { + ASSERT(((type == CPURegister::kRegister) && + (last_reg < kNumberOfRegisters)) || + ((type == CPURegister::kFPRegister) && + (last_reg < kNumberOfFPRegisters))); + ASSERT(last_reg >= first_reg); + list_ = (1UL << (last_reg + 1)) - 1; + list_ &= ~((1UL << first_reg) - 1); + ASSERT(IsValid()); + } + + CPURegister::RegisterType type() const { + ASSERT(IsValid()); + return type_; + } + + RegList list() const { + ASSERT(IsValid()); + return list_; + } + + inline void set_list(RegList new_list) { + ASSERT(IsValid()); + list_ = new_list; + } + + // Combine another CPURegList into this one. Registers that already exist in + // this list are left unchanged. The type and size of the registers in the + // 'other' list must match those in this list. + void Combine(const CPURegList& other); + + // Remove every register in the other CPURegList from this one. Registers that + // do not exist in this list are ignored. The type of the registers in the + // 'other' list must match those in this list. + void Remove(const CPURegList& other); + + // Variants of Combine and Remove which take CPURegisters. + void Combine(const CPURegister& other); + void Remove(const CPURegister& other1, + const CPURegister& other2 = NoCPUReg, + const CPURegister& other3 = NoCPUReg, + const CPURegister& other4 = NoCPUReg); + + // Variants of Combine and Remove which take a single register by its code; + // the type and size of the register is inferred from this list. + void Combine(int code); + void Remove(int code); + + // Remove all callee-saved registers from the list. This can be useful when + // preparing registers for an AAPCS64 function call, for example. + void RemoveCalleeSaved(); + + CPURegister PopLowestIndex(); + CPURegister PopHighestIndex(); + + // AAPCS64 callee-saved registers. + static CPURegList GetCalleeSaved(unsigned size = kXRegSizeInBits); + static CPURegList GetCalleeSavedFP(unsigned size = kDRegSizeInBits); + + // AAPCS64 caller-saved registers. Note that this includes lr. + static CPURegList GetCallerSaved(unsigned size = kXRegSizeInBits); + static CPURegList GetCallerSavedFP(unsigned size = kDRegSizeInBits); + + // Registers saved as safepoints. + static CPURegList GetSafepointSavedRegisters(); + + bool IsEmpty() const { + ASSERT(IsValid()); + return list_ == 0; + } + + bool IncludesAliasOf(const CPURegister& other1, + const CPURegister& other2 = NoCPUReg, + const CPURegister& other3 = NoCPUReg, + const CPURegister& other4 = NoCPUReg) const { + ASSERT(IsValid()); + RegList list = 0; + if (!other1.IsNone() && (other1.type() == type_)) list |= other1.Bit(); + if (!other2.IsNone() && (other2.type() == type_)) list |= other2.Bit(); + if (!other3.IsNone() && (other3.type() == type_)) list |= other3.Bit(); + if (!other4.IsNone() && (other4.type() == type_)) list |= other4.Bit(); + return (list_ & list) != 0; + } + + int Count() const { + ASSERT(IsValid()); + return CountSetBits(list_, kRegListSizeInBits); + } + + unsigned RegisterSizeInBits() const { + ASSERT(IsValid()); + return size_; + } + + unsigned RegisterSizeInBytes() const { + int size_in_bits = RegisterSizeInBits(); + ASSERT((size_in_bits % kBitsPerByte) == 0); + return size_in_bits / kBitsPerByte; + } + + unsigned TotalSizeInBytes() const { + ASSERT(IsValid()); + return RegisterSizeInBytes() * Count(); + } + + private: + RegList list_; + unsigned size_; + CPURegister::RegisterType type_; + + bool IsValid() const { + const RegList kValidRegisters = 0x8000000ffffffff; + const RegList kValidFPRegisters = 0x0000000ffffffff; + switch (type_) { + case CPURegister::kRegister: + return (list_ & kValidRegisters) == list_; + case CPURegister::kFPRegister: + return (list_ & kValidFPRegisters) == list_; + case CPURegister::kNoRegister: + return list_ == 0; + default: + UNREACHABLE(); + return false; + } + } +}; + + +// AAPCS64 callee-saved registers. +#define kCalleeSaved CPURegList::GetCalleeSaved() +#define kCalleeSavedFP CPURegList::GetCalleeSavedFP() + + +// AAPCS64 caller-saved registers. Note that this includes lr. +#define kCallerSaved CPURegList::GetCallerSaved() +#define kCallerSavedFP CPURegList::GetCallerSavedFP() + +// ----------------------------------------------------------------------------- +// Immediates. +class Immediate { + public: + template<typename T> + inline explicit Immediate(Handle<T> handle); + + // This is allowed to be an implicit constructor because Immediate is + // a wrapper class that doesn't normally perform any type conversion. + template<typename T> + inline Immediate(T value); // NOLINT(runtime/explicit) + + template<typename T> + inline Immediate(T value, RelocInfo::Mode rmode); + + int64_t value() const { return value_; } + RelocInfo::Mode rmode() const { return rmode_; } + + private: + void InitializeHandle(Handle<Object> value); + + int64_t value_; + RelocInfo::Mode rmode_; +}; + + +// ----------------------------------------------------------------------------- +// Operands. +const int kSmiShift = kSmiTagSize + kSmiShiftSize; +const uint64_t kSmiShiftMask = (1UL << kSmiShift) - 1; + +// Represents an operand in a machine instruction. +class Operand { + // TODO(all): If necessary, study more in details which methods + // TODO(all): should be inlined or not. + public: + // rm, {<shift> {#<shift_amount>}} + // where <shift> is one of {LSL, LSR, ASR, ROR}. + // <shift_amount> is uint6_t. + // This is allowed to be an implicit constructor because Operand is + // a wrapper class that doesn't normally perform any type conversion. + inline Operand(Register reg, + Shift shift = LSL, + unsigned shift_amount = 0); // NOLINT(runtime/explicit) + + // rm, <extend> {#<shift_amount>} + // where <extend> is one of {UXTB, UXTH, UXTW, UXTX, SXTB, SXTH, SXTW, SXTX}. + // <shift_amount> is uint2_t. + inline Operand(Register reg, + Extend extend, + unsigned shift_amount = 0); + + template<typename T> + inline explicit Operand(Handle<T> handle); + + // Implicit constructor for all int types, ExternalReference, and Smi. + template<typename T> + inline Operand(T t); // NOLINT(runtime/explicit) + + // Implicit constructor for int types. + template<typename T> + inline Operand(T t, RelocInfo::Mode rmode); + + inline bool IsImmediate() const; + inline bool IsShiftedRegister() const; + inline bool IsExtendedRegister() const; + inline bool IsZero() const; + + // This returns an LSL shift (<= 4) operand as an equivalent extend operand, + // which helps in the encoding of instructions that use the stack pointer. + inline Operand ToExtendedRegister() const; + + inline Immediate immediate() const; + inline int64_t ImmediateValue() const; + inline Register reg() const; + inline Shift shift() const; + inline Extend extend() const; + inline unsigned shift_amount() const; + + // Relocation information. + bool NeedsRelocation(const Assembler* assembler) const; + + // Helpers + inline static Operand UntagSmi(Register smi); + inline static Operand UntagSmiAndScale(Register smi, int scale); + + private: + Immediate immediate_; + Register reg_; + Shift shift_; + Extend extend_; + unsigned shift_amount_; +}; + + +// MemOperand represents a memory operand in a load or store instruction. +class MemOperand { + public: + inline explicit MemOperand(); + inline explicit MemOperand(Register base, + ptrdiff_t offset = 0, + AddrMode addrmode = Offset); + inline explicit MemOperand(Register base, + Register regoffset, + Shift shift = LSL, + unsigned shift_amount = 0); + inline explicit MemOperand(Register base, + Register regoffset, + Extend extend, + unsigned shift_amount = 0); + inline explicit MemOperand(Register base, + const Operand& offset, + AddrMode addrmode = Offset); + + const Register& base() const { return base_; } + const Register& regoffset() const { return regoffset_; } + ptrdiff_t offset() const { return offset_; } + AddrMode addrmode() const { return addrmode_; } + Shift shift() const { return shift_; } + Extend extend() const { return extend_; } + unsigned shift_amount() const { return shift_amount_; } + inline bool IsImmediateOffset() const; + inline bool IsRegisterOffset() const; + inline bool IsPreIndex() const; + inline bool IsPostIndex() const; + + // For offset modes, return the offset as an Operand. This helper cannot + // handle indexed modes. + inline Operand OffsetAsOperand() const; + + private: + Register base_; + Register regoffset_; + ptrdiff_t offset_; + AddrMode addrmode_; + Shift shift_; + Extend extend_; + unsigned shift_amount_; +}; + + +// ----------------------------------------------------------------------------- +// Assembler. + +class Assembler : public AssemblerBase { + public: + // Create an assembler. Instructions and relocation information are emitted + // into a buffer, with the instructions starting from the beginning and the + // relocation information starting from the end of the buffer. See CodeDesc + // for a detailed comment on the layout (globals.h). + // + // If the provided buffer is NULL, the assembler allocates and grows its own + // buffer, and buffer_size determines the initial buffer size. The buffer is + // owned by the assembler and deallocated upon destruction of the assembler. + // + // If the provided buffer is not NULL, the assembler uses the provided buffer + // for code generation and assumes its size to be buffer_size. If the buffer + // is too small, a fatal error occurs. No deallocation of the buffer is done + // upon destruction of the assembler. + Assembler(Isolate* arg_isolate, void* buffer, int buffer_size); + + virtual ~Assembler(); + + virtual void AbortedCodeGeneration() { + num_pending_reloc_info_ = 0; + } + + // System functions --------------------------------------------------------- + // Start generating code from the beginning of the buffer, discarding any code + // and data that has already been emitted into the buffer. + // + // In order to avoid any accidental transfer of state, Reset ASSERTs that the + // constant pool is not blocked. + void Reset(); + + // GetCode emits any pending (non-emitted) code and fills the descriptor + // desc. GetCode() is idempotent; it returns the same result if no other + // Assembler functions are invoked in between GetCode() calls. + // + // The descriptor (desc) can be NULL. In that case, the code is finalized as + // usual, but the descriptor is not populated. + void GetCode(CodeDesc* desc); + + // Insert the smallest number of nop instructions + // possible to align the pc offset to a multiple + // of m. m must be a power of 2 (>= 4). + void Align(int m); + + inline void Unreachable(); + + // Label -------------------------------------------------------------------- + // Bind a label to the current pc. Note that labels can only be bound once, + // and if labels are linked to other instructions, they _must_ be bound + // before they go out of scope. + void bind(Label* label); + + + // RelocInfo and pools ------------------------------------------------------ + + // Record relocation information for current pc_. + void RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data = 0); + + // Return the address in the constant pool of the code target address used by + // the branch/call instruction at pc. + inline static Address target_pointer_address_at(Address pc); + + // Read/Modify the code target address in the branch/call instruction at pc. + inline static Address target_address_at(Address pc, + ConstantPoolArray* constant_pool); + inline static void set_target_address_at(Address pc, + ConstantPoolArray* constant_pool, + Address target, + ICacheFlushMode icache_flush_mode = + FLUSH_ICACHE_IF_NEEDED); + static inline Address target_address_at(Address pc, Code* code); + static inline void set_target_address_at(Address pc, + Code* code, + Address target, + ICacheFlushMode icache_flush_mode = + FLUSH_ICACHE_IF_NEEDED); + + // Return the code target address at a call site from the return address of + // that call in the instruction stream. + inline static Address target_address_from_return_address(Address pc); + + // Given the address of the beginning of a call, return the address in the + // instruction stream that call will return from. + inline static Address return_address_from_call_start(Address pc); + + // This sets the branch destination (which is in the constant pool on ARM). + // This is for calls and branches within generated code. + inline static void deserialization_set_special_target_at( + Address constant_pool_entry, Code* code, Address target); + + // All addresses in the constant pool are the same size as pointers. + static const int kSpecialTargetSize = kPointerSize; + + // The sizes of the call sequences emitted by MacroAssembler::Call. + // Wherever possible, use MacroAssembler::CallSize instead of these constants, + // as it will choose the correct value for a given relocation mode. + // + // Without relocation: + // movz temp, #(target & 0x000000000000ffff) + // movk temp, #(target & 0x00000000ffff0000) + // movk temp, #(target & 0x0000ffff00000000) + // blr temp + // + // With relocation: + // ldr temp, =target + // blr temp + static const int kCallSizeWithoutRelocation = 4 * kInstructionSize; + static const int kCallSizeWithRelocation = 2 * kInstructionSize; + + // Size of the generated code in bytes + uint64_t SizeOfGeneratedCode() const { + ASSERT((pc_ >= buffer_) && (pc_ < (buffer_ + buffer_size_))); + return pc_ - buffer_; + } + + // Return the code size generated from label to the current position. + uint64_t SizeOfCodeGeneratedSince(const Label* label) { + ASSERT(label->is_bound()); + ASSERT(pc_offset() >= label->pos()); + ASSERT(pc_offset() < buffer_size_); + return pc_offset() - label->pos(); + } + + // Check the size of the code generated since the given label. This function + // is used primarily to work around comparisons between signed and unsigned + // quantities, since V8 uses both. + // TODO(jbramley): Work out what sign to use for these things and if possible, + // change things to be consistent. + void AssertSizeOfCodeGeneratedSince(const Label* label, ptrdiff_t size) { + ASSERT(size >= 0); + ASSERT(static_cast<uint64_t>(size) == SizeOfCodeGeneratedSince(label)); + } + + // Return the number of instructions generated from label to the + // current position. + int InstructionsGeneratedSince(const Label* label) { + return SizeOfCodeGeneratedSince(label) / kInstructionSize; + } + + // Number of instructions generated for the return sequence in + // FullCodeGenerator::EmitReturnSequence. + static const int kJSRetSequenceInstructions = 7; + // Distance between start of patched return sequence and the emitted address + // to jump to. + static const int kPatchReturnSequenceAddressOffset = 0; + static const int kPatchDebugBreakSlotAddressOffset = 0; + + // Number of instructions necessary to be able to later patch it to a call. + // See DebugCodegen::GenerateSlot() and + // BreakLocationIterator::SetDebugBreakAtSlot(). + static const int kDebugBreakSlotInstructions = 4; + static const int kDebugBreakSlotLength = + kDebugBreakSlotInstructions * kInstructionSize; + + static const int kPatchDebugBreakSlotReturnOffset = 2 * kInstructionSize; + + // Prevent contant pool emission until EndBlockConstPool is called. + // Call to this function can be nested but must be followed by an equal + // number of call to EndBlockConstpool. + void StartBlockConstPool(); + + // Resume constant pool emission. Need to be called as many time as + // StartBlockConstPool to have an effect. + void EndBlockConstPool(); + + bool is_const_pool_blocked() const; + static bool IsConstantPoolAt(Instruction* instr); + static int ConstantPoolSizeAt(Instruction* instr); + // See Assembler::CheckConstPool for more info. + void ConstantPoolMarker(uint32_t size); + void EmitPoolGuard(); + void ConstantPoolGuard(); + + // Prevent veneer pool emission until EndBlockVeneerPool is called. + // Call to this function can be nested but must be followed by an equal + // number of call to EndBlockConstpool. + void StartBlockVeneerPool(); + + // Resume constant pool emission. Need to be called as many time as + // StartBlockVeneerPool to have an effect. + void EndBlockVeneerPool(); + + bool is_veneer_pool_blocked() const { + return veneer_pool_blocked_nesting_ > 0; + } + + // Block/resume emission of constant pools and veneer pools. + void StartBlockPools() { + StartBlockConstPool(); + StartBlockVeneerPool(); + } + void EndBlockPools() { + EndBlockConstPool(); + EndBlockVeneerPool(); + } + + // Debugging ---------------------------------------------------------------- + PositionsRecorder* positions_recorder() { return &positions_recorder_; } + void RecordComment(const char* msg); + int buffer_space() const; + + // Mark address of the ExitJSFrame code. + void RecordJSReturn(); + + // Mark address of a debug break slot. + void RecordDebugBreakSlot(); + + // Record the emission of a constant pool. + // + // The emission of constant and veneer pools depends on the size of the code + // generated and the number of RelocInfo recorded. + // The Debug mechanism needs to map code offsets between two versions of a + // function, compiled with and without debugger support (see for example + // Debug::PrepareForBreakPoints()). + // Compiling functions with debugger support generates additional code + // (DebugCodegen::GenerateSlot()). This may affect the emission of the pools + // and cause the version of the code with debugger support to have pools + // generated in different places. + // Recording the position and size of emitted pools allows to correctly + // compute the offset mappings between the different versions of a function in + // all situations. + // + // The parameter indicates the size of the pool (in bytes), including + // the marker and branch over the data. + void RecordConstPool(int size); + + + // Instruction set functions ------------------------------------------------ + + // Branch / Jump instructions. + // For branches offsets are scaled, i.e. they in instrcutions not in bytes. + // Branch to register. + void br(const Register& xn); + + // Branch-link to register. + void blr(const Register& xn); + + // Branch to register with return hint. + void ret(const Register& xn = lr); + + // Unconditional branch to label. + void b(Label* label); + + // Conditional branch to label. + void b(Label* label, Condition cond); + + // Unconditional branch to PC offset. + void b(int imm26); + + // Conditional branch to PC offset. + void b(int imm19, Condition cond); + + // Branch-link to label / pc offset. + void bl(Label* label); + void bl(int imm26); + + // Compare and branch to label / pc offset if zero. + void cbz(const Register& rt, Label* label); + void cbz(const Register& rt, int imm19); + + // Compare and branch to label / pc offset if not zero. + void cbnz(const Register& rt, Label* label); + void cbnz(const Register& rt, int imm19); + + // Test bit and branch to label / pc offset if zero. + void tbz(const Register& rt, unsigned bit_pos, Label* label); + void tbz(const Register& rt, unsigned bit_pos, int imm14); + + // Test bit and branch to label / pc offset if not zero. + void tbnz(const Register& rt, unsigned bit_pos, Label* label); + void tbnz(const Register& rt, unsigned bit_pos, int imm14); + + // Address calculation instructions. + // Calculate a PC-relative address. Unlike for branches the offset in adr is + // unscaled (i.e. the result can be unaligned). + void adr(const Register& rd, Label* label); + void adr(const Register& rd, int imm21); + + // Data Processing instructions. + // Add. + void add(const Register& rd, + const Register& rn, + const Operand& operand); + + // Add and update status flags. + void adds(const Register& rd, + const Register& rn, + const Operand& operand); + + // Compare negative. + void cmn(const Register& rn, const Operand& operand); + + // Subtract. + void sub(const Register& rd, + const Register& rn, + const Operand& operand); + + // Subtract and update status flags. + void subs(const Register& rd, + const Register& rn, + const Operand& operand); + + // Compare. + void cmp(const Register& rn, const Operand& operand); + + // Negate. + void neg(const Register& rd, + const Operand& operand); + + // Negate and update status flags. + void negs(const Register& rd, + const Operand& operand); + + // Add with carry bit. + void adc(const Register& rd, + const Register& rn, + const Operand& operand); + + // Add with carry bit and update status flags. + void adcs(const Register& rd, + const Register& rn, + const Operand& operand); + + // Subtract with carry bit. + void sbc(const Register& rd, + const Register& rn, + const Operand& operand); + + // Subtract with carry bit and update status flags. + void sbcs(const Register& rd, + const Register& rn, + const Operand& operand); + + // Negate with carry bit. + void ngc(const Register& rd, + const Operand& operand); + + // Negate with carry bit and update status flags. + void ngcs(const Register& rd, + const Operand& operand); + + // Logical instructions. + // Bitwise and (A & B). + void and_(const Register& rd, + const Register& rn, + const Operand& operand); + + // Bitwise and (A & B) and update status flags. + void ands(const Register& rd, + const Register& rn, + const Operand& operand); + + // Bit test, and set flags. + void tst(const Register& rn, const Operand& operand); + + // Bit clear (A & ~B). + void bic(const Register& rd, + const Register& rn, + const Operand& operand); + + // Bit clear (A & ~B) and update status flags. + void bics(const Register& rd, + const Register& rn, + const Operand& operand); + + // Bitwise or (A | B). + void orr(const Register& rd, const Register& rn, const Operand& operand); + + // Bitwise nor (A | ~B). + void orn(const Register& rd, const Register& rn, const Operand& operand); + + // Bitwise eor/xor (A ^ B). + void eor(const Register& rd, const Register& rn, const Operand& operand); + + // Bitwise enor/xnor (A ^ ~B). + void eon(const Register& rd, const Register& rn, const Operand& operand); + + // Logical shift left variable. + void lslv(const Register& rd, const Register& rn, const Register& rm); + + // Logical shift right variable. + void lsrv(const Register& rd, const Register& rn, const Register& rm); + + // Arithmetic shift right variable. + void asrv(const Register& rd, const Register& rn, const Register& rm); + + // Rotate right variable. + void rorv(const Register& rd, const Register& rn, const Register& rm); + + // Bitfield instructions. + // Bitfield move. + void bfm(const Register& rd, + const Register& rn, + unsigned immr, + unsigned imms); + + // Signed bitfield move. + void sbfm(const Register& rd, + const Register& rn, + unsigned immr, + unsigned imms); + + // Unsigned bitfield move. + void ubfm(const Register& rd, + const Register& rn, + unsigned immr, + unsigned imms); + + // Bfm aliases. + // Bitfield insert. + void bfi(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(width >= 1); + ASSERT(lsb + width <= rn.SizeInBits()); + bfm(rd, rn, (rd.SizeInBits() - lsb) & (rd.SizeInBits() - 1), width - 1); + } + + // Bitfield extract and insert low. + void bfxil(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(width >= 1); + ASSERT(lsb + width <= rn.SizeInBits()); + bfm(rd, rn, lsb, lsb + width - 1); + } + + // Sbfm aliases. + // Arithmetic shift right. + void asr(const Register& rd, const Register& rn, unsigned shift) { + ASSERT(shift < rd.SizeInBits()); + sbfm(rd, rn, shift, rd.SizeInBits() - 1); + } + + // Signed bitfield insert in zero. + void sbfiz(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(width >= 1); + ASSERT(lsb + width <= rn.SizeInBits()); + sbfm(rd, rn, (rd.SizeInBits() - lsb) & (rd.SizeInBits() - 1), width - 1); + } + + // Signed bitfield extract. + void sbfx(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(width >= 1); + ASSERT(lsb + width <= rn.SizeInBits()); + sbfm(rd, rn, lsb, lsb + width - 1); + } + + // Signed extend byte. + void sxtb(const Register& rd, const Register& rn) { + sbfm(rd, rn, 0, 7); + } + + // Signed extend halfword. + void sxth(const Register& rd, const Register& rn) { + sbfm(rd, rn, 0, 15); + } + + // Signed extend word. + void sxtw(const Register& rd, const Register& rn) { + sbfm(rd, rn, 0, 31); + } + + // Ubfm aliases. + // Logical shift left. + void lsl(const Register& rd, const Register& rn, unsigned shift) { + unsigned reg_size = rd.SizeInBits(); + ASSERT(shift < reg_size); + ubfm(rd, rn, (reg_size - shift) % reg_size, reg_size - shift - 1); + } + + // Logical shift right. + void lsr(const Register& rd, const Register& rn, unsigned shift) { + ASSERT(shift < rd.SizeInBits()); + ubfm(rd, rn, shift, rd.SizeInBits() - 1); + } + + // Unsigned bitfield insert in zero. + void ubfiz(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(width >= 1); + ASSERT(lsb + width <= rn.SizeInBits()); + ubfm(rd, rn, (rd.SizeInBits() - lsb) & (rd.SizeInBits() - 1), width - 1); + } + + // Unsigned bitfield extract. + void ubfx(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(width >= 1); + ASSERT(lsb + width <= rn.SizeInBits()); + ubfm(rd, rn, lsb, lsb + width - 1); + } + + // Unsigned extend byte. + void uxtb(const Register& rd, const Register& rn) { + ubfm(rd, rn, 0, 7); + } + + // Unsigned extend halfword. + void uxth(const Register& rd, const Register& rn) { + ubfm(rd, rn, 0, 15); + } + + // Unsigned extend word. + void uxtw(const Register& rd, const Register& rn) { + ubfm(rd, rn, 0, 31); + } + + // Extract. + void extr(const Register& rd, + const Register& rn, + const Register& rm, + unsigned lsb); + + // Conditional select: rd = cond ? rn : rm. + void csel(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond); + + // Conditional select increment: rd = cond ? rn : rm + 1. + void csinc(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond); + + // Conditional select inversion: rd = cond ? rn : ~rm. + void csinv(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond); + + // Conditional select negation: rd = cond ? rn : -rm. + void csneg(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond); + + // Conditional set: rd = cond ? 1 : 0. + void cset(const Register& rd, Condition cond); + + // Conditional set minus: rd = cond ? -1 : 0. + void csetm(const Register& rd, Condition cond); + + // Conditional increment: rd = cond ? rn + 1 : rn. + void cinc(const Register& rd, const Register& rn, Condition cond); + + // Conditional invert: rd = cond ? ~rn : rn. + void cinv(const Register& rd, const Register& rn, Condition cond); + + // Conditional negate: rd = cond ? -rn : rn. + void cneg(const Register& rd, const Register& rn, Condition cond); + + // Extr aliases. + void ror(const Register& rd, const Register& rs, unsigned shift) { + extr(rd, rs, rs, shift); + } + + // Conditional comparison. + // Conditional compare negative. + void ccmn(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond); + + // Conditional compare. + void ccmp(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond); + + // Multiplication. + // 32 x 32 -> 32-bit and 64 x 64 -> 64-bit multiply. + void mul(const Register& rd, const Register& rn, const Register& rm); + + // 32 + 32 x 32 -> 32-bit and 64 + 64 x 64 -> 64-bit multiply accumulate. + void madd(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + + // -(32 x 32) -> 32-bit and -(64 x 64) -> 64-bit multiply. + void mneg(const Register& rd, const Register& rn, const Register& rm); + + // 32 - 32 x 32 -> 32-bit and 64 - 64 x 64 -> 64-bit multiply subtract. + void msub(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + + // 32 x 32 -> 64-bit multiply. + void smull(const Register& rd, const Register& rn, const Register& rm); + + // Xd = bits<127:64> of Xn * Xm. + void smulh(const Register& rd, const Register& rn, const Register& rm); + + // Signed 32 x 32 -> 64-bit multiply and accumulate. + void smaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + + // Unsigned 32 x 32 -> 64-bit multiply and accumulate. + void umaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + + // Signed 32 x 32 -> 64-bit multiply and subtract. + void smsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + + // Unsigned 32 x 32 -> 64-bit multiply and subtract. + void umsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + + // Signed integer divide. + void sdiv(const Register& rd, const Register& rn, const Register& rm); + + // Unsigned integer divide. + void udiv(const Register& rd, const Register& rn, const Register& rm); + + // Bit count, bit reverse and endian reverse. + void rbit(const Register& rd, const Register& rn); + void rev16(const Register& rd, const Register& rn); + void rev32(const Register& rd, const Register& rn); + void rev(const Register& rd, const Register& rn); + void clz(const Register& rd, const Register& rn); + void cls(const Register& rd, const Register& rn); + + // Memory instructions. + + // Load integer or FP register. + void ldr(const CPURegister& rt, const MemOperand& src); + + // Store integer or FP register. + void str(const CPURegister& rt, const MemOperand& dst); + + // Load word with sign extension. + void ldrsw(const Register& rt, const MemOperand& src); + + // Load byte. + void ldrb(const Register& rt, const MemOperand& src); + + // Store byte. + void strb(const Register& rt, const MemOperand& dst); + + // Load byte with sign extension. + void ldrsb(const Register& rt, const MemOperand& src); + + // Load half-word. + void ldrh(const Register& rt, const MemOperand& src); + + // Store half-word. + void strh(const Register& rt, const MemOperand& dst); + + // Load half-word with sign extension. + void ldrsh(const Register& rt, const MemOperand& src); + + // Load integer or FP register pair. + void ldp(const CPURegister& rt, const CPURegister& rt2, + const MemOperand& src); + + // Store integer or FP register pair. + void stp(const CPURegister& rt, const CPURegister& rt2, + const MemOperand& dst); + + // Load word pair with sign extension. + void ldpsw(const Register& rt, const Register& rt2, const MemOperand& src); + + // Load integer or FP register pair, non-temporal. + void ldnp(const CPURegister& rt, const CPURegister& rt2, + const MemOperand& src); + + // Store integer or FP register pair, non-temporal. + void stnp(const CPURegister& rt, const CPURegister& rt2, + const MemOperand& dst); + + // Load literal to register from a pc relative address. + void ldr_pcrel(const CPURegister& rt, int imm19); + + // Load literal to register. + void ldr(const CPURegister& rt, const Immediate& imm); + + // Move instructions. The default shift of -1 indicates that the move + // instruction will calculate an appropriate 16-bit immediate and left shift + // that is equal to the 64-bit immediate argument. If an explicit left shift + // is specified (0, 16, 32 or 48), the immediate must be a 16-bit value. + // + // For movk, an explicit shift can be used to indicate which half word should + // be overwritten, eg. movk(x0, 0, 0) will overwrite the least-significant + // half word with zero, whereas movk(x0, 0, 48) will overwrite the + // most-significant. + + // Move and keep. + void movk(const Register& rd, uint64_t imm, int shift = -1) { + MoveWide(rd, imm, shift, MOVK); + } + + // Move with non-zero. + void movn(const Register& rd, uint64_t imm, int shift = -1) { + MoveWide(rd, imm, shift, MOVN); + } + + // Move with zero. + void movz(const Register& rd, uint64_t imm, int shift = -1) { + MoveWide(rd, imm, shift, MOVZ); + } + + // Misc instructions. + // Monitor debug-mode breakpoint. + void brk(int code); + + // Halting debug-mode breakpoint. + void hlt(int code); + + // Move register to register. + void mov(const Register& rd, const Register& rn); + + // Move NOT(operand) to register. + void mvn(const Register& rd, const Operand& operand); + + // System instructions. + // Move to register from system register. + void mrs(const Register& rt, SystemRegister sysreg); + + // Move from register to system register. + void msr(SystemRegister sysreg, const Register& rt); + + // System hint. + void hint(SystemHint code); + + // Data memory barrier + void dmb(BarrierDomain domain, BarrierType type); + + // Data synchronization barrier + void dsb(BarrierDomain domain, BarrierType type); + + // Instruction synchronization barrier + void isb(); + + // Alias for system instructions. + void nop() { hint(NOP); } + + // Different nop operations are used by the code generator to detect certain + // states of the generated code. + enum NopMarkerTypes { + DEBUG_BREAK_NOP, + INTERRUPT_CODE_NOP, + ADR_FAR_NOP, + FIRST_NOP_MARKER = DEBUG_BREAK_NOP, + LAST_NOP_MARKER = ADR_FAR_NOP + }; + + void nop(NopMarkerTypes n) { + ASSERT((FIRST_NOP_MARKER <= n) && (n <= LAST_NOP_MARKER)); + mov(Register::XRegFromCode(n), Register::XRegFromCode(n)); + } + + // FP instructions. + // Move immediate to FP register. + void fmov(FPRegister fd, double imm); + void fmov(FPRegister fd, float imm); + + // Move FP register to register. + void fmov(Register rd, FPRegister fn); + + // Move register to FP register. + void fmov(FPRegister fd, Register rn); + + // Move FP register to FP register. + void fmov(FPRegister fd, FPRegister fn); + + // FP add. + void fadd(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP subtract. + void fsub(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP multiply. + void fmul(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP fused multiply and add. + void fmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + + // FP fused multiply and subtract. + void fmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + + // FP fused multiply, add and negate. + void fnmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + + // FP fused multiply, subtract and negate. + void fnmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + + // FP divide. + void fdiv(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP maximum. + void fmax(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP minimum. + void fmin(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP maximum. + void fmaxnm(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP minimum. + void fminnm(const FPRegister& fd, const FPRegister& fn, const FPRegister& fm); + + // FP absolute. + void fabs(const FPRegister& fd, const FPRegister& fn); + + // FP negate. + void fneg(const FPRegister& fd, const FPRegister& fn); + + // FP square root. + void fsqrt(const FPRegister& fd, const FPRegister& fn); + + // FP round to integer (nearest with ties to away). + void frinta(const FPRegister& fd, const FPRegister& fn); + + // FP round to integer (toward minus infinity). + void frintm(const FPRegister& fd, const FPRegister& fn); + + // FP round to integer (nearest with ties to even). + void frintn(const FPRegister& fd, const FPRegister& fn); + + // FP round to integer (towards zero.) + void frintz(const FPRegister& fd, const FPRegister& fn); + + // FP compare registers. + void fcmp(const FPRegister& fn, const FPRegister& fm); + + // FP compare immediate. + void fcmp(const FPRegister& fn, double value); + + // FP conditional compare. + void fccmp(const FPRegister& fn, + const FPRegister& fm, + StatusFlags nzcv, + Condition cond); + + // FP conditional select. + void fcsel(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + Condition cond); + + // Common FP Convert function + void FPConvertToInt(const Register& rd, + const FPRegister& fn, + FPIntegerConvertOp op); + + // FP convert between single and double precision. + void fcvt(const FPRegister& fd, const FPRegister& fn); + + // Convert FP to unsigned integer (nearest with ties to away). + void fcvtau(const Register& rd, const FPRegister& fn); + + // Convert FP to signed integer (nearest with ties to away). + void fcvtas(const Register& rd, const FPRegister& fn); + + // Convert FP to unsigned integer (round towards -infinity). + void fcvtmu(const Register& rd, const FPRegister& fn); + + // Convert FP to signed integer (round towards -infinity). + void fcvtms(const Register& rd, const FPRegister& fn); + + // Convert FP to unsigned integer (nearest with ties to even). + void fcvtnu(const Register& rd, const FPRegister& fn); + + // Convert FP to signed integer (nearest with ties to even). + void fcvtns(const Register& rd, const FPRegister& fn); + + // Convert FP to unsigned integer (round towards zero). + void fcvtzu(const Register& rd, const FPRegister& fn); + + // Convert FP to signed integer (rounf towards zero). + void fcvtzs(const Register& rd, const FPRegister& fn); + + // Convert signed integer or fixed point to FP. + void scvtf(const FPRegister& fd, const Register& rn, unsigned fbits = 0); + + // Convert unsigned integer or fixed point to FP. + void ucvtf(const FPRegister& fd, const Register& rn, unsigned fbits = 0); + + // Instruction functions used only for test, debug, and patching. + // Emit raw instructions in the instruction stream. + void dci(Instr raw_inst) { Emit(raw_inst); } + + // Emit 8 bits of data in the instruction stream. + void dc8(uint8_t data) { EmitData(&data, sizeof(data)); } + + // Emit 32 bits of data in the instruction stream. + void dc32(uint32_t data) { EmitData(&data, sizeof(data)); } + + // Emit 64 bits of data in the instruction stream. + void dc64(uint64_t data) { EmitData(&data, sizeof(data)); } + + // Copy a string into the instruction stream, including the terminating NULL + // character. The instruction pointer (pc_) is then aligned correctly for + // subsequent instructions. + void EmitStringData(const char * string) { + size_t len = strlen(string) + 1; + ASSERT(RoundUp(len, kInstructionSize) <= static_cast<size_t>(kGap)); + EmitData(string, len); + // Pad with NULL characters until pc_ is aligned. + const char pad[] = {'\0', '\0', '\0', '\0'}; + STATIC_ASSERT(sizeof(pad) == kInstructionSize); + byte* next_pc = AlignUp(pc_, kInstructionSize); + EmitData(&pad, next_pc - pc_); + } + + // Pseudo-instructions ------------------------------------------------------ + + // Parameters are described in arm64/instructions-arm64.h. + void debug(const char* message, uint32_t code, Instr params = BREAK); + + // Required by V8. + void dd(uint32_t data) { dc32(data); } + void db(uint8_t data) { dc8(data); } + + // Code generation helpers -------------------------------------------------- + + unsigned num_pending_reloc_info() const { return num_pending_reloc_info_; } + + Instruction* InstructionAt(int offset) const { + return reinterpret_cast<Instruction*>(buffer_ + offset); + } + + ptrdiff_t InstructionOffset(Instruction* instr) const { + return reinterpret_cast<byte*>(instr) - buffer_; + } + + // Register encoding. + static Instr Rd(CPURegister rd) { + ASSERT(rd.code() != kSPRegInternalCode); + return rd.code() << Rd_offset; + } + + static Instr Rn(CPURegister rn) { + ASSERT(rn.code() != kSPRegInternalCode); + return rn.code() << Rn_offset; + } + + static Instr Rm(CPURegister rm) { + ASSERT(rm.code() != kSPRegInternalCode); + return rm.code() << Rm_offset; + } + + static Instr Ra(CPURegister ra) { + ASSERT(ra.code() != kSPRegInternalCode); + return ra.code() << Ra_offset; + } + + static Instr Rt(CPURegister rt) { + ASSERT(rt.code() != kSPRegInternalCode); + return rt.code() << Rt_offset; + } + + static Instr Rt2(CPURegister rt2) { + ASSERT(rt2.code() != kSPRegInternalCode); + return rt2.code() << Rt2_offset; + } + + // These encoding functions allow the stack pointer to be encoded, and + // disallow the zero register. + static Instr RdSP(Register rd) { + ASSERT(!rd.IsZero()); + return (rd.code() & kRegCodeMask) << Rd_offset; + } + + static Instr RnSP(Register rn) { + ASSERT(!rn.IsZero()); + return (rn.code() & kRegCodeMask) << Rn_offset; + } + + // Flags encoding. + inline static Instr Flags(FlagsUpdate S); + inline static Instr Cond(Condition cond); + + // PC-relative address encoding. + inline static Instr ImmPCRelAddress(int imm21); + + // Branch encoding. + inline static Instr ImmUncondBranch(int imm26); + inline static Instr ImmCondBranch(int imm19); + inline static Instr ImmCmpBranch(int imm19); + inline static Instr ImmTestBranch(int imm14); + inline static Instr ImmTestBranchBit(unsigned bit_pos); + + // Data Processing encoding. + inline static Instr SF(Register rd); + inline static Instr ImmAddSub(int64_t imm); + inline static Instr ImmS(unsigned imms, unsigned reg_size); + inline static Instr ImmR(unsigned immr, unsigned reg_size); + inline static Instr ImmSetBits(unsigned imms, unsigned reg_size); + inline static Instr ImmRotate(unsigned immr, unsigned reg_size); + inline static Instr ImmLLiteral(int imm19); + inline static Instr BitN(unsigned bitn, unsigned reg_size); + inline static Instr ShiftDP(Shift shift); + inline static Instr ImmDPShift(unsigned amount); + inline static Instr ExtendMode(Extend extend); + inline static Instr ImmExtendShift(unsigned left_shift); + inline static Instr ImmCondCmp(unsigned imm); + inline static Instr Nzcv(StatusFlags nzcv); + + static bool IsImmAddSub(int64_t immediate); + static bool IsImmLogical(uint64_t value, + unsigned width, + unsigned* n, + unsigned* imm_s, + unsigned* imm_r); + + // MemOperand offset encoding. + inline static Instr ImmLSUnsigned(int imm12); + inline static Instr ImmLS(int imm9); + inline static Instr ImmLSPair(int imm7, LSDataSize size); + inline static Instr ImmShiftLS(unsigned shift_amount); + inline static Instr ImmException(int imm16); + inline static Instr ImmSystemRegister(int imm15); + inline static Instr ImmHint(int imm7); + inline static Instr ImmBarrierDomain(int imm2); + inline static Instr ImmBarrierType(int imm2); + inline static LSDataSize CalcLSDataSize(LoadStoreOp op); + + // Move immediates encoding. + inline static Instr ImmMoveWide(uint64_t imm); + inline static Instr ShiftMoveWide(int64_t shift); + + // FP Immediates. + static Instr ImmFP32(float imm); + static Instr ImmFP64(double imm); + inline static Instr FPScale(unsigned scale); + + // FP register type. + inline static Instr FPType(FPRegister fd); + + // Class for scoping postponing the constant pool generation. + class BlockConstPoolScope { + public: + explicit BlockConstPoolScope(Assembler* assem) : assem_(assem) { + assem_->StartBlockConstPool(); + } + ~BlockConstPoolScope() { + assem_->EndBlockConstPool(); + } + + private: + Assembler* assem_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(BlockConstPoolScope); + }; + + // Check if is time to emit a constant pool. + void CheckConstPool(bool force_emit, bool require_jump); + + // Allocate a constant pool of the correct size for the generated code. + Handle<ConstantPoolArray> NewConstantPool(Isolate* isolate); + + // Generate the constant pool for the generated code. + void PopulateConstantPool(ConstantPoolArray* constant_pool); + + // Returns true if we should emit a veneer as soon as possible for a branch + // which can at most reach to specified pc. + bool ShouldEmitVeneer(int max_reachable_pc, + int margin = kVeneerDistanceMargin); + bool ShouldEmitVeneers(int margin = kVeneerDistanceMargin) { + return ShouldEmitVeneer(unresolved_branches_first_limit(), margin); + } + + // The maximum code size generated for a veneer. Currently one branch + // instruction. This is for code size checking purposes, and can be extended + // in the future for example if we decide to add nops between the veneers. + static const int kMaxVeneerCodeSize = 1 * kInstructionSize; + + void RecordVeneerPool(int location_offset, int size); + // Emits veneers for branches that are approaching their maximum range. + // If need_protection is true, the veneers are protected by a branch jumping + // over the code. + void EmitVeneers(bool force_emit, bool need_protection, + int margin = kVeneerDistanceMargin); + void EmitVeneersGuard() { EmitPoolGuard(); } + // Checks whether veneers need to be emitted at this point. + // If force_emit is set, a veneer is generated for *all* unresolved branches. + void CheckVeneerPool(bool force_emit, bool require_jump, + int margin = kVeneerDistanceMargin); + + class BlockPoolsScope { + public: + explicit BlockPoolsScope(Assembler* assem) : assem_(assem) { + assem_->StartBlockPools(); + } + ~BlockPoolsScope() { + assem_->EndBlockPools(); + } + + private: + Assembler* assem_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(BlockPoolsScope); + }; + + protected: + inline const Register& AppropriateZeroRegFor(const CPURegister& reg) const; + + void LoadStore(const CPURegister& rt, + const MemOperand& addr, + LoadStoreOp op); + static bool IsImmLSUnscaled(ptrdiff_t offset); + static bool IsImmLSScaled(ptrdiff_t offset, LSDataSize size); + + void Logical(const Register& rd, + const Register& rn, + const Operand& operand, + LogicalOp op); + void LogicalImmediate(const Register& rd, + const Register& rn, + unsigned n, + unsigned imm_s, + unsigned imm_r, + LogicalOp op); + + void ConditionalCompare(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond, + ConditionalCompareOp op); + static bool IsImmConditionalCompare(int64_t immediate); + + void AddSubWithCarry(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubWithCarryOp op); + + // Functions for emulating operands not directly supported by the instruction + // set. + void EmitShift(const Register& rd, + const Register& rn, + Shift shift, + unsigned amount); + void EmitExtendShift(const Register& rd, + const Register& rn, + Extend extend, + unsigned left_shift); + + void AddSub(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubOp op); + + static bool IsImmFP32(float imm); + static bool IsImmFP64(double imm); + + // Find an appropriate LoadStoreOp or LoadStorePairOp for the specified + // registers. Only simple loads are supported; sign- and zero-extension (such + // as in LDPSW_x or LDRB_w) are not supported. + static inline LoadStoreOp LoadOpFor(const CPURegister& rt); + static inline LoadStorePairOp LoadPairOpFor(const CPURegister& rt, + const CPURegister& rt2); + static inline LoadStoreOp StoreOpFor(const CPURegister& rt); + static inline LoadStorePairOp StorePairOpFor(const CPURegister& rt, + const CPURegister& rt2); + static inline LoadStorePairNonTemporalOp LoadPairNonTemporalOpFor( + const CPURegister& rt, const CPURegister& rt2); + static inline LoadStorePairNonTemporalOp StorePairNonTemporalOpFor( + const CPURegister& rt, const CPURegister& rt2); + static inline LoadLiteralOp LoadLiteralOpFor(const CPURegister& rt); + + // Remove the specified branch from the unbound label link chain. + // If available, a veneer for this label can be used for other branches in the + // chain if the link chain cannot be fixed up without this branch. + void RemoveBranchFromLabelLinkChain(Instruction* branch, + Label* label, + Instruction* label_veneer = NULL); + + private: + // Instruction helpers. + void MoveWide(const Register& rd, + uint64_t imm, + int shift, + MoveWideImmediateOp mov_op); + void DataProcShiftedRegister(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + Instr op); + void DataProcExtendedRegister(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + Instr op); + void LoadStorePair(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& addr, + LoadStorePairOp op); + void LoadStorePairNonTemporal(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& addr, + LoadStorePairNonTemporalOp op); + void ConditionalSelect(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond, + ConditionalSelectOp op); + void DataProcessing1Source(const Register& rd, + const Register& rn, + DataProcessing1SourceOp op); + void DataProcessing3Source(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra, + DataProcessing3SourceOp op); + void FPDataProcessing1Source(const FPRegister& fd, + const FPRegister& fn, + FPDataProcessing1SourceOp op); + void FPDataProcessing2Source(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + FPDataProcessing2SourceOp op); + void FPDataProcessing3Source(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa, + FPDataProcessing3SourceOp op); + + // Label helpers. + + // Return an offset for a label-referencing instruction, typically a branch. + int LinkAndGetByteOffsetTo(Label* label); + + // This is the same as LinkAndGetByteOffsetTo, but return an offset + // suitable for fields that take instruction offsets. + inline int LinkAndGetInstructionOffsetTo(Label* label); + + static const int kStartOfLabelLinkChain = 0; + + // Verify that a label's link chain is intact. + void CheckLabelLinkChain(Label const * label); + + void RecordLiteral(int64_t imm, unsigned size); + + // Postpone the generation of the constant pool for the specified number of + // instructions. + void BlockConstPoolFor(int instructions); + + // Emit the instruction at pc_. + void Emit(Instr instruction) { + STATIC_ASSERT(sizeof(*pc_) == 1); + STATIC_ASSERT(sizeof(instruction) == kInstructionSize); + ASSERT((pc_ + sizeof(instruction)) <= (buffer_ + buffer_size_)); + + memcpy(pc_, &instruction, sizeof(instruction)); + pc_ += sizeof(instruction); + CheckBuffer(); + } + + // Emit data inline in the instruction stream. + void EmitData(void const * data, unsigned size) { + ASSERT(sizeof(*pc_) == 1); + ASSERT((pc_ + size) <= (buffer_ + buffer_size_)); + + // TODO(all): Somehow register we have some data here. Then we can + // disassemble it correctly. + memcpy(pc_, data, size); + pc_ += size; + CheckBuffer(); + } + + void GrowBuffer(); + void CheckBufferSpace(); + void CheckBuffer(); + + // Pc offset of the next constant pool check. + int next_constant_pool_check_; + + // Constant pool generation + // Pools are emitted in the instruction stream, preferably after unconditional + // jumps or after returns from functions (in dead code locations). + // If a long code sequence does not contain unconditional jumps, it is + // necessary to emit the constant pool before the pool gets too far from the + // location it is accessed from. In this case, we emit a jump over the emitted + // constant pool. + // Constants in the pool may be addresses of functions that gets relocated; + // if so, a relocation info entry is associated to the constant pool entry. + + // Repeated checking whether the constant pool should be emitted is rather + // expensive. By default we only check again once a number of instructions + // has been generated. That also means that the sizing of the buffers is not + // an exact science, and that we rely on some slop to not overrun buffers. + static const int kCheckConstPoolIntervalInst = 128; + static const int kCheckConstPoolInterval = + kCheckConstPoolIntervalInst * kInstructionSize; + + // Constants in pools are accessed via pc relative addressing, which can + // reach +/-4KB thereby defining a maximum distance between the instruction + // and the accessed constant. + static const int kMaxDistToConstPool = 4 * KB; + static const int kMaxNumPendingRelocInfo = + kMaxDistToConstPool / kInstructionSize; + + + // Average distance beetween a constant pool and the first instruction + // accessing the constant pool. Longer distance should result in less I-cache + // pollution. + // In practice the distance will be smaller since constant pool emission is + // forced after function return and sometimes after unconditional branches. + static const int kAvgDistToConstPool = + kMaxDistToConstPool - kCheckConstPoolInterval; + + // Emission of the constant pool may be blocked in some code sequences. + int const_pool_blocked_nesting_; // Block emission if this is not zero. + int no_const_pool_before_; // Block emission before this pc offset. + + // Keep track of the first instruction requiring a constant pool entry + // since the previous constant pool was emitted. + int first_const_pool_use_; + + // Emission of the veneer pools may be blocked in some code sequences. + int veneer_pool_blocked_nesting_; // Block emission if this is not zero. + + // Relocation info generation + // Each relocation is encoded as a variable size value + static const int kMaxRelocSize = RelocInfoWriter::kMaxSize; + RelocInfoWriter reloc_info_writer; + + // Relocation info records are also used during code generation as temporary + // containers for constants and code target addresses until they are emitted + // to the constant pool. These pending relocation info records are temporarily + // stored in a separate buffer until a constant pool is emitted. + // If every instruction in a long sequence is accessing the pool, we need one + // pending relocation entry per instruction. + + // the buffer of pending relocation info + RelocInfo pending_reloc_info_[kMaxNumPendingRelocInfo]; + // number of pending reloc info entries in the buffer + int num_pending_reloc_info_; + + // Relocation for a type-recording IC has the AST id added to it. This + // member variable is a way to pass the information from the call site to + // the relocation info. + TypeFeedbackId recorded_ast_id_; + + inline TypeFeedbackId RecordedAstId(); + inline void ClearRecordedAstId(); + + protected: + // Record the AST id of the CallIC being compiled, so that it can be placed + // in the relocation information. + void SetRecordedAstId(TypeFeedbackId ast_id) { + ASSERT(recorded_ast_id_.IsNone()); + recorded_ast_id_ = ast_id; + } + + // Code generation + // The relocation writer's position is at least kGap bytes below the end of + // the generated instructions. This is so that multi-instruction sequences do + // not have to check for overflow. The same is true for writes of large + // relocation info entries, and debug strings encoded in the instruction + // stream. + static const int kGap = 128; + + public: + class FarBranchInfo { + public: + FarBranchInfo(int offset, Label* label) + : pc_offset_(offset), label_(label) {} + // Offset of the branch in the code generation buffer. + int pc_offset_; + // The label branched to. + Label* label_; + }; + + protected: + // Information about unresolved (forward) branches. + // The Assembler is only allowed to delete out-of-date information from here + // after a label is bound. The MacroAssembler uses this information to + // generate veneers. + // + // The second member gives information about the unresolved branch. The first + // member of the pair is the maximum offset that the branch can reach in the + // buffer. The map is sorted according to this reachable offset, allowing to + // easily check when veneers need to be emitted. + // Note that the maximum reachable offset (first member of the pairs) should + // always be positive but has the same type as the return value for + // pc_offset() for convenience. + std::multimap<int, FarBranchInfo> unresolved_branches_; + + // We generate a veneer for a branch if we reach within this distance of the + // limit of the range. + static const int kVeneerDistanceMargin = 1 * KB; + // The factor of 2 is a finger in the air guess. With a default margin of + // 1KB, that leaves us an addional 256 instructions to avoid generating a + // protective branch. + static const int kVeneerNoProtectionFactor = 2; + static const int kVeneerDistanceCheckMargin = + kVeneerNoProtectionFactor * kVeneerDistanceMargin; + int unresolved_branches_first_limit() const { + ASSERT(!unresolved_branches_.empty()); + return unresolved_branches_.begin()->first; + } + // This is similar to next_constant_pool_check_ and helps reduce the overhead + // of checking for veneer pools. + // It is maintained to the closest unresolved branch limit minus the maximum + // veneer margin (or kMaxInt if there are no unresolved branches). + int next_veneer_pool_check_; + + private: + // If a veneer is emitted for a branch instruction, that instruction must be + // removed from the associated label's link chain so that the assembler does + // not later attempt (likely unsuccessfully) to patch it to branch directly to + // the label. + void DeleteUnresolvedBranchInfoForLabel(Label* label); + // This function deletes the information related to the label by traversing + // the label chain, and for each PC-relative instruction in the chain checking + // if pending unresolved information exists. Its complexity is proportional to + // the length of the label chain. + void DeleteUnresolvedBranchInfoForLabelTraverse(Label* label); + + private: + PositionsRecorder positions_recorder_; + friend class PositionsRecorder; + friend class EnsureSpace; +}; + +class PatchingAssembler : public Assembler { + public: + // Create an Assembler with a buffer starting at 'start'. + // The buffer size is + // size of instructions to patch + kGap + // Where kGap is the distance from which the Assembler tries to grow the + // buffer. + // If more or fewer instructions than expected are generated or if some + // relocation information takes space in the buffer, the PatchingAssembler + // will crash trying to grow the buffer. + PatchingAssembler(Instruction* start, unsigned count) + : Assembler(NULL, + reinterpret_cast<byte*>(start), + count * kInstructionSize + kGap) { + StartBlockPools(); + } + + PatchingAssembler(byte* start, unsigned count) + : Assembler(NULL, start, count * kInstructionSize + kGap) { + // Block constant pool emission. + StartBlockPools(); + } + + ~PatchingAssembler() { + // Const pool should still be blocked. + ASSERT(is_const_pool_blocked()); + EndBlockPools(); + // Verify we have generated the number of instruction we expected. + ASSERT((pc_offset() + kGap) == buffer_size_); + // Verify no relocation information has been emitted. + ASSERT(num_pending_reloc_info() == 0); + // Flush the Instruction cache. + size_t length = buffer_size_ - kGap; + CPU::FlushICache(buffer_, length); + } + + static const int kMovInt64NInstrs = 4; + void MovInt64(const Register& rd, int64_t imm); + + // See definition of PatchAdrFar() for details. + static const int kAdrFarPatchableNNops = kMovInt64NInstrs - 1; + static const int kAdrFarPatchableNInstrs = kAdrFarPatchableNNops + 3; + void PatchAdrFar(Instruction* target); +}; + + +class EnsureSpace BASE_EMBEDDED { + public: + explicit EnsureSpace(Assembler* assembler) { + assembler->CheckBufferSpace(); + } +}; + +} } // namespace v8::internal + +#endif // V8_ARM64_ASSEMBLER_ARM64_H_ diff --git a/chromium/v8/src/arm64/builtins-arm64.cc b/chromium/v8/src/arm64/builtins-arm64.cc new file mode 100644 index 00000000000..9dc7221c304 --- /dev/null +++ b/chromium/v8/src/arm64/builtins-arm64.cc @@ -0,0 +1,1565 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/codegen.h" +#include "src/debug.h" +#include "src/deoptimizer.h" +#include "src/full-codegen.h" +#include "src/runtime.h" +#include "src/stub-cache.h" + +namespace v8 { +namespace internal { + + +#define __ ACCESS_MASM(masm) + + +// Load the built-in Array function from the current context. +static void GenerateLoadArrayFunction(MacroAssembler* masm, Register result) { + // Load the native context. + __ Ldr(result, GlobalObjectMemOperand()); + __ Ldr(result, + FieldMemOperand(result, GlobalObject::kNativeContextOffset)); + // Load the InternalArray function from the native context. + __ Ldr(result, + MemOperand(result, + Context::SlotOffset(Context::ARRAY_FUNCTION_INDEX))); +} + + +// Load the built-in InternalArray function from the current context. +static void GenerateLoadInternalArrayFunction(MacroAssembler* masm, + Register result) { + // Load the native context. + __ Ldr(result, GlobalObjectMemOperand()); + __ Ldr(result, + FieldMemOperand(result, GlobalObject::kNativeContextOffset)); + // Load the InternalArray function from the native context. + __ Ldr(result, ContextMemOperand(result, + Context::INTERNAL_ARRAY_FUNCTION_INDEX)); +} + + +void Builtins::Generate_Adaptor(MacroAssembler* masm, + CFunctionId id, + BuiltinExtraArguments extra_args) { + // ----------- S t a t e ------------- + // -- x0 : number of arguments excluding receiver + // -- x1 : called function (only guaranteed when + // extra_args requires it) + // -- cp : context + // -- sp[0] : last argument + // -- ... + // -- sp[4 * (argc - 1)] : first argument (argc == x0) + // -- sp[4 * argc] : receiver + // ----------------------------------- + + // Insert extra arguments. + int num_extra_args = 0; + if (extra_args == NEEDS_CALLED_FUNCTION) { + num_extra_args = 1; + __ Push(x1); + } else { + ASSERT(extra_args == NO_EXTRA_ARGUMENTS); + } + + // JumpToExternalReference expects x0 to contain the number of arguments + // including the receiver and the extra arguments. + __ Add(x0, x0, num_extra_args + 1); + __ JumpToExternalReference(ExternalReference(id, masm->isolate())); +} + + +void Builtins::Generate_InternalArrayCode(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : number of arguments + // -- lr : return address + // -- sp[...]: constructor arguments + // ----------------------------------- + ASM_LOCATION("Builtins::Generate_InternalArrayCode"); + Label generic_array_code; + + // Get the InternalArray function. + GenerateLoadInternalArrayFunction(masm, x1); + + if (FLAG_debug_code) { + // Initial map for the builtin InternalArray functions should be maps. + __ Ldr(x10, FieldMemOperand(x1, JSFunction::kPrototypeOrInitialMapOffset)); + __ Tst(x10, kSmiTagMask); + __ Assert(ne, kUnexpectedInitialMapForInternalArrayFunction); + __ CompareObjectType(x10, x11, x12, MAP_TYPE); + __ Assert(eq, kUnexpectedInitialMapForInternalArrayFunction); + } + + // Run the native code for the InternalArray function called as a normal + // function. + InternalArrayConstructorStub stub(masm->isolate()); + __ TailCallStub(&stub); +} + + +void Builtins::Generate_ArrayCode(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : number of arguments + // -- lr : return address + // -- sp[...]: constructor arguments + // ----------------------------------- + ASM_LOCATION("Builtins::Generate_ArrayCode"); + Label generic_array_code, one_or_more_arguments, two_or_more_arguments; + + // Get the Array function. + GenerateLoadArrayFunction(masm, x1); + + if (FLAG_debug_code) { + // Initial map for the builtin Array functions should be maps. + __ Ldr(x10, FieldMemOperand(x1, JSFunction::kPrototypeOrInitialMapOffset)); + __ Tst(x10, kSmiTagMask); + __ Assert(ne, kUnexpectedInitialMapForArrayFunction); + __ CompareObjectType(x10, x11, x12, MAP_TYPE); + __ Assert(eq, kUnexpectedInitialMapForArrayFunction); + } + + // Run the native code for the Array function called as a normal function. + __ LoadRoot(x2, Heap::kUndefinedValueRootIndex); + ArrayConstructorStub stub(masm->isolate()); + __ TailCallStub(&stub); +} + + +void Builtins::Generate_StringConstructCode(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : number of arguments + // -- x1 : constructor function + // -- lr : return address + // -- sp[(argc - n - 1) * 8] : arg[n] (zero based) + // -- sp[argc * 8] : receiver + // ----------------------------------- + ASM_LOCATION("Builtins::Generate_StringConstructCode"); + Counters* counters = masm->isolate()->counters(); + __ IncrementCounter(counters->string_ctor_calls(), 1, x10, x11); + + Register argc = x0; + Register function = x1; + if (FLAG_debug_code) { + __ LoadGlobalFunction(Context::STRING_FUNCTION_INDEX, x10); + __ Cmp(function, x10); + __ Assert(eq, kUnexpectedStringFunction); + } + + // Load the first arguments in x0 and get rid of the rest. + Label no_arguments; + __ Cbz(argc, &no_arguments); + // First args = sp[(argc - 1) * 8]. + __ Sub(argc, argc, 1); + __ Claim(argc, kXRegSize); + // jssp now point to args[0], load and drop args[0] + receiver. + Register arg = argc; + __ Ldr(arg, MemOperand(jssp, 2 * kPointerSize, PostIndex)); + argc = NoReg; + + Register argument = x2; + Label not_cached, argument_is_string; + __ LookupNumberStringCache(arg, // Input. + argument, // Result. + x10, // Scratch. + x11, // Scratch. + x12, // Scratch. + ¬_cached); + __ IncrementCounter(counters->string_ctor_cached_number(), 1, x10, x11); + __ Bind(&argument_is_string); + + // ----------- S t a t e ------------- + // -- x2 : argument converted to string + // -- x1 : constructor function + // -- lr : return address + // ----------------------------------- + + Label gc_required; + Register new_obj = x0; + __ Allocate(JSValue::kSize, new_obj, x10, x11, &gc_required, TAG_OBJECT); + + // Initialize the String object. + Register map = x3; + __ LoadGlobalFunctionInitialMap(function, map, x10); + if (FLAG_debug_code) { + __ Ldrb(x4, FieldMemOperand(map, Map::kInstanceSizeOffset)); + __ Cmp(x4, JSValue::kSize >> kPointerSizeLog2); + __ Assert(eq, kUnexpectedStringWrapperInstanceSize); + __ Ldrb(x4, FieldMemOperand(map, Map::kUnusedPropertyFieldsOffset)); + __ Cmp(x4, 0); + __ Assert(eq, kUnexpectedUnusedPropertiesOfStringWrapper); + } + __ Str(map, FieldMemOperand(new_obj, HeapObject::kMapOffset)); + + Register empty = x3; + __ LoadRoot(empty, Heap::kEmptyFixedArrayRootIndex); + __ Str(empty, FieldMemOperand(new_obj, JSObject::kPropertiesOffset)); + __ Str(empty, FieldMemOperand(new_obj, JSObject::kElementsOffset)); + + __ Str(argument, FieldMemOperand(new_obj, JSValue::kValueOffset)); + + // Ensure the object is fully initialized. + STATIC_ASSERT(JSValue::kSize == (4 * kPointerSize)); + + __ Ret(); + + // The argument was not found in the number to string cache. Check + // if it's a string already before calling the conversion builtin. + Label convert_argument; + __ Bind(¬_cached); + __ JumpIfSmi(arg, &convert_argument); + + // Is it a String? + __ Ldr(x10, FieldMemOperand(x0, HeapObject::kMapOffset)); + __ Ldrb(x11, FieldMemOperand(x10, Map::kInstanceTypeOffset)); + __ Tbnz(x11, MaskToBit(kIsNotStringMask), &convert_argument); + __ Mov(argument, arg); + __ IncrementCounter(counters->string_ctor_string_value(), 1, x10, x11); + __ B(&argument_is_string); + + // Invoke the conversion builtin and put the result into x2. + __ Bind(&convert_argument); + __ Push(function); // Preserve the function. + __ IncrementCounter(counters->string_ctor_conversions(), 1, x10, x11); + { + FrameScope scope(masm, StackFrame::INTERNAL); + __ Push(arg); + __ InvokeBuiltin(Builtins::TO_STRING, CALL_FUNCTION); + } + __ Pop(function); + __ Mov(argument, x0); + __ B(&argument_is_string); + + // Load the empty string into x2, remove the receiver from the + // stack, and jump back to the case where the argument is a string. + __ Bind(&no_arguments); + __ LoadRoot(argument, Heap::kempty_stringRootIndex); + __ Drop(1); + __ B(&argument_is_string); + + // At this point the argument is already a string. Call runtime to create a + // string wrapper. + __ Bind(&gc_required); + __ IncrementCounter(counters->string_ctor_gc_required(), 1, x10, x11); + { + FrameScope scope(masm, StackFrame::INTERNAL); + __ Push(argument); + __ CallRuntime(Runtime::kNewStringWrapper, 1); + } + __ Ret(); +} + + +static void CallRuntimePassFunction(MacroAssembler* masm, + Runtime::FunctionId function_id) { + FrameScope scope(masm, StackFrame::INTERNAL); + // - Push a copy of the function onto the stack. + // - Push another copy as a parameter to the runtime call. + __ Push(x1, x1); + + __ CallRuntime(function_id, 1); + + // - Restore receiver. + __ Pop(x1); +} + + +static void GenerateTailCallToSharedCode(MacroAssembler* masm) { + __ Ldr(x2, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset)); + __ Ldr(x2, FieldMemOperand(x2, SharedFunctionInfo::kCodeOffset)); + __ Add(x2, x2, Code::kHeaderSize - kHeapObjectTag); + __ Br(x2); +} + + +static void GenerateTailCallToReturnedCode(MacroAssembler* masm) { + __ Add(x0, x0, Code::kHeaderSize - kHeapObjectTag); + __ Br(x0); +} + + +void Builtins::Generate_InOptimizationQueue(MacroAssembler* masm) { + // Checking whether the queued function is ready for install is optional, + // since we come across interrupts and stack checks elsewhere. However, not + // checking may delay installing ready functions, and always checking would be + // quite expensive. A good compromise is to first check against stack limit as + // a cue for an interrupt signal. + Label ok; + __ CompareRoot(masm->StackPointer(), Heap::kStackLimitRootIndex); + __ B(hs, &ok); + + CallRuntimePassFunction(masm, Runtime::kHiddenTryInstallOptimizedCode); + GenerateTailCallToReturnedCode(masm); + + __ Bind(&ok); + GenerateTailCallToSharedCode(masm); +} + + +static void Generate_JSConstructStubHelper(MacroAssembler* masm, + bool is_api_function, + bool create_memento) { + // ----------- S t a t e ------------- + // -- x0 : number of arguments + // -- x1 : constructor function + // -- x2 : allocation site or undefined + // -- lr : return address + // -- sp[...]: constructor arguments + // ----------------------------------- + + ASM_LOCATION("Builtins::Generate_JSConstructStubHelper"); + // Should never create mementos for api functions. + ASSERT(!is_api_function || !create_memento); + + Isolate* isolate = masm->isolate(); + + // Enter a construct frame. + { + FrameScope scope(masm, StackFrame::CONSTRUCT); + + // Preserve the three incoming parameters on the stack. + if (create_memento) { + __ AssertUndefinedOrAllocationSite(x2, x10); + __ Push(x2); + } + + Register argc = x0; + Register constructor = x1; + // x1: constructor function + __ SmiTag(argc); + __ Push(argc, constructor); + // sp[0] : Constructor function. + // sp[1]: number of arguments (smi-tagged) + + // Try to allocate the object without transitioning into C code. If any of + // the preconditions is not met, the code bails out to the runtime call. + Label rt_call, allocated; + if (FLAG_inline_new) { + Label undo_allocation; + ExternalReference debug_step_in_fp = + ExternalReference::debug_step_in_fp_address(isolate); + __ Mov(x2, Operand(debug_step_in_fp)); + __ Ldr(x2, MemOperand(x2)); + __ Cbnz(x2, &rt_call); + // Load the initial map and verify that it is in fact a map. + Register init_map = x2; + __ Ldr(init_map, + FieldMemOperand(constructor, + JSFunction::kPrototypeOrInitialMapOffset)); + __ JumpIfSmi(init_map, &rt_call); + __ JumpIfNotObjectType(init_map, x10, x11, MAP_TYPE, &rt_call); + + // Check that the constructor is not constructing a JSFunction (see + // comments in Runtime_NewObject in runtime.cc). In which case the initial + // map's instance type would be JS_FUNCTION_TYPE. + __ CompareInstanceType(init_map, x10, JS_FUNCTION_TYPE); + __ B(eq, &rt_call); + + Register constructon_count = x14; + if (!is_api_function) { + Label allocate; + MemOperand bit_field3 = + FieldMemOperand(init_map, Map::kBitField3Offset); + // Check if slack tracking is enabled. + __ Ldr(x4, bit_field3); + __ DecodeField<Map::ConstructionCount>(constructon_count, x4); + __ Cmp(constructon_count, Operand(JSFunction::kNoSlackTracking)); + __ B(eq, &allocate); + // Decrease generous allocation count. + __ Subs(x4, x4, Operand(1 << Map::ConstructionCount::kShift)); + __ Str(x4, bit_field3); + __ Cmp(constructon_count, Operand(JSFunction::kFinishSlackTracking)); + __ B(ne, &allocate); + + // Push the constructor and map to the stack, and the constructor again + // as argument to the runtime call. + __ Push(constructor, init_map, constructor); + __ CallRuntime(Runtime::kHiddenFinalizeInstanceSize, 1); + __ Pop(init_map, constructor); + __ Mov(constructon_count, Operand(JSFunction::kNoSlackTracking)); + __ Bind(&allocate); + } + + // Now allocate the JSObject on the heap. + Register obj_size = x3; + Register new_obj = x4; + __ Ldrb(obj_size, FieldMemOperand(init_map, Map::kInstanceSizeOffset)); + if (create_memento) { + __ Add(x7, obj_size, + Operand(AllocationMemento::kSize / kPointerSize)); + __ Allocate(x7, new_obj, x10, x11, &rt_call, SIZE_IN_WORDS); + } else { + __ Allocate(obj_size, new_obj, x10, x11, &rt_call, SIZE_IN_WORDS); + } + + // Allocated the JSObject, now initialize the fields. Map is set to + // initial map and properties and elements are set to empty fixed array. + // NB. the object pointer is not tagged, so MemOperand is used. + Register empty = x5; + __ LoadRoot(empty, Heap::kEmptyFixedArrayRootIndex); + __ Str(init_map, MemOperand(new_obj, JSObject::kMapOffset)); + STATIC_ASSERT(JSObject::kElementsOffset == + (JSObject::kPropertiesOffset + kPointerSize)); + __ Stp(empty, empty, MemOperand(new_obj, JSObject::kPropertiesOffset)); + + Register first_prop = x5; + __ Add(first_prop, new_obj, JSObject::kHeaderSize); + + // Fill all of the in-object properties with the appropriate filler. + Register filler = x7; + __ LoadRoot(filler, Heap::kUndefinedValueRootIndex); + + // Obtain number of pre-allocated property fields and in-object + // properties. + Register prealloc_fields = x10; + Register inobject_props = x11; + Register inst_sizes = x11; + __ Ldr(inst_sizes, FieldMemOperand(init_map, Map::kInstanceSizesOffset)); + __ Ubfx(prealloc_fields, inst_sizes, + Map::kPreAllocatedPropertyFieldsByte * kBitsPerByte, + kBitsPerByte); + __ Ubfx(inobject_props, inst_sizes, + Map::kInObjectPropertiesByte * kBitsPerByte, kBitsPerByte); + + // Calculate number of property fields in the object. + Register prop_fields = x6; + __ Sub(prop_fields, obj_size, JSObject::kHeaderSize / kPointerSize); + + if (!is_api_function) { + Label no_inobject_slack_tracking; + + // Check if slack tracking is enabled. + __ Cmp(constructon_count, Operand(JSFunction::kNoSlackTracking)); + __ B(eq, &no_inobject_slack_tracking); + constructon_count = NoReg; + + // Fill the pre-allocated fields with undef. + __ FillFields(first_prop, prealloc_fields, filler); + + // Update first_prop register to be the offset of the first field after + // pre-allocated fields. + __ Add(first_prop, first_prop, + Operand(prealloc_fields, LSL, kPointerSizeLog2)); + + if (FLAG_debug_code) { + Register obj_end = x14; + __ Add(obj_end, new_obj, Operand(obj_size, LSL, kPointerSizeLog2)); + __ Cmp(first_prop, obj_end); + __ Assert(le, kUnexpectedNumberOfPreAllocatedPropertyFields); + } + + // Fill the remaining fields with one pointer filler map. + __ LoadRoot(filler, Heap::kOnePointerFillerMapRootIndex); + __ Sub(prop_fields, prop_fields, prealloc_fields); + + __ bind(&no_inobject_slack_tracking); + } + if (create_memento) { + // Fill the pre-allocated fields with undef. + __ FillFields(first_prop, prop_fields, filler); + __ Add(first_prop, new_obj, Operand(obj_size, LSL, kPointerSizeLog2)); + __ LoadRoot(x14, Heap::kAllocationMementoMapRootIndex); + ASSERT_EQ(0 * kPointerSize, AllocationMemento::kMapOffset); + __ Str(x14, MemOperand(first_prop, kPointerSize, PostIndex)); + // Load the AllocationSite + __ Peek(x14, 2 * kXRegSize); + ASSERT_EQ(1 * kPointerSize, AllocationMemento::kAllocationSiteOffset); + __ Str(x14, MemOperand(first_prop, kPointerSize, PostIndex)); + first_prop = NoReg; + } else { + // Fill all of the property fields with undef. + __ FillFields(first_prop, prop_fields, filler); + first_prop = NoReg; + prop_fields = NoReg; + } + + // Add the object tag to make the JSObject real, so that we can continue + // and jump into the continuation code at any time from now on. Any + // failures need to undo the allocation, so that the heap is in a + // consistent state and verifiable. + __ Add(new_obj, new_obj, kHeapObjectTag); + + // Check if a non-empty properties array is needed. Continue with + // allocated object if not, or fall through to runtime call if it is. + Register element_count = x3; + __ Ldrb(element_count, + FieldMemOperand(init_map, Map::kUnusedPropertyFieldsOffset)); + // The field instance sizes contains both pre-allocated property fields + // and in-object properties. + __ Add(element_count, element_count, prealloc_fields); + __ Subs(element_count, element_count, inobject_props); + + // Done if no extra properties are to be allocated. + __ B(eq, &allocated); + __ Assert(pl, kPropertyAllocationCountFailed); + + // Scale the number of elements by pointer size and add the header for + // FixedArrays to the start of the next object calculation from above. + Register new_array = x5; + Register array_size = x6; + __ Add(array_size, element_count, FixedArray::kHeaderSize / kPointerSize); + __ Allocate(array_size, new_array, x11, x12, &undo_allocation, + static_cast<AllocationFlags>(RESULT_CONTAINS_TOP | + SIZE_IN_WORDS)); + + Register array_map = x10; + __ LoadRoot(array_map, Heap::kFixedArrayMapRootIndex); + __ Str(array_map, MemOperand(new_array, FixedArray::kMapOffset)); + __ SmiTag(x0, element_count); + __ Str(x0, MemOperand(new_array, FixedArray::kLengthOffset)); + + // Initialize the fields to undefined. + Register elements = x10; + __ Add(elements, new_array, FixedArray::kHeaderSize); + __ FillFields(elements, element_count, filler); + + // Store the initialized FixedArray into the properties field of the + // JSObject. + __ Add(new_array, new_array, kHeapObjectTag); + __ Str(new_array, FieldMemOperand(new_obj, JSObject::kPropertiesOffset)); + + // Continue with JSObject being successfully allocated. + __ B(&allocated); + + // Undo the setting of the new top so that the heap is verifiable. For + // example, the map's unused properties potentially do not match the + // allocated objects unused properties. + __ Bind(&undo_allocation); + __ UndoAllocationInNewSpace(new_obj, x14); + } + + // Allocate the new receiver object using the runtime call. + __ Bind(&rt_call); + Label count_incremented; + if (create_memento) { + // Get the cell or allocation site. + __ Peek(x4, 2 * kXRegSize); + __ Push(x4); + __ Push(constructor); // Argument for Runtime_NewObject. + __ CallRuntime(Runtime::kHiddenNewObjectWithAllocationSite, 2); + __ Mov(x4, x0); + // If we ended up using the runtime, and we want a memento, then the + // runtime call made it for us, and we shouldn't do create count + // increment. + __ jmp(&count_incremented); + } else { + __ Push(constructor); // Argument for Runtime_NewObject. + __ CallRuntime(Runtime::kHiddenNewObject, 1); + __ Mov(x4, x0); + } + + // Receiver for constructor call allocated. + // x4: JSObject + __ Bind(&allocated); + + if (create_memento) { + __ Peek(x10, 2 * kXRegSize); + __ JumpIfRoot(x10, Heap::kUndefinedValueRootIndex, &count_incremented); + // r2 is an AllocationSite. We are creating a memento from it, so we + // need to increment the memento create count. + __ Ldr(x5, FieldMemOperand(x10, + AllocationSite::kPretenureCreateCountOffset)); + __ Add(x5, x5, Operand(Smi::FromInt(1))); + __ Str(x5, FieldMemOperand(x10, + AllocationSite::kPretenureCreateCountOffset)); + __ bind(&count_incremented); + } + + __ Push(x4, x4); + + // Reload the number of arguments from the stack. + // Set it up in x0 for the function call below. + // jssp[0]: receiver + // jssp[1]: receiver + // jssp[2]: constructor function + // jssp[3]: number of arguments (smi-tagged) + __ Peek(constructor, 2 * kXRegSize); // Load constructor. + __ Peek(argc, 3 * kXRegSize); // Load number of arguments. + __ SmiUntag(argc); + + // Set up pointer to last argument. + __ Add(x2, fp, StandardFrameConstants::kCallerSPOffset); + + // Copy arguments and receiver to the expression stack. + // Copy 2 values every loop to use ldp/stp. + // x0: number of arguments + // x1: constructor function + // x2: address of last argument (caller sp) + // jssp[0]: receiver + // jssp[1]: receiver + // jssp[2]: constructor function + // jssp[3]: number of arguments (smi-tagged) + // Compute the start address of the copy in x3. + __ Add(x3, x2, Operand(argc, LSL, kPointerSizeLog2)); + Label loop, entry, done_copying_arguments; + __ B(&entry); + __ Bind(&loop); + __ Ldp(x10, x11, MemOperand(x3, -2 * kPointerSize, PreIndex)); + __ Push(x11, x10); + __ Bind(&entry); + __ Cmp(x3, x2); + __ B(gt, &loop); + // Because we copied values 2 by 2 we may have copied one extra value. + // Drop it if that is the case. + __ B(eq, &done_copying_arguments); + __ Drop(1); + __ Bind(&done_copying_arguments); + + // Call the function. + // x0: number of arguments + // x1: constructor function + if (is_api_function) { + __ Ldr(cp, FieldMemOperand(constructor, JSFunction::kContextOffset)); + Handle<Code> code = + masm->isolate()->builtins()->HandleApiCallConstruct(); + __ Call(code, RelocInfo::CODE_TARGET); + } else { + ParameterCount actual(argc); + __ InvokeFunction(constructor, actual, CALL_FUNCTION, NullCallWrapper()); + } + + // Store offset of return address for deoptimizer. + if (!is_api_function) { + masm->isolate()->heap()->SetConstructStubDeoptPCOffset(masm->pc_offset()); + } + + // Restore the context from the frame. + // x0: result + // jssp[0]: receiver + // jssp[1]: constructor function + // jssp[2]: number of arguments (smi-tagged) + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + + // If the result is an object (in the ECMA sense), we should get rid + // of the receiver and use the result; see ECMA-262 section 13.2.2-7 + // on page 74. + Label use_receiver, exit; + + // If the result is a smi, it is *not* an object in the ECMA sense. + // x0: result + // jssp[0]: receiver (newly allocated object) + // jssp[1]: constructor function + // jssp[2]: number of arguments (smi-tagged) + __ JumpIfSmi(x0, &use_receiver); + + // If the type of the result (stored in its map) is less than + // FIRST_SPEC_OBJECT_TYPE, it is not an object in the ECMA sense. + __ JumpIfObjectType(x0, x1, x3, FIRST_SPEC_OBJECT_TYPE, &exit, ge); + + // Throw away the result of the constructor invocation and use the + // on-stack receiver as the result. + __ Bind(&use_receiver); + __ Peek(x0, 0); + + // Remove the receiver from the stack, remove caller arguments, and + // return. + __ Bind(&exit); + // x0: result + // jssp[0]: receiver (newly allocated object) + // jssp[1]: constructor function + // jssp[2]: number of arguments (smi-tagged) + __ Peek(x1, 2 * kXRegSize); + + // Leave construct frame. + } + + __ DropBySMI(x1); + __ Drop(1); + __ IncrementCounter(isolate->counters()->constructed_objects(), 1, x1, x2); + __ Ret(); +} + + +void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) { + Generate_JSConstructStubHelper(masm, false, FLAG_pretenuring_call_new); +} + + +void Builtins::Generate_JSConstructStubApi(MacroAssembler* masm) { + Generate_JSConstructStubHelper(masm, true, false); +} + + +// Input: +// x0: code entry. +// x1: function. +// x2: receiver. +// x3: argc. +// x4: argv. +// Output: +// x0: result. +static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, + bool is_construct) { + // Called from JSEntryStub::GenerateBody(). + Register function = x1; + Register receiver = x2; + Register argc = x3; + Register argv = x4; + + ProfileEntryHookStub::MaybeCallEntryHook(masm); + + // Clear the context before we push it when entering the internal frame. + __ Mov(cp, 0); + + { + // Enter an internal frame. + FrameScope scope(masm, StackFrame::INTERNAL); + + // Set up the context from the function argument. + __ Ldr(cp, FieldMemOperand(function, JSFunction::kContextOffset)); + + __ InitializeRootRegister(); + + // Push the function and the receiver onto the stack. + __ Push(function, receiver); + + // Copy arguments to the stack in a loop, in reverse order. + // x3: argc. + // x4: argv. + Label loop, entry; + // Compute the copy end address. + __ Add(x10, argv, Operand(argc, LSL, kPointerSizeLog2)); + + __ B(&entry); + __ Bind(&loop); + __ Ldr(x11, MemOperand(argv, kPointerSize, PostIndex)); + __ Ldr(x12, MemOperand(x11)); // Dereference the handle. + __ Push(x12); // Push the argument. + __ Bind(&entry); + __ Cmp(x10, argv); + __ B(ne, &loop); + + // Initialize all JavaScript callee-saved registers, since they will be seen + // by the garbage collector as part of handlers. + // The original values have been saved in JSEntryStub::GenerateBody(). + __ LoadRoot(x19, Heap::kUndefinedValueRootIndex); + __ Mov(x20, x19); + __ Mov(x21, x19); + __ Mov(x22, x19); + __ Mov(x23, x19); + __ Mov(x24, x19); + __ Mov(x25, x19); + // Don't initialize the reserved registers. + // x26 : root register (root). + // x27 : context pointer (cp). + // x28 : JS stack pointer (jssp). + // x29 : frame pointer (fp). + + __ Mov(x0, argc); + if (is_construct) { + // No type feedback cell is available. + __ LoadRoot(x2, Heap::kUndefinedValueRootIndex); + + CallConstructStub stub(masm->isolate(), NO_CALL_CONSTRUCTOR_FLAGS); + __ CallStub(&stub); + } else { + ParameterCount actual(x0); + __ InvokeFunction(function, actual, CALL_FUNCTION, NullCallWrapper()); + } + // Exit the JS internal frame and remove the parameters (except function), + // and return. + } + + // Result is in x0. Return. + __ Ret(); +} + + +void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) { + Generate_JSEntryTrampolineHelper(masm, false); +} + + +void Builtins::Generate_JSConstructEntryTrampoline(MacroAssembler* masm) { + Generate_JSEntryTrampolineHelper(masm, true); +} + + +void Builtins::Generate_CompileUnoptimized(MacroAssembler* masm) { + CallRuntimePassFunction(masm, Runtime::kHiddenCompileUnoptimized); + GenerateTailCallToReturnedCode(masm); +} + + +static void CallCompileOptimized(MacroAssembler* masm, bool concurrent) { + FrameScope scope(masm, StackFrame::INTERNAL); + Register function = x1; + + // Preserve function. At the same time, push arguments for + // kHiddenCompileOptimized. + __ LoadObject(x10, masm->isolate()->factory()->ToBoolean(concurrent)); + __ Push(function, function, x10); + + __ CallRuntime(Runtime::kHiddenCompileOptimized, 2); + + // Restore receiver. + __ Pop(function); +} + + +void Builtins::Generate_CompileOptimized(MacroAssembler* masm) { + CallCompileOptimized(masm, false); + GenerateTailCallToReturnedCode(masm); +} + + +void Builtins::Generate_CompileOptimizedConcurrent(MacroAssembler* masm) { + CallCompileOptimized(masm, true); + GenerateTailCallToReturnedCode(masm); +} + + +static void GenerateMakeCodeYoungAgainCommon(MacroAssembler* masm) { + // For now, we are relying on the fact that make_code_young doesn't do any + // garbage collection which allows us to save/restore the registers without + // worrying about which of them contain pointers. We also don't build an + // internal frame to make the code fast, since we shouldn't have to do stack + // crawls in MakeCodeYoung. This seems a bit fragile. + + // The following caller-saved registers must be saved and restored when + // calling through to the runtime: + // x0 - The address from which to resume execution. + // x1 - isolate + // lr - The return address for the JSFunction itself. It has not yet been + // preserved on the stack because the frame setup code was replaced + // with a call to this stub, to handle code ageing. + { + FrameScope scope(masm, StackFrame::MANUAL); + __ Push(x0, x1, fp, lr); + __ Mov(x1, ExternalReference::isolate_address(masm->isolate())); + __ CallCFunction( + ExternalReference::get_make_code_young_function(masm->isolate()), 2); + __ Pop(lr, fp, x1, x0); + } + + // The calling function has been made young again, so return to execute the + // real frame set-up code. + __ Br(x0); +} + +#define DEFINE_CODE_AGE_BUILTIN_GENERATOR(C) \ +void Builtins::Generate_Make##C##CodeYoungAgainEvenMarking( \ + MacroAssembler* masm) { \ + GenerateMakeCodeYoungAgainCommon(masm); \ +} \ +void Builtins::Generate_Make##C##CodeYoungAgainOddMarking( \ + MacroAssembler* masm) { \ + GenerateMakeCodeYoungAgainCommon(masm); \ +} +CODE_AGE_LIST(DEFINE_CODE_AGE_BUILTIN_GENERATOR) +#undef DEFINE_CODE_AGE_BUILTIN_GENERATOR + + +void Builtins::Generate_MarkCodeAsExecutedOnce(MacroAssembler* masm) { + // For now, as in GenerateMakeCodeYoungAgainCommon, we are relying on the fact + // that make_code_young doesn't do any garbage collection which allows us to + // save/restore the registers without worrying about which of them contain + // pointers. + + // The following caller-saved registers must be saved and restored when + // calling through to the runtime: + // x0 - The address from which to resume execution. + // x1 - isolate + // lr - The return address for the JSFunction itself. It has not yet been + // preserved on the stack because the frame setup code was replaced + // with a call to this stub, to handle code ageing. + { + FrameScope scope(masm, StackFrame::MANUAL); + __ Push(x0, x1, fp, lr); + __ Mov(x1, ExternalReference::isolate_address(masm->isolate())); + __ CallCFunction( + ExternalReference::get_mark_code_as_executed_function( + masm->isolate()), 2); + __ Pop(lr, fp, x1, x0); + + // Perform prologue operations usually performed by the young code stub. + __ EmitFrameSetupForCodeAgePatching(masm); + } + + // Jump to point after the code-age stub. + __ Add(x0, x0, kNoCodeAgeSequenceLength); + __ Br(x0); +} + + +void Builtins::Generate_MarkCodeAsExecutedTwice(MacroAssembler* masm) { + GenerateMakeCodeYoungAgainCommon(masm); +} + + +static void Generate_NotifyStubFailureHelper(MacroAssembler* masm, + SaveFPRegsMode save_doubles) { + { + FrameScope scope(masm, StackFrame::INTERNAL); + + // Preserve registers across notification, this is important for compiled + // stubs that tail call the runtime on deopts passing their parameters in + // registers. + // TODO(jbramley): Is it correct (and appropriate) to use safepoint + // registers here? According to the comment above, we should only need to + // preserve the registers with parameters. + __ PushXRegList(kSafepointSavedRegisters); + // Pass the function and deoptimization type to the runtime system. + __ CallRuntime(Runtime::kHiddenNotifyStubFailure, 0, save_doubles); + __ PopXRegList(kSafepointSavedRegisters); + } + + // Ignore state (pushed by Deoptimizer::EntryGenerator::Generate). + __ Drop(1); + + // Jump to the miss handler. Deoptimizer::EntryGenerator::Generate loads this + // into lr before it jumps here. + __ Br(lr); +} + + +void Builtins::Generate_NotifyStubFailure(MacroAssembler* masm) { + Generate_NotifyStubFailureHelper(masm, kDontSaveFPRegs); +} + + +void Builtins::Generate_NotifyStubFailureSaveDoubles(MacroAssembler* masm) { + Generate_NotifyStubFailureHelper(masm, kSaveFPRegs); +} + + +static void Generate_NotifyDeoptimizedHelper(MacroAssembler* masm, + Deoptimizer::BailoutType type) { + { + FrameScope scope(masm, StackFrame::INTERNAL); + // Pass the deoptimization type to the runtime system. + __ Mov(x0, Smi::FromInt(static_cast<int>(type))); + __ Push(x0); + __ CallRuntime(Runtime::kHiddenNotifyDeoptimized, 1); + } + + // Get the full codegen state from the stack and untag it. + Register state = x6; + __ Peek(state, 0); + __ SmiUntag(state); + + // Switch on the state. + Label with_tos_register, unknown_state; + __ CompareAndBranch( + state, FullCodeGenerator::NO_REGISTERS, ne, &with_tos_register); + __ Drop(1); // Remove state. + __ Ret(); + + __ Bind(&with_tos_register); + // Reload TOS register. + __ Peek(x0, kPointerSize); + __ CompareAndBranch(state, FullCodeGenerator::TOS_REG, ne, &unknown_state); + __ Drop(2); // Remove state and TOS. + __ Ret(); + + __ Bind(&unknown_state); + __ Abort(kInvalidFullCodegenState); +} + + +void Builtins::Generate_NotifyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::EAGER); +} + + +void Builtins::Generate_NotifyLazyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::LAZY); +} + + +void Builtins::Generate_NotifySoftDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::SOFT); +} + + +void Builtins::Generate_OnStackReplacement(MacroAssembler* masm) { + // Lookup the function in the JavaScript frame. + __ Ldr(x0, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + { + FrameScope scope(masm, StackFrame::INTERNAL); + // Pass function as argument. + __ Push(x0); + __ CallRuntime(Runtime::kCompileForOnStackReplacement, 1); + } + + // If the code object is null, just return to the unoptimized code. + Label skip; + __ CompareAndBranch(x0, Smi::FromInt(0), ne, &skip); + __ Ret(); + + __ Bind(&skip); + + // Load deoptimization data from the code object. + // <deopt_data> = <code>[#deoptimization_data_offset] + __ Ldr(x1, MemOperand(x0, Code::kDeoptimizationDataOffset - kHeapObjectTag)); + + // Load the OSR entrypoint offset from the deoptimization data. + // <osr_offset> = <deopt_data>[#header_size + #osr_pc_offset] + __ Ldrsw(w1, UntagSmiFieldMemOperand(x1, FixedArray::OffsetOfElementAt( + DeoptimizationInputData::kOsrPcOffsetIndex))); + + // Compute the target address = code_obj + header_size + osr_offset + // <entry_addr> = <code_obj> + #header_size + <osr_offset> + __ Add(x0, x0, x1); + __ Add(lr, x0, Code::kHeaderSize - kHeapObjectTag); + + // And "return" to the OSR entry point of the function. + __ Ret(); +} + + +void Builtins::Generate_OsrAfterStackCheck(MacroAssembler* masm) { + // We check the stack limit as indicator that recompilation might be done. + Label ok; + __ CompareRoot(jssp, Heap::kStackLimitRootIndex); + __ B(hs, &ok); + { + FrameScope scope(masm, StackFrame::INTERNAL); + __ CallRuntime(Runtime::kHiddenStackGuard, 0); + } + __ Jump(masm->isolate()->builtins()->OnStackReplacement(), + RelocInfo::CODE_TARGET); + + __ Bind(&ok); + __ Ret(); +} + + +void Builtins::Generate_FunctionCall(MacroAssembler* masm) { + enum { + call_type_JS_func = 0, + call_type_func_proxy = 1, + call_type_non_func = 2 + }; + Register argc = x0; + Register function = x1; + Register call_type = x4; + Register scratch1 = x10; + Register scratch2 = x11; + Register receiver_type = x13; + + ASM_LOCATION("Builtins::Generate_FunctionCall"); + // 1. Make sure we have at least one argument. + { Label done; + __ Cbnz(argc, &done); + __ LoadRoot(scratch1, Heap::kUndefinedValueRootIndex); + __ Push(scratch1); + __ Mov(argc, 1); + __ Bind(&done); + } + + // 2. Get the function to call (passed as receiver) from the stack, check + // if it is a function. + Label slow, non_function; + __ Peek(function, Operand(argc, LSL, kXRegSizeLog2)); + __ JumpIfSmi(function, &non_function); + __ JumpIfNotObjectType(function, scratch1, receiver_type, + JS_FUNCTION_TYPE, &slow); + + // 3a. Patch the first argument if necessary when calling a function. + Label shift_arguments; + __ Mov(call_type, static_cast<int>(call_type_JS_func)); + { Label convert_to_object, use_global_receiver, patch_receiver; + // Change context eagerly in case we need the global receiver. + __ Ldr(cp, FieldMemOperand(function, JSFunction::kContextOffset)); + + // Do not transform the receiver for strict mode functions. + // Also do not transform the receiver for native (Compilerhints already in + // x3). + __ Ldr(scratch1, + FieldMemOperand(function, JSFunction::kSharedFunctionInfoOffset)); + __ Ldr(scratch2.W(), + FieldMemOperand(scratch1, SharedFunctionInfo::kCompilerHintsOffset)); + __ TestAndBranchIfAnySet( + scratch2.W(), + (1 << SharedFunctionInfo::kStrictModeFunction) | + (1 << SharedFunctionInfo::kNative), + &shift_arguments); + + // Compute the receiver in sloppy mode. + Register receiver = x2; + __ Sub(scratch1, argc, 1); + __ Peek(receiver, Operand(scratch1, LSL, kXRegSizeLog2)); + __ JumpIfSmi(receiver, &convert_to_object); + + __ JumpIfRoot(receiver, Heap::kUndefinedValueRootIndex, + &use_global_receiver); + __ JumpIfRoot(receiver, Heap::kNullValueRootIndex, &use_global_receiver); + + STATIC_ASSERT(LAST_SPEC_OBJECT_TYPE == LAST_TYPE); + __ JumpIfObjectType(receiver, scratch1, scratch2, + FIRST_SPEC_OBJECT_TYPE, &shift_arguments, ge); + + __ Bind(&convert_to_object); + + { + // Enter an internal frame in order to preserve argument count. + FrameScope scope(masm, StackFrame::INTERNAL); + __ SmiTag(argc); + + __ Push(argc, receiver); + __ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); + __ Mov(receiver, x0); + + __ Pop(argc); + __ SmiUntag(argc); + + // Exit the internal frame. + } + + // Restore the function and flag in the registers. + __ Peek(function, Operand(argc, LSL, kXRegSizeLog2)); + __ Mov(call_type, static_cast<int>(call_type_JS_func)); + __ B(&patch_receiver); + + __ Bind(&use_global_receiver); + __ Ldr(receiver, GlobalObjectMemOperand()); + __ Ldr(receiver, + FieldMemOperand(receiver, GlobalObject::kGlobalReceiverOffset)); + + + __ Bind(&patch_receiver); + __ Sub(scratch1, argc, 1); + __ Poke(receiver, Operand(scratch1, LSL, kXRegSizeLog2)); + + __ B(&shift_arguments); + } + + // 3b. Check for function proxy. + __ Bind(&slow); + __ Mov(call_type, static_cast<int>(call_type_func_proxy)); + __ Cmp(receiver_type, JS_FUNCTION_PROXY_TYPE); + __ B(eq, &shift_arguments); + __ Bind(&non_function); + __ Mov(call_type, static_cast<int>(call_type_non_func)); + + // 3c. Patch the first argument when calling a non-function. The + // CALL_NON_FUNCTION builtin expects the non-function callee as + // receiver, so overwrite the first argument which will ultimately + // become the receiver. + // call type (0: JS function, 1: function proxy, 2: non-function) + __ Sub(scratch1, argc, 1); + __ Poke(function, Operand(scratch1, LSL, kXRegSizeLog2)); + + // 4. Shift arguments and return address one slot down on the stack + // (overwriting the original receiver). Adjust argument count to make + // the original first argument the new receiver. + // call type (0: JS function, 1: function proxy, 2: non-function) + __ Bind(&shift_arguments); + { Label loop; + // Calculate the copy start address (destination). Copy end address is jssp. + __ Add(scratch2, jssp, Operand(argc, LSL, kPointerSizeLog2)); + __ Sub(scratch1, scratch2, kPointerSize); + + __ Bind(&loop); + __ Ldr(x12, MemOperand(scratch1, -kPointerSize, PostIndex)); + __ Str(x12, MemOperand(scratch2, -kPointerSize, PostIndex)); + __ Cmp(scratch1, jssp); + __ B(ge, &loop); + // Adjust the actual number of arguments and remove the top element + // (which is a copy of the last argument). + __ Sub(argc, argc, 1); + __ Drop(1); + } + + // 5a. Call non-function via tail call to CALL_NON_FUNCTION builtin, + // or a function proxy via CALL_FUNCTION_PROXY. + // call type (0: JS function, 1: function proxy, 2: non-function) + { Label js_function, non_proxy; + __ Cbz(call_type, &js_function); + // Expected number of arguments is 0 for CALL_NON_FUNCTION. + __ Mov(x2, 0); + __ Cmp(call_type, static_cast<int>(call_type_func_proxy)); + __ B(ne, &non_proxy); + + __ Push(function); // Re-add proxy object as additional argument. + __ Add(argc, argc, 1); + __ GetBuiltinFunction(function, Builtins::CALL_FUNCTION_PROXY); + __ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(), + RelocInfo::CODE_TARGET); + + __ Bind(&non_proxy); + __ GetBuiltinFunction(function, Builtins::CALL_NON_FUNCTION); + __ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(), + RelocInfo::CODE_TARGET); + __ Bind(&js_function); + } + + // 5b. Get the code to call from the function and check that the number of + // expected arguments matches what we're providing. If so, jump + // (tail-call) to the code in register edx without checking arguments. + __ Ldr(x3, FieldMemOperand(function, JSFunction::kSharedFunctionInfoOffset)); + __ Ldrsw(x2, + FieldMemOperand(x3, + SharedFunctionInfo::kFormalParameterCountOffset)); + Label dont_adapt_args; + __ Cmp(x2, argc); // Check formal and actual parameter counts. + __ B(eq, &dont_adapt_args); + __ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(), + RelocInfo::CODE_TARGET); + __ Bind(&dont_adapt_args); + + __ Ldr(x3, FieldMemOperand(function, JSFunction::kCodeEntryOffset)); + ParameterCount expected(0); + __ InvokeCode(x3, expected, expected, JUMP_FUNCTION, NullCallWrapper()); +} + + +void Builtins::Generate_FunctionApply(MacroAssembler* masm) { + ASM_LOCATION("Builtins::Generate_FunctionApply"); + const int kIndexOffset = + StandardFrameConstants::kExpressionsOffset - (2 * kPointerSize); + const int kLimitOffset = + StandardFrameConstants::kExpressionsOffset - (1 * kPointerSize); + const int kArgsOffset = 2 * kPointerSize; + const int kReceiverOffset = 3 * kPointerSize; + const int kFunctionOffset = 4 * kPointerSize; + + { + FrameScope frame_scope(masm, StackFrame::INTERNAL); + + Register args = x12; + Register receiver = x14; + Register function = x15; + + // Get the length of the arguments via a builtin call. + __ Ldr(function, MemOperand(fp, kFunctionOffset)); + __ Ldr(args, MemOperand(fp, kArgsOffset)); + __ Push(function, args); + __ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_FUNCTION); + Register argc = x0; + + // Check the stack for overflow. + // We are not trying to catch interruptions (e.g. debug break and + // preemption) here, so the "real stack limit" is checked. + Label enough_stack_space; + __ LoadRoot(x10, Heap::kRealStackLimitRootIndex); + __ Ldr(function, MemOperand(fp, kFunctionOffset)); + // Make x10 the space we have left. The stack might already be overflowed + // here which will cause x10 to become negative. + // TODO(jbramley): Check that the stack usage here is safe. + __ Sub(x10, jssp, x10); + // Check if the arguments will overflow the stack. + __ Cmp(x10, Operand(argc, LSR, kSmiShift - kPointerSizeLog2)); + __ B(gt, &enough_stack_space); + // There is not enough stack space, so use a builtin to throw an appropriate + // error. + __ Push(function, argc); + __ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION); + // We should never return from the APPLY_OVERFLOW builtin. + if (__ emit_debug_code()) { + __ Unreachable(); + } + + __ Bind(&enough_stack_space); + // Push current limit and index. + __ Mov(x1, 0); // Initial index. + __ Push(argc, x1); + + Label push_receiver; + __ Ldr(receiver, MemOperand(fp, kReceiverOffset)); + + // Check that the function is a JS function. Otherwise it must be a proxy. + // When it is not the function proxy will be invoked later. + __ JumpIfNotObjectType(function, x10, x11, JS_FUNCTION_TYPE, + &push_receiver); + + // Change context eagerly to get the right global object if necessary. + __ Ldr(cp, FieldMemOperand(function, JSFunction::kContextOffset)); + // Load the shared function info. + __ Ldr(x2, FieldMemOperand(function, + JSFunction::kSharedFunctionInfoOffset)); + + // Compute and push the receiver. + // Do not transform the receiver for strict mode functions. + Label convert_receiver_to_object, use_global_receiver; + __ Ldr(w10, FieldMemOperand(x2, SharedFunctionInfo::kCompilerHintsOffset)); + __ Tbnz(x10, SharedFunctionInfo::kStrictModeFunction, &push_receiver); + // Do not transform the receiver for native functions. + __ Tbnz(x10, SharedFunctionInfo::kNative, &push_receiver); + + // Compute the receiver in sloppy mode. + __ JumpIfSmi(receiver, &convert_receiver_to_object); + __ JumpIfRoot(receiver, Heap::kNullValueRootIndex, &use_global_receiver); + __ JumpIfRoot(receiver, Heap::kUndefinedValueRootIndex, + &use_global_receiver); + + // Check if the receiver is already a JavaScript object. + STATIC_ASSERT(LAST_SPEC_OBJECT_TYPE == LAST_TYPE); + __ JumpIfObjectType(receiver, x10, x11, FIRST_SPEC_OBJECT_TYPE, + &push_receiver, ge); + + // Call a builtin to convert the receiver to a regular object. + __ Bind(&convert_receiver_to_object); + __ Push(receiver); + __ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); + __ Mov(receiver, x0); + __ B(&push_receiver); + + __ Bind(&use_global_receiver); + __ Ldr(x10, GlobalObjectMemOperand()); + __ Ldr(receiver, FieldMemOperand(x10, GlobalObject::kGlobalReceiverOffset)); + + // Push the receiver + __ Bind(&push_receiver); + __ Push(receiver); + + // Copy all arguments from the array to the stack. + Label entry, loop; + Register current = x0; + __ Ldr(current, MemOperand(fp, kIndexOffset)); + __ B(&entry); + + __ Bind(&loop); + // Load the current argument from the arguments array and push it. + // TODO(all): Couldn't we optimize this for JS arrays? + + __ Ldr(x1, MemOperand(fp, kArgsOffset)); + __ Push(x1, current); + + // Call the runtime to access the property in the arguments array. + __ CallRuntime(Runtime::kGetProperty, 2); + __ Push(x0); + + // Use inline caching to access the arguments. + __ Ldr(current, MemOperand(fp, kIndexOffset)); + __ Add(current, current, Smi::FromInt(1)); + __ Str(current, MemOperand(fp, kIndexOffset)); + + // Test if the copy loop has finished copying all the elements from the + // arguments object. + __ Bind(&entry); + __ Ldr(x1, MemOperand(fp, kLimitOffset)); + __ Cmp(current, x1); + __ B(ne, &loop); + + // At the end of the loop, the number of arguments is stored in 'current', + // represented as a smi. + + function = x1; // From now on we want the function to be kept in x1; + __ Ldr(function, MemOperand(fp, kFunctionOffset)); + + // Call the function. + Label call_proxy; + ParameterCount actual(current); + __ SmiUntag(current); + __ JumpIfNotObjectType(function, x10, x11, JS_FUNCTION_TYPE, &call_proxy); + __ InvokeFunction(function, actual, CALL_FUNCTION, NullCallWrapper()); + frame_scope.GenerateLeaveFrame(); + __ Drop(3); + __ Ret(); + + // Call the function proxy. + __ Bind(&call_proxy); + // x0 : argc + // x1 : function + __ Push(function); // Add function proxy as last argument. + __ Add(x0, x0, 1); + __ Mov(x2, 0); + __ GetBuiltinFunction(x1, Builtins::CALL_FUNCTION_PROXY); + __ Call(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(), + RelocInfo::CODE_TARGET); + } + __ Drop(3); + __ Ret(); +} + + +static void ArgumentAdaptorStackCheck(MacroAssembler* masm, + Label* stack_overflow) { + // ----------- S t a t e ------------- + // -- x0 : actual number of arguments + // -- x1 : function (passed through to callee) + // -- x2 : expected number of arguments + // ----------------------------------- + // Check the stack for overflow. + // We are not trying to catch interruptions (e.g. debug break and + // preemption) here, so the "real stack limit" is checked. + Label enough_stack_space; + __ LoadRoot(x10, Heap::kRealStackLimitRootIndex); + // Make x10 the space we have left. The stack might already be overflowed + // here which will cause x10 to become negative. + __ Sub(x10, jssp, x10); + // Check if the arguments will overflow the stack. + __ Cmp(x10, Operand(x2, LSL, kPointerSizeLog2)); + __ B(le, stack_overflow); +} + + +static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) { + __ SmiTag(x10, x0); + __ Mov(x11, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ Push(lr, fp); + __ Push(x11, x1, x10); + __ Add(fp, jssp, + StandardFrameConstants::kFixedFrameSizeFromFp + kPointerSize); +} + + +static void LeaveArgumentsAdaptorFrame(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : result being passed through + // ----------------------------------- + // Get the number of arguments passed (as a smi), tear down the frame and + // then drop the parameters and the receiver. + __ Ldr(x10, MemOperand(fp, -(StandardFrameConstants::kFixedFrameSizeFromFp + + kPointerSize))); + __ Mov(jssp, fp); + __ Pop(fp, lr); + __ DropBySMI(x10, kXRegSize); + __ Drop(1); +} + + +void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) { + ASM_LOCATION("Builtins::Generate_ArgumentsAdaptorTrampoline"); + // ----------- S t a t e ------------- + // -- x0 : actual number of arguments + // -- x1 : function (passed through to callee) + // -- x2 : expected number of arguments + // ----------------------------------- + + Label stack_overflow; + ArgumentAdaptorStackCheck(masm, &stack_overflow); + + Register argc_actual = x0; // Excluding the receiver. + Register argc_expected = x2; // Excluding the receiver. + Register function = x1; + Register code_entry = x3; + + Label invoke, dont_adapt_arguments; + + Label enough, too_few; + __ Ldr(code_entry, FieldMemOperand(function, JSFunction::kCodeEntryOffset)); + __ Cmp(argc_actual, argc_expected); + __ B(lt, &too_few); + __ Cmp(argc_expected, SharedFunctionInfo::kDontAdaptArgumentsSentinel); + __ B(eq, &dont_adapt_arguments); + + { // Enough parameters: actual >= expected + EnterArgumentsAdaptorFrame(masm); + + Register copy_start = x10; + Register copy_end = x11; + Register copy_to = x12; + Register scratch1 = x13, scratch2 = x14; + + __ Lsl(argc_expected, argc_expected, kPointerSizeLog2); + + // Adjust for fp, lr, and the receiver. + __ Add(copy_start, fp, 3 * kPointerSize); + __ Add(copy_start, copy_start, Operand(argc_actual, LSL, kPointerSizeLog2)); + __ Sub(copy_end, copy_start, argc_expected); + __ Sub(copy_end, copy_end, kPointerSize); + __ Mov(copy_to, jssp); + + // Claim space for the arguments, the receiver, and one extra slot. + // The extra slot ensures we do not write under jssp. It will be popped + // later. + __ Add(scratch1, argc_expected, 2 * kPointerSize); + __ Claim(scratch1, 1); + + // Copy the arguments (including the receiver) to the new stack frame. + Label copy_2_by_2; + __ Bind(©_2_by_2); + __ Ldp(scratch1, scratch2, + MemOperand(copy_start, - 2 * kPointerSize, PreIndex)); + __ Stp(scratch1, scratch2, + MemOperand(copy_to, - 2 * kPointerSize, PreIndex)); + __ Cmp(copy_start, copy_end); + __ B(hi, ©_2_by_2); + + // Correct the space allocated for the extra slot. + __ Drop(1); + + __ B(&invoke); + } + + { // Too few parameters: Actual < expected + __ Bind(&too_few); + EnterArgumentsAdaptorFrame(masm); + + Register copy_from = x10; + Register copy_end = x11; + Register copy_to = x12; + Register scratch1 = x13, scratch2 = x14; + + __ Lsl(argc_expected, argc_expected, kPointerSizeLog2); + __ Lsl(argc_actual, argc_actual, kPointerSizeLog2); + + // Adjust for fp, lr, and the receiver. + __ Add(copy_from, fp, 3 * kPointerSize); + __ Add(copy_from, copy_from, argc_actual); + __ Mov(copy_to, jssp); + __ Sub(copy_end, copy_to, 1 * kPointerSize); // Adjust for the receiver. + __ Sub(copy_end, copy_end, argc_actual); + + // Claim space for the arguments, the receiver, and one extra slot. + // The extra slot ensures we do not write under jssp. It will be popped + // later. + __ Add(scratch1, argc_expected, 2 * kPointerSize); + __ Claim(scratch1, 1); + + // Copy the arguments (including the receiver) to the new stack frame. + Label copy_2_by_2; + __ Bind(©_2_by_2); + __ Ldp(scratch1, scratch2, + MemOperand(copy_from, - 2 * kPointerSize, PreIndex)); + __ Stp(scratch1, scratch2, + MemOperand(copy_to, - 2 * kPointerSize, PreIndex)); + __ Cmp(copy_to, copy_end); + __ B(hi, ©_2_by_2); + + __ Mov(copy_to, copy_end); + + // Fill the remaining expected arguments with undefined. + __ LoadRoot(scratch1, Heap::kUndefinedValueRootIndex); + __ Add(copy_end, jssp, kPointerSize); + + Label fill; + __ Bind(&fill); + __ Stp(scratch1, scratch1, + MemOperand(copy_to, - 2 * kPointerSize, PreIndex)); + __ Cmp(copy_to, copy_end); + __ B(hi, &fill); + + // Correct the space allocated for the extra slot. + __ Drop(1); + } + + // Arguments have been adapted. Now call the entry point. + __ Bind(&invoke); + __ Call(code_entry); + + // Store offset of return address for deoptimizer. + masm->isolate()->heap()->SetArgumentsAdaptorDeoptPCOffset(masm->pc_offset()); + + // Exit frame and return. + LeaveArgumentsAdaptorFrame(masm); + __ Ret(); + + // Call the entry point without adapting the arguments. + __ Bind(&dont_adapt_arguments); + __ Jump(code_entry); + + __ Bind(&stack_overflow); + { + FrameScope frame(masm, StackFrame::MANUAL); + EnterArgumentsAdaptorFrame(masm); + __ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION); + __ Unreachable(); + } +} + + +#undef __ + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM diff --git a/chromium/v8/src/arm64/code-stubs-arm64.cc b/chromium/v8/src/arm64/code-stubs-arm64.cc new file mode 100644 index 00000000000..70ead443fd6 --- /dev/null +++ b/chromium/v8/src/arm64/code-stubs-arm64.cc @@ -0,0 +1,5555 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/bootstrapper.h" +#include "src/code-stubs.h" +#include "src/regexp-macro-assembler.h" +#include "src/stub-cache.h" + +namespace v8 { +namespace internal { + + +void FastNewClosureStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x2: function info + static Register registers[] = { x2 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kHiddenNewClosureFromStubFailure)->entry; +} + + +void FastNewContextStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x1: function + static Register registers[] = { x1 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = NULL; +} + + +void ToNumberStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x0: value + static Register registers[] = { x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = NULL; +} + + +void NumberToStringStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x0: value + static Register registers[] = { x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kHiddenNumberToString)->entry; +} + + +void FastCloneShallowArrayStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x3: array literals array + // x2: array literal index + // x1: constant elements + static Register registers[] = { x3, x2, x1 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + static Representation representations[] = { + Representation::Tagged(), + Representation::Smi(), + Representation::Tagged() }; + descriptor->register_param_representations_ = representations; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId( + Runtime::kHiddenCreateArrayLiteralStubBailout)->entry; +} + + +void FastCloneShallowObjectStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x3: object literals array + // x2: object literal index + // x1: constant properties + // x0: object literal flags + static Register registers[] = { x3, x2, x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kHiddenCreateObjectLiteral)->entry; +} + + +void CreateAllocationSiteStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x2: feedback vector + // x3: call feedback slot + static Register registers[] = { x2, x3 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = NULL; +} + + +void KeyedLoadGenericElementStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + static Register registers[] = { x1, x0 }; + descriptor->register_param_count_ = 2; + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kKeyedGetProperty)->entry; +} + + +void KeyedLoadFastElementStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x1: receiver + // x0: key + static Register registers[] = { x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + FUNCTION_ADDR(KeyedLoadIC_MissFromStubFailure); +} + + +void KeyedLoadDictionaryElementStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x1: receiver + // x0: key + static Register registers[] = { x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + FUNCTION_ADDR(KeyedLoadIC_MissFromStubFailure); +} + + +void RegExpConstructResultStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x2: length + // x1: index (of last match) + // x0: string + static Register registers[] = { x2, x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kHiddenRegExpConstructResult)->entry; +} + + +void LoadFieldStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x0: receiver + static Register registers[] = { x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = NULL; +} + + +void KeyedLoadFieldStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x1: receiver + static Register registers[] = { x1 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = NULL; +} + + +void StringLengthStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + static Register registers[] = { x0, x2 }; + descriptor->register_param_count_ = 2; + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = NULL; +} + + +void KeyedStringLengthStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + static Register registers[] = { x1, x0 }; + descriptor->register_param_count_ = 2; + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = NULL; +} + + +void KeyedStoreFastElementStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x2: receiver + // x1: key + // x0: value + static Register registers[] = { x2, x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + FUNCTION_ADDR(KeyedStoreIC_MissFromStubFailure); +} + + +void TransitionElementsKindStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x0: value (js_array) + // x1: to_map + static Register registers[] = { x0, x1 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + Address entry = + Runtime::FunctionForId(Runtime::kTransitionElementsKind)->entry; + descriptor->deoptimization_handler_ = FUNCTION_ADDR(entry); +} + + +void CompareNilICStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x0: value to compare + static Register registers[] = { x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + FUNCTION_ADDR(CompareNilIC_Miss); + descriptor->SetMissHandler( + ExternalReference(IC_Utility(IC::kCompareNilIC_Miss), isolate())); +} + + +static void InitializeArrayConstructorDescriptor( + CodeStubInterfaceDescriptor* descriptor, + int constant_stack_parameter_count) { + // x1: function + // x2: allocation site with elements kind + // x0: number of arguments to the constructor function + static Register registers_variable_args[] = { x1, x2, x0 }; + static Register registers_no_args[] = { x1, x2 }; + + if (constant_stack_parameter_count == 0) { + descriptor->register_param_count_ = + sizeof(registers_no_args) / sizeof(registers_no_args[0]); + descriptor->register_params_ = registers_no_args; + } else { + // stack param count needs (constructor pointer, and single argument) + descriptor->handler_arguments_mode_ = PASS_ARGUMENTS; + descriptor->stack_parameter_count_ = x0; + descriptor->register_param_count_ = + sizeof(registers_variable_args) / sizeof(registers_variable_args[0]); + descriptor->register_params_ = registers_variable_args; + static Representation representations[] = { + Representation::Tagged(), + Representation::Tagged(), + Representation::Integer32() }; + descriptor->register_param_representations_ = representations; + } + + descriptor->hint_stack_parameter_count_ = constant_stack_parameter_count; + descriptor->function_mode_ = JS_FUNCTION_STUB_MODE; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kHiddenArrayConstructor)->entry; +} + + +void ArrayNoArgumentConstructorStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + InitializeArrayConstructorDescriptor(descriptor, 0); +} + + +void ArraySingleArgumentConstructorStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + InitializeArrayConstructorDescriptor(descriptor, 1); +} + + +void ArrayNArgumentsConstructorStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + InitializeArrayConstructorDescriptor(descriptor, -1); +} + + +static void InitializeInternalArrayConstructorDescriptor( + CodeStubInterfaceDescriptor* descriptor, + int constant_stack_parameter_count) { + // x1: constructor function + // x0: number of arguments to the constructor function + static Register registers_variable_args[] = { x1, x0 }; + static Register registers_no_args[] = { x1 }; + + if (constant_stack_parameter_count == 0) { + descriptor->register_param_count_ = + sizeof(registers_no_args) / sizeof(registers_no_args[0]); + descriptor->register_params_ = registers_no_args; + } else { + // stack param count needs (constructor pointer, and single argument) + descriptor->handler_arguments_mode_ = PASS_ARGUMENTS; + descriptor->stack_parameter_count_ = x0; + descriptor->register_param_count_ = + sizeof(registers_variable_args) / sizeof(registers_variable_args[0]); + descriptor->register_params_ = registers_variable_args; + static Representation representations[] = { + Representation::Tagged(), + Representation::Integer32() }; + descriptor->register_param_representations_ = representations; + } + + descriptor->hint_stack_parameter_count_ = constant_stack_parameter_count; + descriptor->function_mode_ = JS_FUNCTION_STUB_MODE; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kHiddenInternalArrayConstructor)->entry; +} + + +void InternalArrayNoArgumentConstructorStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + InitializeInternalArrayConstructorDescriptor(descriptor, 0); +} + + +void InternalArraySingleArgumentConstructorStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + InitializeInternalArrayConstructorDescriptor(descriptor, 1); +} + + +void InternalArrayNArgumentsConstructorStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + InitializeInternalArrayConstructorDescriptor(descriptor, -1); +} + + +void ToBooleanStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x0: value + static Register registers[] = { x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = FUNCTION_ADDR(ToBooleanIC_Miss); + descriptor->SetMissHandler( + ExternalReference(IC_Utility(IC::kToBooleanIC_Miss), isolate())); +} + + +void StoreGlobalStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x1: receiver + // x2: key (unused) + // x0: value + static Register registers[] = { x1, x2, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + FUNCTION_ADDR(StoreIC_MissFromStubFailure); +} + + +void ElementsTransitionAndStoreStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x0: value + // x3: target map + // x1: key + // x2: receiver + static Register registers[] = { x0, x3, x1, x2 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + FUNCTION_ADDR(ElementsTransitionAndStoreIC_Miss); +} + + +void BinaryOpICStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x1: left operand + // x0: right operand + static Register registers[] = { x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = FUNCTION_ADDR(BinaryOpIC_Miss); + descriptor->SetMissHandler( + ExternalReference(IC_Utility(IC::kBinaryOpIC_Miss), isolate())); +} + + +void BinaryOpWithAllocationSiteStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x2: allocation site + // x1: left operand + // x0: right operand + static Register registers[] = { x2, x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + FUNCTION_ADDR(BinaryOpIC_MissWithAllocationSite); +} + + +void StringAddStub::InitializeInterfaceDescriptor( + CodeStubInterfaceDescriptor* descriptor) { + // x1: left operand + // x0: right operand + static Register registers[] = { x1, x0 }; + descriptor->register_param_count_ = sizeof(registers) / sizeof(registers[0]); + descriptor->register_params_ = registers; + descriptor->deoptimization_handler_ = + Runtime::FunctionForId(Runtime::kHiddenStringAdd)->entry; +} + + +void CallDescriptors::InitializeForIsolate(Isolate* isolate) { + static PlatformCallInterfaceDescriptor default_descriptor = + PlatformCallInterfaceDescriptor(CAN_INLINE_TARGET_ADDRESS); + + static PlatformCallInterfaceDescriptor noInlineDescriptor = + PlatformCallInterfaceDescriptor(NEVER_INLINE_TARGET_ADDRESS); + + { + CallInterfaceDescriptor* descriptor = + isolate->call_descriptor(Isolate::ArgumentAdaptorCall); + static Register registers[] = { x1, // JSFunction + cp, // context + x0, // actual number of arguments + x2, // expected number of arguments + }; + static Representation representations[] = { + Representation::Tagged(), // JSFunction + Representation::Tagged(), // context + Representation::Integer32(), // actual number of arguments + Representation::Integer32(), // expected number of arguments + }; + descriptor->register_param_count_ = 4; + descriptor->register_params_ = registers; + descriptor->param_representations_ = representations; + descriptor->platform_specific_descriptor_ = &default_descriptor; + } + { + CallInterfaceDescriptor* descriptor = + isolate->call_descriptor(Isolate::KeyedCall); + static Register registers[] = { cp, // context + x2, // key + }; + static Representation representations[] = { + Representation::Tagged(), // context + Representation::Tagged(), // key + }; + descriptor->register_param_count_ = 2; + descriptor->register_params_ = registers; + descriptor->param_representations_ = representations; + descriptor->platform_specific_descriptor_ = &noInlineDescriptor; + } + { + CallInterfaceDescriptor* descriptor = + isolate->call_descriptor(Isolate::NamedCall); + static Register registers[] = { cp, // context + x2, // name + }; + static Representation representations[] = { + Representation::Tagged(), // context + Representation::Tagged(), // name + }; + descriptor->register_param_count_ = 2; + descriptor->register_params_ = registers; + descriptor->param_representations_ = representations; + descriptor->platform_specific_descriptor_ = &noInlineDescriptor; + } + { + CallInterfaceDescriptor* descriptor = + isolate->call_descriptor(Isolate::CallHandler); + static Register registers[] = { cp, // context + x0, // receiver + }; + static Representation representations[] = { + Representation::Tagged(), // context + Representation::Tagged(), // receiver + }; + descriptor->register_param_count_ = 2; + descriptor->register_params_ = registers; + descriptor->param_representations_ = representations; + descriptor->platform_specific_descriptor_ = &default_descriptor; + } + { + CallInterfaceDescriptor* descriptor = + isolate->call_descriptor(Isolate::ApiFunctionCall); + static Register registers[] = { x0, // callee + x4, // call_data + x2, // holder + x1, // api_function_address + cp, // context + }; + static Representation representations[] = { + Representation::Tagged(), // callee + Representation::Tagged(), // call_data + Representation::Tagged(), // holder + Representation::External(), // api_function_address + Representation::Tagged(), // context + }; + descriptor->register_param_count_ = 5; + descriptor->register_params_ = registers; + descriptor->param_representations_ = representations; + descriptor->platform_specific_descriptor_ = &default_descriptor; + } +} + + +#define __ ACCESS_MASM(masm) + + +void HydrogenCodeStub::GenerateLightweightMiss(MacroAssembler* masm) { + // Update the static counter each time a new code stub is generated. + isolate()->counters()->code_stubs()->Increment(); + + CodeStubInterfaceDescriptor* descriptor = GetInterfaceDescriptor(); + int param_count = descriptor->register_param_count_; + { + // Call the runtime system in a fresh internal frame. + FrameScope scope(masm, StackFrame::INTERNAL); + ASSERT((descriptor->register_param_count_ == 0) || + x0.Is(descriptor->register_params_[param_count - 1])); + + // Push arguments + MacroAssembler::PushPopQueue queue(masm); + for (int i = 0; i < param_count; ++i) { + queue.Queue(descriptor->register_params_[i]); + } + queue.PushQueued(); + + ExternalReference miss = descriptor->miss_handler(); + __ CallExternalReference(miss, descriptor->register_param_count_); + } + + __ Ret(); +} + + +void DoubleToIStub::Generate(MacroAssembler* masm) { + Label done; + Register input = source(); + Register result = destination(); + ASSERT(is_truncating()); + + ASSERT(result.Is64Bits()); + ASSERT(jssp.Is(masm->StackPointer())); + + int double_offset = offset(); + + DoubleRegister double_scratch = d0; // only used if !skip_fastpath() + Register scratch1 = GetAllocatableRegisterThatIsNotOneOf(input, result); + Register scratch2 = + GetAllocatableRegisterThatIsNotOneOf(input, result, scratch1); + + __ Push(scratch1, scratch2); + // Account for saved regs if input is jssp. + if (input.is(jssp)) double_offset += 2 * kPointerSize; + + if (!skip_fastpath()) { + __ Push(double_scratch); + if (input.is(jssp)) double_offset += 1 * kDoubleSize; + __ Ldr(double_scratch, MemOperand(input, double_offset)); + // Try to convert with a FPU convert instruction. This handles all + // non-saturating cases. + __ TryConvertDoubleToInt64(result, double_scratch, &done); + __ Fmov(result, double_scratch); + } else { + __ Ldr(result, MemOperand(input, double_offset)); + } + + // If we reach here we need to manually convert the input to an int32. + + // Extract the exponent. + Register exponent = scratch1; + __ Ubfx(exponent, result, HeapNumber::kMantissaBits, + HeapNumber::kExponentBits); + + // It the exponent is >= 84 (kMantissaBits + 32), the result is always 0 since + // the mantissa gets shifted completely out of the int32_t result. + __ Cmp(exponent, HeapNumber::kExponentBias + HeapNumber::kMantissaBits + 32); + __ CzeroX(result, ge); + __ B(ge, &done); + + // The Fcvtzs sequence handles all cases except where the conversion causes + // signed overflow in the int64_t target. Since we've already handled + // exponents >= 84, we can guarantee that 63 <= exponent < 84. + + if (masm->emit_debug_code()) { + __ Cmp(exponent, HeapNumber::kExponentBias + 63); + // Exponents less than this should have been handled by the Fcvt case. + __ Check(ge, kUnexpectedValue); + } + + // Isolate the mantissa bits, and set the implicit '1'. + Register mantissa = scratch2; + __ Ubfx(mantissa, result, 0, HeapNumber::kMantissaBits); + __ Orr(mantissa, mantissa, 1UL << HeapNumber::kMantissaBits); + + // Negate the mantissa if necessary. + __ Tst(result, kXSignMask); + __ Cneg(mantissa, mantissa, ne); + + // Shift the mantissa bits in the correct place. We know that we have to shift + // it left here, because exponent >= 63 >= kMantissaBits. + __ Sub(exponent, exponent, + HeapNumber::kExponentBias + HeapNumber::kMantissaBits); + __ Lsl(result, mantissa, exponent); + + __ Bind(&done); + if (!skip_fastpath()) { + __ Pop(double_scratch); + } + __ Pop(scratch2, scratch1); + __ Ret(); +} + + +// See call site for description. +static void EmitIdenticalObjectComparison(MacroAssembler* masm, + Register left, + Register right, + Register scratch, + FPRegister double_scratch, + Label* slow, + Condition cond) { + ASSERT(!AreAliased(left, right, scratch)); + Label not_identical, return_equal, heap_number; + Register result = x0; + + __ Cmp(right, left); + __ B(ne, ¬_identical); + + // Test for NaN. Sadly, we can't just compare to factory::nan_value(), + // so we do the second best thing - test it ourselves. + // They are both equal and they are not both Smis so both of them are not + // Smis. If it's not a heap number, then return equal. + if ((cond == lt) || (cond == gt)) { + __ JumpIfObjectType(right, scratch, scratch, FIRST_SPEC_OBJECT_TYPE, slow, + ge); + } else { + Register right_type = scratch; + __ JumpIfObjectType(right, right_type, right_type, HEAP_NUMBER_TYPE, + &heap_number); + // Comparing JS objects with <=, >= is complicated. + if (cond != eq) { + __ Cmp(right_type, FIRST_SPEC_OBJECT_TYPE); + __ B(ge, slow); + // Normally here we fall through to return_equal, but undefined is + // special: (undefined == undefined) == true, but + // (undefined <= undefined) == false! See ECMAScript 11.8.5. + if ((cond == le) || (cond == ge)) { + __ Cmp(right_type, ODDBALL_TYPE); + __ B(ne, &return_equal); + __ JumpIfNotRoot(right, Heap::kUndefinedValueRootIndex, &return_equal); + if (cond == le) { + // undefined <= undefined should fail. + __ Mov(result, GREATER); + } else { + // undefined >= undefined should fail. + __ Mov(result, LESS); + } + __ Ret(); + } + } + } + + __ Bind(&return_equal); + if (cond == lt) { + __ Mov(result, GREATER); // Things aren't less than themselves. + } else if (cond == gt) { + __ Mov(result, LESS); // Things aren't greater than themselves. + } else { + __ Mov(result, EQUAL); // Things are <=, >=, ==, === themselves. + } + __ Ret(); + + // Cases lt and gt have been handled earlier, and case ne is never seen, as + // it is handled in the parser (see Parser::ParseBinaryExpression). We are + // only concerned with cases ge, le and eq here. + if ((cond != lt) && (cond != gt)) { + ASSERT((cond == ge) || (cond == le) || (cond == eq)); + __ Bind(&heap_number); + // Left and right are identical pointers to a heap number object. Return + // non-equal if the heap number is a NaN, and equal otherwise. Comparing + // the number to itself will set the overflow flag iff the number is NaN. + __ Ldr(double_scratch, FieldMemOperand(right, HeapNumber::kValueOffset)); + __ Fcmp(double_scratch, double_scratch); + __ B(vc, &return_equal); // Not NaN, so treat as normal heap number. + + if (cond == le) { + __ Mov(result, GREATER); + } else { + __ Mov(result, LESS); + } + __ Ret(); + } + + // No fall through here. + if (FLAG_debug_code) { + __ Unreachable(); + } + + __ Bind(¬_identical); +} + + +// See call site for description. +static void EmitStrictTwoHeapObjectCompare(MacroAssembler* masm, + Register left, + Register right, + Register left_type, + Register right_type, + Register scratch) { + ASSERT(!AreAliased(left, right, left_type, right_type, scratch)); + + if (masm->emit_debug_code()) { + // We assume that the arguments are not identical. + __ Cmp(left, right); + __ Assert(ne, kExpectedNonIdenticalObjects); + } + + // If either operand is a JS object or an oddball value, then they are not + // equal since their pointers are different. + // There is no test for undetectability in strict equality. + STATIC_ASSERT(LAST_TYPE == LAST_SPEC_OBJECT_TYPE); + Label right_non_object; + + __ Cmp(right_type, FIRST_SPEC_OBJECT_TYPE); + __ B(lt, &right_non_object); + + // Return non-zero - x0 already contains a non-zero pointer. + ASSERT(left.is(x0) || right.is(x0)); + Label return_not_equal; + __ Bind(&return_not_equal); + __ Ret(); + + __ Bind(&right_non_object); + + // Check for oddballs: true, false, null, undefined. + __ Cmp(right_type, ODDBALL_TYPE); + + // If right is not ODDBALL, test left. Otherwise, set eq condition. + __ Ccmp(left_type, ODDBALL_TYPE, ZFlag, ne); + + // If right or left is not ODDBALL, test left >= FIRST_SPEC_OBJECT_TYPE. + // Otherwise, right or left is ODDBALL, so set a ge condition. + __ Ccmp(left_type, FIRST_SPEC_OBJECT_TYPE, NVFlag, ne); + + __ B(ge, &return_not_equal); + + // Internalized strings are unique, so they can only be equal if they are the + // same object. We have already tested that case, so if left and right are + // both internalized strings, they cannot be equal. + STATIC_ASSERT((kInternalizedTag == 0) && (kStringTag == 0)); + __ Orr(scratch, left_type, right_type); + __ TestAndBranchIfAllClear( + scratch, kIsNotStringMask | kIsNotInternalizedMask, &return_not_equal); +} + + +// See call site for description. +static void EmitSmiNonsmiComparison(MacroAssembler* masm, + Register left, + Register right, + FPRegister left_d, + FPRegister right_d, + Register scratch, + Label* slow, + bool strict) { + ASSERT(!AreAliased(left, right, scratch)); + ASSERT(!AreAliased(left_d, right_d)); + ASSERT((left.is(x0) && right.is(x1)) || + (right.is(x0) && left.is(x1))); + Register result = x0; + + Label right_is_smi, done; + __ JumpIfSmi(right, &right_is_smi); + + // Left is the smi. Check whether right is a heap number. + if (strict) { + // If right is not a number and left is a smi, then strict equality cannot + // succeed. Return non-equal. + Label is_heap_number; + __ JumpIfObjectType(right, scratch, scratch, HEAP_NUMBER_TYPE, + &is_heap_number); + // Register right is a non-zero pointer, which is a valid NOT_EQUAL result. + if (!right.is(result)) { + __ Mov(result, NOT_EQUAL); + } + __ Ret(); + __ Bind(&is_heap_number); + } else { + // Smi compared non-strictly with a non-smi, non-heap-number. Call the + // runtime. + __ JumpIfNotObjectType(right, scratch, scratch, HEAP_NUMBER_TYPE, slow); + } + + // Left is the smi. Right is a heap number. Load right value into right_d, and + // convert left smi into double in left_d. + __ Ldr(right_d, FieldMemOperand(right, HeapNumber::kValueOffset)); + __ SmiUntagToDouble(left_d, left); + __ B(&done); + + __ Bind(&right_is_smi); + // Right is a smi. Check whether the non-smi left is a heap number. + if (strict) { + // If left is not a number and right is a smi then strict equality cannot + // succeed. Return non-equal. + Label is_heap_number; + __ JumpIfObjectType(left, scratch, scratch, HEAP_NUMBER_TYPE, + &is_heap_number); + // Register left is a non-zero pointer, which is a valid NOT_EQUAL result. + if (!left.is(result)) { + __ Mov(result, NOT_EQUAL); + } + __ Ret(); + __ Bind(&is_heap_number); + } else { + // Smi compared non-strictly with a non-smi, non-heap-number. Call the + // runtime. + __ JumpIfNotObjectType(left, scratch, scratch, HEAP_NUMBER_TYPE, slow); + } + + // Right is the smi. Left is a heap number. Load left value into left_d, and + // convert right smi into double in right_d. + __ Ldr(left_d, FieldMemOperand(left, HeapNumber::kValueOffset)); + __ SmiUntagToDouble(right_d, right); + + // Fall through to both_loaded_as_doubles. + __ Bind(&done); +} + + +// Fast negative check for internalized-to-internalized equality. +// See call site for description. +static void EmitCheckForInternalizedStringsOrObjects(MacroAssembler* masm, + Register left, + Register right, + Register left_map, + Register right_map, + Register left_type, + Register right_type, + Label* possible_strings, + Label* not_both_strings) { + ASSERT(!AreAliased(left, right, left_map, right_map, left_type, right_type)); + Register result = x0; + + Label object_test; + STATIC_ASSERT((kInternalizedTag == 0) && (kStringTag == 0)); + // TODO(all): reexamine this branch sequence for optimisation wrt branch + // prediction. + __ Tbnz(right_type, MaskToBit(kIsNotStringMask), &object_test); + __ Tbnz(right_type, MaskToBit(kIsNotInternalizedMask), possible_strings); + __ Tbnz(left_type, MaskToBit(kIsNotStringMask), not_both_strings); + __ Tbnz(left_type, MaskToBit(kIsNotInternalizedMask), possible_strings); + + // Both are internalized. We already checked that they weren't the same + // pointer, so they are not equal. + __ Mov(result, NOT_EQUAL); + __ Ret(); + + __ Bind(&object_test); + + __ Cmp(right_type, FIRST_SPEC_OBJECT_TYPE); + + // If right >= FIRST_SPEC_OBJECT_TYPE, test left. + // Otherwise, right < FIRST_SPEC_OBJECT_TYPE, so set lt condition. + __ Ccmp(left_type, FIRST_SPEC_OBJECT_TYPE, NFlag, ge); + + __ B(lt, not_both_strings); + + // If both objects are undetectable, they are equal. Otherwise, they are not + // equal, since they are different objects and an object is not equal to + // undefined. + + // Returning here, so we can corrupt right_type and left_type. + Register right_bitfield = right_type; + Register left_bitfield = left_type; + __ Ldrb(right_bitfield, FieldMemOperand(right_map, Map::kBitFieldOffset)); + __ Ldrb(left_bitfield, FieldMemOperand(left_map, Map::kBitFieldOffset)); + __ And(result, right_bitfield, left_bitfield); + __ And(result, result, 1 << Map::kIsUndetectable); + __ Eor(result, result, 1 << Map::kIsUndetectable); + __ Ret(); +} + + +static void ICCompareStub_CheckInputType(MacroAssembler* masm, + Register input, + Register scratch, + CompareIC::State expected, + Label* fail) { + Label ok; + if (expected == CompareIC::SMI) { + __ JumpIfNotSmi(input, fail); + } else if (expected == CompareIC::NUMBER) { + __ JumpIfSmi(input, &ok); + __ CheckMap(input, scratch, Heap::kHeapNumberMapRootIndex, fail, + DONT_DO_SMI_CHECK); + } + // We could be strict about internalized/non-internalized here, but as long as + // hydrogen doesn't care, the stub doesn't have to care either. + __ Bind(&ok); +} + + +void ICCompareStub::GenerateGeneric(MacroAssembler* masm) { + Register lhs = x1; + Register rhs = x0; + Register result = x0; + Condition cond = GetCondition(); + + Label miss; + ICCompareStub_CheckInputType(masm, lhs, x2, left_, &miss); + ICCompareStub_CheckInputType(masm, rhs, x3, right_, &miss); + + Label slow; // Call builtin. + Label not_smis, both_loaded_as_doubles; + Label not_two_smis, smi_done; + __ JumpIfEitherNotSmi(lhs, rhs, ¬_two_smis); + __ SmiUntag(lhs); + __ Sub(result, lhs, Operand::UntagSmi(rhs)); + __ Ret(); + + __ Bind(¬_two_smis); + + // NOTICE! This code is only reached after a smi-fast-case check, so it is + // certain that at least one operand isn't a smi. + + // Handle the case where the objects are identical. Either returns the answer + // or goes to slow. Only falls through if the objects were not identical. + EmitIdenticalObjectComparison(masm, lhs, rhs, x10, d0, &slow, cond); + + // If either is a smi (we know that at least one is not a smi), then they can + // only be strictly equal if the other is a HeapNumber. + __ JumpIfBothNotSmi(lhs, rhs, ¬_smis); + + // Exactly one operand is a smi. EmitSmiNonsmiComparison generates code that + // can: + // 1) Return the answer. + // 2) Branch to the slow case. + // 3) Fall through to both_loaded_as_doubles. + // In case 3, we have found out that we were dealing with a number-number + // comparison. The double values of the numbers have been loaded, right into + // rhs_d, left into lhs_d. + FPRegister rhs_d = d0; + FPRegister lhs_d = d1; + EmitSmiNonsmiComparison(masm, lhs, rhs, lhs_d, rhs_d, x10, &slow, strict()); + + __ Bind(&both_loaded_as_doubles); + // The arguments have been converted to doubles and stored in rhs_d and + // lhs_d. + Label nan; + __ Fcmp(lhs_d, rhs_d); + __ B(vs, &nan); // Overflow flag set if either is NaN. + STATIC_ASSERT((LESS == -1) && (EQUAL == 0) && (GREATER == 1)); + __ Cset(result, gt); // gt => 1, otherwise (lt, eq) => 0 (EQUAL). + __ Csinv(result, result, xzr, ge); // lt => -1, gt => 1, eq => 0. + __ Ret(); + + __ Bind(&nan); + // Left and/or right is a NaN. Load the result register with whatever makes + // the comparison fail, since comparisons with NaN always fail (except ne, + // which is filtered out at a higher level.) + ASSERT(cond != ne); + if ((cond == lt) || (cond == le)) { + __ Mov(result, GREATER); + } else { + __ Mov(result, LESS); + } + __ Ret(); + + __ Bind(¬_smis); + // At this point we know we are dealing with two different objects, and + // neither of them is a smi. The objects are in rhs_ and lhs_. + + // Load the maps and types of the objects. + Register rhs_map = x10; + Register rhs_type = x11; + Register lhs_map = x12; + Register lhs_type = x13; + __ Ldr(rhs_map, FieldMemOperand(rhs, HeapObject::kMapOffset)); + __ Ldr(lhs_map, FieldMemOperand(lhs, HeapObject::kMapOffset)); + __ Ldrb(rhs_type, FieldMemOperand(rhs_map, Map::kInstanceTypeOffset)); + __ Ldrb(lhs_type, FieldMemOperand(lhs_map, Map::kInstanceTypeOffset)); + + if (strict()) { + // This emits a non-equal return sequence for some object types, or falls + // through if it was not lucky. + EmitStrictTwoHeapObjectCompare(masm, lhs, rhs, lhs_type, rhs_type, x14); + } + + Label check_for_internalized_strings; + Label flat_string_check; + // Check for heap number comparison. Branch to earlier double comparison code + // if they are heap numbers, otherwise, branch to internalized string check. + __ Cmp(rhs_type, HEAP_NUMBER_TYPE); + __ B(ne, &check_for_internalized_strings); + __ Cmp(lhs_map, rhs_map); + + // If maps aren't equal, lhs_ and rhs_ are not heap numbers. Branch to flat + // string check. + __ B(ne, &flat_string_check); + + // Both lhs_ and rhs_ are heap numbers. Load them and branch to the double + // comparison code. + __ Ldr(lhs_d, FieldMemOperand(lhs, HeapNumber::kValueOffset)); + __ Ldr(rhs_d, FieldMemOperand(rhs, HeapNumber::kValueOffset)); + __ B(&both_loaded_as_doubles); + + __ Bind(&check_for_internalized_strings); + // In the strict case, the EmitStrictTwoHeapObjectCompare already took care + // of internalized strings. + if ((cond == eq) && !strict()) { + // Returns an answer for two internalized strings or two detectable objects. + // Otherwise branches to the string case or not both strings case. + EmitCheckForInternalizedStringsOrObjects(masm, lhs, rhs, lhs_map, rhs_map, + lhs_type, rhs_type, + &flat_string_check, &slow); + } + + // Check for both being sequential ASCII strings, and inline if that is the + // case. + __ Bind(&flat_string_check); + __ JumpIfBothInstanceTypesAreNotSequentialAscii(lhs_type, rhs_type, x14, + x15, &slow); + + __ IncrementCounter(isolate()->counters()->string_compare_native(), 1, x10, + x11); + if (cond == eq) { + StringCompareStub::GenerateFlatAsciiStringEquals(masm, lhs, rhs, + x10, x11, x12); + } else { + StringCompareStub::GenerateCompareFlatAsciiStrings(masm, lhs, rhs, + x10, x11, x12, x13); + } + + // Never fall through to here. + if (FLAG_debug_code) { + __ Unreachable(); + } + + __ Bind(&slow); + + __ Push(lhs, rhs); + // Figure out which native to call and setup the arguments. + Builtins::JavaScript native; + if (cond == eq) { + native = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS; + } else { + native = Builtins::COMPARE; + int ncr; // NaN compare result + if ((cond == lt) || (cond == le)) { + ncr = GREATER; + } else { + ASSERT((cond == gt) || (cond == ge)); // remaining cases + ncr = LESS; + } + __ Mov(x10, Smi::FromInt(ncr)); + __ Push(x10); + } + + // Call the native; it returns -1 (less), 0 (equal), or 1 (greater) + // tagged as a small integer. + __ InvokeBuiltin(native, JUMP_FUNCTION); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void StoreBufferOverflowStub::Generate(MacroAssembler* masm) { + CPURegList saved_regs = kCallerSaved; + CPURegList saved_fp_regs = kCallerSavedFP; + + // We don't allow a GC during a store buffer overflow so there is no need to + // store the registers in any particular way, but we do have to store and + // restore them. + + // We don't care if MacroAssembler scratch registers are corrupted. + saved_regs.Remove(*(masm->TmpList())); + saved_fp_regs.Remove(*(masm->FPTmpList())); + + __ PushCPURegList(saved_regs); + if (save_doubles_ == kSaveFPRegs) { + __ PushCPURegList(saved_fp_regs); + } + + AllowExternalCallThatCantCauseGC scope(masm); + __ Mov(x0, ExternalReference::isolate_address(isolate())); + __ CallCFunction( + ExternalReference::store_buffer_overflow_function(isolate()), 1, 0); + + if (save_doubles_ == kSaveFPRegs) { + __ PopCPURegList(saved_fp_regs); + } + __ PopCPURegList(saved_regs); + __ Ret(); +} + + +void StoreBufferOverflowStub::GenerateFixedRegStubsAheadOfTime( + Isolate* isolate) { + StoreBufferOverflowStub stub1(isolate, kDontSaveFPRegs); + stub1.GetCode(); + StoreBufferOverflowStub stub2(isolate, kSaveFPRegs); + stub2.GetCode(); +} + + +void StoreRegistersStateStub::Generate(MacroAssembler* masm) { + MacroAssembler::NoUseRealAbortsScope no_use_real_aborts(masm); + UseScratchRegisterScope temps(masm); + Register saved_lr = temps.UnsafeAcquire(to_be_pushed_lr()); + Register return_address = temps.AcquireX(); + __ Mov(return_address, lr); + // Restore lr with the value it had before the call to this stub (the value + // which must be pushed). + __ Mov(lr, saved_lr); + if (save_doubles_ == kSaveFPRegs) { + __ PushSafepointRegistersAndDoubles(); + } else { + __ PushSafepointRegisters(); + } + __ Ret(return_address); +} + + +void RestoreRegistersStateStub::Generate(MacroAssembler* masm) { + MacroAssembler::NoUseRealAbortsScope no_use_real_aborts(masm); + UseScratchRegisterScope temps(masm); + Register return_address = temps.AcquireX(); + // Preserve the return address (lr will be clobbered by the pop). + __ Mov(return_address, lr); + if (save_doubles_ == kSaveFPRegs) { + __ PopSafepointRegistersAndDoubles(); + } else { + __ PopSafepointRegisters(); + } + __ Ret(return_address); +} + + +void MathPowStub::Generate(MacroAssembler* masm) { + // Stack on entry: + // jssp[0]: Exponent (as a tagged value). + // jssp[1]: Base (as a tagged value). + // + // The (tagged) result will be returned in x0, as a heap number. + + Register result_tagged = x0; + Register base_tagged = x10; + Register exponent_tagged = x11; + Register exponent_integer = x12; + Register scratch1 = x14; + Register scratch0 = x15; + Register saved_lr = x19; + FPRegister result_double = d0; + FPRegister base_double = d0; + FPRegister exponent_double = d1; + FPRegister base_double_copy = d2; + FPRegister scratch1_double = d6; + FPRegister scratch0_double = d7; + + // A fast-path for integer exponents. + Label exponent_is_smi, exponent_is_integer; + // Bail out to runtime. + Label call_runtime; + // Allocate a heap number for the result, and return it. + Label done; + + // Unpack the inputs. + if (exponent_type_ == ON_STACK) { + Label base_is_smi; + Label unpack_exponent; + + __ Pop(exponent_tagged, base_tagged); + + __ JumpIfSmi(base_tagged, &base_is_smi); + __ JumpIfNotHeapNumber(base_tagged, &call_runtime); + // base_tagged is a heap number, so load its double value. + __ Ldr(base_double, FieldMemOperand(base_tagged, HeapNumber::kValueOffset)); + __ B(&unpack_exponent); + __ Bind(&base_is_smi); + // base_tagged is a SMI, so untag it and convert it to a double. + __ SmiUntagToDouble(base_double, base_tagged); + + __ Bind(&unpack_exponent); + // x10 base_tagged The tagged base (input). + // x11 exponent_tagged The tagged exponent (input). + // d1 base_double The base as a double. + __ JumpIfSmi(exponent_tagged, &exponent_is_smi); + __ JumpIfNotHeapNumber(exponent_tagged, &call_runtime); + // exponent_tagged is a heap number, so load its double value. + __ Ldr(exponent_double, + FieldMemOperand(exponent_tagged, HeapNumber::kValueOffset)); + } else if (exponent_type_ == TAGGED) { + __ JumpIfSmi(exponent_tagged, &exponent_is_smi); + __ Ldr(exponent_double, + FieldMemOperand(exponent_tagged, HeapNumber::kValueOffset)); + } + + // Handle double (heap number) exponents. + if (exponent_type_ != INTEGER) { + // Detect integer exponents stored as doubles and handle those in the + // integer fast-path. + __ TryRepresentDoubleAsInt64(exponent_integer, exponent_double, + scratch0_double, &exponent_is_integer); + + if (exponent_type_ == ON_STACK) { + FPRegister half_double = d3; + FPRegister minus_half_double = d4; + // Detect square root case. Crankshaft detects constant +/-0.5 at compile + // time and uses DoMathPowHalf instead. We then skip this check for + // non-constant cases of +/-0.5 as these hardly occur. + + __ Fmov(minus_half_double, -0.5); + __ Fmov(half_double, 0.5); + __ Fcmp(minus_half_double, exponent_double); + __ Fccmp(half_double, exponent_double, NZFlag, ne); + // Condition flags at this point: + // 0.5; nZCv // Identified by eq && pl + // -0.5: NZcv // Identified by eq && mi + // other: ?z?? // Identified by ne + __ B(ne, &call_runtime); + + // The exponent is 0.5 or -0.5. + + // Given that exponent is known to be either 0.5 or -0.5, the following + // special cases could apply (according to ECMA-262 15.8.2.13): + // + // base.isNaN(): The result is NaN. + // (base == +INFINITY) || (base == -INFINITY) + // exponent == 0.5: The result is +INFINITY. + // exponent == -0.5: The result is +0. + // (base == +0) || (base == -0) + // exponent == 0.5: The result is +0. + // exponent == -0.5: The result is +INFINITY. + // (base < 0) && base.isFinite(): The result is NaN. + // + // Fsqrt (and Fdiv for the -0.5 case) can handle all of those except + // where base is -INFINITY or -0. + + // Add +0 to base. This has no effect other than turning -0 into +0. + __ Fadd(base_double, base_double, fp_zero); + // The operation -0+0 results in +0 in all cases except where the + // FPCR rounding mode is 'round towards minus infinity' (RM). The + // ARM64 simulator does not currently simulate FPCR (where the rounding + // mode is set), so test the operation with some debug code. + if (masm->emit_debug_code()) { + UseScratchRegisterScope temps(masm); + Register temp = temps.AcquireX(); + __ Fneg(scratch0_double, fp_zero); + // Verify that we correctly generated +0.0 and -0.0. + // bits(+0.0) = 0x0000000000000000 + // bits(-0.0) = 0x8000000000000000 + __ Fmov(temp, fp_zero); + __ CheckRegisterIsClear(temp, kCouldNotGenerateZero); + __ Fmov(temp, scratch0_double); + __ Eor(temp, temp, kDSignMask); + __ CheckRegisterIsClear(temp, kCouldNotGenerateNegativeZero); + // Check that -0.0 + 0.0 == +0.0. + __ Fadd(scratch0_double, scratch0_double, fp_zero); + __ Fmov(temp, scratch0_double); + __ CheckRegisterIsClear(temp, kExpectedPositiveZero); + } + + // If base is -INFINITY, make it +INFINITY. + // * Calculate base - base: All infinities will become NaNs since both + // -INFINITY+INFINITY and +INFINITY-INFINITY are NaN in ARM64. + // * If the result is NaN, calculate abs(base). + __ Fsub(scratch0_double, base_double, base_double); + __ Fcmp(scratch0_double, 0.0); + __ Fabs(scratch1_double, base_double); + __ Fcsel(base_double, scratch1_double, base_double, vs); + + // Calculate the square root of base. + __ Fsqrt(result_double, base_double); + __ Fcmp(exponent_double, 0.0); + __ B(ge, &done); // Finish now for exponents of 0.5. + // Find the inverse for exponents of -0.5. + __ Fmov(scratch0_double, 1.0); + __ Fdiv(result_double, scratch0_double, result_double); + __ B(&done); + } + + { + AllowExternalCallThatCantCauseGC scope(masm); + __ Mov(saved_lr, lr); + __ CallCFunction( + ExternalReference::power_double_double_function(isolate()), + 0, 2); + __ Mov(lr, saved_lr); + __ B(&done); + } + + // Handle SMI exponents. + __ Bind(&exponent_is_smi); + // x10 base_tagged The tagged base (input). + // x11 exponent_tagged The tagged exponent (input). + // d1 base_double The base as a double. + __ SmiUntag(exponent_integer, exponent_tagged); + } + + __ Bind(&exponent_is_integer); + // x10 base_tagged The tagged base (input). + // x11 exponent_tagged The tagged exponent (input). + // x12 exponent_integer The exponent as an integer. + // d1 base_double The base as a double. + + // Find abs(exponent). For negative exponents, we can find the inverse later. + Register exponent_abs = x13; + __ Cmp(exponent_integer, 0); + __ Cneg(exponent_abs, exponent_integer, mi); + // x13 exponent_abs The value of abs(exponent_integer). + + // Repeatedly multiply to calculate the power. + // result = 1.0; + // For each bit n (exponent_integer{n}) { + // if (exponent_integer{n}) { + // result *= base; + // } + // base *= base; + // if (remaining bits in exponent_integer are all zero) { + // break; + // } + // } + Label power_loop, power_loop_entry, power_loop_exit; + __ Fmov(scratch1_double, base_double); + __ Fmov(base_double_copy, base_double); + __ Fmov(result_double, 1.0); + __ B(&power_loop_entry); + + __ Bind(&power_loop); + __ Fmul(scratch1_double, scratch1_double, scratch1_double); + __ Lsr(exponent_abs, exponent_abs, 1); + __ Cbz(exponent_abs, &power_loop_exit); + + __ Bind(&power_loop_entry); + __ Tbz(exponent_abs, 0, &power_loop); + __ Fmul(result_double, result_double, scratch1_double); + __ B(&power_loop); + + __ Bind(&power_loop_exit); + + // If the exponent was positive, result_double holds the result. + __ Tbz(exponent_integer, kXSignBit, &done); + + // The exponent was negative, so find the inverse. + __ Fmov(scratch0_double, 1.0); + __ Fdiv(result_double, scratch0_double, result_double); + // ECMA-262 only requires Math.pow to return an 'implementation-dependent + // approximation' of base^exponent. However, mjsunit/math-pow uses Math.pow + // to calculate the subnormal value 2^-1074. This method of calculating + // negative powers doesn't work because 2^1074 overflows to infinity. To + // catch this corner-case, we bail out if the result was 0. (This can only + // occur if the divisor is infinity or the base is zero.) + __ Fcmp(result_double, 0.0); + __ B(&done, ne); + + if (exponent_type_ == ON_STACK) { + // Bail out to runtime code. + __ Bind(&call_runtime); + // Put the arguments back on the stack. + __ Push(base_tagged, exponent_tagged); + __ TailCallRuntime(Runtime::kHiddenMathPow, 2, 1); + + // Return. + __ Bind(&done); + __ AllocateHeapNumber(result_tagged, &call_runtime, scratch0, scratch1, + result_double); + ASSERT(result_tagged.is(x0)); + __ IncrementCounter( + isolate()->counters()->math_pow(), 1, scratch0, scratch1); + __ Ret(); + } else { + AllowExternalCallThatCantCauseGC scope(masm); + __ Mov(saved_lr, lr); + __ Fmov(base_double, base_double_copy); + __ Scvtf(exponent_double, exponent_integer); + __ CallCFunction( + ExternalReference::power_double_double_function(isolate()), + 0, 2); + __ Mov(lr, saved_lr); + __ Bind(&done); + __ IncrementCounter( + isolate()->counters()->math_pow(), 1, scratch0, scratch1); + __ Ret(); + } +} + + +void CodeStub::GenerateStubsAheadOfTime(Isolate* isolate) { + // It is important that the following stubs are generated in this order + // because pregenerated stubs can only call other pregenerated stubs. + // RecordWriteStub uses StoreBufferOverflowStub, which in turn uses + // CEntryStub. + CEntryStub::GenerateAheadOfTime(isolate); + StoreBufferOverflowStub::GenerateFixedRegStubsAheadOfTime(isolate); + StubFailureTrampolineStub::GenerateAheadOfTime(isolate); + ArrayConstructorStubBase::GenerateStubsAheadOfTime(isolate); + CreateAllocationSiteStub::GenerateAheadOfTime(isolate); + BinaryOpICStub::GenerateAheadOfTime(isolate); + StoreRegistersStateStub::GenerateAheadOfTime(isolate); + RestoreRegistersStateStub::GenerateAheadOfTime(isolate); + BinaryOpICWithAllocationSiteStub::GenerateAheadOfTime(isolate); +} + + +void StoreRegistersStateStub::GenerateAheadOfTime(Isolate* isolate) { + StoreRegistersStateStub stub1(isolate, kDontSaveFPRegs); + stub1.GetCode(); + StoreRegistersStateStub stub2(isolate, kSaveFPRegs); + stub2.GetCode(); +} + + +void RestoreRegistersStateStub::GenerateAheadOfTime(Isolate* isolate) { + RestoreRegistersStateStub stub1(isolate, kDontSaveFPRegs); + stub1.GetCode(); + RestoreRegistersStateStub stub2(isolate, kSaveFPRegs); + stub2.GetCode(); +} + + +void CodeStub::GenerateFPStubs(Isolate* isolate) { + // Floating-point code doesn't get special handling in ARM64, so there's + // nothing to do here. + USE(isolate); +} + + +bool CEntryStub::NeedsImmovableCode() { + // CEntryStub stores the return address on the stack before calling into + // C++ code. In some cases, the VM accesses this address, but it is not used + // when the C++ code returns to the stub because LR holds the return address + // in AAPCS64. If the stub is moved (perhaps during a GC), we could end up + // returning to dead code. + // TODO(jbramley): Whilst this is the only analysis that makes sense, I can't + // find any comment to confirm this, and I don't hit any crashes whatever + // this function returns. The anaylsis should be properly confirmed. + return true; +} + + +void CEntryStub::GenerateAheadOfTime(Isolate* isolate) { + CEntryStub stub(isolate, 1, kDontSaveFPRegs); + stub.GetCode(); + CEntryStub stub_fp(isolate, 1, kSaveFPRegs); + stub_fp.GetCode(); +} + + +void CEntryStub::Generate(MacroAssembler* masm) { + // The Abort mechanism relies on CallRuntime, which in turn relies on + // CEntryStub, so until this stub has been generated, we have to use a + // fall-back Abort mechanism. + // + // Note that this stub must be generated before any use of Abort. + MacroAssembler::NoUseRealAbortsScope no_use_real_aborts(masm); + + ASM_LOCATION("CEntryStub::Generate entry"); + ProfileEntryHookStub::MaybeCallEntryHook(masm); + + // Register parameters: + // x0: argc (including receiver, untagged) + // x1: target + // + // The stack on entry holds the arguments and the receiver, with the receiver + // at the highest address: + // + // jssp]argc-1]: receiver + // jssp[argc-2]: arg[argc-2] + // ... ... + // jssp[1]: arg[1] + // jssp[0]: arg[0] + // + // The arguments are in reverse order, so that arg[argc-2] is actually the + // first argument to the target function and arg[0] is the last. + ASSERT(jssp.Is(__ StackPointer())); + const Register& argc_input = x0; + const Register& target_input = x1; + + // Calculate argv, argc and the target address, and store them in + // callee-saved registers so we can retry the call without having to reload + // these arguments. + // TODO(jbramley): If the first call attempt succeeds in the common case (as + // it should), then we might be better off putting these parameters directly + // into their argument registers, rather than using callee-saved registers and + // preserving them on the stack. + const Register& argv = x21; + const Register& argc = x22; + const Register& target = x23; + + // Derive argv from the stack pointer so that it points to the first argument + // (arg[argc-2]), or just below the receiver in case there are no arguments. + // - Adjust for the arg[] array. + Register temp_argv = x11; + __ Add(temp_argv, jssp, Operand(x0, LSL, kPointerSizeLog2)); + // - Adjust for the receiver. + __ Sub(temp_argv, temp_argv, 1 * kPointerSize); + + // Enter the exit frame. Reserve three slots to preserve x21-x23 callee-saved + // registers. + FrameScope scope(masm, StackFrame::MANUAL); + __ EnterExitFrame(save_doubles_, x10, 3); + ASSERT(csp.Is(__ StackPointer())); + + // Poke callee-saved registers into reserved space. + __ Poke(argv, 1 * kPointerSize); + __ Poke(argc, 2 * kPointerSize); + __ Poke(target, 3 * kPointerSize); + + // We normally only keep tagged values in callee-saved registers, as they + // could be pushed onto the stack by called stubs and functions, and on the + // stack they can confuse the GC. However, we're only calling C functions + // which can push arbitrary data onto the stack anyway, and so the GC won't + // examine that part of the stack. + __ Mov(argc, argc_input); + __ Mov(target, target_input); + __ Mov(argv, temp_argv); + + // x21 : argv + // x22 : argc + // x23 : call target + // + // The stack (on entry) holds the arguments and the receiver, with the + // receiver at the highest address: + // + // argv[8]: receiver + // argv -> argv[0]: arg[argc-2] + // ... ... + // argv[...]: arg[1] + // argv[...]: arg[0] + // + // Immediately below (after) this is the exit frame, as constructed by + // EnterExitFrame: + // fp[8]: CallerPC (lr) + // fp -> fp[0]: CallerFP (old fp) + // fp[-8]: Space reserved for SPOffset. + // fp[-16]: CodeObject() + // csp[...]: Saved doubles, if saved_doubles is true. + // csp[32]: Alignment padding, if necessary. + // csp[24]: Preserved x23 (used for target). + // csp[16]: Preserved x22 (used for argc). + // csp[8]: Preserved x21 (used for argv). + // csp -> csp[0]: Space reserved for the return address. + // + // After a successful call, the exit frame, preserved registers (x21-x23) and + // the arguments (including the receiver) are dropped or popped as + // appropriate. The stub then returns. + // + // After an unsuccessful call, the exit frame and suchlike are left + // untouched, and the stub either throws an exception by jumping to one of + // the exception_returned label. + + ASSERT(csp.Is(__ StackPointer())); + + // Prepare AAPCS64 arguments to pass to the builtin. + __ Mov(x0, argc); + __ Mov(x1, argv); + __ Mov(x2, ExternalReference::isolate_address(isolate())); + + Label return_location; + __ Adr(x12, &return_location); + __ Poke(x12, 0); + + if (__ emit_debug_code()) { + // Verify that the slot below fp[kSPOffset]-8 points to the return location + // (currently in x12). + UseScratchRegisterScope temps(masm); + Register temp = temps.AcquireX(); + __ Ldr(temp, MemOperand(fp, ExitFrameConstants::kSPOffset)); + __ Ldr(temp, MemOperand(temp, -static_cast<int64_t>(kXRegSize))); + __ Cmp(temp, x12); + __ Check(eq, kReturnAddressNotFoundInFrame); + } + + // Call the builtin. + __ Blr(target); + __ Bind(&return_location); + + // x0 result The return code from the call. + // x21 argv + // x22 argc + // x23 target + const Register& result = x0; + + // Check result for exception sentinel. + Label exception_returned; + __ CompareRoot(result, Heap::kExceptionRootIndex); + __ B(eq, &exception_returned); + + // The call succeeded, so unwind the stack and return. + + // Restore callee-saved registers x21-x23. + __ Mov(x11, argc); + + __ Peek(argv, 1 * kPointerSize); + __ Peek(argc, 2 * kPointerSize); + __ Peek(target, 3 * kPointerSize); + + __ LeaveExitFrame(save_doubles_, x10, true); + ASSERT(jssp.Is(__ StackPointer())); + // Pop or drop the remaining stack slots and return from the stub. + // jssp[24]: Arguments array (of size argc), including receiver. + // jssp[16]: Preserved x23 (used for target). + // jssp[8]: Preserved x22 (used for argc). + // jssp[0]: Preserved x21 (used for argv). + __ Drop(x11); + __ AssertFPCRState(); + __ Ret(); + + // The stack pointer is still csp if we aren't returning, and the frame + // hasn't changed (except for the return address). + __ SetStackPointer(csp); + + // Handling of exception. + __ Bind(&exception_returned); + + // Retrieve the pending exception. + ExternalReference pending_exception_address( + Isolate::kPendingExceptionAddress, isolate()); + const Register& exception = result; + const Register& exception_address = x11; + __ Mov(exception_address, Operand(pending_exception_address)); + __ Ldr(exception, MemOperand(exception_address)); + + // Clear the pending exception. + __ Mov(x10, Operand(isolate()->factory()->the_hole_value())); + __ Str(x10, MemOperand(exception_address)); + + // x0 exception The exception descriptor. + // x21 argv + // x22 argc + // x23 target + + // Special handling of termination exceptions, which are uncatchable by + // JavaScript code. + Label throw_termination_exception; + __ Cmp(exception, Operand(isolate()->factory()->termination_exception())); + __ B(eq, &throw_termination_exception); + + // We didn't execute a return case, so the stack frame hasn't been updated + // (except for the return address slot). However, we don't need to initialize + // jssp because the throw method will immediately overwrite it when it + // unwinds the stack. + __ SetStackPointer(jssp); + + ASM_LOCATION("Throw normal"); + __ Mov(argv, 0); + __ Mov(argc, 0); + __ Mov(target, 0); + __ Throw(x0, x10, x11, x12, x13); + + __ Bind(&throw_termination_exception); + ASM_LOCATION("Throw termination"); + __ Mov(argv, 0); + __ Mov(argc, 0); + __ Mov(target, 0); + __ ThrowUncatchable(x0, x10, x11, x12, x13); +} + + +// This is the entry point from C++. 5 arguments are provided in x0-x4. +// See use of the CALL_GENERATED_CODE macro for example in src/execution.cc. +// Input: +// x0: code entry. +// x1: function. +// x2: receiver. +// x3: argc. +// x4: argv. +// Output: +// x0: result. +void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { + ASSERT(jssp.Is(__ StackPointer())); + Register code_entry = x0; + + // Enable instruction instrumentation. This only works on the simulator, and + // will have no effect on the model or real hardware. + __ EnableInstrumentation(); + + Label invoke, handler_entry, exit; + + // Push callee-saved registers and synchronize the system stack pointer (csp) + // and the JavaScript stack pointer (jssp). + // + // We must not write to jssp until after the PushCalleeSavedRegisters() + // call, since jssp is itself a callee-saved register. + __ SetStackPointer(csp); + __ PushCalleeSavedRegisters(); + __ Mov(jssp, csp); + __ SetStackPointer(jssp); + + // Configure the FPCR. We don't restore it, so this is technically not allowed + // according to AAPCS64. However, we only set default-NaN mode and this will + // be harmless for most C code. Also, it works for ARM. + __ ConfigureFPCR(); + + ProfileEntryHookStub::MaybeCallEntryHook(masm); + + // Set up the reserved register for 0.0. + __ Fmov(fp_zero, 0.0); + + // Build an entry frame (see layout below). + int marker = is_construct ? StackFrame::ENTRY_CONSTRUCT : StackFrame::ENTRY; + int64_t bad_frame_pointer = -1L; // Bad frame pointer to fail if it is used. + __ Mov(x13, bad_frame_pointer); + __ Mov(x12, Smi::FromInt(marker)); + __ Mov(x11, ExternalReference(Isolate::kCEntryFPAddress, isolate())); + __ Ldr(x10, MemOperand(x11)); + + __ Push(x13, xzr, x12, x10); + // Set up fp. + __ Sub(fp, jssp, EntryFrameConstants::kCallerFPOffset); + + // Push the JS entry frame marker. Also set js_entry_sp if this is the + // outermost JS call. + Label non_outermost_js, done; + ExternalReference js_entry_sp(Isolate::kJSEntrySPAddress, isolate()); + __ Mov(x10, ExternalReference(js_entry_sp)); + __ Ldr(x11, MemOperand(x10)); + __ Cbnz(x11, &non_outermost_js); + __ Str(fp, MemOperand(x10)); + __ Mov(x12, Smi::FromInt(StackFrame::OUTERMOST_JSENTRY_FRAME)); + __ Push(x12); + __ B(&done); + __ Bind(&non_outermost_js); + // We spare one instruction by pushing xzr since the marker is 0. + ASSERT(Smi::FromInt(StackFrame::INNER_JSENTRY_FRAME) == NULL); + __ Push(xzr); + __ Bind(&done); + + // The frame set up looks like this: + // jssp[0] : JS entry frame marker. + // jssp[1] : C entry FP. + // jssp[2] : stack frame marker. + // jssp[3] : stack frmae marker. + // jssp[4] : bad frame pointer 0xfff...ff <- fp points here. + + + // Jump to a faked try block that does the invoke, with a faked catch + // block that sets the pending exception. + __ B(&invoke); + + // Prevent the constant pool from being emitted between the record of the + // handler_entry position and the first instruction of the sequence here. + // There is no risk because Assembler::Emit() emits the instruction before + // checking for constant pool emission, but we do not want to depend on + // that. + { + Assembler::BlockPoolsScope block_pools(masm); + __ bind(&handler_entry); + handler_offset_ = handler_entry.pos(); + // Caught exception: Store result (exception) in the pending exception + // field in the JSEnv and return a failure sentinel. Coming in here the + // fp will be invalid because the PushTryHandler below sets it to 0 to + // signal the existence of the JSEntry frame. + __ Mov(x10, Operand(ExternalReference(Isolate::kPendingExceptionAddress, + isolate()))); + } + __ Str(code_entry, MemOperand(x10)); + __ LoadRoot(x0, Heap::kExceptionRootIndex); + __ B(&exit); + + // Invoke: Link this frame into the handler chain. There's only one + // handler block in this code object, so its index is 0. + __ Bind(&invoke); + __ PushTryHandler(StackHandler::JS_ENTRY, 0); + // If an exception not caught by another handler occurs, this handler + // returns control to the code after the B(&invoke) above, which + // restores all callee-saved registers (including cp and fp) to their + // saved values before returning a failure to C. + + // Clear any pending exceptions. + __ Mov(x10, Operand(isolate()->factory()->the_hole_value())); + __ Mov(x11, Operand(ExternalReference(Isolate::kPendingExceptionAddress, + isolate()))); + __ Str(x10, MemOperand(x11)); + + // Invoke the function by calling through the JS entry trampoline builtin. + // Notice that we cannot store a reference to the trampoline code directly in + // this stub, because runtime stubs are not traversed when doing GC. + + // Expected registers by Builtins::JSEntryTrampoline + // x0: code entry. + // x1: function. + // x2: receiver. + // x3: argc. + // x4: argv. + ExternalReference entry(is_construct ? Builtins::kJSConstructEntryTrampoline + : Builtins::kJSEntryTrampoline, + isolate()); + __ Mov(x10, entry); + + // Call the JSEntryTrampoline. + __ Ldr(x11, MemOperand(x10)); // Dereference the address. + __ Add(x12, x11, Code::kHeaderSize - kHeapObjectTag); + __ Blr(x12); + + // Unlink this frame from the handler chain. + __ PopTryHandler(); + + + __ Bind(&exit); + // x0 holds the result. + // The stack pointer points to the top of the entry frame pushed on entry from + // C++ (at the beginning of this stub): + // jssp[0] : JS entry frame marker. + // jssp[1] : C entry FP. + // jssp[2] : stack frame marker. + // jssp[3] : stack frmae marker. + // jssp[4] : bad frame pointer 0xfff...ff <- fp points here. + + // Check if the current stack frame is marked as the outermost JS frame. + Label non_outermost_js_2; + __ Pop(x10); + __ Cmp(x10, Smi::FromInt(StackFrame::OUTERMOST_JSENTRY_FRAME)); + __ B(ne, &non_outermost_js_2); + __ Mov(x11, ExternalReference(js_entry_sp)); + __ Str(xzr, MemOperand(x11)); + __ Bind(&non_outermost_js_2); + + // Restore the top frame descriptors from the stack. + __ Pop(x10); + __ Mov(x11, ExternalReference(Isolate::kCEntryFPAddress, isolate())); + __ Str(x10, MemOperand(x11)); + + // Reset the stack to the callee saved registers. + __ Drop(-EntryFrameConstants::kCallerFPOffset, kByteSizeInBytes); + // Restore the callee-saved registers and return. + ASSERT(jssp.Is(__ StackPointer())); + __ Mov(csp, jssp); + __ SetStackPointer(csp); + __ PopCalleeSavedRegisters(); + // After this point, we must not modify jssp because it is a callee-saved + // register which we have just restored. + __ Ret(); +} + + +void FunctionPrototypeStub::Generate(MacroAssembler* masm) { + Label miss; + Register receiver; + if (kind() == Code::KEYED_LOAD_IC) { + // ----------- S t a t e ------------- + // -- lr : return address + // -- x1 : receiver + // -- x0 : key + // ----------------------------------- + Register key = x0; + receiver = x1; + __ Cmp(key, Operand(isolate()->factory()->prototype_string())); + __ B(ne, &miss); + } else { + ASSERT(kind() == Code::LOAD_IC); + // ----------- S t a t e ------------- + // -- lr : return address + // -- x2 : name + // -- x0 : receiver + // -- sp[0] : receiver + // ----------------------------------- + receiver = x0; + } + + StubCompiler::GenerateLoadFunctionPrototype(masm, receiver, x10, x11, &miss); + + __ Bind(&miss); + StubCompiler::TailCallBuiltin(masm, + BaseLoadStoreStubCompiler::MissBuiltin(kind())); +} + + +void InstanceofStub::Generate(MacroAssembler* masm) { + // Stack on entry: + // jssp[0]: function. + // jssp[8]: object. + // + // Returns result in x0. Zero indicates instanceof, smi 1 indicates not + // instanceof. + + Register result = x0; + Register function = right(); + Register object = left(); + Register scratch1 = x6; + Register scratch2 = x7; + Register res_true = x8; + Register res_false = x9; + // Only used if there was an inline map check site. (See + // LCodeGen::DoInstanceOfKnownGlobal().) + Register map_check_site = x4; + // Delta for the instructions generated between the inline map check and the + // instruction setting the result. + const int32_t kDeltaToLoadBoolResult = 4 * kInstructionSize; + + Label not_js_object, slow; + + if (!HasArgsInRegisters()) { + __ Pop(function, object); + } + + if (ReturnTrueFalseObject()) { + __ LoadTrueFalseRoots(res_true, res_false); + } else { + // This is counter-intuitive, but correct. + __ Mov(res_true, Smi::FromInt(0)); + __ Mov(res_false, Smi::FromInt(1)); + } + + // Check that the left hand side is a JS object and load its map as a side + // effect. + Register map = x12; + __ JumpIfSmi(object, ¬_js_object); + __ IsObjectJSObjectType(object, map, scratch2, ¬_js_object); + + // If there is a call site cache, don't look in the global cache, but do the + // real lookup and update the call site cache. + if (!HasCallSiteInlineCheck()) { + Label miss; + __ JumpIfNotRoot(function, Heap::kInstanceofCacheFunctionRootIndex, &miss); + __ JumpIfNotRoot(map, Heap::kInstanceofCacheMapRootIndex, &miss); + __ LoadRoot(result, Heap::kInstanceofCacheAnswerRootIndex); + __ Ret(); + __ Bind(&miss); + } + + // Get the prototype of the function. + Register prototype = x13; + __ TryGetFunctionPrototype(function, prototype, scratch2, &slow, + MacroAssembler::kMissOnBoundFunction); + + // Check that the function prototype is a JS object. + __ JumpIfSmi(prototype, &slow); + __ IsObjectJSObjectType(prototype, scratch1, scratch2, &slow); + + // Update the global instanceof or call site inlined cache with the current + // map and function. The cached answer will be set when it is known below. + if (HasCallSiteInlineCheck()) { + // Patch the (relocated) inlined map check. + __ GetRelocatedValueLocation(map_check_site, scratch1); + // We have a cell, so need another level of dereferencing. + __ Ldr(scratch1, MemOperand(scratch1)); + __ Str(map, FieldMemOperand(scratch1, Cell::kValueOffset)); + } else { + __ StoreRoot(function, Heap::kInstanceofCacheFunctionRootIndex); + __ StoreRoot(map, Heap::kInstanceofCacheMapRootIndex); + } + + Label return_true, return_result; + { + // Loop through the prototype chain looking for the function prototype. + Register chain_map = x1; + Register chain_prototype = x14; + Register null_value = x15; + Label loop; + __ Ldr(chain_prototype, FieldMemOperand(map, Map::kPrototypeOffset)); + __ LoadRoot(null_value, Heap::kNullValueRootIndex); + // Speculatively set a result. + __ Mov(result, res_false); + + __ Bind(&loop); + + // If the chain prototype is the object prototype, return true. + __ Cmp(chain_prototype, prototype); + __ B(eq, &return_true); + + // If the chain prototype is null, we've reached the end of the chain, so + // return false. + __ Cmp(chain_prototype, null_value); + __ B(eq, &return_result); + + // Otherwise, load the next prototype in the chain, and loop. + __ Ldr(chain_map, FieldMemOperand(chain_prototype, HeapObject::kMapOffset)); + __ Ldr(chain_prototype, FieldMemOperand(chain_map, Map::kPrototypeOffset)); + __ B(&loop); + } + + // Return sequence when no arguments are on the stack. + // We cannot fall through to here. + __ Bind(&return_true); + __ Mov(result, res_true); + __ Bind(&return_result); + if (HasCallSiteInlineCheck()) { + ASSERT(ReturnTrueFalseObject()); + __ Add(map_check_site, map_check_site, kDeltaToLoadBoolResult); + __ GetRelocatedValueLocation(map_check_site, scratch2); + __ Str(result, MemOperand(scratch2)); + } else { + __ StoreRoot(result, Heap::kInstanceofCacheAnswerRootIndex); + } + __ Ret(); + + Label object_not_null, object_not_null_or_smi; + + __ Bind(¬_js_object); + Register object_type = x14; + // x0 result result return register (uninit) + // x10 function pointer to function + // x11 object pointer to object + // x14 object_type type of object (uninit) + + // Before null, smi and string checks, check that the rhs is a function. + // For a non-function rhs, an exception must be thrown. + __ JumpIfSmi(function, &slow); + __ JumpIfNotObjectType( + function, scratch1, object_type, JS_FUNCTION_TYPE, &slow); + + __ Mov(result, res_false); + + // Null is not instance of anything. + __ Cmp(object_type, Operand(isolate()->factory()->null_value())); + __ B(ne, &object_not_null); + __ Ret(); + + __ Bind(&object_not_null); + // Smi values are not instances of anything. + __ JumpIfNotSmi(object, &object_not_null_or_smi); + __ Ret(); + + __ Bind(&object_not_null_or_smi); + // String values are not instances of anything. + __ IsObjectJSStringType(object, scratch2, &slow); + __ Ret(); + + // Slow-case. Tail call builtin. + __ Bind(&slow); + { + FrameScope scope(masm, StackFrame::INTERNAL); + // Arguments have either been passed into registers or have been previously + // popped. We need to push them before calling builtin. + __ Push(object, function); + __ InvokeBuiltin(Builtins::INSTANCE_OF, CALL_FUNCTION); + } + if (ReturnTrueFalseObject()) { + // Reload true/false because they were clobbered in the builtin call. + __ LoadTrueFalseRoots(res_true, res_false); + __ Cmp(result, 0); + __ Csel(result, res_true, res_false, eq); + } + __ Ret(); +} + + +Register InstanceofStub::left() { + // Object to check (instanceof lhs). + return x11; +} + + +Register InstanceofStub::right() { + // Constructor function (instanceof rhs). + return x10; +} + + +void ArgumentsAccessStub::GenerateReadElement(MacroAssembler* masm) { + Register arg_count = x0; + Register key = x1; + + // The displacement is the offset of the last parameter (if any) relative + // to the frame pointer. + static const int kDisplacement = + StandardFrameConstants::kCallerSPOffset - kPointerSize; + + // Check that the key is a smi. + Label slow; + __ JumpIfNotSmi(key, &slow); + + // Check if the calling frame is an arguments adaptor frame. + Register local_fp = x11; + Register caller_fp = x11; + Register caller_ctx = x12; + Label skip_adaptor; + __ Ldr(caller_fp, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + __ Ldr(caller_ctx, MemOperand(caller_fp, + StandardFrameConstants::kContextOffset)); + __ Cmp(caller_ctx, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ Csel(local_fp, fp, caller_fp, ne); + __ B(ne, &skip_adaptor); + + // Load the actual arguments limit found in the arguments adaptor frame. + __ Ldr(arg_count, MemOperand(caller_fp, + ArgumentsAdaptorFrameConstants::kLengthOffset)); + __ Bind(&skip_adaptor); + + // Check index against formal parameters count limit. Use unsigned comparison + // to get negative check for free: branch if key < 0 or key >= arg_count. + __ Cmp(key, arg_count); + __ B(hs, &slow); + + // Read the argument from the stack and return it. + __ Sub(x10, arg_count, key); + __ Add(x10, local_fp, Operand::UntagSmiAndScale(x10, kPointerSizeLog2)); + __ Ldr(x0, MemOperand(x10, kDisplacement)); + __ Ret(); + + // Slow case: handle non-smi or out-of-bounds access to arguments by calling + // the runtime system. + __ Bind(&slow); + __ Push(key); + __ TailCallRuntime(Runtime::kGetArgumentsProperty, 1, 1); +} + + +void ArgumentsAccessStub::GenerateNewSloppySlow(MacroAssembler* masm) { + // Stack layout on entry. + // jssp[0]: number of parameters (tagged) + // jssp[8]: address of receiver argument + // jssp[16]: function + + // Check if the calling frame is an arguments adaptor frame. + Label runtime; + Register caller_fp = x10; + __ Ldr(caller_fp, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + // Load and untag the context. + STATIC_ASSERT((kSmiShift / kBitsPerByte) == 4); + __ Ldr(w11, MemOperand(caller_fp, StandardFrameConstants::kContextOffset + + (kSmiShift / kBitsPerByte))); + __ Cmp(w11, StackFrame::ARGUMENTS_ADAPTOR); + __ B(ne, &runtime); + + // Patch the arguments.length and parameters pointer in the current frame. + __ Ldr(x11, MemOperand(caller_fp, + ArgumentsAdaptorFrameConstants::kLengthOffset)); + __ Poke(x11, 0 * kXRegSize); + __ Add(x10, caller_fp, Operand::UntagSmiAndScale(x11, kPointerSizeLog2)); + __ Add(x10, x10, StandardFrameConstants::kCallerSPOffset); + __ Poke(x10, 1 * kXRegSize); + + __ Bind(&runtime); + __ TailCallRuntime(Runtime::kHiddenNewSloppyArguments, 3, 1); +} + + +void ArgumentsAccessStub::GenerateNewSloppyFast(MacroAssembler* masm) { + // Stack layout on entry. + // jssp[0]: number of parameters (tagged) + // jssp[8]: address of receiver argument + // jssp[16]: function + // + // Returns pointer to result object in x0. + + // Note: arg_count_smi is an alias of param_count_smi. + Register arg_count_smi = x3; + Register param_count_smi = x3; + Register param_count = x7; + Register recv_arg = x14; + Register function = x4; + __ Pop(param_count_smi, recv_arg, function); + __ SmiUntag(param_count, param_count_smi); + + // Check if the calling frame is an arguments adaptor frame. + Register caller_fp = x11; + Register caller_ctx = x12; + Label runtime; + Label adaptor_frame, try_allocate; + __ Ldr(caller_fp, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + __ Ldr(caller_ctx, MemOperand(caller_fp, + StandardFrameConstants::kContextOffset)); + __ Cmp(caller_ctx, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ B(eq, &adaptor_frame); + + // No adaptor, parameter count = argument count. + + // x1 mapped_params number of mapped params, min(params, args) (uninit) + // x2 arg_count number of function arguments (uninit) + // x3 arg_count_smi number of function arguments (smi) + // x4 function function pointer + // x7 param_count number of function parameters + // x11 caller_fp caller's frame pointer + // x14 recv_arg pointer to receiver arguments + + Register arg_count = x2; + __ Mov(arg_count, param_count); + __ B(&try_allocate); + + // We have an adaptor frame. Patch the parameters pointer. + __ Bind(&adaptor_frame); + __ Ldr(arg_count_smi, + MemOperand(caller_fp, + ArgumentsAdaptorFrameConstants::kLengthOffset)); + __ SmiUntag(arg_count, arg_count_smi); + __ Add(x10, caller_fp, Operand(arg_count, LSL, kPointerSizeLog2)); + __ Add(recv_arg, x10, StandardFrameConstants::kCallerSPOffset); + + // Compute the mapped parameter count = min(param_count, arg_count) + Register mapped_params = x1; + __ Cmp(param_count, arg_count); + __ Csel(mapped_params, param_count, arg_count, lt); + + __ Bind(&try_allocate); + + // x0 alloc_obj pointer to allocated objects: param map, backing + // store, arguments (uninit) + // x1 mapped_params number of mapped parameters, min(params, args) + // x2 arg_count number of function arguments + // x3 arg_count_smi number of function arguments (smi) + // x4 function function pointer + // x7 param_count number of function parameters + // x10 size size of objects to allocate (uninit) + // x14 recv_arg pointer to receiver arguments + + // Compute the size of backing store, parameter map, and arguments object. + // 1. Parameter map, has two extra words containing context and backing + // store. + const int kParameterMapHeaderSize = + FixedArray::kHeaderSize + 2 * kPointerSize; + + // Calculate the parameter map size, assuming it exists. + Register size = x10; + __ Mov(size, Operand(mapped_params, LSL, kPointerSizeLog2)); + __ Add(size, size, kParameterMapHeaderSize); + + // If there are no mapped parameters, set the running size total to zero. + // Otherwise, use the parameter map size calculated earlier. + __ Cmp(mapped_params, 0); + __ CzeroX(size, eq); + + // 2. Add the size of the backing store and arguments object. + __ Add(size, size, Operand(arg_count, LSL, kPointerSizeLog2)); + __ Add(size, size, + FixedArray::kHeaderSize + Heap::kSloppyArgumentsObjectSize); + + // Do the allocation of all three objects in one go. Assign this to x0, as it + // will be returned to the caller. + Register alloc_obj = x0; + __ Allocate(size, alloc_obj, x11, x12, &runtime, TAG_OBJECT); + + // Get the arguments boilerplate from the current (global) context. + + // x0 alloc_obj pointer to allocated objects (param map, backing + // store, arguments) + // x1 mapped_params number of mapped parameters, min(params, args) + // x2 arg_count number of function arguments + // x3 arg_count_smi number of function arguments (smi) + // x4 function function pointer + // x7 param_count number of function parameters + // x11 args_offset offset to args (or aliased args) boilerplate (uninit) + // x14 recv_arg pointer to receiver arguments + + Register global_object = x10; + Register global_ctx = x10; + Register args_offset = x11; + Register aliased_args_offset = x10; + __ Ldr(global_object, GlobalObjectMemOperand()); + __ Ldr(global_ctx, FieldMemOperand(global_object, + GlobalObject::kNativeContextOffset)); + + __ Ldr(args_offset, + ContextMemOperand(global_ctx, + Context::SLOPPY_ARGUMENTS_BOILERPLATE_INDEX)); + __ Ldr(aliased_args_offset, + ContextMemOperand(global_ctx, + Context::ALIASED_ARGUMENTS_BOILERPLATE_INDEX)); + __ Cmp(mapped_params, 0); + __ CmovX(args_offset, aliased_args_offset, ne); + + // Copy the JS object part. + __ CopyFields(alloc_obj, args_offset, CPURegList(x10, x12, x13), + JSObject::kHeaderSize / kPointerSize); + + // Set up the callee in-object property. + STATIC_ASSERT(Heap::kArgumentsCalleeIndex == 1); + const int kCalleeOffset = JSObject::kHeaderSize + + Heap::kArgumentsCalleeIndex * kPointerSize; + __ Str(function, FieldMemOperand(alloc_obj, kCalleeOffset)); + + // Use the length and set that as an in-object property. + STATIC_ASSERT(Heap::kArgumentsLengthIndex == 0); + const int kLengthOffset = JSObject::kHeaderSize + + Heap::kArgumentsLengthIndex * kPointerSize; + __ Str(arg_count_smi, FieldMemOperand(alloc_obj, kLengthOffset)); + + // Set up the elements pointer in the allocated arguments object. + // If we allocated a parameter map, "elements" will point there, otherwise + // it will point to the backing store. + + // x0 alloc_obj pointer to allocated objects (param map, backing + // store, arguments) + // x1 mapped_params number of mapped parameters, min(params, args) + // x2 arg_count number of function arguments + // x3 arg_count_smi number of function arguments (smi) + // x4 function function pointer + // x5 elements pointer to parameter map or backing store (uninit) + // x6 backing_store pointer to backing store (uninit) + // x7 param_count number of function parameters + // x14 recv_arg pointer to receiver arguments + + Register elements = x5; + __ Add(elements, alloc_obj, Heap::kSloppyArgumentsObjectSize); + __ Str(elements, FieldMemOperand(alloc_obj, JSObject::kElementsOffset)); + + // Initialize parameter map. If there are no mapped arguments, we're done. + Label skip_parameter_map; + __ Cmp(mapped_params, 0); + // Set up backing store address, because it is needed later for filling in + // the unmapped arguments. + Register backing_store = x6; + __ CmovX(backing_store, elements, eq); + __ B(eq, &skip_parameter_map); + + __ LoadRoot(x10, Heap::kSloppyArgumentsElementsMapRootIndex); + __ Str(x10, FieldMemOperand(elements, FixedArray::kMapOffset)); + __ Add(x10, mapped_params, 2); + __ SmiTag(x10); + __ Str(x10, FieldMemOperand(elements, FixedArray::kLengthOffset)); + __ Str(cp, FieldMemOperand(elements, + FixedArray::kHeaderSize + 0 * kPointerSize)); + __ Add(x10, elements, Operand(mapped_params, LSL, kPointerSizeLog2)); + __ Add(x10, x10, kParameterMapHeaderSize); + __ Str(x10, FieldMemOperand(elements, + FixedArray::kHeaderSize + 1 * kPointerSize)); + + // Copy the parameter slots and the holes in the arguments. + // We need to fill in mapped_parameter_count slots. Then index the context, + // where parameters are stored in reverse order, at: + // + // MIN_CONTEXT_SLOTS .. MIN_CONTEXT_SLOTS + parameter_count - 1 + // + // The mapped parameter thus needs to get indices: + // + // MIN_CONTEXT_SLOTS + parameter_count - 1 .. + // MIN_CONTEXT_SLOTS + parameter_count - mapped_parameter_count + // + // We loop from right to left. + + // x0 alloc_obj pointer to allocated objects (param map, backing + // store, arguments) + // x1 mapped_params number of mapped parameters, min(params, args) + // x2 arg_count number of function arguments + // x3 arg_count_smi number of function arguments (smi) + // x4 function function pointer + // x5 elements pointer to parameter map or backing store (uninit) + // x6 backing_store pointer to backing store (uninit) + // x7 param_count number of function parameters + // x11 loop_count parameter loop counter (uninit) + // x12 index parameter index (smi, uninit) + // x13 the_hole hole value (uninit) + // x14 recv_arg pointer to receiver arguments + + Register loop_count = x11; + Register index = x12; + Register the_hole = x13; + Label parameters_loop, parameters_test; + __ Mov(loop_count, mapped_params); + __ Add(index, param_count, static_cast<int>(Context::MIN_CONTEXT_SLOTS)); + __ Sub(index, index, mapped_params); + __ SmiTag(index); + __ LoadRoot(the_hole, Heap::kTheHoleValueRootIndex); + __ Add(backing_store, elements, Operand(loop_count, LSL, kPointerSizeLog2)); + __ Add(backing_store, backing_store, kParameterMapHeaderSize); + + __ B(¶meters_test); + + __ Bind(¶meters_loop); + __ Sub(loop_count, loop_count, 1); + __ Mov(x10, Operand(loop_count, LSL, kPointerSizeLog2)); + __ Add(x10, x10, kParameterMapHeaderSize - kHeapObjectTag); + __ Str(index, MemOperand(elements, x10)); + __ Sub(x10, x10, kParameterMapHeaderSize - FixedArray::kHeaderSize); + __ Str(the_hole, MemOperand(backing_store, x10)); + __ Add(index, index, Smi::FromInt(1)); + __ Bind(¶meters_test); + __ Cbnz(loop_count, ¶meters_loop); + + __ Bind(&skip_parameter_map); + // Copy arguments header and remaining slots (if there are any.) + __ LoadRoot(x10, Heap::kFixedArrayMapRootIndex); + __ Str(x10, FieldMemOperand(backing_store, FixedArray::kMapOffset)); + __ Str(arg_count_smi, FieldMemOperand(backing_store, + FixedArray::kLengthOffset)); + + // x0 alloc_obj pointer to allocated objects (param map, backing + // store, arguments) + // x1 mapped_params number of mapped parameters, min(params, args) + // x2 arg_count number of function arguments + // x4 function function pointer + // x3 arg_count_smi number of function arguments (smi) + // x6 backing_store pointer to backing store (uninit) + // x14 recv_arg pointer to receiver arguments + + Label arguments_loop, arguments_test; + __ Mov(x10, mapped_params); + __ Sub(recv_arg, recv_arg, Operand(x10, LSL, kPointerSizeLog2)); + __ B(&arguments_test); + + __ Bind(&arguments_loop); + __ Sub(recv_arg, recv_arg, kPointerSize); + __ Ldr(x11, MemOperand(recv_arg)); + __ Add(x12, backing_store, Operand(x10, LSL, kPointerSizeLog2)); + __ Str(x11, FieldMemOperand(x12, FixedArray::kHeaderSize)); + __ Add(x10, x10, 1); + + __ Bind(&arguments_test); + __ Cmp(x10, arg_count); + __ B(lt, &arguments_loop); + + __ Ret(); + + // Do the runtime call to allocate the arguments object. + __ Bind(&runtime); + __ Push(function, recv_arg, arg_count_smi); + __ TailCallRuntime(Runtime::kHiddenNewSloppyArguments, 3, 1); +} + + +void ArgumentsAccessStub::GenerateNewStrict(MacroAssembler* masm) { + // Stack layout on entry. + // jssp[0]: number of parameters (tagged) + // jssp[8]: address of receiver argument + // jssp[16]: function + // + // Returns pointer to result object in x0. + + // Get the stub arguments from the frame, and make an untagged copy of the + // parameter count. + Register param_count_smi = x1; + Register params = x2; + Register function = x3; + Register param_count = x13; + __ Pop(param_count_smi, params, function); + __ SmiUntag(param_count, param_count_smi); + + // Test if arguments adaptor needed. + Register caller_fp = x11; + Register caller_ctx = x12; + Label try_allocate, runtime; + __ Ldr(caller_fp, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + __ Ldr(caller_ctx, MemOperand(caller_fp, + StandardFrameConstants::kContextOffset)); + __ Cmp(caller_ctx, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ B(ne, &try_allocate); + + // x1 param_count_smi number of parameters passed to function (smi) + // x2 params pointer to parameters + // x3 function function pointer + // x11 caller_fp caller's frame pointer + // x13 param_count number of parameters passed to function + + // Patch the argument length and parameters pointer. + __ Ldr(param_count_smi, + MemOperand(caller_fp, + ArgumentsAdaptorFrameConstants::kLengthOffset)); + __ SmiUntag(param_count, param_count_smi); + __ Add(x10, caller_fp, Operand(param_count, LSL, kPointerSizeLog2)); + __ Add(params, x10, StandardFrameConstants::kCallerSPOffset); + + // Try the new space allocation. Start out with computing the size of the + // arguments object and the elements array in words. + Register size = x10; + __ Bind(&try_allocate); + __ Add(size, param_count, FixedArray::kHeaderSize / kPointerSize); + __ Cmp(param_count, 0); + __ CzeroX(size, eq); + __ Add(size, size, Heap::kStrictArgumentsObjectSize / kPointerSize); + + // Do the allocation of both objects in one go. Assign this to x0, as it will + // be returned to the caller. + Register alloc_obj = x0; + __ Allocate(size, alloc_obj, x11, x12, &runtime, + static_cast<AllocationFlags>(TAG_OBJECT | SIZE_IN_WORDS)); + + // Get the arguments boilerplate from the current (native) context. + Register global_object = x10; + Register global_ctx = x10; + Register args_offset = x4; + __ Ldr(global_object, GlobalObjectMemOperand()); + __ Ldr(global_ctx, FieldMemOperand(global_object, + GlobalObject::kNativeContextOffset)); + __ Ldr(args_offset, + ContextMemOperand(global_ctx, + Context::STRICT_ARGUMENTS_BOILERPLATE_INDEX)); + + // x0 alloc_obj pointer to allocated objects: parameter array and + // arguments object + // x1 param_count_smi number of parameters passed to function (smi) + // x2 params pointer to parameters + // x3 function function pointer + // x4 args_offset offset to arguments boilerplate + // x13 param_count number of parameters passed to function + + // Copy the JS object part. + __ CopyFields(alloc_obj, args_offset, CPURegList(x5, x6, x7), + JSObject::kHeaderSize / kPointerSize); + + // Set the smi-tagged length as an in-object property. + STATIC_ASSERT(Heap::kArgumentsLengthIndex == 0); + const int kLengthOffset = JSObject::kHeaderSize + + Heap::kArgumentsLengthIndex * kPointerSize; + __ Str(param_count_smi, FieldMemOperand(alloc_obj, kLengthOffset)); + + // If there are no actual arguments, we're done. + Label done; + __ Cbz(param_count, &done); + + // Set up the elements pointer in the allocated arguments object and + // initialize the header in the elements fixed array. + Register elements = x5; + __ Add(elements, alloc_obj, Heap::kStrictArgumentsObjectSize); + __ Str(elements, FieldMemOperand(alloc_obj, JSObject::kElementsOffset)); + __ LoadRoot(x10, Heap::kFixedArrayMapRootIndex); + __ Str(x10, FieldMemOperand(elements, FixedArray::kMapOffset)); + __ Str(param_count_smi, FieldMemOperand(elements, FixedArray::kLengthOffset)); + + // x0 alloc_obj pointer to allocated objects: parameter array and + // arguments object + // x1 param_count_smi number of parameters passed to function (smi) + // x2 params pointer to parameters + // x3 function function pointer + // x4 array pointer to array slot (uninit) + // x5 elements pointer to elements array of alloc_obj + // x13 param_count number of parameters passed to function + + // Copy the fixed array slots. + Label loop; + Register array = x4; + // Set up pointer to first array slot. + __ Add(array, elements, FixedArray::kHeaderSize - kHeapObjectTag); + + __ Bind(&loop); + // Pre-decrement the parameters pointer by kPointerSize on each iteration. + // Pre-decrement in order to skip receiver. + __ Ldr(x10, MemOperand(params, -kPointerSize, PreIndex)); + // Post-increment elements by kPointerSize on each iteration. + __ Str(x10, MemOperand(array, kPointerSize, PostIndex)); + __ Sub(param_count, param_count, 1); + __ Cbnz(param_count, &loop); + + // Return from stub. + __ Bind(&done); + __ Ret(); + + // Do the runtime call to allocate the arguments object. + __ Bind(&runtime); + __ Push(function, params, param_count_smi); + __ TailCallRuntime(Runtime::kHiddenNewStrictArguments, 3, 1); +} + + +void RegExpExecStub::Generate(MacroAssembler* masm) { +#ifdef V8_INTERPRETED_REGEXP + __ TailCallRuntime(Runtime::kHiddenRegExpExec, 4, 1); +#else // V8_INTERPRETED_REGEXP + + // Stack frame on entry. + // jssp[0]: last_match_info (expected JSArray) + // jssp[8]: previous index + // jssp[16]: subject string + // jssp[24]: JSRegExp object + Label runtime; + + // Use of registers for this function. + + // Variable registers: + // x10-x13 used as scratch registers + // w0 string_type type of subject string + // x2 jsstring_length subject string length + // x3 jsregexp_object JSRegExp object + // w4 string_encoding ASCII or UC16 + // w5 sliced_string_offset if the string is a SlicedString + // offset to the underlying string + // w6 string_representation groups attributes of the string: + // - is a string + // - type of the string + // - is a short external string + Register string_type = w0; + Register jsstring_length = x2; + Register jsregexp_object = x3; + Register string_encoding = w4; + Register sliced_string_offset = w5; + Register string_representation = w6; + + // These are in callee save registers and will be preserved by the call + // to the native RegExp code, as this code is called using the normal + // C calling convention. When calling directly from generated code the + // native RegExp code will not do a GC and therefore the content of + // these registers are safe to use after the call. + + // x19 subject subject string + // x20 regexp_data RegExp data (FixedArray) + // x21 last_match_info_elements info relative to the last match + // (FixedArray) + // x22 code_object generated regexp code + Register subject = x19; + Register regexp_data = x20; + Register last_match_info_elements = x21; + Register code_object = x22; + + // TODO(jbramley): Is it necessary to preserve these? I don't think ARM does. + CPURegList used_callee_saved_registers(subject, + regexp_data, + last_match_info_elements, + code_object); + __ PushCPURegList(used_callee_saved_registers); + + // Stack frame. + // jssp[0] : x19 + // jssp[8] : x20 + // jssp[16]: x21 + // jssp[24]: x22 + // jssp[32]: last_match_info (JSArray) + // jssp[40]: previous index + // jssp[48]: subject string + // jssp[56]: JSRegExp object + + const int kLastMatchInfoOffset = 4 * kPointerSize; + const int kPreviousIndexOffset = 5 * kPointerSize; + const int kSubjectOffset = 6 * kPointerSize; + const int kJSRegExpOffset = 7 * kPointerSize; + + // Ensure that a RegExp stack is allocated. + ExternalReference address_of_regexp_stack_memory_address = + ExternalReference::address_of_regexp_stack_memory_address(isolate()); + ExternalReference address_of_regexp_stack_memory_size = + ExternalReference::address_of_regexp_stack_memory_size(isolate()); + __ Mov(x10, address_of_regexp_stack_memory_size); + __ Ldr(x10, MemOperand(x10)); + __ Cbz(x10, &runtime); + + // Check that the first argument is a JSRegExp object. + ASSERT(jssp.Is(__ StackPointer())); + __ Peek(jsregexp_object, kJSRegExpOffset); + __ JumpIfSmi(jsregexp_object, &runtime); + __ JumpIfNotObjectType(jsregexp_object, x10, x10, JS_REGEXP_TYPE, &runtime); + + // Check that the RegExp has been compiled (data contains a fixed array). + __ Ldr(regexp_data, FieldMemOperand(jsregexp_object, JSRegExp::kDataOffset)); + if (FLAG_debug_code) { + STATIC_ASSERT(kSmiTag == 0); + __ Tst(regexp_data, kSmiTagMask); + __ Check(ne, kUnexpectedTypeForRegExpDataFixedArrayExpected); + __ CompareObjectType(regexp_data, x10, x10, FIXED_ARRAY_TYPE); + __ Check(eq, kUnexpectedTypeForRegExpDataFixedArrayExpected); + } + + // Check the type of the RegExp. Only continue if type is JSRegExp::IRREGEXP. + __ Ldr(x10, FieldMemOperand(regexp_data, JSRegExp::kDataTagOffset)); + __ Cmp(x10, Smi::FromInt(JSRegExp::IRREGEXP)); + __ B(ne, &runtime); + + // Check that the number of captures fit in the static offsets vector buffer. + // We have always at least one capture for the whole match, plus additional + // ones due to capturing parentheses. A capture takes 2 registers. + // The number of capture registers then is (number_of_captures + 1) * 2. + __ Ldrsw(x10, + UntagSmiFieldMemOperand(regexp_data, + JSRegExp::kIrregexpCaptureCountOffset)); + // Check (number_of_captures + 1) * 2 <= offsets vector size + // number_of_captures * 2 <= offsets vector size - 2 + STATIC_ASSERT(Isolate::kJSRegexpStaticOffsetsVectorSize >= 2); + __ Add(x10, x10, x10); + __ Cmp(x10, Isolate::kJSRegexpStaticOffsetsVectorSize - 2); + __ B(hi, &runtime); + + // Initialize offset for possibly sliced string. + __ Mov(sliced_string_offset, 0); + + ASSERT(jssp.Is(__ StackPointer())); + __ Peek(subject, kSubjectOffset); + __ JumpIfSmi(subject, &runtime); + + __ Ldr(x10, FieldMemOperand(subject, HeapObject::kMapOffset)); + __ Ldrb(string_type, FieldMemOperand(x10, Map::kInstanceTypeOffset)); + + __ Ldr(jsstring_length, FieldMemOperand(subject, String::kLengthOffset)); + + // Handle subject string according to its encoding and representation: + // (1) Sequential string? If yes, go to (5). + // (2) Anything but sequential or cons? If yes, go to (6). + // (3) Cons string. If the string is flat, replace subject with first string. + // Otherwise bailout. + // (4) Is subject external? If yes, go to (7). + // (5) Sequential string. Load regexp code according to encoding. + // (E) Carry on. + /// [...] + + // Deferred code at the end of the stub: + // (6) Not a long external string? If yes, go to (8). + // (7) External string. Make it, offset-wise, look like a sequential string. + // Go to (5). + // (8) Short external string or not a string? If yes, bail out to runtime. + // (9) Sliced string. Replace subject with parent. Go to (4). + + Label check_underlying; // (4) + Label seq_string; // (5) + Label not_seq_nor_cons; // (6) + Label external_string; // (7) + Label not_long_external; // (8) + + // (1) Sequential string? If yes, go to (5). + __ And(string_representation, + string_type, + kIsNotStringMask | + kStringRepresentationMask | + kShortExternalStringMask); + // We depend on the fact that Strings of type + // SeqString and not ShortExternalString are defined + // by the following pattern: + // string_type: 0XX0 XX00 + // ^ ^ ^^ + // | | || + // | | is a SeqString + // | is not a short external String + // is a String + STATIC_ASSERT((kStringTag | kSeqStringTag) == 0); + STATIC_ASSERT(kShortExternalStringTag != 0); + __ Cbz(string_representation, &seq_string); // Go to (5). + + // (2) Anything but sequential or cons? If yes, go to (6). + STATIC_ASSERT(kConsStringTag < kExternalStringTag); + STATIC_ASSERT(kSlicedStringTag > kExternalStringTag); + STATIC_ASSERT(kIsNotStringMask > kExternalStringTag); + STATIC_ASSERT(kShortExternalStringTag > kExternalStringTag); + __ Cmp(string_representation, kExternalStringTag); + __ B(ge, ¬_seq_nor_cons); // Go to (6). + + // (3) Cons string. Check that it's flat. + __ Ldr(x10, FieldMemOperand(subject, ConsString::kSecondOffset)); + __ JumpIfNotRoot(x10, Heap::kempty_stringRootIndex, &runtime); + // Replace subject with first string. + __ Ldr(subject, FieldMemOperand(subject, ConsString::kFirstOffset)); + + // (4) Is subject external? If yes, go to (7). + __ Bind(&check_underlying); + // Reload the string type. + __ Ldr(x10, FieldMemOperand(subject, HeapObject::kMapOffset)); + __ Ldrb(string_type, FieldMemOperand(x10, Map::kInstanceTypeOffset)); + STATIC_ASSERT(kSeqStringTag == 0); + // The underlying external string is never a short external string. + STATIC_ASSERT(ExternalString::kMaxShortLength < ConsString::kMinLength); + STATIC_ASSERT(ExternalString::kMaxShortLength < SlicedString::kMinLength); + __ TestAndBranchIfAnySet(string_type.X(), + kStringRepresentationMask, + &external_string); // Go to (7). + + // (5) Sequential string. Load regexp code according to encoding. + __ Bind(&seq_string); + + // Check that the third argument is a positive smi less than the subject + // string length. A negative value will be greater (unsigned comparison). + ASSERT(jssp.Is(__ StackPointer())); + __ Peek(x10, kPreviousIndexOffset); + __ JumpIfNotSmi(x10, &runtime); + __ Cmp(jsstring_length, x10); + __ B(ls, &runtime); + + // Argument 2 (x1): We need to load argument 2 (the previous index) into x1 + // before entering the exit frame. + __ SmiUntag(x1, x10); + + // The third bit determines the string encoding in string_type. + STATIC_ASSERT(kOneByteStringTag == 0x04); + STATIC_ASSERT(kTwoByteStringTag == 0x00); + STATIC_ASSERT(kStringEncodingMask == 0x04); + + // Find the code object based on the assumptions above. + // kDataAsciiCodeOffset and kDataUC16CodeOffset are adjacent, adds an offset + // of kPointerSize to reach the latter. + ASSERT_EQ(JSRegExp::kDataAsciiCodeOffset + kPointerSize, + JSRegExp::kDataUC16CodeOffset); + __ Mov(x10, kPointerSize); + // We will need the encoding later: ASCII = 0x04 + // UC16 = 0x00 + __ Ands(string_encoding, string_type, kStringEncodingMask); + __ CzeroX(x10, ne); + __ Add(x10, regexp_data, x10); + __ Ldr(code_object, FieldMemOperand(x10, JSRegExp::kDataAsciiCodeOffset)); + + // (E) Carry on. String handling is done. + + // Check that the irregexp code has been generated for the actual string + // encoding. If it has, the field contains a code object otherwise it contains + // a smi (code flushing support). + __ JumpIfSmi(code_object, &runtime); + + // All checks done. Now push arguments for native regexp code. + __ IncrementCounter(isolate()->counters()->regexp_entry_native(), 1, + x10, + x11); + + // Isolates: note we add an additional parameter here (isolate pointer). + __ EnterExitFrame(false, x10, 1); + ASSERT(csp.Is(__ StackPointer())); + + // We have 9 arguments to pass to the regexp code, therefore we have to pass + // one on the stack and the rest as registers. + + // Note that the placement of the argument on the stack isn't standard + // AAPCS64: + // csp[0]: Space for the return address placed by DirectCEntryStub. + // csp[8]: Argument 9, the current isolate address. + + __ Mov(x10, ExternalReference::isolate_address(isolate())); + __ Poke(x10, kPointerSize); + + Register length = w11; + Register previous_index_in_bytes = w12; + Register start = x13; + + // Load start of the subject string. + __ Add(start, subject, SeqString::kHeaderSize - kHeapObjectTag); + // Load the length from the original subject string from the previous stack + // frame. Therefore we have to use fp, which points exactly to two pointer + // sizes below the previous sp. (Because creating a new stack frame pushes + // the previous fp onto the stack and decrements sp by 2 * kPointerSize.) + __ Ldr(subject, MemOperand(fp, kSubjectOffset + 2 * kPointerSize)); + __ Ldr(length, UntagSmiFieldMemOperand(subject, String::kLengthOffset)); + + // Handle UC16 encoding, two bytes make one character. + // string_encoding: if ASCII: 0x04 + // if UC16: 0x00 + STATIC_ASSERT(kStringEncodingMask == 0x04); + __ Ubfx(string_encoding, string_encoding, 2, 1); + __ Eor(string_encoding, string_encoding, 1); + // string_encoding: if ASCII: 0 + // if UC16: 1 + + // Convert string positions from characters to bytes. + // Previous index is in x1. + __ Lsl(previous_index_in_bytes, w1, string_encoding); + __ Lsl(length, length, string_encoding); + __ Lsl(sliced_string_offset, sliced_string_offset, string_encoding); + + // Argument 1 (x0): Subject string. + __ Mov(x0, subject); + + // Argument 2 (x1): Previous index, already there. + + // Argument 3 (x2): Get the start of input. + // Start of input = start of string + previous index + substring offset + // (0 if the string + // is not sliced). + __ Add(w10, previous_index_in_bytes, sliced_string_offset); + __ Add(x2, start, Operand(w10, UXTW)); + + // Argument 4 (x3): + // End of input = start of input + (length of input - previous index) + __ Sub(w10, length, previous_index_in_bytes); + __ Add(x3, x2, Operand(w10, UXTW)); + + // Argument 5 (x4): static offsets vector buffer. + __ Mov(x4, ExternalReference::address_of_static_offsets_vector(isolate())); + + // Argument 6 (x5): Set the number of capture registers to zero to force + // global regexps to behave as non-global. This stub is not used for global + // regexps. + __ Mov(x5, 0); + + // Argument 7 (x6): Start (high end) of backtracking stack memory area. + __ Mov(x10, address_of_regexp_stack_memory_address); + __ Ldr(x10, MemOperand(x10)); + __ Mov(x11, address_of_regexp_stack_memory_size); + __ Ldr(x11, MemOperand(x11)); + __ Add(x6, x10, x11); + + // Argument 8 (x7): Indicate that this is a direct call from JavaScript. + __ Mov(x7, 1); + + // Locate the code entry and call it. + __ Add(code_object, code_object, Code::kHeaderSize - kHeapObjectTag); + DirectCEntryStub stub(isolate()); + stub.GenerateCall(masm, code_object); + + __ LeaveExitFrame(false, x10, true); + + // The generated regexp code returns an int32 in w0. + Label failure, exception; + __ CompareAndBranch(w0, NativeRegExpMacroAssembler::FAILURE, eq, &failure); + __ CompareAndBranch(w0, + NativeRegExpMacroAssembler::EXCEPTION, + eq, + &exception); + __ CompareAndBranch(w0, NativeRegExpMacroAssembler::RETRY, eq, &runtime); + + // Success: process the result from the native regexp code. + Register number_of_capture_registers = x12; + + // Calculate number of capture registers (number_of_captures + 1) * 2 + // and store it in the last match info. + __ Ldrsw(x10, + UntagSmiFieldMemOperand(regexp_data, + JSRegExp::kIrregexpCaptureCountOffset)); + __ Add(x10, x10, x10); + __ Add(number_of_capture_registers, x10, 2); + + // Check that the fourth object is a JSArray object. + ASSERT(jssp.Is(__ StackPointer())); + __ Peek(x10, kLastMatchInfoOffset); + __ JumpIfSmi(x10, &runtime); + __ JumpIfNotObjectType(x10, x11, x11, JS_ARRAY_TYPE, &runtime); + + // Check that the JSArray is the fast case. + __ Ldr(last_match_info_elements, + FieldMemOperand(x10, JSArray::kElementsOffset)); + __ Ldr(x10, + FieldMemOperand(last_match_info_elements, HeapObject::kMapOffset)); + __ JumpIfNotRoot(x10, Heap::kFixedArrayMapRootIndex, &runtime); + + // Check that the last match info has space for the capture registers and the + // additional information (overhead). + // (number_of_captures + 1) * 2 + overhead <= last match info size + // (number_of_captures * 2) + 2 + overhead <= last match info size + // number_of_capture_registers + overhead <= last match info size + __ Ldrsw(x10, + UntagSmiFieldMemOperand(last_match_info_elements, + FixedArray::kLengthOffset)); + __ Add(x11, number_of_capture_registers, RegExpImpl::kLastMatchOverhead); + __ Cmp(x11, x10); + __ B(gt, &runtime); + + // Store the capture count. + __ SmiTag(x10, number_of_capture_registers); + __ Str(x10, + FieldMemOperand(last_match_info_elements, + RegExpImpl::kLastCaptureCountOffset)); + // Store last subject and last input. + __ Str(subject, + FieldMemOperand(last_match_info_elements, + RegExpImpl::kLastSubjectOffset)); + // Use x10 as the subject string in order to only need + // one RecordWriteStub. + __ Mov(x10, subject); + __ RecordWriteField(last_match_info_elements, + RegExpImpl::kLastSubjectOffset, + x10, + x11, + kLRHasNotBeenSaved, + kDontSaveFPRegs); + __ Str(subject, + FieldMemOperand(last_match_info_elements, + RegExpImpl::kLastInputOffset)); + __ Mov(x10, subject); + __ RecordWriteField(last_match_info_elements, + RegExpImpl::kLastInputOffset, + x10, + x11, + kLRHasNotBeenSaved, + kDontSaveFPRegs); + + Register last_match_offsets = x13; + Register offsets_vector_index = x14; + Register current_offset = x15; + + // Get the static offsets vector filled by the native regexp code + // and fill the last match info. + ExternalReference address_of_static_offsets_vector = + ExternalReference::address_of_static_offsets_vector(isolate()); + __ Mov(offsets_vector_index, address_of_static_offsets_vector); + + Label next_capture, done; + // Capture register counter starts from number of capture registers and + // iterates down to zero (inclusive). + __ Add(last_match_offsets, + last_match_info_elements, + RegExpImpl::kFirstCaptureOffset - kHeapObjectTag); + __ Bind(&next_capture); + __ Subs(number_of_capture_registers, number_of_capture_registers, 2); + __ B(mi, &done); + // Read two 32 bit values from the static offsets vector buffer into + // an X register + __ Ldr(current_offset, + MemOperand(offsets_vector_index, kWRegSize * 2, PostIndex)); + // Store the smi values in the last match info. + __ SmiTag(x10, current_offset); + // Clearing the 32 bottom bits gives us a Smi. + STATIC_ASSERT(kSmiShift == 32); + __ And(x11, current_offset, ~kWRegMask); + __ Stp(x10, + x11, + MemOperand(last_match_offsets, kXRegSize * 2, PostIndex)); + __ B(&next_capture); + __ Bind(&done); + + // Return last match info. + __ Peek(x0, kLastMatchInfoOffset); + __ PopCPURegList(used_callee_saved_registers); + // Drop the 4 arguments of the stub from the stack. + __ Drop(4); + __ Ret(); + + __ Bind(&exception); + Register exception_value = x0; + // A stack overflow (on the backtrack stack) may have occured + // in the RegExp code but no exception has been created yet. + // If there is no pending exception, handle that in the runtime system. + __ Mov(x10, Operand(isolate()->factory()->the_hole_value())); + __ Mov(x11, + Operand(ExternalReference(Isolate::kPendingExceptionAddress, + isolate()))); + __ Ldr(exception_value, MemOperand(x11)); + __ Cmp(x10, exception_value); + __ B(eq, &runtime); + + __ Str(x10, MemOperand(x11)); // Clear pending exception. + + // Check if the exception is a termination. If so, throw as uncatchable. + Label termination_exception; + __ JumpIfRoot(exception_value, + Heap::kTerminationExceptionRootIndex, + &termination_exception); + + __ Throw(exception_value, x10, x11, x12, x13); + + __ Bind(&termination_exception); + __ ThrowUncatchable(exception_value, x10, x11, x12, x13); + + __ Bind(&failure); + __ Mov(x0, Operand(isolate()->factory()->null_value())); + __ PopCPURegList(used_callee_saved_registers); + // Drop the 4 arguments of the stub from the stack. + __ Drop(4); + __ Ret(); + + __ Bind(&runtime); + __ PopCPURegList(used_callee_saved_registers); + __ TailCallRuntime(Runtime::kHiddenRegExpExec, 4, 1); + + // Deferred code for string handling. + // (6) Not a long external string? If yes, go to (8). + __ Bind(¬_seq_nor_cons); + // Compare flags are still set. + __ B(ne, ¬_long_external); // Go to (8). + + // (7) External string. Make it, offset-wise, look like a sequential string. + __ Bind(&external_string); + if (masm->emit_debug_code()) { + // Assert that we do not have a cons or slice (indirect strings) here. + // Sequential strings have already been ruled out. + __ Ldr(x10, FieldMemOperand(subject, HeapObject::kMapOffset)); + __ Ldrb(x10, FieldMemOperand(x10, Map::kInstanceTypeOffset)); + __ Tst(x10, kIsIndirectStringMask); + __ Check(eq, kExternalStringExpectedButNotFound); + __ And(x10, x10, kStringRepresentationMask); + __ Cmp(x10, 0); + __ Check(ne, kExternalStringExpectedButNotFound); + } + __ Ldr(subject, + FieldMemOperand(subject, ExternalString::kResourceDataOffset)); + // Move the pointer so that offset-wise, it looks like a sequential string. + STATIC_ASSERT(SeqTwoByteString::kHeaderSize == SeqOneByteString::kHeaderSize); + __ Sub(subject, subject, SeqTwoByteString::kHeaderSize - kHeapObjectTag); + __ B(&seq_string); // Go to (5). + + // (8) If this is a short external string or not a string, bail out to + // runtime. + __ Bind(¬_long_external); + STATIC_ASSERT(kShortExternalStringTag != 0); + __ TestAndBranchIfAnySet(string_representation, + kShortExternalStringMask | kIsNotStringMask, + &runtime); + + // (9) Sliced string. Replace subject with parent. + __ Ldr(sliced_string_offset, + UntagSmiFieldMemOperand(subject, SlicedString::kOffsetOffset)); + __ Ldr(subject, FieldMemOperand(subject, SlicedString::kParentOffset)); + __ B(&check_underlying); // Go to (4). +#endif +} + + +static void GenerateRecordCallTarget(MacroAssembler* masm, + Register argc, + Register function, + Register feedback_vector, + Register index, + Register scratch1, + Register scratch2) { + ASM_LOCATION("GenerateRecordCallTarget"); + ASSERT(!AreAliased(scratch1, scratch2, + argc, function, feedback_vector, index)); + // Cache the called function in a feedback vector slot. Cache states are + // uninitialized, monomorphic (indicated by a JSFunction), and megamorphic. + // argc : number of arguments to the construct function + // function : the function to call + // feedback_vector : the feedback vector + // index : slot in feedback vector (smi) + Label initialize, done, miss, megamorphic, not_array_function; + + ASSERT_EQ(*TypeFeedbackInfo::MegamorphicSentinel(masm->isolate()), + masm->isolate()->heap()->megamorphic_symbol()); + ASSERT_EQ(*TypeFeedbackInfo::UninitializedSentinel(masm->isolate()), + masm->isolate()->heap()->uninitialized_symbol()); + + // Load the cache state. + __ Add(scratch1, feedback_vector, + Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ Ldr(scratch1, FieldMemOperand(scratch1, FixedArray::kHeaderSize)); + + // A monomorphic cache hit or an already megamorphic state: invoke the + // function without changing the state. + __ Cmp(scratch1, function); + __ B(eq, &done); + + if (!FLAG_pretenuring_call_new) { + // If we came here, we need to see if we are the array function. + // If we didn't have a matching function, and we didn't find the megamorph + // sentinel, then we have in the slot either some other function or an + // AllocationSite. Do a map check on the object in scratch1 register. + __ Ldr(scratch2, FieldMemOperand(scratch1, AllocationSite::kMapOffset)); + __ JumpIfNotRoot(scratch2, Heap::kAllocationSiteMapRootIndex, &miss); + + // Make sure the function is the Array() function + __ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, scratch1); + __ Cmp(function, scratch1); + __ B(ne, &megamorphic); + __ B(&done); + } + + __ Bind(&miss); + + // A monomorphic miss (i.e, here the cache is not uninitialized) goes + // megamorphic. + __ JumpIfRoot(scratch1, Heap::kUninitializedSymbolRootIndex, &initialize); + // MegamorphicSentinel is an immortal immovable object (undefined) so no + // write-barrier is needed. + __ Bind(&megamorphic); + __ Add(scratch1, feedback_vector, + Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ LoadRoot(scratch2, Heap::kMegamorphicSymbolRootIndex); + __ Str(scratch2, FieldMemOperand(scratch1, FixedArray::kHeaderSize)); + __ B(&done); + + // An uninitialized cache is patched with the function or sentinel to + // indicate the ElementsKind if function is the Array constructor. + __ Bind(&initialize); + + if (!FLAG_pretenuring_call_new) { + // Make sure the function is the Array() function + __ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, scratch1); + __ Cmp(function, scratch1); + __ B(ne, ¬_array_function); + + // The target function is the Array constructor, + // Create an AllocationSite if we don't already have it, store it in the + // slot. + { + FrameScope scope(masm, StackFrame::INTERNAL); + CreateAllocationSiteStub create_stub(masm->isolate()); + + // Arguments register must be smi-tagged to call out. + __ SmiTag(argc); + __ Push(argc, function, feedback_vector, index); + + // CreateAllocationSiteStub expect the feedback vector in x2 and the slot + // index in x3. + ASSERT(feedback_vector.Is(x2) && index.Is(x3)); + __ CallStub(&create_stub); + + __ Pop(index, feedback_vector, function, argc); + __ SmiUntag(argc); + } + __ B(&done); + + __ Bind(¬_array_function); + } + + // An uninitialized cache is patched with the function. + + __ Add(scratch1, feedback_vector, + Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ Add(scratch1, scratch1, FixedArray::kHeaderSize - kHeapObjectTag); + __ Str(function, MemOperand(scratch1, 0)); + + __ Push(function); + __ RecordWrite(feedback_vector, scratch1, function, kLRHasNotBeenSaved, + kDontSaveFPRegs, EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); + __ Pop(function); + + __ Bind(&done); +} + + +static void EmitContinueIfStrictOrNative(MacroAssembler* masm, Label* cont) { + // Do not transform the receiver for strict mode functions. + __ Ldr(x3, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset)); + __ Ldr(w4, FieldMemOperand(x3, SharedFunctionInfo::kCompilerHintsOffset)); + __ Tbnz(w4, SharedFunctionInfo::kStrictModeFunction, cont); + + // Do not transform the receiver for native (Compilerhints already in x3). + __ Tbnz(w4, SharedFunctionInfo::kNative, cont); +} + + +static void EmitSlowCase(MacroAssembler* masm, + int argc, + Register function, + Register type, + Label* non_function) { + // Check for function proxy. + // x10 : function type. + __ CompareAndBranch(type, JS_FUNCTION_PROXY_TYPE, ne, non_function); + __ Push(function); // put proxy as additional argument + __ Mov(x0, argc + 1); + __ Mov(x2, 0); + __ GetBuiltinFunction(x1, Builtins::CALL_FUNCTION_PROXY); + { + Handle<Code> adaptor = + masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(); + __ Jump(adaptor, RelocInfo::CODE_TARGET); + } + + // CALL_NON_FUNCTION expects the non-function callee as receiver (instead + // of the original receiver from the call site). + __ Bind(non_function); + __ Poke(function, argc * kXRegSize); + __ Mov(x0, argc); // Set up the number of arguments. + __ Mov(x2, 0); + __ GetBuiltinFunction(function, Builtins::CALL_NON_FUNCTION); + __ Jump(masm->isolate()->builtins()->ArgumentsAdaptorTrampoline(), + RelocInfo::CODE_TARGET); +} + + +static void EmitWrapCase(MacroAssembler* masm, int argc, Label* cont) { + // Wrap the receiver and patch it back onto the stack. + { FrameScope frame_scope(masm, StackFrame::INTERNAL); + __ Push(x1, x3); + __ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); + __ Pop(x1); + } + __ Poke(x0, argc * kPointerSize); + __ B(cont); +} + + +static void CallFunctionNoFeedback(MacroAssembler* masm, + int argc, bool needs_checks, + bool call_as_method) { + // x1 function the function to call + Register function = x1; + Register type = x4; + Label slow, non_function, wrap, cont; + + // TODO(jbramley): This function has a lot of unnamed registers. Name them, + // and tidy things up a bit. + + if (needs_checks) { + // Check that the function is really a JavaScript function. + __ JumpIfSmi(function, &non_function); + + // Goto slow case if we do not have a function. + __ JumpIfNotObjectType(function, x10, type, JS_FUNCTION_TYPE, &slow); + } + + // Fast-case: Invoke the function now. + // x1 function pushed function + ParameterCount actual(argc); + + if (call_as_method) { + if (needs_checks) { + EmitContinueIfStrictOrNative(masm, &cont); + } + + // Compute the receiver in sloppy mode. + __ Peek(x3, argc * kPointerSize); + + if (needs_checks) { + __ JumpIfSmi(x3, &wrap); + __ JumpIfObjectType(x3, x10, type, FIRST_SPEC_OBJECT_TYPE, &wrap, lt); + } else { + __ B(&wrap); + } + + __ Bind(&cont); + } + + __ InvokeFunction(function, + actual, + JUMP_FUNCTION, + NullCallWrapper()); + if (needs_checks) { + // Slow-case: Non-function called. + __ Bind(&slow); + EmitSlowCase(masm, argc, function, type, &non_function); + } + + if (call_as_method) { + __ Bind(&wrap); + EmitWrapCase(masm, argc, &cont); + } +} + + +void CallFunctionStub::Generate(MacroAssembler* masm) { + ASM_LOCATION("CallFunctionStub::Generate"); + CallFunctionNoFeedback(masm, argc_, NeedsChecks(), CallAsMethod()); +} + + +void CallConstructStub::Generate(MacroAssembler* masm) { + ASM_LOCATION("CallConstructStub::Generate"); + // x0 : number of arguments + // x1 : the function to call + // x2 : feedback vector + // x3 : slot in feedback vector (smi) (if r2 is not the megamorphic symbol) + Register function = x1; + Label slow, non_function_call; + + // Check that the function is not a smi. + __ JumpIfSmi(function, &non_function_call); + // Check that the function is a JSFunction. + Register object_type = x10; + __ JumpIfNotObjectType(function, object_type, object_type, JS_FUNCTION_TYPE, + &slow); + + if (RecordCallTarget()) { + GenerateRecordCallTarget(masm, x0, function, x2, x3, x4, x5); + + __ Add(x5, x2, Operand::UntagSmiAndScale(x3, kPointerSizeLog2)); + if (FLAG_pretenuring_call_new) { + // Put the AllocationSite from the feedback vector into x2. + // By adding kPointerSize we encode that we know the AllocationSite + // entry is at the feedback vector slot given by x3 + 1. + __ Ldr(x2, FieldMemOperand(x5, FixedArray::kHeaderSize + kPointerSize)); + } else { + Label feedback_register_initialized; + // Put the AllocationSite from the feedback vector into x2, or undefined. + __ Ldr(x2, FieldMemOperand(x5, FixedArray::kHeaderSize)); + __ Ldr(x5, FieldMemOperand(x2, AllocationSite::kMapOffset)); + __ JumpIfRoot(x5, Heap::kAllocationSiteMapRootIndex, + &feedback_register_initialized); + __ LoadRoot(x2, Heap::kUndefinedValueRootIndex); + __ bind(&feedback_register_initialized); + } + + __ AssertUndefinedOrAllocationSite(x2, x5); + } + + // Jump to the function-specific construct stub. + Register jump_reg = x4; + Register shared_func_info = jump_reg; + Register cons_stub = jump_reg; + Register cons_stub_code = jump_reg; + __ Ldr(shared_func_info, + FieldMemOperand(function, JSFunction::kSharedFunctionInfoOffset)); + __ Ldr(cons_stub, + FieldMemOperand(shared_func_info, + SharedFunctionInfo::kConstructStubOffset)); + __ Add(cons_stub_code, cons_stub, Code::kHeaderSize - kHeapObjectTag); + __ Br(cons_stub_code); + + Label do_call; + __ Bind(&slow); + __ Cmp(object_type, JS_FUNCTION_PROXY_TYPE); + __ B(ne, &non_function_call); + __ GetBuiltinFunction(x1, Builtins::CALL_FUNCTION_PROXY_AS_CONSTRUCTOR); + __ B(&do_call); + + __ Bind(&non_function_call); + __ GetBuiltinFunction(x1, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR); + + __ Bind(&do_call); + // Set expected number of arguments to zero (not changing x0). + __ Mov(x2, 0); + __ Jump(isolate()->builtins()->ArgumentsAdaptorTrampoline(), + RelocInfo::CODE_TARGET); +} + + +static void EmitLoadTypeFeedbackVector(MacroAssembler* masm, Register vector) { + __ Ldr(vector, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ Ldr(vector, FieldMemOperand(vector, + JSFunction::kSharedFunctionInfoOffset)); + __ Ldr(vector, FieldMemOperand(vector, + SharedFunctionInfo::kFeedbackVectorOffset)); +} + + +void CallIC_ArrayStub::Generate(MacroAssembler* masm) { + // x1 - function + // x3 - slot id + Label miss; + Register function = x1; + Register feedback_vector = x2; + Register index = x3; + Register scratch = x4; + + EmitLoadTypeFeedbackVector(masm, feedback_vector); + + __ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, scratch); + __ Cmp(function, scratch); + __ B(ne, &miss); + + Register allocation_site = feedback_vector; + __ Mov(x0, Operand(arg_count())); + + __ Add(scratch, feedback_vector, + Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ Ldr(allocation_site, FieldMemOperand(scratch, FixedArray::kHeaderSize)); + + // Verify that x2 contains an AllocationSite + __ AssertUndefinedOrAllocationSite(allocation_site, scratch); + ArrayConstructorStub stub(masm->isolate(), arg_count()); + __ TailCallStub(&stub); + + __ bind(&miss); + GenerateMiss(masm, IC::kCallIC_Customization_Miss); + + // The slow case, we need this no matter what to complete a call after a miss. + CallFunctionNoFeedback(masm, + arg_count(), + true, + CallAsMethod()); + + __ Unreachable(); +} + + +void CallICStub::Generate(MacroAssembler* masm) { + ASM_LOCATION("CallICStub"); + + // x1 - function + // x3 - slot id (Smi) + Label extra_checks_or_miss, slow_start; + Label slow, non_function, wrap, cont; + Label have_js_function; + int argc = state_.arg_count(); + ParameterCount actual(argc); + + Register function = x1; + Register feedback_vector = x2; + Register index = x3; + Register type = x4; + + EmitLoadTypeFeedbackVector(masm, feedback_vector); + + // The checks. First, does x1 match the recorded monomorphic target? + __ Add(x4, feedback_vector, + Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ Ldr(x4, FieldMemOperand(x4, FixedArray::kHeaderSize)); + + __ Cmp(x4, function); + __ B(ne, &extra_checks_or_miss); + + __ bind(&have_js_function); + if (state_.CallAsMethod()) { + EmitContinueIfStrictOrNative(masm, &cont); + + // Compute the receiver in sloppy mode. + __ Peek(x3, argc * kPointerSize); + + __ JumpIfSmi(x3, &wrap); + __ JumpIfObjectType(x3, x10, type, FIRST_SPEC_OBJECT_TYPE, &wrap, lt); + + __ Bind(&cont); + } + + __ InvokeFunction(function, + actual, + JUMP_FUNCTION, + NullCallWrapper()); + + __ bind(&slow); + EmitSlowCase(masm, argc, function, type, &non_function); + + if (state_.CallAsMethod()) { + __ bind(&wrap); + EmitWrapCase(masm, argc, &cont); + } + + __ bind(&extra_checks_or_miss); + Label miss; + + __ JumpIfRoot(x4, Heap::kMegamorphicSymbolRootIndex, &slow_start); + __ JumpIfRoot(x4, Heap::kUninitializedSymbolRootIndex, &miss); + + if (!FLAG_trace_ic) { + // We are going megamorphic, and we don't want to visit the runtime. + __ Add(x4, feedback_vector, + Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ LoadRoot(x5, Heap::kMegamorphicSymbolRootIndex); + __ Str(x5, FieldMemOperand(x4, FixedArray::kHeaderSize)); + __ B(&slow_start); + } + + // We are here because tracing is on or we are going monomorphic. + __ bind(&miss); + GenerateMiss(masm, IC::kCallIC_Miss); + + // the slow case + __ bind(&slow_start); + + // Check that the function is really a JavaScript function. + __ JumpIfSmi(function, &non_function); + + // Goto slow case if we do not have a function. + __ JumpIfNotObjectType(function, x10, type, JS_FUNCTION_TYPE, &slow); + __ B(&have_js_function); +} + + +void CallICStub::GenerateMiss(MacroAssembler* masm, IC::UtilityId id) { + ASM_LOCATION("CallICStub[Miss]"); + + // Get the receiver of the function from the stack; 1 ~ return address. + __ Peek(x4, (state_.arg_count() + 1) * kPointerSize); + + { + FrameScope scope(masm, StackFrame::INTERNAL); + + // Push the receiver and the function and feedback info. + __ Push(x4, x1, x2, x3); + + // Call the entry. + ExternalReference miss = ExternalReference(IC_Utility(id), + masm->isolate()); + __ CallExternalReference(miss, 4); + + // Move result to edi and exit the internal frame. + __ Mov(x1, x0); + } +} + + +void StringCharCodeAtGenerator::GenerateFast(MacroAssembler* masm) { + // If the receiver is a smi trigger the non-string case. + __ JumpIfSmi(object_, receiver_not_string_); + + // Fetch the instance type of the receiver into result register. + __ Ldr(result_, FieldMemOperand(object_, HeapObject::kMapOffset)); + __ Ldrb(result_, FieldMemOperand(result_, Map::kInstanceTypeOffset)); + + // If the receiver is not a string trigger the non-string case. + __ TestAndBranchIfAnySet(result_, kIsNotStringMask, receiver_not_string_); + + // If the index is non-smi trigger the non-smi case. + __ JumpIfNotSmi(index_, &index_not_smi_); + + __ Bind(&got_smi_index_); + // Check for index out of range. + __ Ldrsw(result_, UntagSmiFieldMemOperand(object_, String::kLengthOffset)); + __ Cmp(result_, Operand::UntagSmi(index_)); + __ B(ls, index_out_of_range_); + + __ SmiUntag(index_); + + StringCharLoadGenerator::Generate(masm, + object_, + index_.W(), + result_, + &call_runtime_); + __ SmiTag(result_); + __ Bind(&exit_); +} + + +void StringCharCodeAtGenerator::GenerateSlow( + MacroAssembler* masm, + const RuntimeCallHelper& call_helper) { + __ Abort(kUnexpectedFallthroughToCharCodeAtSlowCase); + + __ Bind(&index_not_smi_); + // If index is a heap number, try converting it to an integer. + __ CheckMap(index_, + result_, + Heap::kHeapNumberMapRootIndex, + index_not_number_, + DONT_DO_SMI_CHECK); + call_helper.BeforeCall(masm); + // Save object_ on the stack and pass index_ as argument for runtime call. + __ Push(object_, index_); + if (index_flags_ == STRING_INDEX_IS_NUMBER) { + __ CallRuntime(Runtime::kNumberToIntegerMapMinusZero, 1); + } else { + ASSERT(index_flags_ == STRING_INDEX_IS_ARRAY_INDEX); + // NumberToSmi discards numbers that are not exact integers. + __ CallRuntime(Runtime::kHiddenNumberToSmi, 1); + } + // Save the conversion result before the pop instructions below + // have a chance to overwrite it. + __ Mov(index_, x0); + __ Pop(object_); + // Reload the instance type. + __ Ldr(result_, FieldMemOperand(object_, HeapObject::kMapOffset)); + __ Ldrb(result_, FieldMemOperand(result_, Map::kInstanceTypeOffset)); + call_helper.AfterCall(masm); + + // If index is still not a smi, it must be out of range. + __ JumpIfNotSmi(index_, index_out_of_range_); + // Otherwise, return to the fast path. + __ B(&got_smi_index_); + + // Call runtime. We get here when the receiver is a string and the + // index is a number, but the code of getting the actual character + // is too complex (e.g., when the string needs to be flattened). + __ Bind(&call_runtime_); + call_helper.BeforeCall(masm); + __ SmiTag(index_); + __ Push(object_, index_); + __ CallRuntime(Runtime::kHiddenStringCharCodeAt, 2); + __ Mov(result_, x0); + call_helper.AfterCall(masm); + __ B(&exit_); + + __ Abort(kUnexpectedFallthroughFromCharCodeAtSlowCase); +} + + +void StringCharFromCodeGenerator::GenerateFast(MacroAssembler* masm) { + __ JumpIfNotSmi(code_, &slow_case_); + __ Cmp(code_, Smi::FromInt(String::kMaxOneByteCharCode)); + __ B(hi, &slow_case_); + + __ LoadRoot(result_, Heap::kSingleCharacterStringCacheRootIndex); + // At this point code register contains smi tagged ASCII char code. + STATIC_ASSERT(kSmiShift > kPointerSizeLog2); + __ Add(result_, result_, Operand(code_, LSR, kSmiShift - kPointerSizeLog2)); + __ Ldr(result_, FieldMemOperand(result_, FixedArray::kHeaderSize)); + __ JumpIfRoot(result_, Heap::kUndefinedValueRootIndex, &slow_case_); + __ Bind(&exit_); +} + + +void StringCharFromCodeGenerator::GenerateSlow( + MacroAssembler* masm, + const RuntimeCallHelper& call_helper) { + __ Abort(kUnexpectedFallthroughToCharFromCodeSlowCase); + + __ Bind(&slow_case_); + call_helper.BeforeCall(masm); + __ Push(code_); + __ CallRuntime(Runtime::kCharFromCode, 1); + __ Mov(result_, x0); + call_helper.AfterCall(masm); + __ B(&exit_); + + __ Abort(kUnexpectedFallthroughFromCharFromCodeSlowCase); +} + + +void ICCompareStub::GenerateSmis(MacroAssembler* masm) { + // Inputs are in x0 (lhs) and x1 (rhs). + ASSERT(state_ == CompareIC::SMI); + ASM_LOCATION("ICCompareStub[Smis]"); + Label miss; + // Bail out (to 'miss') unless both x0 and x1 are smis. + __ JumpIfEitherNotSmi(x0, x1, &miss); + + if (GetCondition() == eq) { + // For equality we do not care about the sign of the result. + __ Sub(x0, x0, x1); + } else { + // Untag before subtracting to avoid handling overflow. + __ SmiUntag(x1); + __ Sub(x0, x1, Operand::UntagSmi(x0)); + } + __ Ret(); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateNumbers(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::NUMBER); + ASM_LOCATION("ICCompareStub[HeapNumbers]"); + + Label unordered, maybe_undefined1, maybe_undefined2; + Label miss, handle_lhs, values_in_d_regs; + Label untag_rhs, untag_lhs; + + Register result = x0; + Register rhs = x0; + Register lhs = x1; + FPRegister rhs_d = d0; + FPRegister lhs_d = d1; + + if (left_ == CompareIC::SMI) { + __ JumpIfNotSmi(lhs, &miss); + } + if (right_ == CompareIC::SMI) { + __ JumpIfNotSmi(rhs, &miss); + } + + __ SmiUntagToDouble(rhs_d, rhs, kSpeculativeUntag); + __ SmiUntagToDouble(lhs_d, lhs, kSpeculativeUntag); + + // Load rhs if it's a heap number. + __ JumpIfSmi(rhs, &handle_lhs); + __ CheckMap(rhs, x10, Heap::kHeapNumberMapRootIndex, &maybe_undefined1, + DONT_DO_SMI_CHECK); + __ Ldr(rhs_d, FieldMemOperand(rhs, HeapNumber::kValueOffset)); + + // Load lhs if it's a heap number. + __ Bind(&handle_lhs); + __ JumpIfSmi(lhs, &values_in_d_regs); + __ CheckMap(lhs, x10, Heap::kHeapNumberMapRootIndex, &maybe_undefined2, + DONT_DO_SMI_CHECK); + __ Ldr(lhs_d, FieldMemOperand(lhs, HeapNumber::kValueOffset)); + + __ Bind(&values_in_d_regs); + __ Fcmp(lhs_d, rhs_d); + __ B(vs, &unordered); // Overflow flag set if either is NaN. + STATIC_ASSERT((LESS == -1) && (EQUAL == 0) && (GREATER == 1)); + __ Cset(result, gt); // gt => 1, otherwise (lt, eq) => 0 (EQUAL). + __ Csinv(result, result, xzr, ge); // lt => -1, gt => 1, eq => 0. + __ Ret(); + + __ Bind(&unordered); + ICCompareStub stub(isolate(), op_, CompareIC::GENERIC, CompareIC::GENERIC, + CompareIC::GENERIC); + __ Jump(stub.GetCode(), RelocInfo::CODE_TARGET); + + __ Bind(&maybe_undefined1); + if (Token::IsOrderedRelationalCompareOp(op_)) { + __ JumpIfNotRoot(rhs, Heap::kUndefinedValueRootIndex, &miss); + __ JumpIfSmi(lhs, &unordered); + __ JumpIfNotObjectType(lhs, x10, x10, HEAP_NUMBER_TYPE, &maybe_undefined2); + __ B(&unordered); + } + + __ Bind(&maybe_undefined2); + if (Token::IsOrderedRelationalCompareOp(op_)) { + __ JumpIfRoot(lhs, Heap::kUndefinedValueRootIndex, &unordered); + } + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateInternalizedStrings(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::INTERNALIZED_STRING); + ASM_LOCATION("ICCompareStub[InternalizedStrings]"); + Label miss; + + Register result = x0; + Register rhs = x0; + Register lhs = x1; + + // Check that both operands are heap objects. + __ JumpIfEitherSmi(lhs, rhs, &miss); + + // Check that both operands are internalized strings. + Register rhs_map = x10; + Register lhs_map = x11; + Register rhs_type = x10; + Register lhs_type = x11; + __ Ldr(lhs_map, FieldMemOperand(lhs, HeapObject::kMapOffset)); + __ Ldr(rhs_map, FieldMemOperand(rhs, HeapObject::kMapOffset)); + __ Ldrb(lhs_type, FieldMemOperand(lhs_map, Map::kInstanceTypeOffset)); + __ Ldrb(rhs_type, FieldMemOperand(rhs_map, Map::kInstanceTypeOffset)); + + STATIC_ASSERT((kInternalizedTag == 0) && (kStringTag == 0)); + __ Orr(x12, lhs_type, rhs_type); + __ TestAndBranchIfAnySet( + x12, kIsNotStringMask | kIsNotInternalizedMask, &miss); + + // Internalized strings are compared by identity. + STATIC_ASSERT(EQUAL == 0); + __ Cmp(lhs, rhs); + __ Cset(result, ne); + __ Ret(); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateUniqueNames(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::UNIQUE_NAME); + ASM_LOCATION("ICCompareStub[UniqueNames]"); + ASSERT(GetCondition() == eq); + Label miss; + + Register result = x0; + Register rhs = x0; + Register lhs = x1; + + Register lhs_instance_type = w2; + Register rhs_instance_type = w3; + + // Check that both operands are heap objects. + __ JumpIfEitherSmi(lhs, rhs, &miss); + + // Check that both operands are unique names. This leaves the instance + // types loaded in tmp1 and tmp2. + __ Ldr(x10, FieldMemOperand(lhs, HeapObject::kMapOffset)); + __ Ldr(x11, FieldMemOperand(rhs, HeapObject::kMapOffset)); + __ Ldrb(lhs_instance_type, FieldMemOperand(x10, Map::kInstanceTypeOffset)); + __ Ldrb(rhs_instance_type, FieldMemOperand(x11, Map::kInstanceTypeOffset)); + + // To avoid a miss, each instance type should be either SYMBOL_TYPE or it + // should have kInternalizedTag set. + __ JumpIfNotUniqueName(lhs_instance_type, &miss); + __ JumpIfNotUniqueName(rhs_instance_type, &miss); + + // Unique names are compared by identity. + STATIC_ASSERT(EQUAL == 0); + __ Cmp(lhs, rhs); + __ Cset(result, ne); + __ Ret(); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateStrings(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::STRING); + ASM_LOCATION("ICCompareStub[Strings]"); + + Label miss; + + bool equality = Token::IsEqualityOp(op_); + + Register result = x0; + Register rhs = x0; + Register lhs = x1; + + // Check that both operands are heap objects. + __ JumpIfEitherSmi(rhs, lhs, &miss); + + // Check that both operands are strings. + Register rhs_map = x10; + Register lhs_map = x11; + Register rhs_type = x10; + Register lhs_type = x11; + __ Ldr(lhs_map, FieldMemOperand(lhs, HeapObject::kMapOffset)); + __ Ldr(rhs_map, FieldMemOperand(rhs, HeapObject::kMapOffset)); + __ Ldrb(lhs_type, FieldMemOperand(lhs_map, Map::kInstanceTypeOffset)); + __ Ldrb(rhs_type, FieldMemOperand(rhs_map, Map::kInstanceTypeOffset)); + STATIC_ASSERT(kNotStringTag != 0); + __ Orr(x12, lhs_type, rhs_type); + __ Tbnz(x12, MaskToBit(kIsNotStringMask), &miss); + + // Fast check for identical strings. + Label not_equal; + __ Cmp(lhs, rhs); + __ B(ne, ¬_equal); + __ Mov(result, EQUAL); + __ Ret(); + + __ Bind(¬_equal); + // Handle not identical strings + + // Check that both strings are internalized strings. If they are, we're done + // because we already know they are not identical. We know they are both + // strings. + if (equality) { + ASSERT(GetCondition() == eq); + STATIC_ASSERT(kInternalizedTag == 0); + Label not_internalized_strings; + __ Orr(x12, lhs_type, rhs_type); + __ TestAndBranchIfAnySet( + x12, kIsNotInternalizedMask, ¬_internalized_strings); + // Result is in rhs (x0), and not EQUAL, as rhs is not a smi. + __ Ret(); + __ Bind(¬_internalized_strings); + } + + // Check that both strings are sequential ASCII. + Label runtime; + __ JumpIfBothInstanceTypesAreNotSequentialAscii( + lhs_type, rhs_type, x12, x13, &runtime); + + // Compare flat ASCII strings. Returns when done. + if (equality) { + StringCompareStub::GenerateFlatAsciiStringEquals( + masm, lhs, rhs, x10, x11, x12); + } else { + StringCompareStub::GenerateCompareFlatAsciiStrings( + masm, lhs, rhs, x10, x11, x12, x13); + } + + // Handle more complex cases in runtime. + __ Bind(&runtime); + __ Push(lhs, rhs); + if (equality) { + __ TailCallRuntime(Runtime::kStringEquals, 2, 1); + } else { + __ TailCallRuntime(Runtime::kHiddenStringCompare, 2, 1); + } + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateObjects(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::OBJECT); + ASM_LOCATION("ICCompareStub[Objects]"); + + Label miss; + + Register result = x0; + Register rhs = x0; + Register lhs = x1; + + __ JumpIfEitherSmi(rhs, lhs, &miss); + + __ JumpIfNotObjectType(rhs, x10, x10, JS_OBJECT_TYPE, &miss); + __ JumpIfNotObjectType(lhs, x10, x10, JS_OBJECT_TYPE, &miss); + + ASSERT(GetCondition() == eq); + __ Sub(result, rhs, lhs); + __ Ret(); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateKnownObjects(MacroAssembler* masm) { + ASM_LOCATION("ICCompareStub[KnownObjects]"); + + Label miss; + + Register result = x0; + Register rhs = x0; + Register lhs = x1; + + __ JumpIfEitherSmi(rhs, lhs, &miss); + + Register rhs_map = x10; + Register lhs_map = x11; + __ Ldr(rhs_map, FieldMemOperand(rhs, HeapObject::kMapOffset)); + __ Ldr(lhs_map, FieldMemOperand(lhs, HeapObject::kMapOffset)); + __ Cmp(rhs_map, Operand(known_map_)); + __ B(ne, &miss); + __ Cmp(lhs_map, Operand(known_map_)); + __ B(ne, &miss); + + __ Sub(result, rhs, lhs); + __ Ret(); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +// This method handles the case where a compare stub had the wrong +// implementation. It calls a miss handler, which re-writes the stub. All other +// ICCompareStub::Generate* methods should fall back into this one if their +// operands were not the expected types. +void ICCompareStub::GenerateMiss(MacroAssembler* masm) { + ASM_LOCATION("ICCompareStub[Miss]"); + + Register stub_entry = x11; + { + ExternalReference miss = + ExternalReference(IC_Utility(IC::kCompareIC_Miss), isolate()); + + FrameScope scope(masm, StackFrame::INTERNAL); + Register op = x10; + Register left = x1; + Register right = x0; + // Preserve some caller-saved registers. + __ Push(x1, x0, lr); + // Push the arguments. + __ Mov(op, Smi::FromInt(op_)); + __ Push(left, right, op); + + // Call the miss handler. This also pops the arguments. + __ CallExternalReference(miss, 3); + + // Compute the entry point of the rewritten stub. + __ Add(stub_entry, x0, Code::kHeaderSize - kHeapObjectTag); + // Restore caller-saved registers. + __ Pop(lr, x0, x1); + } + + // Tail-call to the new stub. + __ Jump(stub_entry); +} + + +void StringHelper::GenerateHashInit(MacroAssembler* masm, + Register hash, + Register character) { + ASSERT(!AreAliased(hash, character)); + + // hash = character + (character << 10); + __ LoadRoot(hash, Heap::kHashSeedRootIndex); + // Untag smi seed and add the character. + __ Add(hash, character, Operand(hash, LSR, kSmiShift)); + + // Compute hashes modulo 2^32 using a 32-bit W register. + Register hash_w = hash.W(); + + // hash += hash << 10; + __ Add(hash_w, hash_w, Operand(hash_w, LSL, 10)); + // hash ^= hash >> 6; + __ Eor(hash_w, hash_w, Operand(hash_w, LSR, 6)); +} + + +void StringHelper::GenerateHashAddCharacter(MacroAssembler* masm, + Register hash, + Register character) { + ASSERT(!AreAliased(hash, character)); + + // hash += character; + __ Add(hash, hash, character); + + // Compute hashes modulo 2^32 using a 32-bit W register. + Register hash_w = hash.W(); + + // hash += hash << 10; + __ Add(hash_w, hash_w, Operand(hash_w, LSL, 10)); + // hash ^= hash >> 6; + __ Eor(hash_w, hash_w, Operand(hash_w, LSR, 6)); +} + + +void StringHelper::GenerateHashGetHash(MacroAssembler* masm, + Register hash, + Register scratch) { + // Compute hashes modulo 2^32 using a 32-bit W register. + Register hash_w = hash.W(); + Register scratch_w = scratch.W(); + ASSERT(!AreAliased(hash_w, scratch_w)); + + // hash += hash << 3; + __ Add(hash_w, hash_w, Operand(hash_w, LSL, 3)); + // hash ^= hash >> 11; + __ Eor(hash_w, hash_w, Operand(hash_w, LSR, 11)); + // hash += hash << 15; + __ Add(hash_w, hash_w, Operand(hash_w, LSL, 15)); + + __ Ands(hash_w, hash_w, String::kHashBitMask); + + // if (hash == 0) hash = 27; + __ Mov(scratch_w, StringHasher::kZeroHash); + __ Csel(hash_w, scratch_w, hash_w, eq); +} + + +void SubStringStub::Generate(MacroAssembler* masm) { + ASM_LOCATION("SubStringStub::Generate"); + Label runtime; + + // Stack frame on entry. + // lr: return address + // jssp[0]: substring "to" offset + // jssp[8]: substring "from" offset + // jssp[16]: pointer to string object + + // This stub is called from the native-call %_SubString(...), so + // nothing can be assumed about the arguments. It is tested that: + // "string" is a sequential string, + // both "from" and "to" are smis, and + // 0 <= from <= to <= string.length (in debug mode.) + // If any of these assumptions fail, we call the runtime system. + + static const int kToOffset = 0 * kPointerSize; + static const int kFromOffset = 1 * kPointerSize; + static const int kStringOffset = 2 * kPointerSize; + + Register to = x0; + Register from = x15; + Register input_string = x10; + Register input_length = x11; + Register input_type = x12; + Register result_string = x0; + Register result_length = x1; + Register temp = x3; + + __ Peek(to, kToOffset); + __ Peek(from, kFromOffset); + + // Check that both from and to are smis. If not, jump to runtime. + __ JumpIfEitherNotSmi(from, to, &runtime); + __ SmiUntag(from); + __ SmiUntag(to); + + // Calculate difference between from and to. If to < from, branch to runtime. + __ Subs(result_length, to, from); + __ B(mi, &runtime); + + // Check from is positive. + __ Tbnz(from, kWSignBit, &runtime); + + // Make sure first argument is a string. + __ Peek(input_string, kStringOffset); + __ JumpIfSmi(input_string, &runtime); + __ IsObjectJSStringType(input_string, input_type, &runtime); + + Label single_char; + __ Cmp(result_length, 1); + __ B(eq, &single_char); + + // Short-cut for the case of trivial substring. + Label return_x0; + __ Ldrsw(input_length, + UntagSmiFieldMemOperand(input_string, String::kLengthOffset)); + + __ Cmp(result_length, input_length); + __ CmovX(x0, input_string, eq); + // Return original string. + __ B(eq, &return_x0); + + // Longer than original string's length or negative: unsafe arguments. + __ B(hi, &runtime); + + // Shorter than original string's length: an actual substring. + + // x0 to substring end character offset + // x1 result_length length of substring result + // x10 input_string pointer to input string object + // x10 unpacked_string pointer to unpacked string object + // x11 input_length length of input string + // x12 input_type instance type of input string + // x15 from substring start character offset + + // Deal with different string types: update the index if necessary and put + // the underlying string into register unpacked_string. + Label underlying_unpacked, sliced_string, seq_or_external_string; + Label update_instance_type; + // If the string is not indirect, it can only be sequential or external. + STATIC_ASSERT(kIsIndirectStringMask == (kSlicedStringTag & kConsStringTag)); + STATIC_ASSERT(kIsIndirectStringMask != 0); + + // Test for string types, and branch/fall through to appropriate unpacking + // code. + __ Tst(input_type, kIsIndirectStringMask); + __ B(eq, &seq_or_external_string); + __ Tst(input_type, kSlicedNotConsMask); + __ B(ne, &sliced_string); + + Register unpacked_string = input_string; + + // Cons string. Check whether it is flat, then fetch first part. + __ Ldr(temp, FieldMemOperand(input_string, ConsString::kSecondOffset)); + __ JumpIfNotRoot(temp, Heap::kempty_stringRootIndex, &runtime); + __ Ldr(unpacked_string, + FieldMemOperand(input_string, ConsString::kFirstOffset)); + __ B(&update_instance_type); + + __ Bind(&sliced_string); + // Sliced string. Fetch parent and correct start index by offset. + __ Ldrsw(temp, + UntagSmiFieldMemOperand(input_string, SlicedString::kOffsetOffset)); + __ Add(from, from, temp); + __ Ldr(unpacked_string, + FieldMemOperand(input_string, SlicedString::kParentOffset)); + + __ Bind(&update_instance_type); + __ Ldr(temp, FieldMemOperand(unpacked_string, HeapObject::kMapOffset)); + __ Ldrb(input_type, FieldMemOperand(temp, Map::kInstanceTypeOffset)); + // Now control must go to &underlying_unpacked. Since the no code is generated + // before then we fall through instead of generating a useless branch. + + __ Bind(&seq_or_external_string); + // Sequential or external string. Registers unpacked_string and input_string + // alias, so there's nothing to do here. + // Note that if code is added here, the above code must be updated. + + // x0 result_string pointer to result string object (uninit) + // x1 result_length length of substring result + // x10 unpacked_string pointer to unpacked string object + // x11 input_length length of input string + // x12 input_type instance type of input string + // x15 from substring start character offset + __ Bind(&underlying_unpacked); + + if (FLAG_string_slices) { + Label copy_routine; + __ Cmp(result_length, SlicedString::kMinLength); + // Short slice. Copy instead of slicing. + __ B(lt, ©_routine); + // Allocate new sliced string. At this point we do not reload the instance + // type including the string encoding because we simply rely on the info + // provided by the original string. It does not matter if the original + // string's encoding is wrong because we always have to recheck encoding of + // the newly created string's parent anyway due to externalized strings. + Label two_byte_slice, set_slice_header; + STATIC_ASSERT((kStringEncodingMask & kOneByteStringTag) != 0); + STATIC_ASSERT((kStringEncodingMask & kTwoByteStringTag) == 0); + __ Tbz(input_type, MaskToBit(kStringEncodingMask), &two_byte_slice); + __ AllocateAsciiSlicedString(result_string, result_length, x3, x4, + &runtime); + __ B(&set_slice_header); + + __ Bind(&two_byte_slice); + __ AllocateTwoByteSlicedString(result_string, result_length, x3, x4, + &runtime); + + __ Bind(&set_slice_header); + __ SmiTag(from); + __ Str(from, FieldMemOperand(result_string, SlicedString::kOffsetOffset)); + __ Str(unpacked_string, + FieldMemOperand(result_string, SlicedString::kParentOffset)); + __ B(&return_x0); + + __ Bind(©_routine); + } + + // x0 result_string pointer to result string object (uninit) + // x1 result_length length of substring result + // x10 unpacked_string pointer to unpacked string object + // x11 input_length length of input string + // x12 input_type instance type of input string + // x13 unpacked_char0 pointer to first char of unpacked string (uninit) + // x13 substring_char0 pointer to first char of substring (uninit) + // x14 result_char0 pointer to first char of result (uninit) + // x15 from substring start character offset + Register unpacked_char0 = x13; + Register substring_char0 = x13; + Register result_char0 = x14; + Label two_byte_sequential, sequential_string, allocate_result; + STATIC_ASSERT(kExternalStringTag != 0); + STATIC_ASSERT(kSeqStringTag == 0); + + __ Tst(input_type, kExternalStringTag); + __ B(eq, &sequential_string); + + __ Tst(input_type, kShortExternalStringTag); + __ B(ne, &runtime); + __ Ldr(unpacked_char0, + FieldMemOperand(unpacked_string, ExternalString::kResourceDataOffset)); + // unpacked_char0 points to the first character of the underlying string. + __ B(&allocate_result); + + __ Bind(&sequential_string); + // Locate first character of underlying subject string. + STATIC_ASSERT(SeqTwoByteString::kHeaderSize == SeqOneByteString::kHeaderSize); + __ Add(unpacked_char0, unpacked_string, + SeqOneByteString::kHeaderSize - kHeapObjectTag); + + __ Bind(&allocate_result); + // Sequential ASCII string. Allocate the result. + STATIC_ASSERT((kOneByteStringTag & kStringEncodingMask) != 0); + __ Tbz(input_type, MaskToBit(kStringEncodingMask), &two_byte_sequential); + + // Allocate and copy the resulting ASCII string. + __ AllocateAsciiString(result_string, result_length, x3, x4, x5, &runtime); + + // Locate first character of substring to copy. + __ Add(substring_char0, unpacked_char0, from); + + // Locate first character of result. + __ Add(result_char0, result_string, + SeqOneByteString::kHeaderSize - kHeapObjectTag); + + STATIC_ASSERT((SeqOneByteString::kHeaderSize & kObjectAlignmentMask) == 0); + __ CopyBytes(result_char0, substring_char0, result_length, x3, kCopyLong); + __ B(&return_x0); + + // Allocate and copy the resulting two-byte string. + __ Bind(&two_byte_sequential); + __ AllocateTwoByteString(result_string, result_length, x3, x4, x5, &runtime); + + // Locate first character of substring to copy. + __ Add(substring_char0, unpacked_char0, Operand(from, LSL, 1)); + + // Locate first character of result. + __ Add(result_char0, result_string, + SeqTwoByteString::kHeaderSize - kHeapObjectTag); + + STATIC_ASSERT((SeqTwoByteString::kHeaderSize & kObjectAlignmentMask) == 0); + __ Add(result_length, result_length, result_length); + __ CopyBytes(result_char0, substring_char0, result_length, x3, kCopyLong); + + __ Bind(&return_x0); + Counters* counters = isolate()->counters(); + __ IncrementCounter(counters->sub_string_native(), 1, x3, x4); + __ Drop(3); + __ Ret(); + + __ Bind(&runtime); + __ TailCallRuntime(Runtime::kHiddenSubString, 3, 1); + + __ bind(&single_char); + // x1: result_length + // x10: input_string + // x12: input_type + // x15: from (untagged) + __ SmiTag(from); + StringCharAtGenerator generator( + input_string, from, result_length, x0, + &runtime, &runtime, &runtime, STRING_INDEX_IS_NUMBER); + generator.GenerateFast(masm); + __ Drop(3); + __ Ret(); + generator.SkipSlow(masm, &runtime); +} + + +void StringCompareStub::GenerateFlatAsciiStringEquals(MacroAssembler* masm, + Register left, + Register right, + Register scratch1, + Register scratch2, + Register scratch3) { + ASSERT(!AreAliased(left, right, scratch1, scratch2, scratch3)); + Register result = x0; + Register left_length = scratch1; + Register right_length = scratch2; + + // Compare lengths. If lengths differ, strings can't be equal. Lengths are + // smis, and don't need to be untagged. + Label strings_not_equal, check_zero_length; + __ Ldr(left_length, FieldMemOperand(left, String::kLengthOffset)); + __ Ldr(right_length, FieldMemOperand(right, String::kLengthOffset)); + __ Cmp(left_length, right_length); + __ B(eq, &check_zero_length); + + __ Bind(&strings_not_equal); + __ Mov(result, Smi::FromInt(NOT_EQUAL)); + __ Ret(); + + // Check if the length is zero. If so, the strings must be equal (and empty.) + Label compare_chars; + __ Bind(&check_zero_length); + STATIC_ASSERT(kSmiTag == 0); + __ Cbnz(left_length, &compare_chars); + __ Mov(result, Smi::FromInt(EQUAL)); + __ Ret(); + + // Compare characters. Falls through if all characters are equal. + __ Bind(&compare_chars); + GenerateAsciiCharsCompareLoop(masm, left, right, left_length, scratch2, + scratch3, &strings_not_equal); + + // Characters in strings are equal. + __ Mov(result, Smi::FromInt(EQUAL)); + __ Ret(); +} + + +void StringCompareStub::GenerateCompareFlatAsciiStrings(MacroAssembler* masm, + Register left, + Register right, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4) { + ASSERT(!AreAliased(left, right, scratch1, scratch2, scratch3, scratch4)); + Label result_not_equal, compare_lengths; + + // Find minimum length and length difference. + Register length_delta = scratch3; + __ Ldr(scratch1, FieldMemOperand(left, String::kLengthOffset)); + __ Ldr(scratch2, FieldMemOperand(right, String::kLengthOffset)); + __ Subs(length_delta, scratch1, scratch2); + + Register min_length = scratch1; + __ Csel(min_length, scratch2, scratch1, gt); + __ Cbz(min_length, &compare_lengths); + + // Compare loop. + GenerateAsciiCharsCompareLoop(masm, + left, right, min_length, scratch2, scratch4, + &result_not_equal); + + // Compare lengths - strings up to min-length are equal. + __ Bind(&compare_lengths); + + ASSERT(Smi::FromInt(EQUAL) == static_cast<Smi*>(0)); + + // Use length_delta as result if it's zero. + Register result = x0; + __ Subs(result, length_delta, 0); + + __ Bind(&result_not_equal); + Register greater = x10; + Register less = x11; + __ Mov(greater, Smi::FromInt(GREATER)); + __ Mov(less, Smi::FromInt(LESS)); + __ CmovX(result, greater, gt); + __ CmovX(result, less, lt); + __ Ret(); +} + + +void StringCompareStub::GenerateAsciiCharsCompareLoop( + MacroAssembler* masm, + Register left, + Register right, + Register length, + Register scratch1, + Register scratch2, + Label* chars_not_equal) { + ASSERT(!AreAliased(left, right, length, scratch1, scratch2)); + + // Change index to run from -length to -1 by adding length to string + // start. This means that loop ends when index reaches zero, which + // doesn't need an additional compare. + __ SmiUntag(length); + __ Add(scratch1, length, SeqOneByteString::kHeaderSize - kHeapObjectTag); + __ Add(left, left, scratch1); + __ Add(right, right, scratch1); + + Register index = length; + __ Neg(index, length); // index = -length; + + // Compare loop + Label loop; + __ Bind(&loop); + __ Ldrb(scratch1, MemOperand(left, index)); + __ Ldrb(scratch2, MemOperand(right, index)); + __ Cmp(scratch1, scratch2); + __ B(ne, chars_not_equal); + __ Add(index, index, 1); + __ Cbnz(index, &loop); +} + + +void StringCompareStub::Generate(MacroAssembler* masm) { + Label runtime; + + Counters* counters = isolate()->counters(); + + // Stack frame on entry. + // sp[0]: right string + // sp[8]: left string + Register right = x10; + Register left = x11; + Register result = x0; + __ Pop(right, left); + + Label not_same; + __ Subs(result, right, left); + __ B(ne, ¬_same); + STATIC_ASSERT(EQUAL == 0); + __ IncrementCounter(counters->string_compare_native(), 1, x3, x4); + __ Ret(); + + __ Bind(¬_same); + + // Check that both objects are sequential ASCII strings. + __ JumpIfEitherIsNotSequentialAsciiStrings(left, right, x12, x13, &runtime); + + // Compare flat ASCII strings natively. Remove arguments from stack first, + // as this function will generate a return. + __ IncrementCounter(counters->string_compare_native(), 1, x3, x4); + GenerateCompareFlatAsciiStrings(masm, left, right, x12, x13, x14, x15); + + __ Bind(&runtime); + + // Push arguments back on to the stack. + // sp[0] = right string + // sp[8] = left string. + __ Push(left, right); + + // Call the runtime. + // Returns -1 (less), 0 (equal), or 1 (greater) tagged as a small integer. + __ TailCallRuntime(Runtime::kHiddenStringCompare, 2, 1); +} + + +void BinaryOpICWithAllocationSiteStub::Generate(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x1 : left + // -- x0 : right + // -- lr : return address + // ----------------------------------- + + // Load x2 with the allocation site. We stick an undefined dummy value here + // and replace it with the real allocation site later when we instantiate this + // stub in BinaryOpICWithAllocationSiteStub::GetCodeCopyFromTemplate(). + __ LoadObject(x2, handle(isolate()->heap()->undefined_value())); + + // Make sure that we actually patched the allocation site. + if (FLAG_debug_code) { + __ AssertNotSmi(x2, kExpectedAllocationSite); + __ Ldr(x10, FieldMemOperand(x2, HeapObject::kMapOffset)); + __ AssertRegisterIsRoot(x10, Heap::kAllocationSiteMapRootIndex, + kExpectedAllocationSite); + } + + // Tail call into the stub that handles binary operations with allocation + // sites. + BinaryOpWithAllocationSiteStub stub(isolate(), state_); + __ TailCallStub(&stub); +} + + +void RecordWriteStub::GenerateIncremental(MacroAssembler* masm, Mode mode) { + // We need some extra registers for this stub, they have been allocated + // but we need to save them before using them. + regs_.Save(masm); + + if (remembered_set_action_ == EMIT_REMEMBERED_SET) { + Label dont_need_remembered_set; + + Register value = regs_.scratch0(); + __ Ldr(value, MemOperand(regs_.address())); + __ JumpIfNotInNewSpace(value, &dont_need_remembered_set); + + __ CheckPageFlagSet(regs_.object(), + value, + 1 << MemoryChunk::SCAN_ON_SCAVENGE, + &dont_need_remembered_set); + + // First notify the incremental marker if necessary, then update the + // remembered set. + CheckNeedsToInformIncrementalMarker( + masm, kUpdateRememberedSetOnNoNeedToInformIncrementalMarker, mode); + InformIncrementalMarker(masm); + regs_.Restore(masm); // Restore the extra scratch registers we used. + + __ RememberedSetHelper(object_, + address_, + value_, // scratch1 + save_fp_regs_mode_, + MacroAssembler::kReturnAtEnd); + + __ Bind(&dont_need_remembered_set); + } + + CheckNeedsToInformIncrementalMarker( + masm, kReturnOnNoNeedToInformIncrementalMarker, mode); + InformIncrementalMarker(masm); + regs_.Restore(masm); // Restore the extra scratch registers we used. + __ Ret(); +} + + +void RecordWriteStub::InformIncrementalMarker(MacroAssembler* masm) { + regs_.SaveCallerSaveRegisters(masm, save_fp_regs_mode_); + Register address = + x0.Is(regs_.address()) ? regs_.scratch0() : regs_.address(); + ASSERT(!address.Is(regs_.object())); + ASSERT(!address.Is(x0)); + __ Mov(address, regs_.address()); + __ Mov(x0, regs_.object()); + __ Mov(x1, address); + __ Mov(x2, ExternalReference::isolate_address(isolate())); + + AllowExternalCallThatCantCauseGC scope(masm); + ExternalReference function = + ExternalReference::incremental_marking_record_write_function( + isolate()); + __ CallCFunction(function, 3, 0); + + regs_.RestoreCallerSaveRegisters(masm, save_fp_regs_mode_); +} + + +void RecordWriteStub::CheckNeedsToInformIncrementalMarker( + MacroAssembler* masm, + OnNoNeedToInformIncrementalMarker on_no_need, + Mode mode) { + Label on_black; + Label need_incremental; + Label need_incremental_pop_scratch; + + Register mem_chunk = regs_.scratch0(); + Register counter = regs_.scratch1(); + __ Bic(mem_chunk, regs_.object(), Page::kPageAlignmentMask); + __ Ldr(counter, + MemOperand(mem_chunk, MemoryChunk::kWriteBarrierCounterOffset)); + __ Subs(counter, counter, 1); + __ Str(counter, + MemOperand(mem_chunk, MemoryChunk::kWriteBarrierCounterOffset)); + __ B(mi, &need_incremental); + + // If the object is not black we don't have to inform the incremental marker. + __ JumpIfBlack(regs_.object(), regs_.scratch0(), regs_.scratch1(), &on_black); + + regs_.Restore(masm); // Restore the extra scratch registers we used. + if (on_no_need == kUpdateRememberedSetOnNoNeedToInformIncrementalMarker) { + __ RememberedSetHelper(object_, + address_, + value_, // scratch1 + save_fp_regs_mode_, + MacroAssembler::kReturnAtEnd); + } else { + __ Ret(); + } + + __ Bind(&on_black); + // Get the value from the slot. + Register value = regs_.scratch0(); + __ Ldr(value, MemOperand(regs_.address())); + + if (mode == INCREMENTAL_COMPACTION) { + Label ensure_not_white; + + __ CheckPageFlagClear(value, + regs_.scratch1(), + MemoryChunk::kEvacuationCandidateMask, + &ensure_not_white); + + __ CheckPageFlagClear(regs_.object(), + regs_.scratch1(), + MemoryChunk::kSkipEvacuationSlotsRecordingMask, + &need_incremental); + + __ Bind(&ensure_not_white); + } + + // We need extra registers for this, so we push the object and the address + // register temporarily. + __ Push(regs_.address(), regs_.object()); + __ EnsureNotWhite(value, + regs_.scratch1(), // Scratch. + regs_.object(), // Scratch. + regs_.address(), // Scratch. + regs_.scratch2(), // Scratch. + &need_incremental_pop_scratch); + __ Pop(regs_.object(), regs_.address()); + + regs_.Restore(masm); // Restore the extra scratch registers we used. + if (on_no_need == kUpdateRememberedSetOnNoNeedToInformIncrementalMarker) { + __ RememberedSetHelper(object_, + address_, + value_, // scratch1 + save_fp_regs_mode_, + MacroAssembler::kReturnAtEnd); + } else { + __ Ret(); + } + + __ Bind(&need_incremental_pop_scratch); + __ Pop(regs_.object(), regs_.address()); + + __ Bind(&need_incremental); + // Fall through when we need to inform the incremental marker. +} + + +void RecordWriteStub::Generate(MacroAssembler* masm) { + Label skip_to_incremental_noncompacting; + Label skip_to_incremental_compacting; + + // We patch these two first instructions back and forth between a nop and + // real branch when we start and stop incremental heap marking. + // Initially the stub is expected to be in STORE_BUFFER_ONLY mode, so 2 nops + // are generated. + // See RecordWriteStub::Patch for details. + { + InstructionAccurateScope scope(masm, 2); + __ adr(xzr, &skip_to_incremental_noncompacting); + __ adr(xzr, &skip_to_incremental_compacting); + } + + if (remembered_set_action_ == EMIT_REMEMBERED_SET) { + __ RememberedSetHelper(object_, + address_, + value_, // scratch1 + save_fp_regs_mode_, + MacroAssembler::kReturnAtEnd); + } + __ Ret(); + + __ Bind(&skip_to_incremental_noncompacting); + GenerateIncremental(masm, INCREMENTAL); + + __ Bind(&skip_to_incremental_compacting); + GenerateIncremental(masm, INCREMENTAL_COMPACTION); +} + + +void StoreArrayLiteralElementStub::Generate(MacroAssembler* masm) { + // x0 value element value to store + // x3 index_smi element index as smi + // sp[0] array_index_smi array literal index in function as smi + // sp[1] array array literal + + Register value = x0; + Register index_smi = x3; + + Register array = x1; + Register array_map = x2; + Register array_index_smi = x4; + __ PeekPair(array_index_smi, array, 0); + __ Ldr(array_map, FieldMemOperand(array, JSObject::kMapOffset)); + + Label double_elements, smi_element, fast_elements, slow_elements; + Register bitfield2 = x10; + __ Ldrb(bitfield2, FieldMemOperand(array_map, Map::kBitField2Offset)); + + // Jump if array's ElementsKind is not FAST*_SMI_ELEMENTS, FAST_ELEMENTS or + // FAST_HOLEY_ELEMENTS. + STATIC_ASSERT(FAST_SMI_ELEMENTS == 0); + STATIC_ASSERT(FAST_HOLEY_SMI_ELEMENTS == 1); + STATIC_ASSERT(FAST_ELEMENTS == 2); + STATIC_ASSERT(FAST_HOLEY_ELEMENTS == 3); + __ Cmp(bitfield2, Map::kMaximumBitField2FastHoleyElementValue); + __ B(hi, &double_elements); + + __ JumpIfSmi(value, &smi_element); + + // Jump if array's ElementsKind is not FAST_ELEMENTS or FAST_HOLEY_ELEMENTS. + __ Tbnz(bitfield2, MaskToBit(FAST_ELEMENTS << Map::ElementsKindBits::kShift), + &fast_elements); + + // Store into the array literal requires an elements transition. Call into + // the runtime. + __ Bind(&slow_elements); + __ Push(array, index_smi, value); + __ Ldr(x10, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ Ldr(x11, FieldMemOperand(x10, JSFunction::kLiteralsOffset)); + __ Push(x11, array_index_smi); + __ TailCallRuntime(Runtime::kStoreArrayLiteralElement, 5, 1); + + // Array literal has ElementsKind of FAST_*_ELEMENTS and value is an object. + __ Bind(&fast_elements); + __ Ldr(x10, FieldMemOperand(array, JSObject::kElementsOffset)); + __ Add(x11, x10, Operand::UntagSmiAndScale(index_smi, kPointerSizeLog2)); + __ Add(x11, x11, FixedArray::kHeaderSize - kHeapObjectTag); + __ Str(value, MemOperand(x11)); + // Update the write barrier for the array store. + __ RecordWrite(x10, x11, value, kLRHasNotBeenSaved, kDontSaveFPRegs, + EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); + __ Ret(); + + // Array literal has ElementsKind of FAST_*_SMI_ELEMENTS or FAST_*_ELEMENTS, + // and value is Smi. + __ Bind(&smi_element); + __ Ldr(x10, FieldMemOperand(array, JSObject::kElementsOffset)); + __ Add(x11, x10, Operand::UntagSmiAndScale(index_smi, kPointerSizeLog2)); + __ Str(value, FieldMemOperand(x11, FixedArray::kHeaderSize)); + __ Ret(); + + __ Bind(&double_elements); + __ Ldr(x10, FieldMemOperand(array, JSObject::kElementsOffset)); + __ StoreNumberToDoubleElements(value, index_smi, x10, x11, d0, + &slow_elements); + __ Ret(); +} + + +void StubFailureTrampolineStub::Generate(MacroAssembler* masm) { + CEntryStub ces(isolate(), 1, kSaveFPRegs); + __ Call(ces.GetCode(), RelocInfo::CODE_TARGET); + int parameter_count_offset = + StubFailureTrampolineFrame::kCallerStackParameterCountFrameOffset; + __ Ldr(x1, MemOperand(fp, parameter_count_offset)); + if (function_mode_ == JS_FUNCTION_STUB_MODE) { + __ Add(x1, x1, 1); + } + masm->LeaveFrame(StackFrame::STUB_FAILURE_TRAMPOLINE); + __ Drop(x1); + // Return to IC Miss stub, continuation still on stack. + __ Ret(); +} + + +static unsigned int GetProfileEntryHookCallSize(MacroAssembler* masm) { + // The entry hook is a "BumpSystemStackPointer" instruction (sub), + // followed by a "Push lr" instruction, followed by a call. + unsigned int size = + Assembler::kCallSizeWithRelocation + (2 * kInstructionSize); + if (CpuFeatures::IsSupported(ALWAYS_ALIGN_CSP)) { + // If ALWAYS_ALIGN_CSP then there will be an extra bic instruction in + // "BumpSystemStackPointer". + size += kInstructionSize; + } + return size; +} + + +void ProfileEntryHookStub::MaybeCallEntryHook(MacroAssembler* masm) { + if (masm->isolate()->function_entry_hook() != NULL) { + ProfileEntryHookStub stub(masm->isolate()); + Assembler::BlockConstPoolScope no_const_pools(masm); + DontEmitDebugCodeScope no_debug_code(masm); + Label entry_hook_call_start; + __ Bind(&entry_hook_call_start); + __ Push(lr); + __ CallStub(&stub); + ASSERT(masm->SizeOfCodeGeneratedSince(&entry_hook_call_start) == + GetProfileEntryHookCallSize(masm)); + + __ Pop(lr); + } +} + + +void ProfileEntryHookStub::Generate(MacroAssembler* masm) { + MacroAssembler::NoUseRealAbortsScope no_use_real_aborts(masm); + + // Save all kCallerSaved registers (including lr), since this can be called + // from anywhere. + // TODO(jbramley): What about FP registers? + __ PushCPURegList(kCallerSaved); + ASSERT(kCallerSaved.IncludesAliasOf(lr)); + const int kNumSavedRegs = kCallerSaved.Count(); + + // Compute the function's address as the first argument. + __ Sub(x0, lr, GetProfileEntryHookCallSize(masm)); + +#if V8_HOST_ARCH_ARM64 + uintptr_t entry_hook = + reinterpret_cast<uintptr_t>(isolate()->function_entry_hook()); + __ Mov(x10, entry_hook); +#else + // Under the simulator we need to indirect the entry hook through a trampoline + // function at a known address. + ApiFunction dispatcher(FUNCTION_ADDR(EntryHookTrampoline)); + __ Mov(x10, Operand(ExternalReference(&dispatcher, + ExternalReference::BUILTIN_CALL, + isolate()))); + // It additionally takes an isolate as a third parameter + __ Mov(x2, ExternalReference::isolate_address(isolate())); +#endif + + // The caller's return address is above the saved temporaries. + // Grab its location for the second argument to the hook. + __ Add(x1, __ StackPointer(), kNumSavedRegs * kPointerSize); + + { + // Create a dummy frame, as CallCFunction requires this. + FrameScope frame(masm, StackFrame::MANUAL); + __ CallCFunction(x10, 2, 0); + } + + __ PopCPURegList(kCallerSaved); + __ Ret(); +} + + +void DirectCEntryStub::Generate(MacroAssembler* masm) { + // When calling into C++ code the stack pointer must be csp. + // Therefore this code must use csp for peek/poke operations when the + // stub is generated. When the stub is called + // (via DirectCEntryStub::GenerateCall), the caller must setup an ExitFrame + // and configure the stack pointer *before* doing the call. + const Register old_stack_pointer = __ StackPointer(); + __ SetStackPointer(csp); + + // Put return address on the stack (accessible to GC through exit frame pc). + __ Poke(lr, 0); + // Call the C++ function. + __ Blr(x10); + // Return to calling code. + __ Peek(lr, 0); + __ AssertFPCRState(); + __ Ret(); + + __ SetStackPointer(old_stack_pointer); +} + +void DirectCEntryStub::GenerateCall(MacroAssembler* masm, + Register target) { + // Make sure the caller configured the stack pointer (see comment in + // DirectCEntryStub::Generate). + ASSERT(csp.Is(__ StackPointer())); + + intptr_t code = + reinterpret_cast<intptr_t>(GetCode().location()); + __ Mov(lr, Operand(code, RelocInfo::CODE_TARGET)); + __ Mov(x10, target); + // Branch to the stub. + __ Blr(lr); +} + + +// Probe the name dictionary in the 'elements' register. +// Jump to the 'done' label if a property with the given name is found. +// Jump to the 'miss' label otherwise. +// +// If lookup was successful 'scratch2' will be equal to elements + 4 * index. +// 'elements' and 'name' registers are preserved on miss. +void NameDictionaryLookupStub::GeneratePositiveLookup( + MacroAssembler* masm, + Label* miss, + Label* done, + Register elements, + Register name, + Register scratch1, + Register scratch2) { + ASSERT(!AreAliased(elements, name, scratch1, scratch2)); + + // Assert that name contains a string. + __ AssertName(name); + + // Compute the capacity mask. + __ Ldrsw(scratch1, UntagSmiFieldMemOperand(elements, kCapacityOffset)); + __ Sub(scratch1, scratch1, 1); + + // Generate an unrolled loop that performs a few probes before giving up. + for (int i = 0; i < kInlinedProbes; i++) { + // Compute the masked index: (hash + i + i * i) & mask. + __ Ldr(scratch2, FieldMemOperand(name, Name::kHashFieldOffset)); + if (i > 0) { + // Add the probe offset (i + i * i) left shifted to avoid right shifting + // the hash in a separate instruction. The value hash + i + i * i is right + // shifted in the following and instruction. + ASSERT(NameDictionary::GetProbeOffset(i) < + 1 << (32 - Name::kHashFieldOffset)); + __ Add(scratch2, scratch2, Operand( + NameDictionary::GetProbeOffset(i) << Name::kHashShift)); + } + __ And(scratch2, scratch1, Operand(scratch2, LSR, Name::kHashShift)); + + // Scale the index by multiplying by the element size. + ASSERT(NameDictionary::kEntrySize == 3); + __ Add(scratch2, scratch2, Operand(scratch2, LSL, 1)); + + // Check if the key is identical to the name. + UseScratchRegisterScope temps(masm); + Register scratch3 = temps.AcquireX(); + __ Add(scratch2, elements, Operand(scratch2, LSL, kPointerSizeLog2)); + __ Ldr(scratch3, FieldMemOperand(scratch2, kElementsStartOffset)); + __ Cmp(name, scratch3); + __ B(eq, done); + } + + // The inlined probes didn't find the entry. + // Call the complete stub to scan the whole dictionary. + + CPURegList spill_list(CPURegister::kRegister, kXRegSizeInBits, 0, 6); + spill_list.Combine(lr); + spill_list.Remove(scratch1); + spill_list.Remove(scratch2); + + __ PushCPURegList(spill_list); + + if (name.is(x0)) { + ASSERT(!elements.is(x1)); + __ Mov(x1, name); + __ Mov(x0, elements); + } else { + __ Mov(x0, elements); + __ Mov(x1, name); + } + + Label not_found; + NameDictionaryLookupStub stub(masm->isolate(), POSITIVE_LOOKUP); + __ CallStub(&stub); + __ Cbz(x0, ¬_found); + __ Mov(scratch2, x2); // Move entry index into scratch2. + __ PopCPURegList(spill_list); + __ B(done); + + __ Bind(¬_found); + __ PopCPURegList(spill_list); + __ B(miss); +} + + +void NameDictionaryLookupStub::GenerateNegativeLookup(MacroAssembler* masm, + Label* miss, + Label* done, + Register receiver, + Register properties, + Handle<Name> name, + Register scratch0) { + ASSERT(!AreAliased(receiver, properties, scratch0)); + ASSERT(name->IsUniqueName()); + // If names of slots in range from 1 to kProbes - 1 for the hash value are + // not equal to the name and kProbes-th slot is not used (its name is the + // undefined value), it guarantees the hash table doesn't contain the + // property. It's true even if some slots represent deleted properties + // (their names are the hole value). + for (int i = 0; i < kInlinedProbes; i++) { + // scratch0 points to properties hash. + // Compute the masked index: (hash + i + i * i) & mask. + Register index = scratch0; + // Capacity is smi 2^n. + __ Ldrsw(index, UntagSmiFieldMemOperand(properties, kCapacityOffset)); + __ Sub(index, index, 1); + __ And(index, index, name->Hash() + NameDictionary::GetProbeOffset(i)); + + // Scale the index by multiplying by the entry size. + ASSERT(NameDictionary::kEntrySize == 3); + __ Add(index, index, Operand(index, LSL, 1)); // index *= 3. + + Register entity_name = scratch0; + // Having undefined at this place means the name is not contained. + Register tmp = index; + __ Add(tmp, properties, Operand(index, LSL, kPointerSizeLog2)); + __ Ldr(entity_name, FieldMemOperand(tmp, kElementsStartOffset)); + + __ JumpIfRoot(entity_name, Heap::kUndefinedValueRootIndex, done); + + // Stop if found the property. + __ Cmp(entity_name, Operand(name)); + __ B(eq, miss); + + Label good; + __ JumpIfRoot(entity_name, Heap::kTheHoleValueRootIndex, &good); + + // Check if the entry name is not a unique name. + __ Ldr(entity_name, FieldMemOperand(entity_name, HeapObject::kMapOffset)); + __ Ldrb(entity_name, + FieldMemOperand(entity_name, Map::kInstanceTypeOffset)); + __ JumpIfNotUniqueName(entity_name, miss); + __ Bind(&good); + } + + CPURegList spill_list(CPURegister::kRegister, kXRegSizeInBits, 0, 6); + spill_list.Combine(lr); + spill_list.Remove(scratch0); // Scratch registers don't need to be preserved. + + __ PushCPURegList(spill_list); + + __ Ldr(x0, FieldMemOperand(receiver, JSObject::kPropertiesOffset)); + __ Mov(x1, Operand(name)); + NameDictionaryLookupStub stub(masm->isolate(), NEGATIVE_LOOKUP); + __ CallStub(&stub); + // Move stub return value to scratch0. Note that scratch0 is not included in + // spill_list and won't be clobbered by PopCPURegList. + __ Mov(scratch0, x0); + __ PopCPURegList(spill_list); + + __ Cbz(scratch0, done); + __ B(miss); +} + + +void NameDictionaryLookupStub::Generate(MacroAssembler* masm) { + // This stub overrides SometimesSetsUpAFrame() to return false. That means + // we cannot call anything that could cause a GC from this stub. + // + // Arguments are in x0 and x1: + // x0: property dictionary. + // x1: the name of the property we are looking for. + // + // Return value is in x0 and is zero if lookup failed, non zero otherwise. + // If the lookup is successful, x2 will contains the index of the entry. + + Register result = x0; + Register dictionary = x0; + Register key = x1; + Register index = x2; + Register mask = x3; + Register hash = x4; + Register undefined = x5; + Register entry_key = x6; + + Label in_dictionary, maybe_in_dictionary, not_in_dictionary; + + __ Ldrsw(mask, UntagSmiFieldMemOperand(dictionary, kCapacityOffset)); + __ Sub(mask, mask, 1); + + __ Ldr(hash, FieldMemOperand(key, Name::kHashFieldOffset)); + __ LoadRoot(undefined, Heap::kUndefinedValueRootIndex); + + for (int i = kInlinedProbes; i < kTotalProbes; i++) { + // Compute the masked index: (hash + i + i * i) & mask. + // Capacity is smi 2^n. + if (i > 0) { + // Add the probe offset (i + i * i) left shifted to avoid right shifting + // the hash in a separate instruction. The value hash + i + i * i is right + // shifted in the following and instruction. + ASSERT(NameDictionary::GetProbeOffset(i) < + 1 << (32 - Name::kHashFieldOffset)); + __ Add(index, hash, + NameDictionary::GetProbeOffset(i) << Name::kHashShift); + } else { + __ Mov(index, hash); + } + __ And(index, mask, Operand(index, LSR, Name::kHashShift)); + + // Scale the index by multiplying by the entry size. + ASSERT(NameDictionary::kEntrySize == 3); + __ Add(index, index, Operand(index, LSL, 1)); // index *= 3. + + __ Add(index, dictionary, Operand(index, LSL, kPointerSizeLog2)); + __ Ldr(entry_key, FieldMemOperand(index, kElementsStartOffset)); + + // Having undefined at this place means the name is not contained. + __ Cmp(entry_key, undefined); + __ B(eq, ¬_in_dictionary); + + // Stop if found the property. + __ Cmp(entry_key, key); + __ B(eq, &in_dictionary); + + if (i != kTotalProbes - 1 && mode_ == NEGATIVE_LOOKUP) { + // Check if the entry name is not a unique name. + __ Ldr(entry_key, FieldMemOperand(entry_key, HeapObject::kMapOffset)); + __ Ldrb(entry_key, FieldMemOperand(entry_key, Map::kInstanceTypeOffset)); + __ JumpIfNotUniqueName(entry_key, &maybe_in_dictionary); + } + } + + __ Bind(&maybe_in_dictionary); + // If we are doing negative lookup then probing failure should be + // treated as a lookup success. For positive lookup, probing failure + // should be treated as lookup failure. + if (mode_ == POSITIVE_LOOKUP) { + __ Mov(result, 0); + __ Ret(); + } + + __ Bind(&in_dictionary); + __ Mov(result, 1); + __ Ret(); + + __ Bind(¬_in_dictionary); + __ Mov(result, 0); + __ Ret(); +} + + +template<class T> +static void CreateArrayDispatch(MacroAssembler* masm, + AllocationSiteOverrideMode mode) { + ASM_LOCATION("CreateArrayDispatch"); + if (mode == DISABLE_ALLOCATION_SITES) { + T stub(masm->isolate(), GetInitialFastElementsKind(), mode); + __ TailCallStub(&stub); + + } else if (mode == DONT_OVERRIDE) { + Register kind = x3; + int last_index = + GetSequenceIndexFromFastElementsKind(TERMINAL_FAST_ELEMENTS_KIND); + for (int i = 0; i <= last_index; ++i) { + Label next; + ElementsKind candidate_kind = GetFastElementsKindFromSequenceIndex(i); + // TODO(jbramley): Is this the best way to handle this? Can we make the + // tail calls conditional, rather than hopping over each one? + __ CompareAndBranch(kind, candidate_kind, ne, &next); + T stub(masm->isolate(), candidate_kind); + __ TailCallStub(&stub); + __ Bind(&next); + } + + // If we reached this point there is a problem. + __ Abort(kUnexpectedElementsKindInArrayConstructor); + + } else { + UNREACHABLE(); + } +} + + +// TODO(jbramley): If this needs to be a special case, make it a proper template +// specialization, and not a separate function. +static void CreateArrayDispatchOneArgument(MacroAssembler* masm, + AllocationSiteOverrideMode mode) { + ASM_LOCATION("CreateArrayDispatchOneArgument"); + // x0 - argc + // x1 - constructor? + // x2 - allocation site (if mode != DISABLE_ALLOCATION_SITES) + // x3 - kind (if mode != DISABLE_ALLOCATION_SITES) + // sp[0] - last argument + + Register allocation_site = x2; + Register kind = x3; + + Label normal_sequence; + if (mode == DONT_OVERRIDE) { + STATIC_ASSERT(FAST_SMI_ELEMENTS == 0); + STATIC_ASSERT(FAST_HOLEY_SMI_ELEMENTS == 1); + STATIC_ASSERT(FAST_ELEMENTS == 2); + STATIC_ASSERT(FAST_HOLEY_ELEMENTS == 3); + STATIC_ASSERT(FAST_DOUBLE_ELEMENTS == 4); + STATIC_ASSERT(FAST_HOLEY_DOUBLE_ELEMENTS == 5); + + // Is the low bit set? If so, the array is holey. + __ Tbnz(kind, 0, &normal_sequence); + } + + // Look at the last argument. + // TODO(jbramley): What does a 0 argument represent? + __ Peek(x10, 0); + __ Cbz(x10, &normal_sequence); + + if (mode == DISABLE_ALLOCATION_SITES) { + ElementsKind initial = GetInitialFastElementsKind(); + ElementsKind holey_initial = GetHoleyElementsKind(initial); + + ArraySingleArgumentConstructorStub stub_holey(masm->isolate(), + holey_initial, + DISABLE_ALLOCATION_SITES); + __ TailCallStub(&stub_holey); + + __ Bind(&normal_sequence); + ArraySingleArgumentConstructorStub stub(masm->isolate(), + initial, + DISABLE_ALLOCATION_SITES); + __ TailCallStub(&stub); + } else if (mode == DONT_OVERRIDE) { + // We are going to create a holey array, but our kind is non-holey. + // Fix kind and retry (only if we have an allocation site in the slot). + __ Orr(kind, kind, 1); + + if (FLAG_debug_code) { + __ Ldr(x10, FieldMemOperand(allocation_site, 0)); + __ JumpIfNotRoot(x10, Heap::kAllocationSiteMapRootIndex, + &normal_sequence); + __ Assert(eq, kExpectedAllocationSite); + } + + // Save the resulting elements kind in type info. We can't just store 'kind' + // in the AllocationSite::transition_info field because elements kind is + // restricted to a portion of the field; upper bits need to be left alone. + STATIC_ASSERT(AllocationSite::ElementsKindBits::kShift == 0); + __ Ldr(x11, FieldMemOperand(allocation_site, + AllocationSite::kTransitionInfoOffset)); + __ Add(x11, x11, Smi::FromInt(kFastElementsKindPackedToHoley)); + __ Str(x11, FieldMemOperand(allocation_site, + AllocationSite::kTransitionInfoOffset)); + + __ Bind(&normal_sequence); + int last_index = + GetSequenceIndexFromFastElementsKind(TERMINAL_FAST_ELEMENTS_KIND); + for (int i = 0; i <= last_index; ++i) { + Label next; + ElementsKind candidate_kind = GetFastElementsKindFromSequenceIndex(i); + __ CompareAndBranch(kind, candidate_kind, ne, &next); + ArraySingleArgumentConstructorStub stub(masm->isolate(), candidate_kind); + __ TailCallStub(&stub); + __ Bind(&next); + } + + // If we reached this point there is a problem. + __ Abort(kUnexpectedElementsKindInArrayConstructor); + } else { + UNREACHABLE(); + } +} + + +template<class T> +static void ArrayConstructorStubAheadOfTimeHelper(Isolate* isolate) { + int to_index = GetSequenceIndexFromFastElementsKind( + TERMINAL_FAST_ELEMENTS_KIND); + for (int i = 0; i <= to_index; ++i) { + ElementsKind kind = GetFastElementsKindFromSequenceIndex(i); + T stub(isolate, kind); + stub.GetCode(); + if (AllocationSite::GetMode(kind) != DONT_TRACK_ALLOCATION_SITE) { + T stub1(isolate, kind, DISABLE_ALLOCATION_SITES); + stub1.GetCode(); + } + } +} + + +void ArrayConstructorStubBase::GenerateStubsAheadOfTime(Isolate* isolate) { + ArrayConstructorStubAheadOfTimeHelper<ArrayNoArgumentConstructorStub>( + isolate); + ArrayConstructorStubAheadOfTimeHelper<ArraySingleArgumentConstructorStub>( + isolate); + ArrayConstructorStubAheadOfTimeHelper<ArrayNArgumentsConstructorStub>( + isolate); +} + + +void InternalArrayConstructorStubBase::GenerateStubsAheadOfTime( + Isolate* isolate) { + ElementsKind kinds[2] = { FAST_ELEMENTS, FAST_HOLEY_ELEMENTS }; + for (int i = 0; i < 2; i++) { + // For internal arrays we only need a few things + InternalArrayNoArgumentConstructorStub stubh1(isolate, kinds[i]); + stubh1.GetCode(); + InternalArraySingleArgumentConstructorStub stubh2(isolate, kinds[i]); + stubh2.GetCode(); + InternalArrayNArgumentsConstructorStub stubh3(isolate, kinds[i]); + stubh3.GetCode(); + } +} + + +void ArrayConstructorStub::GenerateDispatchToArrayStub( + MacroAssembler* masm, + AllocationSiteOverrideMode mode) { + Register argc = x0; + if (argument_count_ == ANY) { + Label zero_case, n_case; + __ Cbz(argc, &zero_case); + __ Cmp(argc, 1); + __ B(ne, &n_case); + + // One argument. + CreateArrayDispatchOneArgument(masm, mode); + + __ Bind(&zero_case); + // No arguments. + CreateArrayDispatch<ArrayNoArgumentConstructorStub>(masm, mode); + + __ Bind(&n_case); + // N arguments. + CreateArrayDispatch<ArrayNArgumentsConstructorStub>(masm, mode); + + } else if (argument_count_ == NONE) { + CreateArrayDispatch<ArrayNoArgumentConstructorStub>(masm, mode); + } else if (argument_count_ == ONE) { + CreateArrayDispatchOneArgument(masm, mode); + } else if (argument_count_ == MORE_THAN_ONE) { + CreateArrayDispatch<ArrayNArgumentsConstructorStub>(masm, mode); + } else { + UNREACHABLE(); + } +} + + +void ArrayConstructorStub::Generate(MacroAssembler* masm) { + ASM_LOCATION("ArrayConstructorStub::Generate"); + // ----------- S t a t e ------------- + // -- x0 : argc (only if argument_count_ == ANY) + // -- x1 : constructor + // -- x2 : AllocationSite or undefined + // -- sp[0] : return address + // -- sp[4] : last argument + // ----------------------------------- + Register constructor = x1; + Register allocation_site = x2; + + if (FLAG_debug_code) { + // The array construct code is only set for the global and natives + // builtin Array functions which always have maps. + + Label unexpected_map, map_ok; + // Initial map for the builtin Array function should be a map. + __ Ldr(x10, FieldMemOperand(constructor, + JSFunction::kPrototypeOrInitialMapOffset)); + // Will both indicate a NULL and a Smi. + __ JumpIfSmi(x10, &unexpected_map); + __ JumpIfObjectType(x10, x10, x11, MAP_TYPE, &map_ok); + __ Bind(&unexpected_map); + __ Abort(kUnexpectedInitialMapForArrayFunction); + __ Bind(&map_ok); + + // We should either have undefined in the allocation_site register or a + // valid AllocationSite. + __ AssertUndefinedOrAllocationSite(allocation_site, x10); + } + + Register kind = x3; + Label no_info; + // Get the elements kind and case on that. + __ JumpIfRoot(allocation_site, Heap::kUndefinedValueRootIndex, &no_info); + + __ Ldrsw(kind, + UntagSmiFieldMemOperand(allocation_site, + AllocationSite::kTransitionInfoOffset)); + __ And(kind, kind, AllocationSite::ElementsKindBits::kMask); + GenerateDispatchToArrayStub(masm, DONT_OVERRIDE); + + __ Bind(&no_info); + GenerateDispatchToArrayStub(masm, DISABLE_ALLOCATION_SITES); +} + + +void InternalArrayConstructorStub::GenerateCase( + MacroAssembler* masm, ElementsKind kind) { + Label zero_case, n_case; + Register argc = x0; + + __ Cbz(argc, &zero_case); + __ CompareAndBranch(argc, 1, ne, &n_case); + + // One argument. + if (IsFastPackedElementsKind(kind)) { + Label packed_case; + + // We might need to create a holey array; look at the first argument. + __ Peek(x10, 0); + __ Cbz(x10, &packed_case); + + InternalArraySingleArgumentConstructorStub + stub1_holey(isolate(), GetHoleyElementsKind(kind)); + __ TailCallStub(&stub1_holey); + + __ Bind(&packed_case); + } + InternalArraySingleArgumentConstructorStub stub1(isolate(), kind); + __ TailCallStub(&stub1); + + __ Bind(&zero_case); + // No arguments. + InternalArrayNoArgumentConstructorStub stub0(isolate(), kind); + __ TailCallStub(&stub0); + + __ Bind(&n_case); + // N arguments. + InternalArrayNArgumentsConstructorStub stubN(isolate(), kind); + __ TailCallStub(&stubN); +} + + +void InternalArrayConstructorStub::Generate(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : argc + // -- x1 : constructor + // -- sp[0] : return address + // -- sp[4] : last argument + // ----------------------------------- + + Register constructor = x1; + + if (FLAG_debug_code) { + // The array construct code is only set for the global and natives + // builtin Array functions which always have maps. + + Label unexpected_map, map_ok; + // Initial map for the builtin Array function should be a map. + __ Ldr(x10, FieldMemOperand(constructor, + JSFunction::kPrototypeOrInitialMapOffset)); + // Will both indicate a NULL and a Smi. + __ JumpIfSmi(x10, &unexpected_map); + __ JumpIfObjectType(x10, x10, x11, MAP_TYPE, &map_ok); + __ Bind(&unexpected_map); + __ Abort(kUnexpectedInitialMapForArrayFunction); + __ Bind(&map_ok); + } + + Register kind = w3; + // Figure out the right elements kind + __ Ldr(x10, FieldMemOperand(constructor, + JSFunction::kPrototypeOrInitialMapOffset)); + + // Retrieve elements_kind from map. + __ LoadElementsKindFromMap(kind, x10); + + if (FLAG_debug_code) { + Label done; + __ Cmp(x3, FAST_ELEMENTS); + __ Ccmp(x3, FAST_HOLEY_ELEMENTS, ZFlag, ne); + __ Assert(eq, kInvalidElementsKindForInternalArrayOrInternalPackedArray); + } + + Label fast_elements_case; + __ CompareAndBranch(kind, FAST_ELEMENTS, eq, &fast_elements_case); + GenerateCase(masm, FAST_HOLEY_ELEMENTS); + + __ Bind(&fast_elements_case); + GenerateCase(masm, FAST_ELEMENTS); +} + + +void CallApiFunctionStub::Generate(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : callee + // -- x4 : call_data + // -- x2 : holder + // -- x1 : api_function_address + // -- cp : context + // -- + // -- sp[0] : last argument + // -- ... + // -- sp[(argc - 1) * 8] : first argument + // -- sp[argc * 8] : receiver + // ----------------------------------- + + Register callee = x0; + Register call_data = x4; + Register holder = x2; + Register api_function_address = x1; + Register context = cp; + + int argc = ArgumentBits::decode(bit_field_); + bool is_store = IsStoreBits::decode(bit_field_); + bool call_data_undefined = CallDataUndefinedBits::decode(bit_field_); + + typedef FunctionCallbackArguments FCA; + + STATIC_ASSERT(FCA::kContextSaveIndex == 6); + STATIC_ASSERT(FCA::kCalleeIndex == 5); + STATIC_ASSERT(FCA::kDataIndex == 4); + STATIC_ASSERT(FCA::kReturnValueOffset == 3); + STATIC_ASSERT(FCA::kReturnValueDefaultValueIndex == 2); + STATIC_ASSERT(FCA::kIsolateIndex == 1); + STATIC_ASSERT(FCA::kHolderIndex == 0); + STATIC_ASSERT(FCA::kArgsLength == 7); + + // FunctionCallbackArguments: context, callee and call data. + __ Push(context, callee, call_data); + + // Load context from callee + __ Ldr(context, FieldMemOperand(callee, JSFunction::kContextOffset)); + + if (!call_data_undefined) { + __ LoadRoot(call_data, Heap::kUndefinedValueRootIndex); + } + Register isolate_reg = x5; + __ Mov(isolate_reg, ExternalReference::isolate_address(isolate())); + + // FunctionCallbackArguments: + // return value, return value default, isolate, holder. + __ Push(call_data, call_data, isolate_reg, holder); + + // Prepare arguments. + Register args = x6; + __ Mov(args, masm->StackPointer()); + + // Allocate the v8::Arguments structure in the arguments' space, since it's + // not controlled by GC. + const int kApiStackSpace = 4; + + // Allocate space for CallApiFunctionAndReturn can store some scratch + // registeres on the stack. + const int kCallApiFunctionSpillSpace = 4; + + FrameScope frame_scope(masm, StackFrame::MANUAL); + __ EnterExitFrame(false, x10, kApiStackSpace + kCallApiFunctionSpillSpace); + + ASSERT(!AreAliased(x0, api_function_address)); + // x0 = FunctionCallbackInfo& + // Arguments is after the return address. + __ Add(x0, masm->StackPointer(), 1 * kPointerSize); + // FunctionCallbackInfo::implicit_args_ and FunctionCallbackInfo::values_ + __ Add(x10, args, Operand((FCA::kArgsLength - 1 + argc) * kPointerSize)); + __ Stp(args, x10, MemOperand(x0, 0 * kPointerSize)); + // FunctionCallbackInfo::length_ = argc and + // FunctionCallbackInfo::is_construct_call = 0 + __ Mov(x10, argc); + __ Stp(x10, xzr, MemOperand(x0, 2 * kPointerSize)); + + const int kStackUnwindSpace = argc + FCA::kArgsLength + 1; + ExternalReference thunk_ref = + ExternalReference::invoke_function_callback(isolate()); + + AllowExternalCallThatCantCauseGC scope(masm); + MemOperand context_restore_operand( + fp, (2 + FCA::kContextSaveIndex) * kPointerSize); + // Stores return the first js argument + int return_value_offset = 0; + if (is_store) { + return_value_offset = 2 + FCA::kArgsLength; + } else { + return_value_offset = 2 + FCA::kReturnValueOffset; + } + MemOperand return_value_operand(fp, return_value_offset * kPointerSize); + + const int spill_offset = 1 + kApiStackSpace; + __ CallApiFunctionAndReturn(api_function_address, + thunk_ref, + kStackUnwindSpace, + spill_offset, + return_value_operand, + &context_restore_operand); +} + + +void CallApiGetterStub::Generate(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- sp[0] : name + // -- sp[8 - kArgsLength*8] : PropertyCallbackArguments object + // -- ... + // -- x2 : api_function_address + // ----------------------------------- + + Register api_function_address = x2; + + __ Mov(x0, masm->StackPointer()); // x0 = Handle<Name> + __ Add(x1, x0, 1 * kPointerSize); // x1 = PCA + + const int kApiStackSpace = 1; + + // Allocate space for CallApiFunctionAndReturn can store some scratch + // registeres on the stack. + const int kCallApiFunctionSpillSpace = 4; + + FrameScope frame_scope(masm, StackFrame::MANUAL); + __ EnterExitFrame(false, x10, kApiStackSpace + kCallApiFunctionSpillSpace); + + // Create PropertyAccessorInfo instance on the stack above the exit frame with + // x1 (internal::Object** args_) as the data. + __ Poke(x1, 1 * kPointerSize); + __ Add(x1, masm->StackPointer(), 1 * kPointerSize); // x1 = AccessorInfo& + + const int kStackUnwindSpace = PropertyCallbackArguments::kArgsLength + 1; + + ExternalReference thunk_ref = + ExternalReference::invoke_accessor_getter_callback(isolate()); + + const int spill_offset = 1 + kApiStackSpace; + __ CallApiFunctionAndReturn(api_function_address, + thunk_ref, + kStackUnwindSpace, + spill_offset, + MemOperand(fp, 6 * kPointerSize), + NULL); +} + + +#undef __ + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/code-stubs-arm64.h b/chromium/v8/src/arm64/code-stubs-arm64.h new file mode 100644 index 00000000000..6baf96989ad --- /dev/null +++ b/chromium/v8/src/arm64/code-stubs-arm64.h @@ -0,0 +1,478 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_CODE_STUBS_ARM64_H_ +#define V8_ARM64_CODE_STUBS_ARM64_H_ + +#include "src/ic-inl.h" + +namespace v8 { +namespace internal { + + +void ArrayNativeCode(MacroAssembler* masm, Label* call_generic_code); + + +class StoreBufferOverflowStub: public PlatformCodeStub { + public: + StoreBufferOverflowStub(Isolate* isolate, SaveFPRegsMode save_fp) + : PlatformCodeStub(isolate), save_doubles_(save_fp) { } + + void Generate(MacroAssembler* masm); + + static void GenerateFixedRegStubsAheadOfTime(Isolate* isolate); + virtual bool SometimesSetsUpAFrame() { return false; } + + private: + SaveFPRegsMode save_doubles_; + + Major MajorKey() { return StoreBufferOverflow; } + int MinorKey() { return (save_doubles_ == kSaveFPRegs) ? 1 : 0; } +}; + + +class StringHelper : public AllStatic { + public: + // TODO(all): These don't seem to be used any more. Delete them. + + // Generate string hash. + static void GenerateHashInit(MacroAssembler* masm, + Register hash, + Register character); + + static void GenerateHashAddCharacter(MacroAssembler* masm, + Register hash, + Register character); + + static void GenerateHashGetHash(MacroAssembler* masm, + Register hash, + Register scratch); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(StringHelper); +}; + + +class StoreRegistersStateStub: public PlatformCodeStub { + public: + StoreRegistersStateStub(Isolate* isolate, SaveFPRegsMode with_fp) + : PlatformCodeStub(isolate), save_doubles_(with_fp) {} + + static Register to_be_pushed_lr() { return ip0; } + static void GenerateAheadOfTime(Isolate* isolate); + private: + Major MajorKey() { return StoreRegistersState; } + int MinorKey() { return (save_doubles_ == kSaveFPRegs) ? 1 : 0; } + SaveFPRegsMode save_doubles_; + + void Generate(MacroAssembler* masm); +}; + + +class RestoreRegistersStateStub: public PlatformCodeStub { + public: + RestoreRegistersStateStub(Isolate* isolate, SaveFPRegsMode with_fp) + : PlatformCodeStub(isolate), save_doubles_(with_fp) {} + + static void GenerateAheadOfTime(Isolate* isolate); + private: + Major MajorKey() { return RestoreRegistersState; } + int MinorKey() { return (save_doubles_ == kSaveFPRegs) ? 1 : 0; } + SaveFPRegsMode save_doubles_; + + void Generate(MacroAssembler* masm); +}; + + +class RecordWriteStub: public PlatformCodeStub { + public: + // Stub to record the write of 'value' at 'address' in 'object'. + // Typically 'address' = 'object' + <some offset>. + // See MacroAssembler::RecordWriteField() for example. + RecordWriteStub(Isolate* isolate, + Register object, + Register value, + Register address, + RememberedSetAction remembered_set_action, + SaveFPRegsMode fp_mode) + : PlatformCodeStub(isolate), + object_(object), + value_(value), + address_(address), + remembered_set_action_(remembered_set_action), + save_fp_regs_mode_(fp_mode), + regs_(object, // An input reg. + address, // An input reg. + value) { // One scratch reg. + } + + enum Mode { + STORE_BUFFER_ONLY, + INCREMENTAL, + INCREMENTAL_COMPACTION + }; + + virtual bool SometimesSetsUpAFrame() { return false; } + + static Mode GetMode(Code* stub) { + // Find the mode depending on the first two instructions. + Instruction* instr1 = + reinterpret_cast<Instruction*>(stub->instruction_start()); + Instruction* instr2 = instr1->following(); + + if (instr1->IsUncondBranchImm()) { + ASSERT(instr2->IsPCRelAddressing() && (instr2->Rd() == xzr.code())); + return INCREMENTAL; + } + + ASSERT(instr1->IsPCRelAddressing() && (instr1->Rd() == xzr.code())); + + if (instr2->IsUncondBranchImm()) { + return INCREMENTAL_COMPACTION; + } + + ASSERT(instr2->IsPCRelAddressing()); + + return STORE_BUFFER_ONLY; + } + + // We patch the two first instructions of the stub back and forth between an + // adr and branch when we start and stop incremental heap marking. + // The branch is + // b label + // The adr is + // adr xzr label + // so effectively a nop. + static void Patch(Code* stub, Mode mode) { + // We are going to patch the two first instructions of the stub. + PatchingAssembler patcher( + reinterpret_cast<Instruction*>(stub->instruction_start()), 2); + Instruction* instr1 = patcher.InstructionAt(0); + Instruction* instr2 = patcher.InstructionAt(kInstructionSize); + // Instructions must be either 'adr' or 'b'. + ASSERT(instr1->IsPCRelAddressing() || instr1->IsUncondBranchImm()); + ASSERT(instr2->IsPCRelAddressing() || instr2->IsUncondBranchImm()); + // Retrieve the offsets to the labels. + int32_t offset_to_incremental_noncompacting = instr1->ImmPCOffset(); + int32_t offset_to_incremental_compacting = instr2->ImmPCOffset(); + + switch (mode) { + case STORE_BUFFER_ONLY: + ASSERT(GetMode(stub) == INCREMENTAL || + GetMode(stub) == INCREMENTAL_COMPACTION); + patcher.adr(xzr, offset_to_incremental_noncompacting); + patcher.adr(xzr, offset_to_incremental_compacting); + break; + case INCREMENTAL: + ASSERT(GetMode(stub) == STORE_BUFFER_ONLY); + patcher.b(offset_to_incremental_noncompacting >> kInstructionSizeLog2); + patcher.adr(xzr, offset_to_incremental_compacting); + break; + case INCREMENTAL_COMPACTION: + ASSERT(GetMode(stub) == STORE_BUFFER_ONLY); + patcher.adr(xzr, offset_to_incremental_noncompacting); + patcher.b(offset_to_incremental_compacting >> kInstructionSizeLog2); + break; + } + ASSERT(GetMode(stub) == mode); + } + + private: + // This is a helper class to manage the registers associated with the stub. + // The 'object' and 'address' registers must be preserved. + class RegisterAllocation { + public: + RegisterAllocation(Register object, + Register address, + Register scratch) + : object_(object), + address_(address), + scratch0_(scratch), + saved_regs_(kCallerSaved), + saved_fp_regs_(kCallerSavedFP) { + ASSERT(!AreAliased(scratch, object, address)); + + // The SaveCallerSaveRegisters method needs to save caller-saved + // registers, but we don't bother saving MacroAssembler scratch registers. + saved_regs_.Remove(MacroAssembler::DefaultTmpList()); + saved_fp_regs_.Remove(MacroAssembler::DefaultFPTmpList()); + + // We would like to require more scratch registers for this stub, + // but the number of registers comes down to the ones used in + // FullCodeGen::SetVar(), which is architecture independent. + // We allocate 2 extra scratch registers that we'll save on the stack. + CPURegList pool_available = GetValidRegistersForAllocation(); + CPURegList used_regs(object, address, scratch); + pool_available.Remove(used_regs); + scratch1_ = Register(pool_available.PopLowestIndex()); + scratch2_ = Register(pool_available.PopLowestIndex()); + + // The scratch registers will be restored by other means so we don't need + // to save them with the other caller saved registers. + saved_regs_.Remove(scratch0_); + saved_regs_.Remove(scratch1_); + saved_regs_.Remove(scratch2_); + } + + void Save(MacroAssembler* masm) { + // We don't have to save scratch0_ because it was given to us as + // a scratch register. + masm->Push(scratch1_, scratch2_); + } + + void Restore(MacroAssembler* masm) { + masm->Pop(scratch2_, scratch1_); + } + + // If we have to call into C then we need to save and restore all caller- + // saved registers that were not already preserved. + void SaveCallerSaveRegisters(MacroAssembler* masm, SaveFPRegsMode mode) { + // TODO(all): This can be very expensive, and it is likely that not every + // register will need to be preserved. Can we improve this? + masm->PushCPURegList(saved_regs_); + if (mode == kSaveFPRegs) { + masm->PushCPURegList(saved_fp_regs_); + } + } + + void RestoreCallerSaveRegisters(MacroAssembler*masm, SaveFPRegsMode mode) { + // TODO(all): This can be very expensive, and it is likely that not every + // register will need to be preserved. Can we improve this? + if (mode == kSaveFPRegs) { + masm->PopCPURegList(saved_fp_regs_); + } + masm->PopCPURegList(saved_regs_); + } + + Register object() { return object_; } + Register address() { return address_; } + Register scratch0() { return scratch0_; } + Register scratch1() { return scratch1_; } + Register scratch2() { return scratch2_; } + + private: + Register object_; + Register address_; + Register scratch0_; + Register scratch1_; + Register scratch2_; + CPURegList saved_regs_; + CPURegList saved_fp_regs_; + + // TODO(all): We should consider moving this somewhere else. + static CPURegList GetValidRegistersForAllocation() { + // The list of valid registers for allocation is defined as all the + // registers without those with a special meaning. + // + // The default list excludes registers x26 to x31 because they are + // reserved for the following purpose: + // - x26 root register + // - x27 context pointer register + // - x28 jssp + // - x29 frame pointer + // - x30 link register(lr) + // - x31 xzr/stack pointer + CPURegList list(CPURegister::kRegister, kXRegSizeInBits, 0, 25); + + // We also remove MacroAssembler's scratch registers. + list.Remove(MacroAssembler::DefaultTmpList()); + + return list; + } + + friend class RecordWriteStub; + }; + + // A list of stub variants which are pregenerated. + // The variants are stored in the same format as the minor key, so + // MinorKeyFor() can be used to populate and check this list. + static const int kAheadOfTime[]; + + void Generate(MacroAssembler* masm); + void GenerateIncremental(MacroAssembler* masm, Mode mode); + + enum OnNoNeedToInformIncrementalMarker { + kReturnOnNoNeedToInformIncrementalMarker, + kUpdateRememberedSetOnNoNeedToInformIncrementalMarker + }; + + void CheckNeedsToInformIncrementalMarker( + MacroAssembler* masm, + OnNoNeedToInformIncrementalMarker on_no_need, + Mode mode); + void InformIncrementalMarker(MacroAssembler* masm); + + Major MajorKey() { return RecordWrite; } + + int MinorKey() { + return MinorKeyFor(object_, value_, address_, remembered_set_action_, + save_fp_regs_mode_); + } + + static int MinorKeyFor(Register object, + Register value, + Register address, + RememberedSetAction action, + SaveFPRegsMode fp_mode) { + ASSERT(object.Is64Bits()); + ASSERT(value.Is64Bits()); + ASSERT(address.Is64Bits()); + return ObjectBits::encode(object.code()) | + ValueBits::encode(value.code()) | + AddressBits::encode(address.code()) | + RememberedSetActionBits::encode(action) | + SaveFPRegsModeBits::encode(fp_mode); + } + + void Activate(Code* code) { + code->GetHeap()->incremental_marking()->ActivateGeneratedStub(code); + } + + class ObjectBits: public BitField<int, 0, 5> {}; + class ValueBits: public BitField<int, 5, 5> {}; + class AddressBits: public BitField<int, 10, 5> {}; + class RememberedSetActionBits: public BitField<RememberedSetAction, 15, 1> {}; + class SaveFPRegsModeBits: public BitField<SaveFPRegsMode, 16, 1> {}; + + Register object_; + Register value_; + Register address_; + RememberedSetAction remembered_set_action_; + SaveFPRegsMode save_fp_regs_mode_; + Label slow_; + RegisterAllocation regs_; +}; + + +// Helper to call C++ functions from generated code. The caller must prepare +// the exit frame before doing the call with GenerateCall. +class DirectCEntryStub: public PlatformCodeStub { + public: + explicit DirectCEntryStub(Isolate* isolate) : PlatformCodeStub(isolate) {} + void Generate(MacroAssembler* masm); + void GenerateCall(MacroAssembler* masm, Register target); + + private: + Major MajorKey() { return DirectCEntry; } + int MinorKey() { return 0; } + + bool NeedsImmovableCode() { return true; } +}; + + +class NameDictionaryLookupStub: public PlatformCodeStub { + public: + enum LookupMode { POSITIVE_LOOKUP, NEGATIVE_LOOKUP }; + + NameDictionaryLookupStub(Isolate* isolate, LookupMode mode) + : PlatformCodeStub(isolate), mode_(mode) { } + + void Generate(MacroAssembler* masm); + + static void GenerateNegativeLookup(MacroAssembler* masm, + Label* miss, + Label* done, + Register receiver, + Register properties, + Handle<Name> name, + Register scratch0); + + static void GeneratePositiveLookup(MacroAssembler* masm, + Label* miss, + Label* done, + Register elements, + Register name, + Register scratch1, + Register scratch2); + + virtual bool SometimesSetsUpAFrame() { return false; } + + private: + static const int kInlinedProbes = 4; + static const int kTotalProbes = 20; + + static const int kCapacityOffset = + NameDictionary::kHeaderSize + + NameDictionary::kCapacityIndex * kPointerSize; + + static const int kElementsStartOffset = + NameDictionary::kHeaderSize + + NameDictionary::kElementsStartIndex * kPointerSize; + + Major MajorKey() { return NameDictionaryLookup; } + + int MinorKey() { + return LookupModeBits::encode(mode_); + } + + class LookupModeBits: public BitField<LookupMode, 0, 1> {}; + + LookupMode mode_; +}; + + +class SubStringStub: public PlatformCodeStub { + public: + explicit SubStringStub(Isolate* isolate) : PlatformCodeStub(isolate) {} + + private: + Major MajorKey() { return SubString; } + int MinorKey() { return 0; } + + void Generate(MacroAssembler* masm); +}; + + +class StringCompareStub: public PlatformCodeStub { + public: + explicit StringCompareStub(Isolate* isolate) : PlatformCodeStub(isolate) { } + + // Compares two flat ASCII strings and returns result in x0. + static void GenerateCompareFlatAsciiStrings(MacroAssembler* masm, + Register left, + Register right, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4); + + // Compare two flat ASCII strings for equality and returns result + // in x0. + static void GenerateFlatAsciiStringEquals(MacroAssembler* masm, + Register left, + Register right, + Register scratch1, + Register scratch2, + Register scratch3); + + private: + virtual Major MajorKey() { return StringCompare; } + virtual int MinorKey() { return 0; } + virtual void Generate(MacroAssembler* masm); + + static void GenerateAsciiCharsCompareLoop(MacroAssembler* masm, + Register left, + Register right, + Register length, + Register scratch1, + Register scratch2, + Label* chars_not_equal); +}; + + +struct PlatformCallInterfaceDescriptor { + explicit PlatformCallInterfaceDescriptor( + TargetAddressStorageMode storage_mode) + : storage_mode_(storage_mode) { } + + TargetAddressStorageMode storage_mode() { return storage_mode_; } + + private: + TargetAddressStorageMode storage_mode_; +}; + + +} } // namespace v8::internal + +#endif // V8_ARM64_CODE_STUBS_ARM64_H_ diff --git a/chromium/v8/src/arm64/codegen-arm64.cc b/chromium/v8/src/arm64/codegen-arm64.cc new file mode 100644 index 00000000000..9eb0d4a5f76 --- /dev/null +++ b/chromium/v8/src/arm64/codegen-arm64.cc @@ -0,0 +1,620 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/codegen.h" +#include "src/macro-assembler.h" +#include "src/arm64/simulator-arm64.h" + +namespace v8 { +namespace internal { + +#define __ ACCESS_MASM(masm) + +#if defined(USE_SIMULATOR) +byte* fast_exp_arm64_machine_code = NULL; +double fast_exp_simulator(double x) { + Simulator * simulator = Simulator::current(Isolate::Current()); + Simulator::CallArgument args[] = { + Simulator::CallArgument(x), + Simulator::CallArgument::End() + }; + return simulator->CallDouble(fast_exp_arm64_machine_code, args); +} +#endif + + +UnaryMathFunction CreateExpFunction() { + if (!FLAG_fast_math) return &std::exp; + + // Use the Math.exp implemetation in MathExpGenerator::EmitMathExp() to create + // an AAPCS64-compliant exp() function. This will be faster than the C + // library's exp() function, but probably less accurate. + size_t actual_size; + byte* buffer = static_cast<byte*>(OS::Allocate(1 * KB, &actual_size, true)); + if (buffer == NULL) return &std::exp; + + ExternalReference::InitializeMathExpData(); + MacroAssembler masm(NULL, buffer, static_cast<int>(actual_size)); + masm.SetStackPointer(csp); + + // The argument will be in d0 on entry. + DoubleRegister input = d0; + // Use other caller-saved registers for all other values. + DoubleRegister result = d1; + DoubleRegister double_temp1 = d2; + DoubleRegister double_temp2 = d3; + Register temp1 = x10; + Register temp2 = x11; + Register temp3 = x12; + + MathExpGenerator::EmitMathExp(&masm, input, result, + double_temp1, double_temp2, + temp1, temp2, temp3); + // Move the result to the return register. + masm.Fmov(d0, result); + masm.Ret(); + + CodeDesc desc; + masm.GetCode(&desc); + ASSERT(!RelocInfo::RequiresRelocation(desc)); + + CPU::FlushICache(buffer, actual_size); + OS::ProtectCode(buffer, actual_size); + +#if !defined(USE_SIMULATOR) + return FUNCTION_CAST<UnaryMathFunction>(buffer); +#else + fast_exp_arm64_machine_code = buffer; + return &fast_exp_simulator; +#endif +} + + +UnaryMathFunction CreateSqrtFunction() { + return &std::sqrt; +} + + +// ------------------------------------------------------------------------- +// Platform-specific RuntimeCallHelper functions. + +void StubRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { + masm->EnterFrame(StackFrame::INTERNAL); + ASSERT(!masm->has_frame()); + masm->set_has_frame(true); +} + + +void StubRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { + masm->LeaveFrame(StackFrame::INTERNAL); + ASSERT(masm->has_frame()); + masm->set_has_frame(false); +} + + +// ------------------------------------------------------------------------- +// Code generators + +void ElementsTransitionGenerator::GenerateMapChangeElementsTransition( + MacroAssembler* masm, AllocationSiteMode mode, + Label* allocation_memento_found) { + // ----------- S t a t e ------------- + // -- x2 : receiver + // -- x3 : target map + // ----------------------------------- + Register receiver = x2; + Register map = x3; + + if (mode == TRACK_ALLOCATION_SITE) { + ASSERT(allocation_memento_found != NULL); + __ JumpIfJSArrayHasAllocationMemento(receiver, x10, x11, + allocation_memento_found); + } + + // Set transitioned map. + __ Str(map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ RecordWriteField(receiver, + HeapObject::kMapOffset, + map, + x10, + kLRHasNotBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); +} + + +void ElementsTransitionGenerator::GenerateSmiToDouble( + MacroAssembler* masm, AllocationSiteMode mode, Label* fail) { + ASM_LOCATION("ElementsTransitionGenerator::GenerateSmiToDouble"); + // ----------- S t a t e ------------- + // -- lr : return address + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // -- x3 : target map, scratch for subsequent call + // ----------------------------------- + Register receiver = x2; + Register target_map = x3; + + Label gc_required, only_change_map; + + if (mode == TRACK_ALLOCATION_SITE) { + __ JumpIfJSArrayHasAllocationMemento(receiver, x10, x11, fail); + } + + // Check for empty arrays, which only require a map transition and no changes + // to the backing store. + Register elements = x4; + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ JumpIfRoot(elements, Heap::kEmptyFixedArrayRootIndex, &only_change_map); + + __ Push(lr); + Register length = x5; + __ Ldrsw(length, UntagSmiFieldMemOperand(elements, + FixedArray::kLengthOffset)); + + // Allocate new FixedDoubleArray. + Register array_size = x6; + Register array = x7; + __ Lsl(array_size, length, kDoubleSizeLog2); + __ Add(array_size, array_size, FixedDoubleArray::kHeaderSize); + __ Allocate(array_size, array, x10, x11, &gc_required, DOUBLE_ALIGNMENT); + // Register array is non-tagged heap object. + + // Set the destination FixedDoubleArray's length and map. + Register map_root = x6; + __ LoadRoot(map_root, Heap::kFixedDoubleArrayMapRootIndex); + __ SmiTag(x11, length); + __ Str(x11, MemOperand(array, FixedDoubleArray::kLengthOffset)); + __ Str(map_root, MemOperand(array, HeapObject::kMapOffset)); + + __ Str(target_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ RecordWriteField(receiver, HeapObject::kMapOffset, target_map, x6, + kLRHasBeenSaved, kDontSaveFPRegs, OMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); + + // Replace receiver's backing store with newly created FixedDoubleArray. + __ Add(x10, array, kHeapObjectTag); + __ Str(x10, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ RecordWriteField(receiver, JSObject::kElementsOffset, x10, + x6, kLRHasBeenSaved, kDontSaveFPRegs, + EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); + + // Prepare for conversion loop. + Register src_elements = x10; + Register dst_elements = x11; + Register dst_end = x12; + __ Add(src_elements, elements, FixedArray::kHeaderSize - kHeapObjectTag); + __ Add(dst_elements, array, FixedDoubleArray::kHeaderSize); + __ Add(dst_end, dst_elements, Operand(length, LSL, kDoubleSizeLog2)); + + FPRegister nan_d = d1; + __ Fmov(nan_d, rawbits_to_double(kHoleNanInt64)); + + Label entry, done; + __ B(&entry); + + __ Bind(&only_change_map); + __ Str(target_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ RecordWriteField(receiver, HeapObject::kMapOffset, target_map, x6, + kLRHasNotBeenSaved, kDontSaveFPRegs, OMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); + __ B(&done); + + // Call into runtime if GC is required. + __ Bind(&gc_required); + __ Pop(lr); + __ B(fail); + + // Iterate over the array, copying and coverting smis to doubles. If an + // element is non-smi, write a hole to the destination. + { + Label loop; + __ Bind(&loop); + __ Ldr(x13, MemOperand(src_elements, kPointerSize, PostIndex)); + __ SmiUntagToDouble(d0, x13, kSpeculativeUntag); + __ Tst(x13, kSmiTagMask); + __ Fcsel(d0, d0, nan_d, eq); + __ Str(d0, MemOperand(dst_elements, kDoubleSize, PostIndex)); + + __ Bind(&entry); + __ Cmp(dst_elements, dst_end); + __ B(lt, &loop); + } + + __ Pop(lr); + __ Bind(&done); +} + + +void ElementsTransitionGenerator::GenerateDoubleToObject( + MacroAssembler* masm, AllocationSiteMode mode, Label* fail) { + ASM_LOCATION("ElementsTransitionGenerator::GenerateDoubleToObject"); + // ----------- S t a t e ------------- + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // -- lr : return address + // -- x3 : target map, scratch for subsequent call + // -- x4 : scratch (elements) + // ----------------------------------- + Register value = x0; + Register key = x1; + Register receiver = x2; + Register target_map = x3; + + if (mode == TRACK_ALLOCATION_SITE) { + __ JumpIfJSArrayHasAllocationMemento(receiver, x10, x11, fail); + } + + // Check for empty arrays, which only require a map transition and no changes + // to the backing store. + Label only_change_map; + Register elements = x4; + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ JumpIfRoot(elements, Heap::kEmptyFixedArrayRootIndex, &only_change_map); + + __ Push(lr); + // TODO(all): These registers may not need to be pushed. Examine + // RecordWriteStub and check whether it's needed. + __ Push(target_map, receiver, key, value); + Register length = x5; + __ Ldrsw(length, UntagSmiFieldMemOperand(elements, + FixedArray::kLengthOffset)); + + // Allocate new FixedArray. + Register array_size = x6; + Register array = x7; + Label gc_required; + __ Mov(array_size, FixedDoubleArray::kHeaderSize); + __ Add(array_size, array_size, Operand(length, LSL, kPointerSizeLog2)); + __ Allocate(array_size, array, x10, x11, &gc_required, NO_ALLOCATION_FLAGS); + + // Set destination FixedDoubleArray's length and map. + Register map_root = x6; + __ LoadRoot(map_root, Heap::kFixedArrayMapRootIndex); + __ SmiTag(x11, length); + __ Str(x11, MemOperand(array, FixedDoubleArray::kLengthOffset)); + __ Str(map_root, MemOperand(array, HeapObject::kMapOffset)); + + // Prepare for conversion loop. + Register src_elements = x10; + Register dst_elements = x11; + Register dst_end = x12; + __ Add(src_elements, elements, + FixedDoubleArray::kHeaderSize - kHeapObjectTag); + __ Add(dst_elements, array, FixedArray::kHeaderSize); + __ Add(array, array, kHeapObjectTag); + __ Add(dst_end, dst_elements, Operand(length, LSL, kPointerSizeLog2)); + + Register the_hole = x14; + Register heap_num_map = x15; + __ LoadRoot(the_hole, Heap::kTheHoleValueRootIndex); + __ LoadRoot(heap_num_map, Heap::kHeapNumberMapRootIndex); + + Label entry; + __ B(&entry); + + // Call into runtime if GC is required. + __ Bind(&gc_required); + __ Pop(value, key, receiver, target_map); + __ Pop(lr); + __ B(fail); + + { + Label loop, convert_hole; + __ Bind(&loop); + __ Ldr(x13, MemOperand(src_elements, kPointerSize, PostIndex)); + __ Cmp(x13, kHoleNanInt64); + __ B(eq, &convert_hole); + + // Non-hole double, copy value into a heap number. + Register heap_num = x5; + __ AllocateHeapNumber(heap_num, &gc_required, x6, x4, + x13, heap_num_map); + __ Mov(x13, dst_elements); + __ Str(heap_num, MemOperand(dst_elements, kPointerSize, PostIndex)); + __ RecordWrite(array, x13, heap_num, kLRHasBeenSaved, kDontSaveFPRegs, + EMIT_REMEMBERED_SET, OMIT_SMI_CHECK); + + __ B(&entry); + + // Replace the-hole NaN with the-hole pointer. + __ Bind(&convert_hole); + __ Str(the_hole, MemOperand(dst_elements, kPointerSize, PostIndex)); + + __ Bind(&entry); + __ Cmp(dst_elements, dst_end); + __ B(lt, &loop); + } + + __ Pop(value, key, receiver, target_map); + // Replace receiver's backing store with newly created and filled FixedArray. + __ Str(array, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ RecordWriteField(receiver, JSObject::kElementsOffset, array, x13, + kLRHasBeenSaved, kDontSaveFPRegs, EMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); + __ Pop(lr); + + __ Bind(&only_change_map); + __ Str(target_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ RecordWriteField(receiver, HeapObject::kMapOffset, target_map, x13, + kLRHasNotBeenSaved, kDontSaveFPRegs, OMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); +} + + +CodeAgingHelper::CodeAgingHelper() { + ASSERT(young_sequence_.length() == kNoCodeAgeSequenceLength); + // The sequence of instructions that is patched out for aging code is the + // following boilerplate stack-building prologue that is found both in + // FUNCTION and OPTIMIZED_FUNCTION code: + PatchingAssembler patcher(young_sequence_.start(), + young_sequence_.length() / kInstructionSize); + // The young sequence is the frame setup code for FUNCTION code types. It is + // generated by FullCodeGenerator::Generate. + MacroAssembler::EmitFrameSetupForCodeAgePatching(&patcher); + +#ifdef DEBUG + const int length = kCodeAgeStubEntryOffset / kInstructionSize; + ASSERT(old_sequence_.length() >= kCodeAgeStubEntryOffset); + PatchingAssembler patcher_old(old_sequence_.start(), length); + MacroAssembler::EmitCodeAgeSequence(&patcher_old, NULL); +#endif +} + + +#ifdef DEBUG +bool CodeAgingHelper::IsOld(byte* candidate) const { + return memcmp(candidate, old_sequence_.start(), kCodeAgeStubEntryOffset) == 0; +} +#endif + + +bool Code::IsYoungSequence(Isolate* isolate, byte* sequence) { + return MacroAssembler::IsYoungSequence(isolate, sequence); +} + + +void Code::GetCodeAgeAndParity(Isolate* isolate, byte* sequence, Age* age, + MarkingParity* parity) { + if (IsYoungSequence(isolate, sequence)) { + *age = kNoAgeCodeAge; + *parity = NO_MARKING_PARITY; + } else { + byte* target = sequence + kCodeAgeStubEntryOffset; + Code* stub = GetCodeFromTargetAddress(Memory::Address_at(target)); + GetCodeAgeAndParity(stub, age, parity); + } +} + + +void Code::PatchPlatformCodeAge(Isolate* isolate, + byte* sequence, + Code::Age age, + MarkingParity parity) { + PatchingAssembler patcher(sequence, + kNoCodeAgeSequenceLength / kInstructionSize); + if (age == kNoAgeCodeAge) { + MacroAssembler::EmitFrameSetupForCodeAgePatching(&patcher); + } else { + Code * stub = GetCodeAgeStub(isolate, age, parity); + MacroAssembler::EmitCodeAgeSequence(&patcher, stub); + } +} + + +void StringCharLoadGenerator::Generate(MacroAssembler* masm, + Register string, + Register index, + Register result, + Label* call_runtime) { + ASSERT(string.Is64Bits() && index.Is32Bits() && result.Is64Bits()); + // Fetch the instance type of the receiver into result register. + __ Ldr(result, FieldMemOperand(string, HeapObject::kMapOffset)); + __ Ldrb(result, FieldMemOperand(result, Map::kInstanceTypeOffset)); + + // We need special handling for indirect strings. + Label check_sequential; + __ TestAndBranchIfAllClear(result, kIsIndirectStringMask, &check_sequential); + + // Dispatch on the indirect string shape: slice or cons. + Label cons_string; + __ TestAndBranchIfAllClear(result, kSlicedNotConsMask, &cons_string); + + // Handle slices. + Label indirect_string_loaded; + __ Ldr(result.W(), + UntagSmiFieldMemOperand(string, SlicedString::kOffsetOffset)); + __ Ldr(string, FieldMemOperand(string, SlicedString::kParentOffset)); + __ Add(index, index, result.W()); + __ B(&indirect_string_loaded); + + // Handle cons strings. + // Check whether the right hand side is the empty string (i.e. if + // this is really a flat string in a cons string). If that is not + // the case we would rather go to the runtime system now to flatten + // the string. + __ Bind(&cons_string); + __ Ldr(result, FieldMemOperand(string, ConsString::kSecondOffset)); + __ JumpIfNotRoot(result, Heap::kempty_stringRootIndex, call_runtime); + // Get the first of the two strings and load its instance type. + __ Ldr(string, FieldMemOperand(string, ConsString::kFirstOffset)); + + __ Bind(&indirect_string_loaded); + __ Ldr(result, FieldMemOperand(string, HeapObject::kMapOffset)); + __ Ldrb(result, FieldMemOperand(result, Map::kInstanceTypeOffset)); + + // Distinguish sequential and external strings. Only these two string + // representations can reach here (slices and flat cons strings have been + // reduced to the underlying sequential or external string). + Label external_string, check_encoding; + __ Bind(&check_sequential); + STATIC_ASSERT(kSeqStringTag == 0); + __ TestAndBranchIfAnySet(result, kStringRepresentationMask, &external_string); + + // Prepare sequential strings + STATIC_ASSERT(SeqTwoByteString::kHeaderSize == SeqOneByteString::kHeaderSize); + __ Add(string, string, SeqTwoByteString::kHeaderSize - kHeapObjectTag); + __ B(&check_encoding); + + // Handle external strings. + __ Bind(&external_string); + if (FLAG_debug_code) { + // Assert that we do not have a cons or slice (indirect strings) here. + // Sequential strings have already been ruled out. + __ Tst(result, kIsIndirectStringMask); + __ Assert(eq, kExternalStringExpectedButNotFound); + } + // Rule out short external strings. + STATIC_ASSERT(kShortExternalStringTag != 0); + // TestAndBranchIfAnySet can emit Tbnz. Do not use it because call_runtime + // can be bound far away in deferred code. + __ Tst(result, kShortExternalStringMask); + __ B(ne, call_runtime); + __ Ldr(string, FieldMemOperand(string, ExternalString::kResourceDataOffset)); + + Label ascii, done; + __ Bind(&check_encoding); + STATIC_ASSERT(kTwoByteStringTag == 0); + __ TestAndBranchIfAnySet(result, kStringEncodingMask, &ascii); + // Two-byte string. + __ Ldrh(result, MemOperand(string, index, SXTW, 1)); + __ B(&done); + __ Bind(&ascii); + // Ascii string. + __ Ldrb(result, MemOperand(string, index, SXTW)); + __ Bind(&done); +} + + +static MemOperand ExpConstant(Register base, int index) { + return MemOperand(base, index * kDoubleSize); +} + + +void MathExpGenerator::EmitMathExp(MacroAssembler* masm, + DoubleRegister input, + DoubleRegister result, + DoubleRegister double_temp1, + DoubleRegister double_temp2, + Register temp1, + Register temp2, + Register temp3) { + // TODO(jbramley): There are several instances where fnmsub could be used + // instead of fmul and fsub. Doing this changes the result, but since this is + // an estimation anyway, does it matter? + + ASSERT(!AreAliased(input, result, + double_temp1, double_temp2, + temp1, temp2, temp3)); + ASSERT(ExternalReference::math_exp_constants(0).address() != NULL); + + Label done; + DoubleRegister double_temp3 = result; + Register constants = temp3; + + // The algorithm used relies on some magic constants which are initialized in + // ExternalReference::InitializeMathExpData(). + + // Load the address of the start of the array. + __ Mov(constants, ExternalReference::math_exp_constants(0)); + + // We have to do a four-way split here: + // - If input <= about -708.4, the output always rounds to zero. + // - If input >= about 709.8, the output always rounds to +infinity. + // - If the input is NaN, the output is NaN. + // - Otherwise, the result needs to be calculated. + Label result_is_finite_non_zero; + // Assert that we can load offset 0 (the small input threshold) and offset 1 + // (the large input threshold) with a single ldp. + ASSERT(kDRegSize == (ExpConstant(constants, 1).offset() - + ExpConstant(constants, 0).offset())); + __ Ldp(double_temp1, double_temp2, ExpConstant(constants, 0)); + + __ Fcmp(input, double_temp1); + __ Fccmp(input, double_temp2, NoFlag, hi); + // At this point, the condition flags can be in one of five states: + // NZCV + // 1000 -708.4 < input < 709.8 result = exp(input) + // 0110 input == 709.8 result = +infinity + // 0010 input > 709.8 result = +infinity + // 0011 input is NaN result = input + // 0000 input <= -708.4 result = +0.0 + + // Continue the common case first. 'mi' tests N == 1. + __ B(&result_is_finite_non_zero, mi); + + // TODO(jbramley): Consider adding a +infinity register for ARM64. + __ Ldr(double_temp2, ExpConstant(constants, 2)); // Synthesize +infinity. + + // Select between +0.0 and +infinity. 'lo' tests C == 0. + __ Fcsel(result, fp_zero, double_temp2, lo); + // Select between {+0.0 or +infinity} and input. 'vc' tests V == 0. + __ Fcsel(result, result, input, vc); + __ B(&done); + + // The rest is magic, as described in InitializeMathExpData(). + __ Bind(&result_is_finite_non_zero); + + // Assert that we can load offset 3 and offset 4 with a single ldp. + ASSERT(kDRegSize == (ExpConstant(constants, 4).offset() - + ExpConstant(constants, 3).offset())); + __ Ldp(double_temp1, double_temp3, ExpConstant(constants, 3)); + __ Fmadd(double_temp1, double_temp1, input, double_temp3); + __ Fmov(temp2.W(), double_temp1.S()); + __ Fsub(double_temp1, double_temp1, double_temp3); + + // Assert that we can load offset 5 and offset 6 with a single ldp. + ASSERT(kDRegSize == (ExpConstant(constants, 6).offset() - + ExpConstant(constants, 5).offset())); + __ Ldp(double_temp2, double_temp3, ExpConstant(constants, 5)); + // TODO(jbramley): Consider using Fnmsub here. + __ Fmul(double_temp1, double_temp1, double_temp2); + __ Fsub(double_temp1, double_temp1, input); + + __ Fmul(double_temp2, double_temp1, double_temp1); + __ Fsub(double_temp3, double_temp3, double_temp1); + __ Fmul(double_temp3, double_temp3, double_temp2); + + __ Mov(temp1.W(), Operand(temp2.W(), LSR, 11)); + + __ Ldr(double_temp2, ExpConstant(constants, 7)); + // TODO(jbramley): Consider using Fnmsub here. + __ Fmul(double_temp3, double_temp3, double_temp2); + __ Fsub(double_temp3, double_temp3, double_temp1); + + // The 8th constant is 1.0, so use an immediate move rather than a load. + // We can't generate a runtime assertion here as we would need to call Abort + // in the runtime and we don't have an Isolate when we generate this code. + __ Fmov(double_temp2, 1.0); + __ Fadd(double_temp3, double_temp3, double_temp2); + + __ And(temp2, temp2, 0x7ff); + __ Add(temp1, temp1, 0x3ff); + + // Do the final table lookup. + __ Mov(temp3, ExternalReference::math_exp_log_table()); + + __ Add(temp3, temp3, Operand(temp2, LSL, kDRegSizeLog2)); + __ Ldp(temp2.W(), temp3.W(), MemOperand(temp3)); + __ Orr(temp1.W(), temp3.W(), Operand(temp1.W(), LSL, 20)); + __ Bfi(temp2, temp1, 32, 32); + __ Fmov(double_temp1, temp2); + + __ Fmul(result, double_temp3, double_temp1); + + __ Bind(&done); +} + +#undef __ + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/codegen-arm64.h b/chromium/v8/src/arm64/codegen-arm64.h new file mode 100644 index 00000000000..9ef148cc409 --- /dev/null +++ b/chromium/v8/src/arm64/codegen-arm64.h @@ -0,0 +1,48 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_CODEGEN_ARM64_H_ +#define V8_ARM64_CODEGEN_ARM64_H_ + +#include "src/ast.h" +#include "src/ic-inl.h" + +namespace v8 { +namespace internal { + +class StringCharLoadGenerator : public AllStatic { + public: + // Generates the code for handling different string types and loading the + // indexed character into |result|. We expect |index| as untagged input and + // |result| as untagged output. Register index is asserted to be a 32-bit W + // register. + static void Generate(MacroAssembler* masm, + Register string, + Register index, + Register result, + Label* call_runtime); + + private: + DISALLOW_COPY_AND_ASSIGN(StringCharLoadGenerator); +}; + + +class MathExpGenerator : public AllStatic { + public: + static void EmitMathExp(MacroAssembler* masm, + DoubleRegister input, + DoubleRegister result, + DoubleRegister double_scratch1, + DoubleRegister double_scratch2, + Register temp1, + Register temp2, + Register temp3); + + private: + DISALLOW_COPY_AND_ASSIGN(MathExpGenerator); +}; + +} } // namespace v8::internal + +#endif // V8_ARM64_CODEGEN_ARM64_H_ diff --git a/chromium/v8/src/arm64/constants-arm64.h b/chromium/v8/src/arm64/constants-arm64.h new file mode 100644 index 00000000000..f459b4b7576 --- /dev/null +++ b/chromium/v8/src/arm64/constants-arm64.h @@ -0,0 +1,1250 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_CONSTANTS_ARM64_H_ +#define V8_ARM64_CONSTANTS_ARM64_H_ + + +// Assert that this is an LP64 system. +STATIC_ASSERT(sizeof(int) == sizeof(int32_t)); // NOLINT(runtime/sizeof) +STATIC_ASSERT(sizeof(long) == sizeof(int64_t)); // NOLINT(runtime/int) +STATIC_ASSERT(sizeof(void *) == sizeof(int64_t)); // NOLINT(runtime/sizeof) +STATIC_ASSERT(sizeof(1) == sizeof(int32_t)); // NOLINT(runtime/sizeof) +STATIC_ASSERT(sizeof(1L) == sizeof(int64_t)); // NOLINT(runtime/sizeof) + + +// Get the standard printf format macros for C99 stdint types. +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + + +namespace v8 { +namespace internal { + + +const unsigned kInstructionSize = 4; +const unsigned kInstructionSizeLog2 = 2; +const unsigned kLoadLiteralScaleLog2 = 2; +const unsigned kMaxLoadLiteralRange = 1 * MB; + +const unsigned kNumberOfRegisters = 32; +const unsigned kNumberOfFPRegisters = 32; +// Callee saved registers are x19-x30(lr). +const int kNumberOfCalleeSavedRegisters = 11; +const int kFirstCalleeSavedRegisterIndex = 19; +// Callee saved FP registers are d8-d15. +const int kNumberOfCalleeSavedFPRegisters = 8; +const int kFirstCalleeSavedFPRegisterIndex = 8; +// Callee saved registers with no specific purpose in JS are x19-x25. +const unsigned kJSCalleeSavedRegList = 0x03f80000; +// TODO(all): k<Y>RegSize should probably be k<Y>RegSizeInBits. +const unsigned kWRegSizeInBits = 32; +const unsigned kWRegSizeInBitsLog2 = 5; +const unsigned kWRegSize = kWRegSizeInBits >> 3; +const unsigned kWRegSizeLog2 = kWRegSizeInBitsLog2 - 3; +const unsigned kXRegSizeInBits = 64; +const unsigned kXRegSizeInBitsLog2 = 6; +const unsigned kXRegSize = kXRegSizeInBits >> 3; +const unsigned kXRegSizeLog2 = kXRegSizeInBitsLog2 - 3; +const unsigned kSRegSizeInBits = 32; +const unsigned kSRegSizeInBitsLog2 = 5; +const unsigned kSRegSize = kSRegSizeInBits >> 3; +const unsigned kSRegSizeLog2 = kSRegSizeInBitsLog2 - 3; +const unsigned kDRegSizeInBits = 64; +const unsigned kDRegSizeInBitsLog2 = 6; +const unsigned kDRegSize = kDRegSizeInBits >> 3; +const unsigned kDRegSizeLog2 = kDRegSizeInBitsLog2 - 3; +const int64_t kWRegMask = 0x00000000ffffffffL; +const int64_t kXRegMask = 0xffffffffffffffffL; +const int64_t kSRegMask = 0x00000000ffffffffL; +const int64_t kDRegMask = 0xffffffffffffffffL; +// TODO(all) check if the expression below works on all compilers or if it +// triggers an overflow error. +const int64_t kDSignBit = 63; +const int64_t kDSignMask = 0x1L << kDSignBit; +const int64_t kSSignBit = 31; +const int64_t kSSignMask = 0x1L << kSSignBit; +const int64_t kXSignBit = 63; +const int64_t kXSignMask = 0x1L << kXSignBit; +const int64_t kWSignBit = 31; +const int64_t kWSignMask = 0x1L << kWSignBit; +const int64_t kDQuietNanBit = 51; +const int64_t kDQuietNanMask = 0x1L << kDQuietNanBit; +const int64_t kSQuietNanBit = 22; +const int64_t kSQuietNanMask = 0x1L << kSQuietNanBit; +const int64_t kByteMask = 0xffL; +const int64_t kHalfWordMask = 0xffffL; +const int64_t kWordMask = 0xffffffffL; +const uint64_t kXMaxUInt = 0xffffffffffffffffUL; +const uint64_t kWMaxUInt = 0xffffffffUL; +const int64_t kXMaxInt = 0x7fffffffffffffffL; +const int64_t kXMinInt = 0x8000000000000000L; +const int32_t kWMaxInt = 0x7fffffff; +const int32_t kWMinInt = 0x80000000; +const unsigned kFramePointerRegCode = 29; +const unsigned kLinkRegCode = 30; +const unsigned kZeroRegCode = 31; +const unsigned kJSSPCode = 28; +const unsigned kSPRegInternalCode = 63; +const unsigned kRegCodeMask = 0x1f; +const unsigned kShiftAmountWRegMask = 0x1f; +const unsigned kShiftAmountXRegMask = 0x3f; +// Standard machine types defined by AAPCS64. +const unsigned kByteSize = 8; +const unsigned kByteSizeInBytes = kByteSize >> 3; +const unsigned kHalfWordSize = 16; +const unsigned kHalfWordSizeLog2 = 4; +const unsigned kHalfWordSizeInBytes = kHalfWordSize >> 3; +const unsigned kHalfWordSizeInBytesLog2 = kHalfWordSizeLog2 - 3; +const unsigned kWordSize = 32; +const unsigned kWordSizeLog2 = 5; +const unsigned kWordSizeInBytes = kWordSize >> 3; +const unsigned kWordSizeInBytesLog2 = kWordSizeLog2 - 3; +const unsigned kDoubleWordSize = 64; +const unsigned kDoubleWordSizeInBytes = kDoubleWordSize >> 3; +const unsigned kQuadWordSize = 128; +const unsigned kQuadWordSizeInBytes = kQuadWordSize >> 3; +// AArch64 floating-point specifics. These match IEEE-754. +const unsigned kDoubleMantissaBits = 52; +const unsigned kDoubleExponentBits = 11; +const unsigned kDoubleExponentBias = 1023; +const unsigned kFloatMantissaBits = 23; +const unsigned kFloatExponentBits = 8; + +#define REGISTER_CODE_LIST(R) \ +R(0) R(1) R(2) R(3) R(4) R(5) R(6) R(7) \ +R(8) R(9) R(10) R(11) R(12) R(13) R(14) R(15) \ +R(16) R(17) R(18) R(19) R(20) R(21) R(22) R(23) \ +R(24) R(25) R(26) R(27) R(28) R(29) R(30) R(31) + +#define INSTRUCTION_FIELDS_LIST(V_) \ +/* Register fields */ \ +V_(Rd, 4, 0, Bits) /* Destination register. */ \ +V_(Rn, 9, 5, Bits) /* First source register. */ \ +V_(Rm, 20, 16, Bits) /* Second source register. */ \ +V_(Ra, 14, 10, Bits) /* Third source register. */ \ +V_(Rt, 4, 0, Bits) /* Load dest / store source. */ \ +V_(Rt2, 14, 10, Bits) /* Load second dest / */ \ + /* store second source. */ \ +V_(PrefetchMode, 4, 0, Bits) \ + \ +/* Common bits */ \ +V_(SixtyFourBits, 31, 31, Bits) \ +V_(FlagsUpdate, 29, 29, Bits) \ + \ +/* PC relative addressing */ \ +V_(ImmPCRelHi, 23, 5, SignedBits) \ +V_(ImmPCRelLo, 30, 29, Bits) \ + \ +/* Add/subtract/logical shift register */ \ +V_(ShiftDP, 23, 22, Bits) \ +V_(ImmDPShift, 15, 10, Bits) \ + \ +/* Add/subtract immediate */ \ +V_(ImmAddSub, 21, 10, Bits) \ +V_(ShiftAddSub, 23, 22, Bits) \ + \ +/* Add/substract extend */ \ +V_(ImmExtendShift, 12, 10, Bits) \ +V_(ExtendMode, 15, 13, Bits) \ + \ +/* Move wide */ \ +V_(ImmMoveWide, 20, 5, Bits) \ +V_(ShiftMoveWide, 22, 21, Bits) \ + \ +/* Logical immediate, bitfield and extract */ \ +V_(BitN, 22, 22, Bits) \ +V_(ImmRotate, 21, 16, Bits) \ +V_(ImmSetBits, 15, 10, Bits) \ +V_(ImmR, 21, 16, Bits) \ +V_(ImmS, 15, 10, Bits) \ + \ +/* Test and branch immediate */ \ +V_(ImmTestBranch, 18, 5, SignedBits) \ +V_(ImmTestBranchBit40, 23, 19, Bits) \ +V_(ImmTestBranchBit5, 31, 31, Bits) \ + \ +/* Conditionals */ \ +V_(Condition, 15, 12, Bits) \ +V_(ConditionBranch, 3, 0, Bits) \ +V_(Nzcv, 3, 0, Bits) \ +V_(ImmCondCmp, 20, 16, Bits) \ +V_(ImmCondBranch, 23, 5, SignedBits) \ + \ +/* Floating point */ \ +V_(FPType, 23, 22, Bits) \ +V_(ImmFP, 20, 13, Bits) \ +V_(FPScale, 15, 10, Bits) \ + \ +/* Load Store */ \ +V_(ImmLS, 20, 12, SignedBits) \ +V_(ImmLSUnsigned, 21, 10, Bits) \ +V_(ImmLSPair, 21, 15, SignedBits) \ +V_(SizeLS, 31, 30, Bits) \ +V_(ImmShiftLS, 12, 12, Bits) \ + \ +/* Other immediates */ \ +V_(ImmUncondBranch, 25, 0, SignedBits) \ +V_(ImmCmpBranch, 23, 5, SignedBits) \ +V_(ImmLLiteral, 23, 5, SignedBits) \ +V_(ImmException, 20, 5, Bits) \ +V_(ImmHint, 11, 5, Bits) \ +V_(ImmBarrierDomain, 11, 10, Bits) \ +V_(ImmBarrierType, 9, 8, Bits) \ + \ +/* System (MRS, MSR) */ \ +V_(ImmSystemRegister, 19, 5, Bits) \ +V_(SysO0, 19, 19, Bits) \ +V_(SysOp1, 18, 16, Bits) \ +V_(SysOp2, 7, 5, Bits) \ +V_(CRn, 15, 12, Bits) \ +V_(CRm, 11, 8, Bits) \ + + +#define SYSTEM_REGISTER_FIELDS_LIST(V_, M_) \ +/* NZCV */ \ +V_(Flags, 31, 28, Bits, uint32_t) \ +V_(N, 31, 31, Bits, bool) \ +V_(Z, 30, 30, Bits, bool) \ +V_(C, 29, 29, Bits, bool) \ +V_(V, 28, 28, Bits, uint32_t) \ +M_(NZCV, Flags_mask) \ + \ +/* FPCR */ \ +V_(AHP, 26, 26, Bits, bool) \ +V_(DN, 25, 25, Bits, bool) \ +V_(FZ, 24, 24, Bits, bool) \ +V_(RMode, 23, 22, Bits, FPRounding) \ +M_(FPCR, AHP_mask | DN_mask | FZ_mask | RMode_mask) + + +// Fields offsets. +#define DECLARE_FIELDS_OFFSETS(Name, HighBit, LowBit, unused_1, unused_2) \ + const int Name##_offset = LowBit; \ + const int Name##_width = HighBit - LowBit + 1; \ + const uint32_t Name##_mask = ((1 << Name##_width) - 1) << LowBit; +#define DECLARE_INSTRUCTION_FIELDS_OFFSETS(Name, HighBit, LowBit, unused_1) \ + DECLARE_FIELDS_OFFSETS(Name, HighBit, LowBit, unused_1, unused_2) +#define NOTHING(A, B) +INSTRUCTION_FIELDS_LIST(DECLARE_INSTRUCTION_FIELDS_OFFSETS) +SYSTEM_REGISTER_FIELDS_LIST(DECLARE_FIELDS_OFFSETS, NOTHING) +#undef NOTHING +#undef DECLARE_FIELDS_OFFSETS +#undef DECLARE_INSTRUCTION_FIELDS_OFFSETS + +// ImmPCRel is a compound field (not present in INSTRUCTION_FIELDS_LIST), formed +// from ImmPCRelLo and ImmPCRelHi. +const int ImmPCRel_mask = ImmPCRelLo_mask | ImmPCRelHi_mask; + +// Condition codes. +enum Condition { + eq = 0, + ne = 1, + hs = 2, cs = hs, + lo = 3, cc = lo, + mi = 4, + pl = 5, + vs = 6, + vc = 7, + hi = 8, + ls = 9, + ge = 10, + lt = 11, + gt = 12, + le = 13, + al = 14, + nv = 15 // Behaves as always/al. +}; + +inline Condition NegateCondition(Condition cond) { + // Conditions al and nv behave identically, as "always true". They can't be + // inverted, because there is no never condition. + ASSERT((cond != al) && (cond != nv)); + return static_cast<Condition>(cond ^ 1); +} + +// Commute a condition such that {a cond b == b cond' a}. +inline Condition CommuteCondition(Condition cond) { + switch (cond) { + case lo: + return hi; + case hi: + return lo; + case hs: + return ls; + case ls: + return hs; + case lt: + return gt; + case gt: + return lt; + case ge: + return le; + case le: + return ge; + case eq: + return eq; + default: + // In practice this function is only used with a condition coming from + // TokenToCondition in lithium-codegen-arm64.cc. Any other condition is + // invalid as it doesn't necessary make sense to reverse it (consider + // 'mi' for instance). + UNREACHABLE(); + return nv; + } +} + +enum FlagsUpdate { + SetFlags = 1, + LeaveFlags = 0 +}; + +enum StatusFlags { + NoFlag = 0, + + // Derive the flag combinations from the system register bit descriptions. + NFlag = N_mask, + ZFlag = Z_mask, + CFlag = C_mask, + VFlag = V_mask, + NZFlag = NFlag | ZFlag, + NCFlag = NFlag | CFlag, + NVFlag = NFlag | VFlag, + ZCFlag = ZFlag | CFlag, + ZVFlag = ZFlag | VFlag, + CVFlag = CFlag | VFlag, + NZCFlag = NFlag | ZFlag | CFlag, + NZVFlag = NFlag | ZFlag | VFlag, + NCVFlag = NFlag | CFlag | VFlag, + ZCVFlag = ZFlag | CFlag | VFlag, + NZCVFlag = NFlag | ZFlag | CFlag | VFlag, + + // Floating-point comparison results. + FPEqualFlag = ZCFlag, + FPLessThanFlag = NFlag, + FPGreaterThanFlag = CFlag, + FPUnorderedFlag = CVFlag +}; + +enum Shift { + NO_SHIFT = -1, + LSL = 0x0, + LSR = 0x1, + ASR = 0x2, + ROR = 0x3 +}; + +enum Extend { + NO_EXTEND = -1, + UXTB = 0, + UXTH = 1, + UXTW = 2, + UXTX = 3, + SXTB = 4, + SXTH = 5, + SXTW = 6, + SXTX = 7 +}; + +enum SystemHint { + NOP = 0, + YIELD = 1, + WFE = 2, + WFI = 3, + SEV = 4, + SEVL = 5 +}; + +enum BarrierDomain { + OuterShareable = 0, + NonShareable = 1, + InnerShareable = 2, + FullSystem = 3 +}; + +enum BarrierType { + BarrierOther = 0, + BarrierReads = 1, + BarrierWrites = 2, + BarrierAll = 3 +}; + +// System/special register names. +// This information is not encoded as one field but as the concatenation of +// multiple fields (Op0<0>, Op1, Crn, Crm, Op2). +enum SystemRegister { + NZCV = ((0x1 << SysO0_offset) | + (0x3 << SysOp1_offset) | + (0x4 << CRn_offset) | + (0x2 << CRm_offset) | + (0x0 << SysOp2_offset)) >> ImmSystemRegister_offset, + FPCR = ((0x1 << SysO0_offset) | + (0x3 << SysOp1_offset) | + (0x4 << CRn_offset) | + (0x4 << CRm_offset) | + (0x0 << SysOp2_offset)) >> ImmSystemRegister_offset +}; + +// Instruction enumerations. +// +// These are the masks that define a class of instructions, and the list of +// instructions within each class. Each enumeration has a Fixed, FMask and +// Mask value. +// +// Fixed: The fixed bits in this instruction class. +// FMask: The mask used to extract the fixed bits in the class. +// Mask: The mask used to identify the instructions within a class. +// +// The enumerations can be used like this: +// +// ASSERT(instr->Mask(PCRelAddressingFMask) == PCRelAddressingFixed); +// switch(instr->Mask(PCRelAddressingMask)) { +// case ADR: Format("adr 'Xd, 'AddrPCRelByte"); break; +// case ADRP: Format("adrp 'Xd, 'AddrPCRelPage"); break; +// default: printf("Unknown instruction\n"); +// } + + +// Generic fields. +enum GenericInstrField { + SixtyFourBits = 0x80000000, + ThirtyTwoBits = 0x00000000, + FP32 = 0x00000000, + FP64 = 0x00400000 +}; + +// PC relative addressing. +enum PCRelAddressingOp { + PCRelAddressingFixed = 0x10000000, + PCRelAddressingFMask = 0x1F000000, + PCRelAddressingMask = 0x9F000000, + ADR = PCRelAddressingFixed | 0x00000000, + ADRP = PCRelAddressingFixed | 0x80000000 +}; + +// Add/sub (immediate, shifted and extended.) +const int kSFOffset = 31; +enum AddSubOp { + AddSubOpMask = 0x60000000, + AddSubSetFlagsBit = 0x20000000, + ADD = 0x00000000, + ADDS = ADD | AddSubSetFlagsBit, + SUB = 0x40000000, + SUBS = SUB | AddSubSetFlagsBit +}; + +#define ADD_SUB_OP_LIST(V) \ + V(ADD), \ + V(ADDS), \ + V(SUB), \ + V(SUBS) + +enum AddSubImmediateOp { + AddSubImmediateFixed = 0x11000000, + AddSubImmediateFMask = 0x1F000000, + AddSubImmediateMask = 0xFF000000, + #define ADD_SUB_IMMEDIATE(A) \ + A##_w_imm = AddSubImmediateFixed | A, \ + A##_x_imm = AddSubImmediateFixed | A | SixtyFourBits + ADD_SUB_OP_LIST(ADD_SUB_IMMEDIATE) + #undef ADD_SUB_IMMEDIATE +}; + +enum AddSubShiftedOp { + AddSubShiftedFixed = 0x0B000000, + AddSubShiftedFMask = 0x1F200000, + AddSubShiftedMask = 0xFF200000, + #define ADD_SUB_SHIFTED(A) \ + A##_w_shift = AddSubShiftedFixed | A, \ + A##_x_shift = AddSubShiftedFixed | A | SixtyFourBits + ADD_SUB_OP_LIST(ADD_SUB_SHIFTED) + #undef ADD_SUB_SHIFTED +}; + +enum AddSubExtendedOp { + AddSubExtendedFixed = 0x0B200000, + AddSubExtendedFMask = 0x1F200000, + AddSubExtendedMask = 0xFFE00000, + #define ADD_SUB_EXTENDED(A) \ + A##_w_ext = AddSubExtendedFixed | A, \ + A##_x_ext = AddSubExtendedFixed | A | SixtyFourBits + ADD_SUB_OP_LIST(ADD_SUB_EXTENDED) + #undef ADD_SUB_EXTENDED +}; + +// Add/sub with carry. +enum AddSubWithCarryOp { + AddSubWithCarryFixed = 0x1A000000, + AddSubWithCarryFMask = 0x1FE00000, + AddSubWithCarryMask = 0xFFE0FC00, + ADC_w = AddSubWithCarryFixed | ADD, + ADC_x = AddSubWithCarryFixed | ADD | SixtyFourBits, + ADC = ADC_w, + ADCS_w = AddSubWithCarryFixed | ADDS, + ADCS_x = AddSubWithCarryFixed | ADDS | SixtyFourBits, + SBC_w = AddSubWithCarryFixed | SUB, + SBC_x = AddSubWithCarryFixed | SUB | SixtyFourBits, + SBC = SBC_w, + SBCS_w = AddSubWithCarryFixed | SUBS, + SBCS_x = AddSubWithCarryFixed | SUBS | SixtyFourBits +}; + + +// Logical (immediate and shifted register). +enum LogicalOp { + LogicalOpMask = 0x60200000, + NOT = 0x00200000, + AND = 0x00000000, + BIC = AND | NOT, + ORR = 0x20000000, + ORN = ORR | NOT, + EOR = 0x40000000, + EON = EOR | NOT, + ANDS = 0x60000000, + BICS = ANDS | NOT +}; + +// Logical immediate. +enum LogicalImmediateOp { + LogicalImmediateFixed = 0x12000000, + LogicalImmediateFMask = 0x1F800000, + LogicalImmediateMask = 0xFF800000, + AND_w_imm = LogicalImmediateFixed | AND, + AND_x_imm = LogicalImmediateFixed | AND | SixtyFourBits, + ORR_w_imm = LogicalImmediateFixed | ORR, + ORR_x_imm = LogicalImmediateFixed | ORR | SixtyFourBits, + EOR_w_imm = LogicalImmediateFixed | EOR, + EOR_x_imm = LogicalImmediateFixed | EOR | SixtyFourBits, + ANDS_w_imm = LogicalImmediateFixed | ANDS, + ANDS_x_imm = LogicalImmediateFixed | ANDS | SixtyFourBits +}; + +// Logical shifted register. +enum LogicalShiftedOp { + LogicalShiftedFixed = 0x0A000000, + LogicalShiftedFMask = 0x1F000000, + LogicalShiftedMask = 0xFF200000, + AND_w = LogicalShiftedFixed | AND, + AND_x = LogicalShiftedFixed | AND | SixtyFourBits, + AND_shift = AND_w, + BIC_w = LogicalShiftedFixed | BIC, + BIC_x = LogicalShiftedFixed | BIC | SixtyFourBits, + BIC_shift = BIC_w, + ORR_w = LogicalShiftedFixed | ORR, + ORR_x = LogicalShiftedFixed | ORR | SixtyFourBits, + ORR_shift = ORR_w, + ORN_w = LogicalShiftedFixed | ORN, + ORN_x = LogicalShiftedFixed | ORN | SixtyFourBits, + ORN_shift = ORN_w, + EOR_w = LogicalShiftedFixed | EOR, + EOR_x = LogicalShiftedFixed | EOR | SixtyFourBits, + EOR_shift = EOR_w, + EON_w = LogicalShiftedFixed | EON, + EON_x = LogicalShiftedFixed | EON | SixtyFourBits, + EON_shift = EON_w, + ANDS_w = LogicalShiftedFixed | ANDS, + ANDS_x = LogicalShiftedFixed | ANDS | SixtyFourBits, + ANDS_shift = ANDS_w, + BICS_w = LogicalShiftedFixed | BICS, + BICS_x = LogicalShiftedFixed | BICS | SixtyFourBits, + BICS_shift = BICS_w +}; + +// Move wide immediate. +enum MoveWideImmediateOp { + MoveWideImmediateFixed = 0x12800000, + MoveWideImmediateFMask = 0x1F800000, + MoveWideImmediateMask = 0xFF800000, + MOVN = 0x00000000, + MOVZ = 0x40000000, + MOVK = 0x60000000, + MOVN_w = MoveWideImmediateFixed | MOVN, + MOVN_x = MoveWideImmediateFixed | MOVN | SixtyFourBits, + MOVZ_w = MoveWideImmediateFixed | MOVZ, + MOVZ_x = MoveWideImmediateFixed | MOVZ | SixtyFourBits, + MOVK_w = MoveWideImmediateFixed | MOVK, + MOVK_x = MoveWideImmediateFixed | MOVK | SixtyFourBits +}; + +// Bitfield. +const int kBitfieldNOffset = 22; +enum BitfieldOp { + BitfieldFixed = 0x13000000, + BitfieldFMask = 0x1F800000, + BitfieldMask = 0xFF800000, + SBFM_w = BitfieldFixed | 0x00000000, + SBFM_x = BitfieldFixed | 0x80000000, + SBFM = SBFM_w, + BFM_w = BitfieldFixed | 0x20000000, + BFM_x = BitfieldFixed | 0xA0000000, + BFM = BFM_w, + UBFM_w = BitfieldFixed | 0x40000000, + UBFM_x = BitfieldFixed | 0xC0000000, + UBFM = UBFM_w + // Bitfield N field. +}; + +// Extract. +enum ExtractOp { + ExtractFixed = 0x13800000, + ExtractFMask = 0x1F800000, + ExtractMask = 0xFFA00000, + EXTR_w = ExtractFixed | 0x00000000, + EXTR_x = ExtractFixed | 0x80000000, + EXTR = EXTR_w +}; + +// Unconditional branch. +enum UnconditionalBranchOp { + UnconditionalBranchFixed = 0x14000000, + UnconditionalBranchFMask = 0x7C000000, + UnconditionalBranchMask = 0xFC000000, + B = UnconditionalBranchFixed | 0x00000000, + BL = UnconditionalBranchFixed | 0x80000000 +}; + +// Unconditional branch to register. +enum UnconditionalBranchToRegisterOp { + UnconditionalBranchToRegisterFixed = 0xD6000000, + UnconditionalBranchToRegisterFMask = 0xFE000000, + UnconditionalBranchToRegisterMask = 0xFFFFFC1F, + BR = UnconditionalBranchToRegisterFixed | 0x001F0000, + BLR = UnconditionalBranchToRegisterFixed | 0x003F0000, + RET = UnconditionalBranchToRegisterFixed | 0x005F0000 +}; + +// Compare and branch. +enum CompareBranchOp { + CompareBranchFixed = 0x34000000, + CompareBranchFMask = 0x7E000000, + CompareBranchMask = 0xFF000000, + CBZ_w = CompareBranchFixed | 0x00000000, + CBZ_x = CompareBranchFixed | 0x80000000, + CBZ = CBZ_w, + CBNZ_w = CompareBranchFixed | 0x01000000, + CBNZ_x = CompareBranchFixed | 0x81000000, + CBNZ = CBNZ_w +}; + +// Test and branch. +enum TestBranchOp { + TestBranchFixed = 0x36000000, + TestBranchFMask = 0x7E000000, + TestBranchMask = 0x7F000000, + TBZ = TestBranchFixed | 0x00000000, + TBNZ = TestBranchFixed | 0x01000000 +}; + +// Conditional branch. +enum ConditionalBranchOp { + ConditionalBranchFixed = 0x54000000, + ConditionalBranchFMask = 0xFE000000, + ConditionalBranchMask = 0xFF000010, + B_cond = ConditionalBranchFixed | 0x00000000 +}; + +// System. +// System instruction encoding is complicated because some instructions use op +// and CR fields to encode parameters. To handle this cleanly, the system +// instructions are split into more than one enum. + +enum SystemOp { + SystemFixed = 0xD5000000, + SystemFMask = 0xFFC00000 +}; + +enum SystemSysRegOp { + SystemSysRegFixed = 0xD5100000, + SystemSysRegFMask = 0xFFD00000, + SystemSysRegMask = 0xFFF00000, + MRS = SystemSysRegFixed | 0x00200000, + MSR = SystemSysRegFixed | 0x00000000 +}; + +enum SystemHintOp { + SystemHintFixed = 0xD503201F, + SystemHintFMask = 0xFFFFF01F, + SystemHintMask = 0xFFFFF01F, + HINT = SystemHintFixed | 0x00000000 +}; + +// Exception. +enum ExceptionOp { + ExceptionFixed = 0xD4000000, + ExceptionFMask = 0xFF000000, + ExceptionMask = 0xFFE0001F, + HLT = ExceptionFixed | 0x00400000, + BRK = ExceptionFixed | 0x00200000, + SVC = ExceptionFixed | 0x00000001, + HVC = ExceptionFixed | 0x00000002, + SMC = ExceptionFixed | 0x00000003, + DCPS1 = ExceptionFixed | 0x00A00001, + DCPS2 = ExceptionFixed | 0x00A00002, + DCPS3 = ExceptionFixed | 0x00A00003 +}; +// Code used to spot hlt instructions that should not be hit. +const int kHltBadCode = 0xbad; + +enum MemBarrierOp { + MemBarrierFixed = 0xD503309F, + MemBarrierFMask = 0xFFFFF09F, + MemBarrierMask = 0xFFFFF0FF, + DSB = MemBarrierFixed | 0x00000000, + DMB = MemBarrierFixed | 0x00000020, + ISB = MemBarrierFixed | 0x00000040 +}; + +// Any load or store (including pair). +enum LoadStoreAnyOp { + LoadStoreAnyFMask = 0x0a000000, + LoadStoreAnyFixed = 0x08000000 +}; + +// Any load pair or store pair. +enum LoadStorePairAnyOp { + LoadStorePairAnyFMask = 0x3a000000, + LoadStorePairAnyFixed = 0x28000000 +}; + +#define LOAD_STORE_PAIR_OP_LIST(V) \ + V(STP, w, 0x00000000), \ + V(LDP, w, 0x00400000), \ + V(LDPSW, x, 0x40400000), \ + V(STP, x, 0x80000000), \ + V(LDP, x, 0x80400000), \ + V(STP, s, 0x04000000), \ + V(LDP, s, 0x04400000), \ + V(STP, d, 0x44000000), \ + V(LDP, d, 0x44400000) + +// Load/store pair (post, pre and offset.) +enum LoadStorePairOp { + LoadStorePairMask = 0xC4400000, + LoadStorePairLBit = 1 << 22, + #define LOAD_STORE_PAIR(A, B, C) \ + A##_##B = C + LOAD_STORE_PAIR_OP_LIST(LOAD_STORE_PAIR) + #undef LOAD_STORE_PAIR +}; + +enum LoadStorePairPostIndexOp { + LoadStorePairPostIndexFixed = 0x28800000, + LoadStorePairPostIndexFMask = 0x3B800000, + LoadStorePairPostIndexMask = 0xFFC00000, + #define LOAD_STORE_PAIR_POST_INDEX(A, B, C) \ + A##_##B##_post = LoadStorePairPostIndexFixed | A##_##B + LOAD_STORE_PAIR_OP_LIST(LOAD_STORE_PAIR_POST_INDEX) + #undef LOAD_STORE_PAIR_POST_INDEX +}; + +enum LoadStorePairPreIndexOp { + LoadStorePairPreIndexFixed = 0x29800000, + LoadStorePairPreIndexFMask = 0x3B800000, + LoadStorePairPreIndexMask = 0xFFC00000, + #define LOAD_STORE_PAIR_PRE_INDEX(A, B, C) \ + A##_##B##_pre = LoadStorePairPreIndexFixed | A##_##B + LOAD_STORE_PAIR_OP_LIST(LOAD_STORE_PAIR_PRE_INDEX) + #undef LOAD_STORE_PAIR_PRE_INDEX +}; + +enum LoadStorePairOffsetOp { + LoadStorePairOffsetFixed = 0x29000000, + LoadStorePairOffsetFMask = 0x3B800000, + LoadStorePairOffsetMask = 0xFFC00000, + #define LOAD_STORE_PAIR_OFFSET(A, B, C) \ + A##_##B##_off = LoadStorePairOffsetFixed | A##_##B + LOAD_STORE_PAIR_OP_LIST(LOAD_STORE_PAIR_OFFSET) + #undef LOAD_STORE_PAIR_OFFSET +}; + +enum LoadStorePairNonTemporalOp { + LoadStorePairNonTemporalFixed = 0x28000000, + LoadStorePairNonTemporalFMask = 0x3B800000, + LoadStorePairNonTemporalMask = 0xFFC00000, + STNP_w = LoadStorePairNonTemporalFixed | STP_w, + LDNP_w = LoadStorePairNonTemporalFixed | LDP_w, + STNP_x = LoadStorePairNonTemporalFixed | STP_x, + LDNP_x = LoadStorePairNonTemporalFixed | LDP_x, + STNP_s = LoadStorePairNonTemporalFixed | STP_s, + LDNP_s = LoadStorePairNonTemporalFixed | LDP_s, + STNP_d = LoadStorePairNonTemporalFixed | STP_d, + LDNP_d = LoadStorePairNonTemporalFixed | LDP_d +}; + +// Load literal. +enum LoadLiteralOp { + LoadLiteralFixed = 0x18000000, + LoadLiteralFMask = 0x3B000000, + LoadLiteralMask = 0xFF000000, + LDR_w_lit = LoadLiteralFixed | 0x00000000, + LDR_x_lit = LoadLiteralFixed | 0x40000000, + LDRSW_x_lit = LoadLiteralFixed | 0x80000000, + PRFM_lit = LoadLiteralFixed | 0xC0000000, + LDR_s_lit = LoadLiteralFixed | 0x04000000, + LDR_d_lit = LoadLiteralFixed | 0x44000000 +}; + +#define LOAD_STORE_OP_LIST(V) \ + V(ST, RB, w, 0x00000000), \ + V(ST, RH, w, 0x40000000), \ + V(ST, R, w, 0x80000000), \ + V(ST, R, x, 0xC0000000), \ + V(LD, RB, w, 0x00400000), \ + V(LD, RH, w, 0x40400000), \ + V(LD, R, w, 0x80400000), \ + V(LD, R, x, 0xC0400000), \ + V(LD, RSB, x, 0x00800000), \ + V(LD, RSH, x, 0x40800000), \ + V(LD, RSW, x, 0x80800000), \ + V(LD, RSB, w, 0x00C00000), \ + V(LD, RSH, w, 0x40C00000), \ + V(ST, R, s, 0x84000000), \ + V(ST, R, d, 0xC4000000), \ + V(LD, R, s, 0x84400000), \ + V(LD, R, d, 0xC4400000) + + +// Load/store unscaled offset. +enum LoadStoreUnscaledOffsetOp { + LoadStoreUnscaledOffsetFixed = 0x38000000, + LoadStoreUnscaledOffsetFMask = 0x3B200C00, + LoadStoreUnscaledOffsetMask = 0xFFE00C00, + #define LOAD_STORE_UNSCALED(A, B, C, D) \ + A##U##B##_##C = LoadStoreUnscaledOffsetFixed | D + LOAD_STORE_OP_LIST(LOAD_STORE_UNSCALED) + #undef LOAD_STORE_UNSCALED +}; + +// Load/store (post, pre, offset and unsigned.) +enum LoadStoreOp { + LoadStoreOpMask = 0xC4C00000, + #define LOAD_STORE(A, B, C, D) \ + A##B##_##C = D + LOAD_STORE_OP_LIST(LOAD_STORE), + #undef LOAD_STORE + PRFM = 0xC0800000 +}; + +// Load/store post index. +enum LoadStorePostIndex { + LoadStorePostIndexFixed = 0x38000400, + LoadStorePostIndexFMask = 0x3B200C00, + LoadStorePostIndexMask = 0xFFE00C00, + #define LOAD_STORE_POST_INDEX(A, B, C, D) \ + A##B##_##C##_post = LoadStorePostIndexFixed | D + LOAD_STORE_OP_LIST(LOAD_STORE_POST_INDEX) + #undef LOAD_STORE_POST_INDEX +}; + +// Load/store pre index. +enum LoadStorePreIndex { + LoadStorePreIndexFixed = 0x38000C00, + LoadStorePreIndexFMask = 0x3B200C00, + LoadStorePreIndexMask = 0xFFE00C00, + #define LOAD_STORE_PRE_INDEX(A, B, C, D) \ + A##B##_##C##_pre = LoadStorePreIndexFixed | D + LOAD_STORE_OP_LIST(LOAD_STORE_PRE_INDEX) + #undef LOAD_STORE_PRE_INDEX +}; + +// Load/store unsigned offset. +enum LoadStoreUnsignedOffset { + LoadStoreUnsignedOffsetFixed = 0x39000000, + LoadStoreUnsignedOffsetFMask = 0x3B000000, + LoadStoreUnsignedOffsetMask = 0xFFC00000, + PRFM_unsigned = LoadStoreUnsignedOffsetFixed | PRFM, + #define LOAD_STORE_UNSIGNED_OFFSET(A, B, C, D) \ + A##B##_##C##_unsigned = LoadStoreUnsignedOffsetFixed | D + LOAD_STORE_OP_LIST(LOAD_STORE_UNSIGNED_OFFSET) + #undef LOAD_STORE_UNSIGNED_OFFSET +}; + +// Load/store register offset. +enum LoadStoreRegisterOffset { + LoadStoreRegisterOffsetFixed = 0x38200800, + LoadStoreRegisterOffsetFMask = 0x3B200C00, + LoadStoreRegisterOffsetMask = 0xFFE00C00, + PRFM_reg = LoadStoreRegisterOffsetFixed | PRFM, + #define LOAD_STORE_REGISTER_OFFSET(A, B, C, D) \ + A##B##_##C##_reg = LoadStoreRegisterOffsetFixed | D + LOAD_STORE_OP_LIST(LOAD_STORE_REGISTER_OFFSET) + #undef LOAD_STORE_REGISTER_OFFSET +}; + +// Conditional compare. +enum ConditionalCompareOp { + ConditionalCompareMask = 0x60000000, + CCMN = 0x20000000, + CCMP = 0x60000000 +}; + +// Conditional compare register. +enum ConditionalCompareRegisterOp { + ConditionalCompareRegisterFixed = 0x1A400000, + ConditionalCompareRegisterFMask = 0x1FE00800, + ConditionalCompareRegisterMask = 0xFFE00C10, + CCMN_w = ConditionalCompareRegisterFixed | CCMN, + CCMN_x = ConditionalCompareRegisterFixed | SixtyFourBits | CCMN, + CCMP_w = ConditionalCompareRegisterFixed | CCMP, + CCMP_x = ConditionalCompareRegisterFixed | SixtyFourBits | CCMP +}; + +// Conditional compare immediate. +enum ConditionalCompareImmediateOp { + ConditionalCompareImmediateFixed = 0x1A400800, + ConditionalCompareImmediateFMask = 0x1FE00800, + ConditionalCompareImmediateMask = 0xFFE00C10, + CCMN_w_imm = ConditionalCompareImmediateFixed | CCMN, + CCMN_x_imm = ConditionalCompareImmediateFixed | SixtyFourBits | CCMN, + CCMP_w_imm = ConditionalCompareImmediateFixed | CCMP, + CCMP_x_imm = ConditionalCompareImmediateFixed | SixtyFourBits | CCMP +}; + +// Conditional select. +enum ConditionalSelectOp { + ConditionalSelectFixed = 0x1A800000, + ConditionalSelectFMask = 0x1FE00000, + ConditionalSelectMask = 0xFFE00C00, + CSEL_w = ConditionalSelectFixed | 0x00000000, + CSEL_x = ConditionalSelectFixed | 0x80000000, + CSEL = CSEL_w, + CSINC_w = ConditionalSelectFixed | 0x00000400, + CSINC_x = ConditionalSelectFixed | 0x80000400, + CSINC = CSINC_w, + CSINV_w = ConditionalSelectFixed | 0x40000000, + CSINV_x = ConditionalSelectFixed | 0xC0000000, + CSINV = CSINV_w, + CSNEG_w = ConditionalSelectFixed | 0x40000400, + CSNEG_x = ConditionalSelectFixed | 0xC0000400, + CSNEG = CSNEG_w +}; + +// Data processing 1 source. +enum DataProcessing1SourceOp { + DataProcessing1SourceFixed = 0x5AC00000, + DataProcessing1SourceFMask = 0x5FE00000, + DataProcessing1SourceMask = 0xFFFFFC00, + RBIT = DataProcessing1SourceFixed | 0x00000000, + RBIT_w = RBIT, + RBIT_x = RBIT | SixtyFourBits, + REV16 = DataProcessing1SourceFixed | 0x00000400, + REV16_w = REV16, + REV16_x = REV16 | SixtyFourBits, + REV = DataProcessing1SourceFixed | 0x00000800, + REV_w = REV, + REV32_x = REV | SixtyFourBits, + REV_x = DataProcessing1SourceFixed | SixtyFourBits | 0x00000C00, + CLZ = DataProcessing1SourceFixed | 0x00001000, + CLZ_w = CLZ, + CLZ_x = CLZ | SixtyFourBits, + CLS = DataProcessing1SourceFixed | 0x00001400, + CLS_w = CLS, + CLS_x = CLS | SixtyFourBits +}; + +// Data processing 2 source. +enum DataProcessing2SourceOp { + DataProcessing2SourceFixed = 0x1AC00000, + DataProcessing2SourceFMask = 0x5FE00000, + DataProcessing2SourceMask = 0xFFE0FC00, + UDIV_w = DataProcessing2SourceFixed | 0x00000800, + UDIV_x = DataProcessing2SourceFixed | 0x80000800, + UDIV = UDIV_w, + SDIV_w = DataProcessing2SourceFixed | 0x00000C00, + SDIV_x = DataProcessing2SourceFixed | 0x80000C00, + SDIV = SDIV_w, + LSLV_w = DataProcessing2SourceFixed | 0x00002000, + LSLV_x = DataProcessing2SourceFixed | 0x80002000, + LSLV = LSLV_w, + LSRV_w = DataProcessing2SourceFixed | 0x00002400, + LSRV_x = DataProcessing2SourceFixed | 0x80002400, + LSRV = LSRV_w, + ASRV_w = DataProcessing2SourceFixed | 0x00002800, + ASRV_x = DataProcessing2SourceFixed | 0x80002800, + ASRV = ASRV_w, + RORV_w = DataProcessing2SourceFixed | 0x00002C00, + RORV_x = DataProcessing2SourceFixed | 0x80002C00, + RORV = RORV_w, + CRC32B = DataProcessing2SourceFixed | 0x00004000, + CRC32H = DataProcessing2SourceFixed | 0x00004400, + CRC32W = DataProcessing2SourceFixed | 0x00004800, + CRC32X = DataProcessing2SourceFixed | SixtyFourBits | 0x00004C00, + CRC32CB = DataProcessing2SourceFixed | 0x00005000, + CRC32CH = DataProcessing2SourceFixed | 0x00005400, + CRC32CW = DataProcessing2SourceFixed | 0x00005800, + CRC32CX = DataProcessing2SourceFixed | SixtyFourBits | 0x00005C00 +}; + +// Data processing 3 source. +enum DataProcessing3SourceOp { + DataProcessing3SourceFixed = 0x1B000000, + DataProcessing3SourceFMask = 0x1F000000, + DataProcessing3SourceMask = 0xFFE08000, + MADD_w = DataProcessing3SourceFixed | 0x00000000, + MADD_x = DataProcessing3SourceFixed | 0x80000000, + MADD = MADD_w, + MSUB_w = DataProcessing3SourceFixed | 0x00008000, + MSUB_x = DataProcessing3SourceFixed | 0x80008000, + MSUB = MSUB_w, + SMADDL_x = DataProcessing3SourceFixed | 0x80200000, + SMSUBL_x = DataProcessing3SourceFixed | 0x80208000, + SMULH_x = DataProcessing3SourceFixed | 0x80400000, + UMADDL_x = DataProcessing3SourceFixed | 0x80A00000, + UMSUBL_x = DataProcessing3SourceFixed | 0x80A08000, + UMULH_x = DataProcessing3SourceFixed | 0x80C00000 +}; + +// Floating point compare. +enum FPCompareOp { + FPCompareFixed = 0x1E202000, + FPCompareFMask = 0x5F203C00, + FPCompareMask = 0xFFE0FC1F, + FCMP_s = FPCompareFixed | 0x00000000, + FCMP_d = FPCompareFixed | FP64 | 0x00000000, + FCMP = FCMP_s, + FCMP_s_zero = FPCompareFixed | 0x00000008, + FCMP_d_zero = FPCompareFixed | FP64 | 0x00000008, + FCMP_zero = FCMP_s_zero, + FCMPE_s = FPCompareFixed | 0x00000010, + FCMPE_d = FPCompareFixed | FP64 | 0x00000010, + FCMPE_s_zero = FPCompareFixed | 0x00000018, + FCMPE_d_zero = FPCompareFixed | FP64 | 0x00000018 +}; + +// Floating point conditional compare. +enum FPConditionalCompareOp { + FPConditionalCompareFixed = 0x1E200400, + FPConditionalCompareFMask = 0x5F200C00, + FPConditionalCompareMask = 0xFFE00C10, + FCCMP_s = FPConditionalCompareFixed | 0x00000000, + FCCMP_d = FPConditionalCompareFixed | FP64 | 0x00000000, + FCCMP = FCCMP_s, + FCCMPE_s = FPConditionalCompareFixed | 0x00000010, + FCCMPE_d = FPConditionalCompareFixed | FP64 | 0x00000010, + FCCMPE = FCCMPE_s +}; + +// Floating point conditional select. +enum FPConditionalSelectOp { + FPConditionalSelectFixed = 0x1E200C00, + FPConditionalSelectFMask = 0x5F200C00, + FPConditionalSelectMask = 0xFFE00C00, + FCSEL_s = FPConditionalSelectFixed | 0x00000000, + FCSEL_d = FPConditionalSelectFixed | FP64 | 0x00000000, + FCSEL = FCSEL_s +}; + +// Floating point immediate. +enum FPImmediateOp { + FPImmediateFixed = 0x1E201000, + FPImmediateFMask = 0x5F201C00, + FPImmediateMask = 0xFFE01C00, + FMOV_s_imm = FPImmediateFixed | 0x00000000, + FMOV_d_imm = FPImmediateFixed | FP64 | 0x00000000 +}; + +// Floating point data processing 1 source. +enum FPDataProcessing1SourceOp { + FPDataProcessing1SourceFixed = 0x1E204000, + FPDataProcessing1SourceFMask = 0x5F207C00, + FPDataProcessing1SourceMask = 0xFFFFFC00, + FMOV_s = FPDataProcessing1SourceFixed | 0x00000000, + FMOV_d = FPDataProcessing1SourceFixed | FP64 | 0x00000000, + FMOV = FMOV_s, + FABS_s = FPDataProcessing1SourceFixed | 0x00008000, + FABS_d = FPDataProcessing1SourceFixed | FP64 | 0x00008000, + FABS = FABS_s, + FNEG_s = FPDataProcessing1SourceFixed | 0x00010000, + FNEG_d = FPDataProcessing1SourceFixed | FP64 | 0x00010000, + FNEG = FNEG_s, + FSQRT_s = FPDataProcessing1SourceFixed | 0x00018000, + FSQRT_d = FPDataProcessing1SourceFixed | FP64 | 0x00018000, + FSQRT = FSQRT_s, + FCVT_ds = FPDataProcessing1SourceFixed | 0x00028000, + FCVT_sd = FPDataProcessing1SourceFixed | FP64 | 0x00020000, + FRINTN_s = FPDataProcessing1SourceFixed | 0x00040000, + FRINTN_d = FPDataProcessing1SourceFixed | FP64 | 0x00040000, + FRINTN = FRINTN_s, + FRINTP_s = FPDataProcessing1SourceFixed | 0x00048000, + FRINTP_d = FPDataProcessing1SourceFixed | FP64 | 0x00048000, + FRINTP = FRINTP_s, + FRINTM_s = FPDataProcessing1SourceFixed | 0x00050000, + FRINTM_d = FPDataProcessing1SourceFixed | FP64 | 0x00050000, + FRINTM = FRINTM_s, + FRINTZ_s = FPDataProcessing1SourceFixed | 0x00058000, + FRINTZ_d = FPDataProcessing1SourceFixed | FP64 | 0x00058000, + FRINTZ = FRINTZ_s, + FRINTA_s = FPDataProcessing1SourceFixed | 0x00060000, + FRINTA_d = FPDataProcessing1SourceFixed | FP64 | 0x00060000, + FRINTA = FRINTA_s, + FRINTX_s = FPDataProcessing1SourceFixed | 0x00070000, + FRINTX_d = FPDataProcessing1SourceFixed | FP64 | 0x00070000, + FRINTX = FRINTX_s, + FRINTI_s = FPDataProcessing1SourceFixed | 0x00078000, + FRINTI_d = FPDataProcessing1SourceFixed | FP64 | 0x00078000, + FRINTI = FRINTI_s +}; + +// Floating point data processing 2 source. +enum FPDataProcessing2SourceOp { + FPDataProcessing2SourceFixed = 0x1E200800, + FPDataProcessing2SourceFMask = 0x5F200C00, + FPDataProcessing2SourceMask = 0xFFE0FC00, + FMUL = FPDataProcessing2SourceFixed | 0x00000000, + FMUL_s = FMUL, + FMUL_d = FMUL | FP64, + FDIV = FPDataProcessing2SourceFixed | 0x00001000, + FDIV_s = FDIV, + FDIV_d = FDIV | FP64, + FADD = FPDataProcessing2SourceFixed | 0x00002000, + FADD_s = FADD, + FADD_d = FADD | FP64, + FSUB = FPDataProcessing2SourceFixed | 0x00003000, + FSUB_s = FSUB, + FSUB_d = FSUB | FP64, + FMAX = FPDataProcessing2SourceFixed | 0x00004000, + FMAX_s = FMAX, + FMAX_d = FMAX | FP64, + FMIN = FPDataProcessing2SourceFixed | 0x00005000, + FMIN_s = FMIN, + FMIN_d = FMIN | FP64, + FMAXNM = FPDataProcessing2SourceFixed | 0x00006000, + FMAXNM_s = FMAXNM, + FMAXNM_d = FMAXNM | FP64, + FMINNM = FPDataProcessing2SourceFixed | 0x00007000, + FMINNM_s = FMINNM, + FMINNM_d = FMINNM | FP64, + FNMUL = FPDataProcessing2SourceFixed | 0x00008000, + FNMUL_s = FNMUL, + FNMUL_d = FNMUL | FP64 +}; + +// Floating point data processing 3 source. +enum FPDataProcessing3SourceOp { + FPDataProcessing3SourceFixed = 0x1F000000, + FPDataProcessing3SourceFMask = 0x5F000000, + FPDataProcessing3SourceMask = 0xFFE08000, + FMADD_s = FPDataProcessing3SourceFixed | 0x00000000, + FMSUB_s = FPDataProcessing3SourceFixed | 0x00008000, + FNMADD_s = FPDataProcessing3SourceFixed | 0x00200000, + FNMSUB_s = FPDataProcessing3SourceFixed | 0x00208000, + FMADD_d = FPDataProcessing3SourceFixed | 0x00400000, + FMSUB_d = FPDataProcessing3SourceFixed | 0x00408000, + FNMADD_d = FPDataProcessing3SourceFixed | 0x00600000, + FNMSUB_d = FPDataProcessing3SourceFixed | 0x00608000 +}; + +// Conversion between floating point and integer. +enum FPIntegerConvertOp { + FPIntegerConvertFixed = 0x1E200000, + FPIntegerConvertFMask = 0x5F20FC00, + FPIntegerConvertMask = 0xFFFFFC00, + FCVTNS = FPIntegerConvertFixed | 0x00000000, + FCVTNS_ws = FCVTNS, + FCVTNS_xs = FCVTNS | SixtyFourBits, + FCVTNS_wd = FCVTNS | FP64, + FCVTNS_xd = FCVTNS | SixtyFourBits | FP64, + FCVTNU = FPIntegerConvertFixed | 0x00010000, + FCVTNU_ws = FCVTNU, + FCVTNU_xs = FCVTNU | SixtyFourBits, + FCVTNU_wd = FCVTNU | FP64, + FCVTNU_xd = FCVTNU | SixtyFourBits | FP64, + FCVTPS = FPIntegerConvertFixed | 0x00080000, + FCVTPS_ws = FCVTPS, + FCVTPS_xs = FCVTPS | SixtyFourBits, + FCVTPS_wd = FCVTPS | FP64, + FCVTPS_xd = FCVTPS | SixtyFourBits | FP64, + FCVTPU = FPIntegerConvertFixed | 0x00090000, + FCVTPU_ws = FCVTPU, + FCVTPU_xs = FCVTPU | SixtyFourBits, + FCVTPU_wd = FCVTPU | FP64, + FCVTPU_xd = FCVTPU | SixtyFourBits | FP64, + FCVTMS = FPIntegerConvertFixed | 0x00100000, + FCVTMS_ws = FCVTMS, + FCVTMS_xs = FCVTMS | SixtyFourBits, + FCVTMS_wd = FCVTMS | FP64, + FCVTMS_xd = FCVTMS | SixtyFourBits | FP64, + FCVTMU = FPIntegerConvertFixed | 0x00110000, + FCVTMU_ws = FCVTMU, + FCVTMU_xs = FCVTMU | SixtyFourBits, + FCVTMU_wd = FCVTMU | FP64, + FCVTMU_xd = FCVTMU | SixtyFourBits | FP64, + FCVTZS = FPIntegerConvertFixed | 0x00180000, + FCVTZS_ws = FCVTZS, + FCVTZS_xs = FCVTZS | SixtyFourBits, + FCVTZS_wd = FCVTZS | FP64, + FCVTZS_xd = FCVTZS | SixtyFourBits | FP64, + FCVTZU = FPIntegerConvertFixed | 0x00190000, + FCVTZU_ws = FCVTZU, + FCVTZU_xs = FCVTZU | SixtyFourBits, + FCVTZU_wd = FCVTZU | FP64, + FCVTZU_xd = FCVTZU | SixtyFourBits | FP64, + SCVTF = FPIntegerConvertFixed | 0x00020000, + SCVTF_sw = SCVTF, + SCVTF_sx = SCVTF | SixtyFourBits, + SCVTF_dw = SCVTF | FP64, + SCVTF_dx = SCVTF | SixtyFourBits | FP64, + UCVTF = FPIntegerConvertFixed | 0x00030000, + UCVTF_sw = UCVTF, + UCVTF_sx = UCVTF | SixtyFourBits, + UCVTF_dw = UCVTF | FP64, + UCVTF_dx = UCVTF | SixtyFourBits | FP64, + FCVTAS = FPIntegerConvertFixed | 0x00040000, + FCVTAS_ws = FCVTAS, + FCVTAS_xs = FCVTAS | SixtyFourBits, + FCVTAS_wd = FCVTAS | FP64, + FCVTAS_xd = FCVTAS | SixtyFourBits | FP64, + FCVTAU = FPIntegerConvertFixed | 0x00050000, + FCVTAU_ws = FCVTAU, + FCVTAU_xs = FCVTAU | SixtyFourBits, + FCVTAU_wd = FCVTAU | FP64, + FCVTAU_xd = FCVTAU | SixtyFourBits | FP64, + FMOV_ws = FPIntegerConvertFixed | 0x00060000, + FMOV_sw = FPIntegerConvertFixed | 0x00070000, + FMOV_xd = FMOV_ws | SixtyFourBits | FP64, + FMOV_dx = FMOV_sw | SixtyFourBits | FP64 +}; + +// Conversion between fixed point and floating point. +enum FPFixedPointConvertOp { + FPFixedPointConvertFixed = 0x1E000000, + FPFixedPointConvertFMask = 0x5F200000, + FPFixedPointConvertMask = 0xFFFF0000, + FCVTZS_fixed = FPFixedPointConvertFixed | 0x00180000, + FCVTZS_ws_fixed = FCVTZS_fixed, + FCVTZS_xs_fixed = FCVTZS_fixed | SixtyFourBits, + FCVTZS_wd_fixed = FCVTZS_fixed | FP64, + FCVTZS_xd_fixed = FCVTZS_fixed | SixtyFourBits | FP64, + FCVTZU_fixed = FPFixedPointConvertFixed | 0x00190000, + FCVTZU_ws_fixed = FCVTZU_fixed, + FCVTZU_xs_fixed = FCVTZU_fixed | SixtyFourBits, + FCVTZU_wd_fixed = FCVTZU_fixed | FP64, + FCVTZU_xd_fixed = FCVTZU_fixed | SixtyFourBits | FP64, + SCVTF_fixed = FPFixedPointConvertFixed | 0x00020000, + SCVTF_sw_fixed = SCVTF_fixed, + SCVTF_sx_fixed = SCVTF_fixed | SixtyFourBits, + SCVTF_dw_fixed = SCVTF_fixed | FP64, + SCVTF_dx_fixed = SCVTF_fixed | SixtyFourBits | FP64, + UCVTF_fixed = FPFixedPointConvertFixed | 0x00030000, + UCVTF_sw_fixed = UCVTF_fixed, + UCVTF_sx_fixed = UCVTF_fixed | SixtyFourBits, + UCVTF_dw_fixed = UCVTF_fixed | FP64, + UCVTF_dx_fixed = UCVTF_fixed | SixtyFourBits | FP64 +}; + +// Unimplemented and unallocated instructions. These are defined to make fixed +// bit assertion easier. +enum UnimplementedOp { + UnimplementedFixed = 0x00000000, + UnimplementedFMask = 0x00000000 +}; + +enum UnallocatedOp { + UnallocatedFixed = 0x00000000, + UnallocatedFMask = 0x00000000 +}; + +} } // namespace v8::internal + +#endif // V8_ARM64_CONSTANTS_ARM64_H_ diff --git a/chromium/v8/src/arm64/cpu-arm64.cc b/chromium/v8/src/arm64/cpu-arm64.cc new file mode 100644 index 00000000000..4cfc4f04b62 --- /dev/null +++ b/chromium/v8/src/arm64/cpu-arm64.cc @@ -0,0 +1,123 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// CPU specific code for arm independent of OS goes here. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/cpu.h" +#include "src/arm64/utils-arm64.h" + +namespace v8 { +namespace internal { + +class CacheLineSizes { + public: + CacheLineSizes() { +#ifdef USE_SIMULATOR + cache_type_register_ = 0; +#else + // Copy the content of the cache type register to a core register. + __asm__ __volatile__ ("mrs %[ctr], ctr_el0" // NOLINT + : [ctr] "=r" (cache_type_register_)); +#endif + } + + uint32_t icache_line_size() const { return ExtractCacheLineSize(0); } + uint32_t dcache_line_size() const { return ExtractCacheLineSize(16); } + + private: + uint32_t ExtractCacheLineSize(int cache_line_size_shift) const { + // The cache type register holds the size of cache lines in words as a + // power of two. + return 4 << ((cache_type_register_ >> cache_line_size_shift) & 0xf); + } + + uint32_t cache_type_register_; +}; + + +void CPU::FlushICache(void* address, size_t length) { + if (length == 0) return; + +#ifdef USE_SIMULATOR + // TODO(all): consider doing some cache simulation to ensure every address + // run has been synced. + USE(address); + USE(length); +#else + // The code below assumes user space cache operations are allowed. The goal + // of this routine is to make sure the code generated is visible to the I + // side of the CPU. + + uintptr_t start = reinterpret_cast<uintptr_t>(address); + // Sizes will be used to generate a mask big enough to cover a pointer. + CacheLineSizes sizes; + uintptr_t dsize = sizes.dcache_line_size(); + uintptr_t isize = sizes.icache_line_size(); + // Cache line sizes are always a power of 2. + ASSERT(CountSetBits(dsize, 64) == 1); + ASSERT(CountSetBits(isize, 64) == 1); + uintptr_t dstart = start & ~(dsize - 1); + uintptr_t istart = start & ~(isize - 1); + uintptr_t end = start + length; + + __asm__ __volatile__ ( // NOLINT + // Clean every line of the D cache containing the target data. + "0: \n\t" + // dc : Data Cache maintenance + // c : Clean + // va : by (Virtual) Address + // u : to the point of Unification + // The point of unification for a processor is the point by which the + // instruction and data caches are guaranteed to see the same copy of a + // memory location. See ARM DDI 0406B page B2-12 for more information. + "dc cvau, %[dline] \n\t" + "add %[dline], %[dline], %[dsize] \n\t" + "cmp %[dline], %[end] \n\t" + "b.lt 0b \n\t" + // Barrier to make sure the effect of the code above is visible to the rest + // of the world. + // dsb : Data Synchronisation Barrier + // ish : Inner SHareable domain + // The point of unification for an Inner Shareable shareability domain is + // the point by which the instruction and data caches of all the processors + // in that Inner Shareable shareability domain are guaranteed to see the + // same copy of a memory location. See ARM DDI 0406B page B2-12 for more + // information. + "dsb ish \n\t" + // Invalidate every line of the I cache containing the target data. + "1: \n\t" + // ic : instruction cache maintenance + // i : invalidate + // va : by address + // u : to the point of unification + "ic ivau, %[iline] \n\t" + "add %[iline], %[iline], %[isize] \n\t" + "cmp %[iline], %[end] \n\t" + "b.lt 1b \n\t" + // Barrier to make sure the effect of the code above is visible to the rest + // of the world. + "dsb ish \n\t" + // Barrier to ensure any prefetching which happened before this code is + // discarded. + // isb : Instruction Synchronisation Barrier + "isb \n\t" + : [dline] "+r" (dstart), + [iline] "+r" (istart) + : [dsize] "r" (dsize), + [isize] "r" (isize), + [end] "r" (end) + // This code does not write to memory but without the dependency gcc might + // move this code before the code is generated. + : "cc", "memory" + ); // NOLINT +#endif +} + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/debug-arm64.cc b/chromium/v8/src/arm64/debug-arm64.cc new file mode 100644 index 00000000000..556231602e6 --- /dev/null +++ b/chromium/v8/src/arm64/debug-arm64.cc @@ -0,0 +1,357 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/codegen.h" +#include "src/debug.h" + +namespace v8 { +namespace internal { + + +#define __ ACCESS_MASM(masm) + +bool BreakLocationIterator::IsDebugBreakAtReturn() { + return Debug::IsDebugBreakAtReturn(rinfo()); +} + + +void BreakLocationIterator::SetDebugBreakAtReturn() { + // Patch the code emitted by FullCodeGenerator::EmitReturnSequence, changing + // the return from JS function sequence from + // mov sp, fp + // ldp fp, lr, [sp] #16 + // lrd ip0, [pc, #(3 * kInstructionSize)] + // add sp, sp, ip0 + // ret + // <number of paramters ... + // ... plus one (64 bits)> + // to a call to the debug break return code. + // ldr ip0, [pc, #(3 * kInstructionSize)] + // blr ip0 + // hlt kHltBadCode @ code should not return, catch if it does. + // <debug break return code ... + // ... entry point address (64 bits)> + + // The patching code must not overflow the space occupied by the return + // sequence. + STATIC_ASSERT(Assembler::kJSRetSequenceInstructions >= 5); + PatchingAssembler patcher(reinterpret_cast<Instruction*>(rinfo()->pc()), 5); + byte* entry = + debug_info_->GetIsolate()->builtins()->Return_DebugBreak()->entry(); + + // The first instruction of a patched return sequence must be a load literal + // loading the address of the debug break return code. + patcher.ldr_pcrel(ip0, (3 * kInstructionSize) >> kLoadLiteralScaleLog2); + // TODO(all): check the following is correct. + // The debug break return code will push a frame and call statically compiled + // code. By using blr, even though control will not return after the branch, + // this call site will be registered in the frame (lr being saved as the pc + // of the next instruction to execute for this frame). The debugger can now + // iterate on the frames to find call to debug break return code. + patcher.blr(ip0); + patcher.hlt(kHltBadCode); + patcher.dc64(reinterpret_cast<int64_t>(entry)); +} + + +void BreakLocationIterator::ClearDebugBreakAtReturn() { + // Reset the code emitted by EmitReturnSequence to its original state. + rinfo()->PatchCode(original_rinfo()->pc(), + Assembler::kJSRetSequenceInstructions); +} + + +bool Debug::IsDebugBreakAtReturn(RelocInfo* rinfo) { + ASSERT(RelocInfo::IsJSReturn(rinfo->rmode())); + return rinfo->IsPatchedReturnSequence(); +} + + +bool BreakLocationIterator::IsDebugBreakAtSlot() { + ASSERT(IsDebugBreakSlot()); + // Check whether the debug break slot instructions have been patched. + return rinfo()->IsPatchedDebugBreakSlotSequence(); +} + + +void BreakLocationIterator::SetDebugBreakAtSlot() { + // Patch the code emitted by DebugCodegen::GenerateSlots, changing the debug + // break slot code from + // mov x0, x0 @ nop DEBUG_BREAK_NOP + // mov x0, x0 @ nop DEBUG_BREAK_NOP + // mov x0, x0 @ nop DEBUG_BREAK_NOP + // mov x0, x0 @ nop DEBUG_BREAK_NOP + // to a call to the debug slot code. + // ldr ip0, [pc, #(2 * kInstructionSize)] + // blr ip0 + // <debug break slot code ... + // ... entry point address (64 bits)> + + // TODO(all): consider adding a hlt instruction after the blr as we don't + // expect control to return here. This implies increasing + // kDebugBreakSlotInstructions to 5 instructions. + + // The patching code must not overflow the space occupied by the return + // sequence. + STATIC_ASSERT(Assembler::kDebugBreakSlotInstructions >= 4); + PatchingAssembler patcher(reinterpret_cast<Instruction*>(rinfo()->pc()), 4); + byte* entry = + debug_info_->GetIsolate()->builtins()->Slot_DebugBreak()->entry(); + + // The first instruction of a patched debug break slot must be a load literal + // loading the address of the debug break slot code. + patcher.ldr_pcrel(ip0, (2 * kInstructionSize) >> kLoadLiteralScaleLog2); + // TODO(all): check the following is correct. + // The debug break slot code will push a frame and call statically compiled + // code. By using blr, event hough control will not return after the branch, + // this call site will be registered in the frame (lr being saved as the pc + // of the next instruction to execute for this frame). The debugger can now + // iterate on the frames to find call to debug break slot code. + patcher.blr(ip0); + patcher.dc64(reinterpret_cast<int64_t>(entry)); +} + + +void BreakLocationIterator::ClearDebugBreakAtSlot() { + ASSERT(IsDebugBreakSlot()); + rinfo()->PatchCode(original_rinfo()->pc(), + Assembler::kDebugBreakSlotInstructions); +} + + +static void Generate_DebugBreakCallHelper(MacroAssembler* masm, + RegList object_regs, + RegList non_object_regs, + Register scratch) { + { + FrameScope scope(masm, StackFrame::INTERNAL); + + // Any live values (object_regs and non_object_regs) in caller-saved + // registers (or lr) need to be stored on the stack so that their values are + // safely preserved for a call into C code. + // + // Also: + // * object_regs may be modified during the C code by the garbage + // collector. Every object register must be a valid tagged pointer or + // SMI. + // + // * non_object_regs will be converted to SMIs so that the garbage + // collector doesn't try to interpret them as pointers. + // + // TODO(jbramley): Why can't this handle callee-saved registers? + ASSERT((~kCallerSaved.list() & object_regs) == 0); + ASSERT((~kCallerSaved.list() & non_object_regs) == 0); + ASSERT((object_regs & non_object_regs) == 0); + ASSERT((scratch.Bit() & object_regs) == 0); + ASSERT((scratch.Bit() & non_object_regs) == 0); + ASSERT((masm->TmpList()->list() & (object_regs | non_object_regs)) == 0); + STATIC_ASSERT(kSmiValueSize == 32); + + CPURegList non_object_list = + CPURegList(CPURegister::kRegister, kXRegSizeInBits, non_object_regs); + while (!non_object_list.IsEmpty()) { + // Store each non-object register as two SMIs. + Register reg = Register(non_object_list.PopLowestIndex()); + __ Lsr(scratch, reg, 32); + __ SmiTagAndPush(scratch, reg); + + // Stack: + // jssp[12]: reg[63:32] + // jssp[8]: 0x00000000 (SMI tag & padding) + // jssp[4]: reg[31:0] + // jssp[0]: 0x00000000 (SMI tag & padding) + STATIC_ASSERT((kSmiTag == 0) && (kSmiShift == 32)); + } + + if (object_regs != 0) { + __ PushXRegList(object_regs); + } + +#ifdef DEBUG + __ RecordComment("// Calling from debug break to runtime - come in - over"); +#endif + __ Mov(x0, 0); // No arguments. + __ Mov(x1, ExternalReference::debug_break(masm->isolate())); + + CEntryStub stub(masm->isolate(), 1); + __ CallStub(&stub); + + // Restore the register values from the expression stack. + if (object_regs != 0) { + __ PopXRegList(object_regs); + } + + non_object_list = + CPURegList(CPURegister::kRegister, kXRegSizeInBits, non_object_regs); + while (!non_object_list.IsEmpty()) { + // Load each non-object register from two SMIs. + // Stack: + // jssp[12]: reg[63:32] + // jssp[8]: 0x00000000 (SMI tag & padding) + // jssp[4]: reg[31:0] + // jssp[0]: 0x00000000 (SMI tag & padding) + Register reg = Register(non_object_list.PopHighestIndex()); + __ Pop(scratch, reg); + __ Bfxil(reg, scratch, 32, 32); + } + + // Leave the internal frame. + } + + // Now that the break point has been handled, resume normal execution by + // jumping to the target address intended by the caller and that was + // overwritten by the address of DebugBreakXXX. + ExternalReference after_break_target = + ExternalReference::debug_after_break_target_address(masm->isolate()); + __ Mov(scratch, after_break_target); + __ Ldr(scratch, MemOperand(scratch)); + __ Br(scratch); +} + + +void DebugCodegen::GenerateCallICStubDebugBreak(MacroAssembler* masm) { + // Register state for CallICStub + // ----------- S t a t e ------------- + // -- x1 : function + // -- x3 : slot in feedback array + // ----------------------------------- + Generate_DebugBreakCallHelper(masm, x1.Bit() | x3.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateLoadICDebugBreak(MacroAssembler* masm) { + // Calling convention for IC load (from ic-arm.cc). + // ----------- S t a t e ------------- + // -- x2 : name + // -- lr : return address + // -- x0 : receiver + // -- [sp] : receiver + // ----------------------------------- + // Registers x0 and x2 contain objects that need to be pushed on the + // expression stack of the fake JS frame. + Generate_DebugBreakCallHelper(masm, x0.Bit() | x2.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateStoreICDebugBreak(MacroAssembler* masm) { + // Calling convention for IC store (from ic-arm.cc). + // ----------- S t a t e ------------- + // -- x0 : value + // -- x1 : receiver + // -- x2 : name + // -- lr : return address + // ----------------------------------- + // Registers x0, x1, and x2 contain objects that need to be pushed on the + // expression stack of the fake JS frame. + Generate_DebugBreakCallHelper(masm, x0.Bit() | x1.Bit() | x2.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateKeyedLoadICDebugBreak(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key + // -- x1 : receiver + Generate_DebugBreakCallHelper(masm, x0.Bit() | x1.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateKeyedStoreICDebugBreak(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // -- lr : return address + Generate_DebugBreakCallHelper(masm, x0.Bit() | x1.Bit() | x2.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateCompareNilICDebugBreak(MacroAssembler* masm) { + // Register state for CompareNil IC + // ----------- S t a t e ------------- + // -- r0 : value + // ----------------------------------- + Generate_DebugBreakCallHelper(masm, x0.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateReturnDebugBreak(MacroAssembler* masm) { + // In places other than IC call sites it is expected that r0 is TOS which + // is an object - this is not generally the case so this should be used with + // care. + Generate_DebugBreakCallHelper(masm, x0.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateCallFunctionStubDebugBreak(MacroAssembler* masm) { + // Register state for CallFunctionStub (from code-stubs-arm64.cc). + // ----------- S t a t e ------------- + // -- x1 : function + // ----------------------------------- + Generate_DebugBreakCallHelper(masm, x1.Bit(), 0, x10); +} + + +void DebugCodegen::GenerateCallConstructStubDebugBreak(MacroAssembler* masm) { + // Calling convention for CallConstructStub (from code-stubs-arm64.cc). + // ----------- S t a t e ------------- + // -- x0 : number of arguments (not smi) + // -- x1 : constructor function + // ----------------------------------- + Generate_DebugBreakCallHelper(masm, x1.Bit(), x0.Bit(), x10); +} + + +void DebugCodegen::GenerateCallConstructStubRecordDebugBreak( + MacroAssembler* masm) { + // Calling convention for CallConstructStub (from code-stubs-arm64.cc). + // ----------- S t a t e ------------- + // -- x0 : number of arguments (not smi) + // -- x1 : constructor function + // -- x2 : feedback array + // -- x3 : feedback slot (smi) + // ----------------------------------- + Generate_DebugBreakCallHelper( + masm, x1.Bit() | x2.Bit() | x3.Bit(), x0.Bit(), x10); +} + + +void DebugCodegen::GenerateSlot(MacroAssembler* masm) { + // Generate enough nop's to make space for a call instruction. Avoid emitting + // the constant pool in the debug break slot code. + InstructionAccurateScope scope(masm, Assembler::kDebugBreakSlotInstructions); + + __ RecordDebugBreakSlot(); + for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) { + __ nop(Assembler::DEBUG_BREAK_NOP); + } +} + + +void DebugCodegen::GenerateSlotDebugBreak(MacroAssembler* masm) { + // In the places where a debug break slot is inserted no registers can contain + // object pointers. + Generate_DebugBreakCallHelper(masm, 0, 0, x10); +} + + +void DebugCodegen::GeneratePlainReturnLiveEdit(MacroAssembler* masm) { + masm->Abort(kLiveEditFrameDroppingIsNotSupportedOnARM64); +} + + +void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) { + masm->Abort(kLiveEditFrameDroppingIsNotSupportedOnARM64); +} + + +const bool LiveEdit::kFrameDropperSupported = false; + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/decoder-arm64-inl.h b/chromium/v8/src/arm64/decoder-arm64-inl.h new file mode 100644 index 00000000000..e8eef5e14eb --- /dev/null +++ b/chromium/v8/src/arm64/decoder-arm64-inl.h @@ -0,0 +1,648 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_DECODER_ARM64_INL_H_ +#define V8_ARM64_DECODER_ARM64_INL_H_ + +#include "src/arm64/decoder-arm64.h" +#include "src/globals.h" +#include "src/utils.h" + + +namespace v8 { +namespace internal { + + +// Top-level instruction decode function. +template<typename V> +void Decoder<V>::Decode(Instruction *instr) { + if (instr->Bits(28, 27) == 0) { + V::VisitUnallocated(instr); + } else { + switch (instr->Bits(27, 24)) { + // 0: PC relative addressing. + case 0x0: DecodePCRelAddressing(instr); break; + + // 1: Add/sub immediate. + case 0x1: DecodeAddSubImmediate(instr); break; + + // A: Logical shifted register. + // Add/sub with carry. + // Conditional compare register. + // Conditional compare immediate. + // Conditional select. + // Data processing 1 source. + // Data processing 2 source. + // B: Add/sub shifted register. + // Add/sub extended register. + // Data processing 3 source. + case 0xA: + case 0xB: DecodeDataProcessing(instr); break; + + // 2: Logical immediate. + // Move wide immediate. + case 0x2: DecodeLogical(instr); break; + + // 3: Bitfield. + // Extract. + case 0x3: DecodeBitfieldExtract(instr); break; + + // 4: Unconditional branch immediate. + // Exception generation. + // Compare and branch immediate. + // 5: Compare and branch immediate. + // Conditional branch. + // System. + // 6,7: Unconditional branch. + // Test and branch immediate. + case 0x4: + case 0x5: + case 0x6: + case 0x7: DecodeBranchSystemException(instr); break; + + // 8,9: Load/store register pair post-index. + // Load register literal. + // Load/store register unscaled immediate. + // Load/store register immediate post-index. + // Load/store register immediate pre-index. + // Load/store register offset. + // C,D: Load/store register pair offset. + // Load/store register pair pre-index. + // Load/store register unsigned immediate. + // Advanced SIMD. + case 0x8: + case 0x9: + case 0xC: + case 0xD: DecodeLoadStore(instr); break; + + // E: FP fixed point conversion. + // FP integer conversion. + // FP data processing 1 source. + // FP compare. + // FP immediate. + // FP data processing 2 source. + // FP conditional compare. + // FP conditional select. + // Advanced SIMD. + // F: FP data processing 3 source. + // Advanced SIMD. + case 0xE: + case 0xF: DecodeFP(instr); break; + } + } +} + + +template<typename V> +void Decoder<V>::DecodePCRelAddressing(Instruction* instr) { + ASSERT(instr->Bits(27, 24) == 0x0); + // We know bit 28 is set, as <b28:b27> = 0 is filtered out at the top level + // decode. + ASSERT(instr->Bit(28) == 0x1); + V::VisitPCRelAddressing(instr); +} + + +template<typename V> +void Decoder<V>::DecodeBranchSystemException(Instruction* instr) { + ASSERT((instr->Bits(27, 24) == 0x4) || + (instr->Bits(27, 24) == 0x5) || + (instr->Bits(27, 24) == 0x6) || + (instr->Bits(27, 24) == 0x7) ); + + switch (instr->Bits(31, 29)) { + case 0: + case 4: { + V::VisitUnconditionalBranch(instr); + break; + } + case 1: + case 5: { + if (instr->Bit(25) == 0) { + V::VisitCompareBranch(instr); + } else { + V::VisitTestBranch(instr); + } + break; + } + case 2: { + if (instr->Bit(25) == 0) { + if ((instr->Bit(24) == 0x1) || + (instr->Mask(0x01000010) == 0x00000010)) { + V::VisitUnallocated(instr); + } else { + V::VisitConditionalBranch(instr); + } + } else { + V::VisitUnallocated(instr); + } + break; + } + case 6: { + if (instr->Bit(25) == 0) { + if (instr->Bit(24) == 0) { + if ((instr->Bits(4, 2) != 0) || + (instr->Mask(0x00E0001D) == 0x00200001) || + (instr->Mask(0x00E0001D) == 0x00400001) || + (instr->Mask(0x00E0001E) == 0x00200002) || + (instr->Mask(0x00E0001E) == 0x00400002) || + (instr->Mask(0x00E0001C) == 0x00600000) || + (instr->Mask(0x00E0001C) == 0x00800000) || + (instr->Mask(0x00E0001F) == 0x00A00000) || + (instr->Mask(0x00C0001C) == 0x00C00000)) { + V::VisitUnallocated(instr); + } else { + V::VisitException(instr); + } + } else { + if (instr->Bits(23, 22) == 0) { + const Instr masked_003FF0E0 = instr->Mask(0x003FF0E0); + if ((instr->Bits(21, 19) == 0x4) || + (masked_003FF0E0 == 0x00033000) || + (masked_003FF0E0 == 0x003FF020) || + (masked_003FF0E0 == 0x003FF060) || + (masked_003FF0E0 == 0x003FF0E0) || + (instr->Mask(0x00388000) == 0x00008000) || + (instr->Mask(0x0038E000) == 0x00000000) || + (instr->Mask(0x0039E000) == 0x00002000) || + (instr->Mask(0x003AE000) == 0x00002000) || + (instr->Mask(0x003CE000) == 0x00042000) || + (instr->Mask(0x003FFFC0) == 0x000320C0) || + (instr->Mask(0x003FF100) == 0x00032100) || + (instr->Mask(0x003FF200) == 0x00032200) || + (instr->Mask(0x003FF400) == 0x00032400) || + (instr->Mask(0x003FF800) == 0x00032800) || + (instr->Mask(0x0038F000) == 0x00005000) || + (instr->Mask(0x0038E000) == 0x00006000)) { + V::VisitUnallocated(instr); + } else { + V::VisitSystem(instr); + } + } else { + V::VisitUnallocated(instr); + } + } + } else { + if ((instr->Bit(24) == 0x1) || + (instr->Bits(20, 16) != 0x1F) || + (instr->Bits(15, 10) != 0) || + (instr->Bits(4, 0) != 0) || + (instr->Bits(24, 21) == 0x3) || + (instr->Bits(24, 22) == 0x3)) { + V::VisitUnallocated(instr); + } else { + V::VisitUnconditionalBranchToRegister(instr); + } + } + break; + } + case 3: + case 7: { + V::VisitUnallocated(instr); + break; + } + } +} + + +template<typename V> +void Decoder<V>::DecodeLoadStore(Instruction* instr) { + ASSERT((instr->Bits(27, 24) == 0x8) || + (instr->Bits(27, 24) == 0x9) || + (instr->Bits(27, 24) == 0xC) || + (instr->Bits(27, 24) == 0xD) ); + + if (instr->Bit(24) == 0) { + if (instr->Bit(28) == 0) { + if (instr->Bit(29) == 0) { + if (instr->Bit(26) == 0) { + // TODO(all): VisitLoadStoreExclusive. + V::VisitUnimplemented(instr); + } else { + DecodeAdvSIMDLoadStore(instr); + } + } else { + if ((instr->Bits(31, 30) == 0x3) || + (instr->Mask(0xC4400000) == 0x40000000)) { + V::VisitUnallocated(instr); + } else { + if (instr->Bit(23) == 0) { + if (instr->Mask(0xC4400000) == 0xC0400000) { + V::VisitUnallocated(instr); + } else { + V::VisitLoadStorePairNonTemporal(instr); + } + } else { + V::VisitLoadStorePairPostIndex(instr); + } + } + } + } else { + if (instr->Bit(29) == 0) { + if (instr->Mask(0xC4000000) == 0xC4000000) { + V::VisitUnallocated(instr); + } else { + V::VisitLoadLiteral(instr); + } + } else { + if ((instr->Mask(0x84C00000) == 0x80C00000) || + (instr->Mask(0x44800000) == 0x44800000) || + (instr->Mask(0x84800000) == 0x84800000)) { + V::VisitUnallocated(instr); + } else { + if (instr->Bit(21) == 0) { + switch (instr->Bits(11, 10)) { + case 0: { + V::VisitLoadStoreUnscaledOffset(instr); + break; + } + case 1: { + if (instr->Mask(0xC4C00000) == 0xC0800000) { + V::VisitUnallocated(instr); + } else { + V::VisitLoadStorePostIndex(instr); + } + break; + } + case 2: { + // TODO(all): VisitLoadStoreRegisterOffsetUnpriv. + V::VisitUnimplemented(instr); + break; + } + case 3: { + if (instr->Mask(0xC4C00000) == 0xC0800000) { + V::VisitUnallocated(instr); + } else { + V::VisitLoadStorePreIndex(instr); + } + break; + } + } + } else { + if (instr->Bits(11, 10) == 0x2) { + if (instr->Bit(14) == 0) { + V::VisitUnallocated(instr); + } else { + V::VisitLoadStoreRegisterOffset(instr); + } + } else { + V::VisitUnallocated(instr); + } + } + } + } + } + } else { + if (instr->Bit(28) == 0) { + if (instr->Bit(29) == 0) { + V::VisitUnallocated(instr); + } else { + if ((instr->Bits(31, 30) == 0x3) || + (instr->Mask(0xC4400000) == 0x40000000)) { + V::VisitUnallocated(instr); + } else { + if (instr->Bit(23) == 0) { + V::VisitLoadStorePairOffset(instr); + } else { + V::VisitLoadStorePairPreIndex(instr); + } + } + } + } else { + if (instr->Bit(29) == 0) { + V::VisitUnallocated(instr); + } else { + if ((instr->Mask(0x84C00000) == 0x80C00000) || + (instr->Mask(0x44800000) == 0x44800000) || + (instr->Mask(0x84800000) == 0x84800000)) { + V::VisitUnallocated(instr); + } else { + V::VisitLoadStoreUnsignedOffset(instr); + } + } + } + } +} + + +template<typename V> +void Decoder<V>::DecodeLogical(Instruction* instr) { + ASSERT(instr->Bits(27, 24) == 0x2); + + if (instr->Mask(0x80400000) == 0x00400000) { + V::VisitUnallocated(instr); + } else { + if (instr->Bit(23) == 0) { + V::VisitLogicalImmediate(instr); + } else { + if (instr->Bits(30, 29) == 0x1) { + V::VisitUnallocated(instr); + } else { + V::VisitMoveWideImmediate(instr); + } + } + } +} + + +template<typename V> +void Decoder<V>::DecodeBitfieldExtract(Instruction* instr) { + ASSERT(instr->Bits(27, 24) == 0x3); + + if ((instr->Mask(0x80400000) == 0x80000000) || + (instr->Mask(0x80400000) == 0x00400000) || + (instr->Mask(0x80008000) == 0x00008000)) { + V::VisitUnallocated(instr); + } else if (instr->Bit(23) == 0) { + if ((instr->Mask(0x80200000) == 0x00200000) || + (instr->Mask(0x60000000) == 0x60000000)) { + V::VisitUnallocated(instr); + } else { + V::VisitBitfield(instr); + } + } else { + if ((instr->Mask(0x60200000) == 0x00200000) || + (instr->Mask(0x60000000) != 0x00000000)) { + V::VisitUnallocated(instr); + } else { + V::VisitExtract(instr); + } + } +} + + +template<typename V> +void Decoder<V>::DecodeAddSubImmediate(Instruction* instr) { + ASSERT(instr->Bits(27, 24) == 0x1); + if (instr->Bit(23) == 1) { + V::VisitUnallocated(instr); + } else { + V::VisitAddSubImmediate(instr); + } +} + + +template<typename V> +void Decoder<V>::DecodeDataProcessing(Instruction* instr) { + ASSERT((instr->Bits(27, 24) == 0xA) || + (instr->Bits(27, 24) == 0xB) ); + + if (instr->Bit(24) == 0) { + if (instr->Bit(28) == 0) { + if (instr->Mask(0x80008000) == 0x00008000) { + V::VisitUnallocated(instr); + } else { + V::VisitLogicalShifted(instr); + } + } else { + switch (instr->Bits(23, 21)) { + case 0: { + if (instr->Mask(0x0000FC00) != 0) { + V::VisitUnallocated(instr); + } else { + V::VisitAddSubWithCarry(instr); + } + break; + } + case 2: { + if ((instr->Bit(29) == 0) || + (instr->Mask(0x00000410) != 0)) { + V::VisitUnallocated(instr); + } else { + if (instr->Bit(11) == 0) { + V::VisitConditionalCompareRegister(instr); + } else { + V::VisitConditionalCompareImmediate(instr); + } + } + break; + } + case 4: { + if (instr->Mask(0x20000800) != 0x00000000) { + V::VisitUnallocated(instr); + } else { + V::VisitConditionalSelect(instr); + } + break; + } + case 6: { + if (instr->Bit(29) == 0x1) { + V::VisitUnallocated(instr); + } else { + if (instr->Bit(30) == 0) { + if ((instr->Bit(15) == 0x1) || + (instr->Bits(15, 11) == 0) || + (instr->Bits(15, 12) == 0x1) || + (instr->Bits(15, 12) == 0x3) || + (instr->Bits(15, 13) == 0x3) || + (instr->Mask(0x8000EC00) == 0x00004C00) || + (instr->Mask(0x8000E800) == 0x80004000) || + (instr->Mask(0x8000E400) == 0x80004000)) { + V::VisitUnallocated(instr); + } else { + V::VisitDataProcessing2Source(instr); + } + } else { + if ((instr->Bit(13) == 1) || + (instr->Bits(20, 16) != 0) || + (instr->Bits(15, 14) != 0) || + (instr->Mask(0xA01FFC00) == 0x00000C00) || + (instr->Mask(0x201FF800) == 0x00001800)) { + V::VisitUnallocated(instr); + } else { + V::VisitDataProcessing1Source(instr); + } + } + break; + } + } + case 1: + case 3: + case 5: + case 7: V::VisitUnallocated(instr); break; + } + } + } else { + if (instr->Bit(28) == 0) { + if (instr->Bit(21) == 0) { + if ((instr->Bits(23, 22) == 0x3) || + (instr->Mask(0x80008000) == 0x00008000)) { + V::VisitUnallocated(instr); + } else { + V::VisitAddSubShifted(instr); + } + } else { + if ((instr->Mask(0x00C00000) != 0x00000000) || + (instr->Mask(0x00001400) == 0x00001400) || + (instr->Mask(0x00001800) == 0x00001800)) { + V::VisitUnallocated(instr); + } else { + V::VisitAddSubExtended(instr); + } + } + } else { + if ((instr->Bit(30) == 0x1) || + (instr->Bits(30, 29) == 0x1) || + (instr->Mask(0xE0600000) == 0x00200000) || + (instr->Mask(0xE0608000) == 0x00400000) || + (instr->Mask(0x60608000) == 0x00408000) || + (instr->Mask(0x60E00000) == 0x00E00000) || + (instr->Mask(0x60E00000) == 0x00800000) || + (instr->Mask(0x60E00000) == 0x00600000)) { + V::VisitUnallocated(instr); + } else { + V::VisitDataProcessing3Source(instr); + } + } + } +} + + +template<typename V> +void Decoder<V>::DecodeFP(Instruction* instr) { + ASSERT((instr->Bits(27, 24) == 0xE) || + (instr->Bits(27, 24) == 0xF) ); + + if (instr->Bit(28) == 0) { + DecodeAdvSIMDDataProcessing(instr); + } else { + if (instr->Bit(29) == 1) { + V::VisitUnallocated(instr); + } else { + if (instr->Bits(31, 30) == 0x3) { + V::VisitUnallocated(instr); + } else if (instr->Bits(31, 30) == 0x1) { + DecodeAdvSIMDDataProcessing(instr); + } else { + if (instr->Bit(24) == 0) { + if (instr->Bit(21) == 0) { + if ((instr->Bit(23) == 1) || + (instr->Bit(18) == 1) || + (instr->Mask(0x80008000) == 0x00000000) || + (instr->Mask(0x000E0000) == 0x00000000) || + (instr->Mask(0x000E0000) == 0x000A0000) || + (instr->Mask(0x00160000) == 0x00000000) || + (instr->Mask(0x00160000) == 0x00120000)) { + V::VisitUnallocated(instr); + } else { + V::VisitFPFixedPointConvert(instr); + } + } else { + if (instr->Bits(15, 10) == 32) { + V::VisitUnallocated(instr); + } else if (instr->Bits(15, 10) == 0) { + if ((instr->Bits(23, 22) == 0x3) || + (instr->Mask(0x000E0000) == 0x000A0000) || + (instr->Mask(0x000E0000) == 0x000C0000) || + (instr->Mask(0x00160000) == 0x00120000) || + (instr->Mask(0x00160000) == 0x00140000) || + (instr->Mask(0x20C40000) == 0x00800000) || + (instr->Mask(0x20C60000) == 0x00840000) || + (instr->Mask(0xA0C60000) == 0x80060000) || + (instr->Mask(0xA0C60000) == 0x00860000) || + (instr->Mask(0xA0C60000) == 0x00460000) || + (instr->Mask(0xA0CE0000) == 0x80860000) || + (instr->Mask(0xA0CE0000) == 0x804E0000) || + (instr->Mask(0xA0CE0000) == 0x000E0000) || + (instr->Mask(0xA0D60000) == 0x00160000) || + (instr->Mask(0xA0D60000) == 0x80560000) || + (instr->Mask(0xA0D60000) == 0x80960000)) { + V::VisitUnallocated(instr); + } else { + V::VisitFPIntegerConvert(instr); + } + } else if (instr->Bits(14, 10) == 16) { + const Instr masked_A0DF8000 = instr->Mask(0xA0DF8000); + if ((instr->Mask(0x80180000) != 0) || + (masked_A0DF8000 == 0x00020000) || + (masked_A0DF8000 == 0x00030000) || + (masked_A0DF8000 == 0x00068000) || + (masked_A0DF8000 == 0x00428000) || + (masked_A0DF8000 == 0x00430000) || + (masked_A0DF8000 == 0x00468000) || + (instr->Mask(0xA0D80000) == 0x00800000) || + (instr->Mask(0xA0DE0000) == 0x00C00000) || + (instr->Mask(0xA0DF0000) == 0x00C30000) || + (instr->Mask(0xA0DC0000) == 0x00C40000)) { + V::VisitUnallocated(instr); + } else { + V::VisitFPDataProcessing1Source(instr); + } + } else if (instr->Bits(13, 10) == 8) { + if ((instr->Bits(15, 14) != 0) || + (instr->Bits(2, 0) != 0) || + (instr->Mask(0x80800000) != 0x00000000)) { + V::VisitUnallocated(instr); + } else { + V::VisitFPCompare(instr); + } + } else if (instr->Bits(12, 10) == 4) { + if ((instr->Bits(9, 5) != 0) || + (instr->Mask(0x80800000) != 0x00000000)) { + V::VisitUnallocated(instr); + } else { + V::VisitFPImmediate(instr); + } + } else { + if (instr->Mask(0x80800000) != 0x00000000) { + V::VisitUnallocated(instr); + } else { + switch (instr->Bits(11, 10)) { + case 1: { + V::VisitFPConditionalCompare(instr); + break; + } + case 2: { + if ((instr->Bits(15, 14) == 0x3) || + (instr->Mask(0x00009000) == 0x00009000) || + (instr->Mask(0x0000A000) == 0x0000A000)) { + V::VisitUnallocated(instr); + } else { + V::VisitFPDataProcessing2Source(instr); + } + break; + } + case 3: { + V::VisitFPConditionalSelect(instr); + break; + } + default: UNREACHABLE(); + } + } + } + } + } else { + // Bit 30 == 1 has been handled earlier. + ASSERT(instr->Bit(30) == 0); + if (instr->Mask(0xA0800000) != 0) { + V::VisitUnallocated(instr); + } else { + V::VisitFPDataProcessing3Source(instr); + } + } + } + } + } +} + + +template<typename V> +void Decoder<V>::DecodeAdvSIMDLoadStore(Instruction* instr) { + // TODO(all): Implement Advanced SIMD load/store instruction decode. + ASSERT(instr->Bits(29, 25) == 0x6); + V::VisitUnimplemented(instr); +} + + +template<typename V> +void Decoder<V>::DecodeAdvSIMDDataProcessing(Instruction* instr) { + // TODO(all): Implement Advanced SIMD data processing instruction decode. + ASSERT(instr->Bits(27, 25) == 0x7); + V::VisitUnimplemented(instr); +} + + +} } // namespace v8::internal + +#endif // V8_ARM64_DECODER_ARM64_INL_H_ diff --git a/chromium/v8/src/arm64/decoder-arm64.cc b/chromium/v8/src/arm64/decoder-arm64.cc new file mode 100644 index 00000000000..08881c2d5a5 --- /dev/null +++ b/chromium/v8/src/arm64/decoder-arm64.cc @@ -0,0 +1,86 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/globals.h" +#include "src/utils.h" +#include "src/arm64/decoder-arm64.h" + + +namespace v8 { +namespace internal { + + +void DispatchingDecoderVisitor::AppendVisitor(DecoderVisitor* new_visitor) { + visitors_.remove(new_visitor); + visitors_.push_front(new_visitor); +} + + +void DispatchingDecoderVisitor::PrependVisitor(DecoderVisitor* new_visitor) { + visitors_.remove(new_visitor); + visitors_.push_back(new_visitor); +} + + +void DispatchingDecoderVisitor::InsertVisitorBefore( + DecoderVisitor* new_visitor, DecoderVisitor* registered_visitor) { + visitors_.remove(new_visitor); + std::list<DecoderVisitor*>::iterator it; + for (it = visitors_.begin(); it != visitors_.end(); it++) { + if (*it == registered_visitor) { + visitors_.insert(it, new_visitor); + return; + } + } + // We reached the end of the list. The last element must be + // registered_visitor. + ASSERT(*it == registered_visitor); + visitors_.insert(it, new_visitor); +} + + +void DispatchingDecoderVisitor::InsertVisitorAfter( + DecoderVisitor* new_visitor, DecoderVisitor* registered_visitor) { + visitors_.remove(new_visitor); + std::list<DecoderVisitor*>::iterator it; + for (it = visitors_.begin(); it != visitors_.end(); it++) { + if (*it == registered_visitor) { + it++; + visitors_.insert(it, new_visitor); + return; + } + } + // We reached the end of the list. The last element must be + // registered_visitor. + ASSERT(*it == registered_visitor); + visitors_.push_back(new_visitor); +} + + +void DispatchingDecoderVisitor::RemoveVisitor(DecoderVisitor* visitor) { + visitors_.remove(visitor); +} + + +#define DEFINE_VISITOR_CALLERS(A) \ + void DispatchingDecoderVisitor::Visit##A(Instruction* instr) { \ + if (!(instr->Mask(A##FMask) == A##Fixed)) { \ + ASSERT(instr->Mask(A##FMask) == A##Fixed); \ + } \ + std::list<DecoderVisitor*>::iterator it; \ + for (it = visitors_.begin(); it != visitors_.end(); it++) { \ + (*it)->Visit##A(instr); \ + } \ + } +VISITOR_LIST(DEFINE_VISITOR_CALLERS) +#undef DEFINE_VISITOR_CALLERS + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/decoder-arm64.h b/chromium/v8/src/arm64/decoder-arm64.h new file mode 100644 index 00000000000..0ce84253baa --- /dev/null +++ b/chromium/v8/src/arm64/decoder-arm64.h @@ -0,0 +1,187 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_DECODER_ARM64_H_ +#define V8_ARM64_DECODER_ARM64_H_ + +#include <list> + +#include "src/globals.h" +#include "src/arm64/instructions-arm64.h" + +namespace v8 { +namespace internal { + + +// List macro containing all visitors needed by the decoder class. + +#define VISITOR_LIST(V) \ + V(PCRelAddressing) \ + V(AddSubImmediate) \ + V(LogicalImmediate) \ + V(MoveWideImmediate) \ + V(Bitfield) \ + V(Extract) \ + V(UnconditionalBranch) \ + V(UnconditionalBranchToRegister) \ + V(CompareBranch) \ + V(TestBranch) \ + V(ConditionalBranch) \ + V(System) \ + V(Exception) \ + V(LoadStorePairPostIndex) \ + V(LoadStorePairOffset) \ + V(LoadStorePairPreIndex) \ + V(LoadStorePairNonTemporal) \ + V(LoadLiteral) \ + V(LoadStoreUnscaledOffset) \ + V(LoadStorePostIndex) \ + V(LoadStorePreIndex) \ + V(LoadStoreRegisterOffset) \ + V(LoadStoreUnsignedOffset) \ + V(LogicalShifted) \ + V(AddSubShifted) \ + V(AddSubExtended) \ + V(AddSubWithCarry) \ + V(ConditionalCompareRegister) \ + V(ConditionalCompareImmediate) \ + V(ConditionalSelect) \ + V(DataProcessing1Source) \ + V(DataProcessing2Source) \ + V(DataProcessing3Source) \ + V(FPCompare) \ + V(FPConditionalCompare) \ + V(FPConditionalSelect) \ + V(FPImmediate) \ + V(FPDataProcessing1Source) \ + V(FPDataProcessing2Source) \ + V(FPDataProcessing3Source) \ + V(FPIntegerConvert) \ + V(FPFixedPointConvert) \ + V(Unallocated) \ + V(Unimplemented) + +// The Visitor interface. Disassembler and simulator (and other tools) +// must provide implementations for all of these functions. +class DecoderVisitor { + public: + virtual ~DecoderVisitor() {} + + #define DECLARE(A) virtual void Visit##A(Instruction* instr) = 0; + VISITOR_LIST(DECLARE) + #undef DECLARE +}; + + +// A visitor that dispatches to a list of visitors. +class DispatchingDecoderVisitor : public DecoderVisitor { + public: + DispatchingDecoderVisitor() {} + virtual ~DispatchingDecoderVisitor() {} + + // Register a new visitor class with the decoder. + // Decode() will call the corresponding visitor method from all registered + // visitor classes when decoding reaches the leaf node of the instruction + // decode tree. + // Visitors are called in the order. + // A visitor can only be registered once. + // Registering an already registered visitor will update its position. + // + // d.AppendVisitor(V1); + // d.AppendVisitor(V2); + // d.PrependVisitor(V2); // Move V2 at the start of the list. + // d.InsertVisitorBefore(V3, V2); + // d.AppendVisitor(V4); + // d.AppendVisitor(V4); // No effect. + // + // d.Decode(i); + // + // will call in order visitor methods in V3, V2, V1, V4. + void AppendVisitor(DecoderVisitor* visitor); + void PrependVisitor(DecoderVisitor* visitor); + void InsertVisitorBefore(DecoderVisitor* new_visitor, + DecoderVisitor* registered_visitor); + void InsertVisitorAfter(DecoderVisitor* new_visitor, + DecoderVisitor* registered_visitor); + + // Remove a previously registered visitor class from the list of visitors + // stored by the decoder. + void RemoveVisitor(DecoderVisitor* visitor); + + #define DECLARE(A) void Visit##A(Instruction* instr); + VISITOR_LIST(DECLARE) + #undef DECLARE + + private: + // Visitors are registered in a list. + std::list<DecoderVisitor*> visitors_; +}; + + +template<typename V> +class Decoder : public V { + public: + Decoder() {} + virtual ~Decoder() {} + + // Top-level instruction decoder function. Decodes an instruction and calls + // the visitor functions registered with the Decoder class. + virtual void Decode(Instruction *instr); + + private: + // Decode the PC relative addressing instruction, and call the corresponding + // visitors. + // On entry, instruction bits 27:24 = 0x0. + void DecodePCRelAddressing(Instruction* instr); + + // Decode the add/subtract immediate instruction, and call the corresponding + // visitors. + // On entry, instruction bits 27:24 = 0x1. + void DecodeAddSubImmediate(Instruction* instr); + + // Decode the branch, system command, and exception generation parts of + // the instruction tree, and call the corresponding visitors. + // On entry, instruction bits 27:24 = {0x4, 0x5, 0x6, 0x7}. + void DecodeBranchSystemException(Instruction* instr); + + // Decode the load and store parts of the instruction tree, and call + // the corresponding visitors. + // On entry, instruction bits 27:24 = {0x8, 0x9, 0xC, 0xD}. + void DecodeLoadStore(Instruction* instr); + + // Decode the logical immediate and move wide immediate parts of the + // instruction tree, and call the corresponding visitors. + // On entry, instruction bits 27:24 = 0x2. + void DecodeLogical(Instruction* instr); + + // Decode the bitfield and extraction parts of the instruction tree, + // and call the corresponding visitors. + // On entry, instruction bits 27:24 = 0x3. + void DecodeBitfieldExtract(Instruction* instr); + + // Decode the data processing parts of the instruction tree, and call the + // corresponding visitors. + // On entry, instruction bits 27:24 = {0x1, 0xA, 0xB}. + void DecodeDataProcessing(Instruction* instr); + + // Decode the floating point parts of the instruction tree, and call the + // corresponding visitors. + // On entry, instruction bits 27:24 = {0xE, 0xF}. + void DecodeFP(Instruction* instr); + + // Decode the Advanced SIMD (NEON) load/store part of the instruction tree, + // and call the corresponding visitors. + // On entry, instruction bits 29:25 = 0x6. + void DecodeAdvSIMDLoadStore(Instruction* instr); + + // Decode the Advanced SIMD (NEON) data processing part of the instruction + // tree, and call the corresponding visitors. + // On entry, instruction bits 27:25 = 0x7. + void DecodeAdvSIMDDataProcessing(Instruction* instr); +}; + + +} } // namespace v8::internal + +#endif // V8_ARM64_DECODER_ARM64_H_ diff --git a/chromium/v8/src/arm64/deoptimizer-arm64.cc b/chromium/v8/src/arm64/deoptimizer-arm64.cc new file mode 100644 index 00000000000..7ac5bd0d2d5 --- /dev/null +++ b/chromium/v8/src/arm64/deoptimizer-arm64.cc @@ -0,0 +1,385 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#include "src/codegen.h" +#include "src/deoptimizer.h" +#include "src/full-codegen.h" +#include "src/safepoint-table.h" + + +namespace v8 { +namespace internal { + + +int Deoptimizer::patch_size() { + // Size of the code used to patch lazy bailout points. + // Patching is done by Deoptimizer::DeoptimizeFunction. + return 4 * kInstructionSize; +} + + + +void Deoptimizer::PatchCodeForDeoptimization(Isolate* isolate, Code* code) { + // Invalidate the relocation information, as it will become invalid by the + // code patching below, and is not needed any more. + code->InvalidateRelocation(); + + // TODO(jkummerow): if (FLAG_zap_code_space), make the code object's + // entry sequence unusable (see other architectures). + + DeoptimizationInputData* deopt_data = + DeoptimizationInputData::cast(code->deoptimization_data()); + SharedFunctionInfo* shared = + SharedFunctionInfo::cast(deopt_data->SharedFunctionInfo()); + shared->EvictFromOptimizedCodeMap(code, "deoptimized code"); + Address code_start_address = code->instruction_start(); +#ifdef DEBUG + Address prev_call_address = NULL; +#endif + // For each LLazyBailout instruction insert a call to the corresponding + // deoptimization entry. + for (int i = 0; i < deopt_data->DeoptCount(); i++) { + if (deopt_data->Pc(i)->value() == -1) continue; + + Address call_address = code_start_address + deopt_data->Pc(i)->value(); + Address deopt_entry = GetDeoptimizationEntry(isolate, i, LAZY); + + PatchingAssembler patcher(call_address, patch_size() / kInstructionSize); + patcher.ldr_pcrel(ip0, (2 * kInstructionSize) >> kLoadLiteralScaleLog2); + patcher.blr(ip0); + patcher.dc64(reinterpret_cast<intptr_t>(deopt_entry)); + + ASSERT((prev_call_address == NULL) || + (call_address >= prev_call_address + patch_size())); + ASSERT(call_address + patch_size() <= code->instruction_end()); +#ifdef DEBUG + prev_call_address = call_address; +#endif + } +} + + +void Deoptimizer::FillInputFrame(Address tos, JavaScriptFrame* frame) { + // Set the register values. The values are not important as there are no + // callee saved registers in JavaScript frames, so all registers are + // spilled. Registers fp and sp are set to the correct values though. + for (int i = 0; i < Register::NumRegisters(); i++) { + input_->SetRegister(i, 0); + } + + // TODO(all): Do we also need to set a value to csp? + input_->SetRegister(jssp.code(), reinterpret_cast<intptr_t>(frame->sp())); + input_->SetRegister(fp.code(), reinterpret_cast<intptr_t>(frame->fp())); + + for (int i = 0; i < DoubleRegister::NumAllocatableRegisters(); i++) { + input_->SetDoubleRegister(i, 0.0); + } + + // Fill the frame content from the actual data on the frame. + for (unsigned i = 0; i < input_->GetFrameSize(); i += kPointerSize) { + input_->SetFrameSlot(i, Memory::uint64_at(tos + i)); + } +} + + +bool Deoptimizer::HasAlignmentPadding(JSFunction* function) { + // There is no dynamic alignment padding on ARM64 in the input frame. + return false; +} + + +void Deoptimizer::SetPlatformCompiledStubRegisters( + FrameDescription* output_frame, CodeStubInterfaceDescriptor* descriptor) { + ApiFunction function(descriptor->deoptimization_handler_); + ExternalReference xref(&function, ExternalReference::BUILTIN_CALL, isolate_); + intptr_t handler = reinterpret_cast<intptr_t>(xref.address()); + int params = descriptor->GetHandlerParameterCount(); + output_frame->SetRegister(x0.code(), params); + output_frame->SetRegister(x1.code(), handler); +} + + +void Deoptimizer::CopyDoubleRegisters(FrameDescription* output_frame) { + for (int i = 0; i < DoubleRegister::kMaxNumRegisters; ++i) { + double double_value = input_->GetDoubleRegister(i); + output_frame->SetDoubleRegister(i, double_value); + } +} + + +#define __ masm-> + +static void CopyRegisterDumpToFrame(MacroAssembler* masm, + Register frame, + CPURegList reg_list, + Register scratch1, + Register scratch2, + int src_offset, + int dst_offset) { + int offset0, offset1; + CPURegList copy_to_input = reg_list; + int reg_count = reg_list.Count(); + int reg_size = reg_list.RegisterSizeInBytes(); + for (int i = 0; i < (reg_count / 2); i++) { + __ PeekPair(scratch1, scratch2, src_offset + (i * reg_size * 2)); + + offset0 = (copy_to_input.PopLowestIndex().code() * reg_size) + dst_offset; + offset1 = (copy_to_input.PopLowestIndex().code() * reg_size) + dst_offset; + + if ((offset0 + reg_size) == offset1) { + // Registers are adjacent: store in pairs. + __ Stp(scratch1, scratch2, MemOperand(frame, offset0)); + } else { + // Registers are not adjacent: store individually. + __ Str(scratch1, MemOperand(frame, offset0)); + __ Str(scratch2, MemOperand(frame, offset1)); + } + } + if ((reg_count & 1) != 0) { + __ Peek(scratch1, src_offset + (reg_count - 1) * reg_size); + offset0 = (copy_to_input.PopLowestIndex().code() * reg_size) + dst_offset; + __ Str(scratch1, MemOperand(frame, offset0)); + } +} + +#undef __ + +#define __ masm()-> + +void Deoptimizer::EntryGenerator::Generate() { + GeneratePrologue(); + + // TODO(all): This code needs to be revisited. We probably only need to save + // caller-saved registers here. Callee-saved registers can be stored directly + // in the input frame. + + // Save all allocatable floating point registers. + CPURegList saved_fp_registers(CPURegister::kFPRegister, kDRegSizeInBits, + FPRegister::kAllocatableFPRegisters); + __ PushCPURegList(saved_fp_registers); + + // We save all the registers expcept jssp, sp and lr. + CPURegList saved_registers(CPURegister::kRegister, kXRegSizeInBits, 0, 27); + saved_registers.Combine(fp); + __ PushCPURegList(saved_registers); + + const int kSavedRegistersAreaSize = + (saved_registers.Count() * kXRegSize) + + (saved_fp_registers.Count() * kDRegSize); + + // Floating point registers are saved on the stack above core registers. + const int kFPRegistersOffset = saved_registers.Count() * kXRegSize; + + // Get the bailout id from the stack. + Register bailout_id = x2; + __ Peek(bailout_id, kSavedRegistersAreaSize); + + Register code_object = x3; + Register fp_to_sp = x4; + // Get the address of the location in the code object. This is the return + // address for lazy deoptimization. + __ Mov(code_object, lr); + // Compute the fp-to-sp delta, and correct one word for bailout id. + __ Add(fp_to_sp, masm()->StackPointer(), + kSavedRegistersAreaSize + (1 * kPointerSize)); + __ Sub(fp_to_sp, fp, fp_to_sp); + + // Allocate a new deoptimizer object. + __ Ldr(x0, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ Mov(x1, type()); + // Following arguments are already loaded: + // - x2: bailout id + // - x3: code object address + // - x4: fp-to-sp delta + __ Mov(x5, ExternalReference::isolate_address(isolate())); + + { + // Call Deoptimizer::New(). + AllowExternalCallThatCantCauseGC scope(masm()); + __ CallCFunction(ExternalReference::new_deoptimizer_function(isolate()), 6); + } + + // Preserve "deoptimizer" object in register x0. + Register deoptimizer = x0; + + // Get the input frame descriptor pointer. + __ Ldr(x1, MemOperand(deoptimizer, Deoptimizer::input_offset())); + + // Copy core registers into the input frame. + CopyRegisterDumpToFrame(masm(), x1, saved_registers, x2, x4, 0, + FrameDescription::registers_offset()); + + // Copy FP registers to the input frame. + CopyRegisterDumpToFrame(masm(), x1, saved_fp_registers, x2, x4, + kFPRegistersOffset, + FrameDescription::double_registers_offset()); + + // Remove the bailout id and the saved registers from the stack. + __ Drop(1 + (kSavedRegistersAreaSize / kXRegSize)); + + // Compute a pointer to the unwinding limit in register x2; that is + // the first stack slot not part of the input frame. + Register unwind_limit = x2; + __ Ldr(unwind_limit, MemOperand(x1, FrameDescription::frame_size_offset())); + __ Add(unwind_limit, unwind_limit, __ StackPointer()); + + // Unwind the stack down to - but not including - the unwinding + // limit and copy the contents of the activation frame to the input + // frame description. + __ Add(x3, x1, FrameDescription::frame_content_offset()); + Label pop_loop; + Label pop_loop_header; + __ B(&pop_loop_header); + __ Bind(&pop_loop); + __ Pop(x4); + __ Str(x4, MemOperand(x3, kPointerSize, PostIndex)); + __ Bind(&pop_loop_header); + __ Cmp(unwind_limit, __ StackPointer()); + __ B(ne, &pop_loop); + + // Compute the output frame in the deoptimizer. + __ Push(x0); // Preserve deoptimizer object across call. + + { + // Call Deoptimizer::ComputeOutputFrames(). + AllowExternalCallThatCantCauseGC scope(masm()); + __ CallCFunction( + ExternalReference::compute_output_frames_function(isolate()), 1); + } + __ Pop(x4); // Restore deoptimizer object (class Deoptimizer). + + // Replace the current (input) frame with the output frames. + Label outer_push_loop, inner_push_loop, + outer_loop_header, inner_loop_header; + __ Ldrsw(x1, MemOperand(x4, Deoptimizer::output_count_offset())); + __ Ldr(x0, MemOperand(x4, Deoptimizer::output_offset())); + __ Add(x1, x0, Operand(x1, LSL, kPointerSizeLog2)); + __ B(&outer_loop_header); + + __ Bind(&outer_push_loop); + Register current_frame = x2; + __ Ldr(current_frame, MemOperand(x0, 0)); + __ Ldr(x3, MemOperand(current_frame, FrameDescription::frame_size_offset())); + __ B(&inner_loop_header); + + __ Bind(&inner_push_loop); + __ Sub(x3, x3, kPointerSize); + __ Add(x6, current_frame, x3); + __ Ldr(x7, MemOperand(x6, FrameDescription::frame_content_offset())); + __ Push(x7); + __ Bind(&inner_loop_header); + __ Cbnz(x3, &inner_push_loop); + + __ Add(x0, x0, kPointerSize); + __ Bind(&outer_loop_header); + __ Cmp(x0, x1); + __ B(lt, &outer_push_loop); + + __ Ldr(x1, MemOperand(x4, Deoptimizer::input_offset())); + ASSERT(!saved_fp_registers.IncludesAliasOf(crankshaft_fp_scratch) && + !saved_fp_registers.IncludesAliasOf(fp_zero) && + !saved_fp_registers.IncludesAliasOf(fp_scratch)); + int src_offset = FrameDescription::double_registers_offset(); + while (!saved_fp_registers.IsEmpty()) { + const CPURegister reg = saved_fp_registers.PopLowestIndex(); + __ Ldr(reg, MemOperand(x1, src_offset)); + src_offset += kDoubleSize; + } + + // Push state from the last output frame. + __ Ldr(x6, MemOperand(current_frame, FrameDescription::state_offset())); + __ Push(x6); + + // TODO(all): ARM copies a lot (if not all) of the last output frame onto the + // stack, then pops it all into registers. Here, we try to load it directly + // into the relevant registers. Is this correct? If so, we should improve the + // ARM code. + + // TODO(all): This code needs to be revisited, We probably don't need to + // restore all the registers as fullcodegen does not keep live values in + // registers (note that at least fp must be restored though). + + // Restore registers from the last output frame. + // Note that lr is not in the list of saved_registers and will be restored + // later. We can use it to hold the address of last output frame while + // reloading the other registers. + ASSERT(!saved_registers.IncludesAliasOf(lr)); + Register last_output_frame = lr; + __ Mov(last_output_frame, current_frame); + + // We don't need to restore x7 as it will be clobbered later to hold the + // continuation address. + Register continuation = x7; + saved_registers.Remove(continuation); + + while (!saved_registers.IsEmpty()) { + // TODO(all): Look for opportunities to optimize this by using ldp. + CPURegister current_reg = saved_registers.PopLowestIndex(); + int offset = (current_reg.code() * kPointerSize) + + FrameDescription::registers_offset(); + __ Ldr(current_reg, MemOperand(last_output_frame, offset)); + } + + __ Ldr(continuation, MemOperand(last_output_frame, + FrameDescription::continuation_offset())); + __ Ldr(lr, MemOperand(last_output_frame, FrameDescription::pc_offset())); + __ InitializeRootRegister(); + __ Br(continuation); +} + + +// Size of an entry of the second level deopt table. +// This is the code size generated by GeneratePrologue for one entry. +const int Deoptimizer::table_entry_size_ = 2 * kInstructionSize; + + +void Deoptimizer::TableEntryGenerator::GeneratePrologue() { + UseScratchRegisterScope temps(masm()); + Register entry_id = temps.AcquireX(); + + // Create a sequence of deoptimization entries. + // Note that registers are still live when jumping to an entry. + Label done; + { + InstructionAccurateScope scope(masm()); + + // The number of entry will never exceed kMaxNumberOfEntries. + // As long as kMaxNumberOfEntries is a valid 16 bits immediate you can use + // a movz instruction to load the entry id. + ASSERT(is_uint16(Deoptimizer::kMaxNumberOfEntries)); + + for (int i = 0; i < count(); i++) { + int start = masm()->pc_offset(); + USE(start); + __ movz(entry_id, i); + __ b(&done); + ASSERT(masm()->pc_offset() - start == table_entry_size_); + } + } + __ Bind(&done); + __ Push(entry_id); +} + + +void FrameDescription::SetCallerPc(unsigned offset, intptr_t value) { + SetFrameSlot(offset, value); +} + + +void FrameDescription::SetCallerFp(unsigned offset, intptr_t value) { + SetFrameSlot(offset, value); +} + + +void FrameDescription::SetCallerConstantPool(unsigned offset, intptr_t value) { + // No out-of-line constant pool support. + UNREACHABLE(); +} + + +#undef __ + +} } // namespace v8::internal diff --git a/chromium/v8/src/arm64/disasm-arm64.cc b/chromium/v8/src/arm64/disasm-arm64.cc new file mode 100644 index 00000000000..e6a30b477e8 --- /dev/null +++ b/chromium/v8/src/arm64/disasm-arm64.cc @@ -0,0 +1,1832 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <assert.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/disasm.h" +#include "src/arm64/decoder-arm64-inl.h" +#include "src/arm64/disasm-arm64.h" +#include "src/macro-assembler.h" +#include "src/platform.h" + +namespace v8 { +namespace internal { + + +Disassembler::Disassembler() { + buffer_size_ = 256; + buffer_ = reinterpret_cast<char*>(malloc(buffer_size_)); + buffer_pos_ = 0; + own_buffer_ = true; +} + + +Disassembler::Disassembler(char* text_buffer, int buffer_size) { + buffer_size_ = buffer_size; + buffer_ = text_buffer; + buffer_pos_ = 0; + own_buffer_ = false; +} + + +Disassembler::~Disassembler() { + if (own_buffer_) { + free(buffer_); + } +} + + +char* Disassembler::GetOutput() { + return buffer_; +} + + +void Disassembler::VisitAddSubImmediate(Instruction* instr) { + bool rd_is_zr = RdIsZROrSP(instr); + bool stack_op = (rd_is_zr || RnIsZROrSP(instr)) && + (instr->ImmAddSub() == 0) ? true : false; + const char *mnemonic = ""; + const char *form = "'Rds, 'Rns, 'IAddSub"; + const char *form_cmp = "'Rns, 'IAddSub"; + const char *form_mov = "'Rds, 'Rns"; + + switch (instr->Mask(AddSubImmediateMask)) { + case ADD_w_imm: + case ADD_x_imm: { + mnemonic = "add"; + if (stack_op) { + mnemonic = "mov"; + form = form_mov; + } + break; + } + case ADDS_w_imm: + case ADDS_x_imm: { + mnemonic = "adds"; + if (rd_is_zr) { + mnemonic = "cmn"; + form = form_cmp; + } + break; + } + case SUB_w_imm: + case SUB_x_imm: mnemonic = "sub"; break; + case SUBS_w_imm: + case SUBS_x_imm: { + mnemonic = "subs"; + if (rd_is_zr) { + mnemonic = "cmp"; + form = form_cmp; + } + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitAddSubShifted(Instruction* instr) { + bool rd_is_zr = RdIsZROrSP(instr); + bool rn_is_zr = RnIsZROrSP(instr); + const char *mnemonic = ""; + const char *form = "'Rd, 'Rn, 'Rm'HDP"; + const char *form_cmp = "'Rn, 'Rm'HDP"; + const char *form_neg = "'Rd, 'Rm'HDP"; + + switch (instr->Mask(AddSubShiftedMask)) { + case ADD_w_shift: + case ADD_x_shift: mnemonic = "add"; break; + case ADDS_w_shift: + case ADDS_x_shift: { + mnemonic = "adds"; + if (rd_is_zr) { + mnemonic = "cmn"; + form = form_cmp; + } + break; + } + case SUB_w_shift: + case SUB_x_shift: { + mnemonic = "sub"; + if (rn_is_zr) { + mnemonic = "neg"; + form = form_neg; + } + break; + } + case SUBS_w_shift: + case SUBS_x_shift: { + mnemonic = "subs"; + if (rd_is_zr) { + mnemonic = "cmp"; + form = form_cmp; + } else if (rn_is_zr) { + mnemonic = "negs"; + form = form_neg; + } + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitAddSubExtended(Instruction* instr) { + bool rd_is_zr = RdIsZROrSP(instr); + const char *mnemonic = ""; + Extend mode = static_cast<Extend>(instr->ExtendMode()); + const char *form = ((mode == UXTX) || (mode == SXTX)) ? + "'Rds, 'Rns, 'Xm'Ext" : "'Rds, 'Rns, 'Wm'Ext"; + const char *form_cmp = ((mode == UXTX) || (mode == SXTX)) ? + "'Rns, 'Xm'Ext" : "'Rns, 'Wm'Ext"; + + switch (instr->Mask(AddSubExtendedMask)) { + case ADD_w_ext: + case ADD_x_ext: mnemonic = "add"; break; + case ADDS_w_ext: + case ADDS_x_ext: { + mnemonic = "adds"; + if (rd_is_zr) { + mnemonic = "cmn"; + form = form_cmp; + } + break; + } + case SUB_w_ext: + case SUB_x_ext: mnemonic = "sub"; break; + case SUBS_w_ext: + case SUBS_x_ext: { + mnemonic = "subs"; + if (rd_is_zr) { + mnemonic = "cmp"; + form = form_cmp; + } + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitAddSubWithCarry(Instruction* instr) { + bool rn_is_zr = RnIsZROrSP(instr); + const char *mnemonic = ""; + const char *form = "'Rd, 'Rn, 'Rm"; + const char *form_neg = "'Rd, 'Rm"; + + switch (instr->Mask(AddSubWithCarryMask)) { + case ADC_w: + case ADC_x: mnemonic = "adc"; break; + case ADCS_w: + case ADCS_x: mnemonic = "adcs"; break; + case SBC_w: + case SBC_x: { + mnemonic = "sbc"; + if (rn_is_zr) { + mnemonic = "ngc"; + form = form_neg; + } + break; + } + case SBCS_w: + case SBCS_x: { + mnemonic = "sbcs"; + if (rn_is_zr) { + mnemonic = "ngcs"; + form = form_neg; + } + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLogicalImmediate(Instruction* instr) { + bool rd_is_zr = RdIsZROrSP(instr); + bool rn_is_zr = RnIsZROrSP(instr); + const char *mnemonic = ""; + const char *form = "'Rds, 'Rn, 'ITri"; + + if (instr->ImmLogical() == 0) { + // The immediate encoded in the instruction is not in the expected format. + Format(instr, "unallocated", "(LogicalImmediate)"); + return; + } + + switch (instr->Mask(LogicalImmediateMask)) { + case AND_w_imm: + case AND_x_imm: mnemonic = "and"; break; + case ORR_w_imm: + case ORR_x_imm: { + mnemonic = "orr"; + unsigned reg_size = (instr->SixtyFourBits() == 1) ? kXRegSizeInBits + : kWRegSizeInBits; + if (rn_is_zr && !IsMovzMovnImm(reg_size, instr->ImmLogical())) { + mnemonic = "mov"; + form = "'Rds, 'ITri"; + } + break; + } + case EOR_w_imm: + case EOR_x_imm: mnemonic = "eor"; break; + case ANDS_w_imm: + case ANDS_x_imm: { + mnemonic = "ands"; + if (rd_is_zr) { + mnemonic = "tst"; + form = "'Rn, 'ITri"; + } + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +bool Disassembler::IsMovzMovnImm(unsigned reg_size, uint64_t value) { + ASSERT((reg_size == kXRegSizeInBits) || + ((reg_size == kWRegSizeInBits) && (value <= 0xffffffff))); + + // Test for movz: 16-bits set at positions 0, 16, 32 or 48. + if (((value & 0xffffffffffff0000UL) == 0UL) || + ((value & 0xffffffff0000ffffUL) == 0UL) || + ((value & 0xffff0000ffffffffUL) == 0UL) || + ((value & 0x0000ffffffffffffUL) == 0UL)) { + return true; + } + + // Test for movn: NOT(16-bits set at positions 0, 16, 32 or 48). + if ((reg_size == kXRegSizeInBits) && + (((value & 0xffffffffffff0000UL) == 0xffffffffffff0000UL) || + ((value & 0xffffffff0000ffffUL) == 0xffffffff0000ffffUL) || + ((value & 0xffff0000ffffffffUL) == 0xffff0000ffffffffUL) || + ((value & 0x0000ffffffffffffUL) == 0x0000ffffffffffffUL))) { + return true; + } + if ((reg_size == kWRegSizeInBits) && + (((value & 0xffff0000) == 0xffff0000) || + ((value & 0x0000ffff) == 0x0000ffff))) { + return true; + } + return false; +} + + +void Disassembler::VisitLogicalShifted(Instruction* instr) { + bool rd_is_zr = RdIsZROrSP(instr); + bool rn_is_zr = RnIsZROrSP(instr); + const char *mnemonic = ""; + const char *form = "'Rd, 'Rn, 'Rm'HLo"; + + switch (instr->Mask(LogicalShiftedMask)) { + case AND_w: + case AND_x: mnemonic = "and"; break; + case BIC_w: + case BIC_x: mnemonic = "bic"; break; + case EOR_w: + case EOR_x: mnemonic = "eor"; break; + case EON_w: + case EON_x: mnemonic = "eon"; break; + case BICS_w: + case BICS_x: mnemonic = "bics"; break; + case ANDS_w: + case ANDS_x: { + mnemonic = "ands"; + if (rd_is_zr) { + mnemonic = "tst"; + form = "'Rn, 'Rm'HLo"; + } + break; + } + case ORR_w: + case ORR_x: { + mnemonic = "orr"; + if (rn_is_zr && (instr->ImmDPShift() == 0) && (instr->ShiftDP() == LSL)) { + mnemonic = "mov"; + form = "'Rd, 'Rm"; + } + break; + } + case ORN_w: + case ORN_x: { + mnemonic = "orn"; + if (rn_is_zr) { + mnemonic = "mvn"; + form = "'Rd, 'Rm'HLo"; + } + break; + } + default: UNREACHABLE(); + } + + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitConditionalCompareRegister(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Rn, 'Rm, 'INzcv, 'Cond"; + + switch (instr->Mask(ConditionalCompareRegisterMask)) { + case CCMN_w: + case CCMN_x: mnemonic = "ccmn"; break; + case CCMP_w: + case CCMP_x: mnemonic = "ccmp"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitConditionalCompareImmediate(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Rn, 'IP, 'INzcv, 'Cond"; + + switch (instr->Mask(ConditionalCompareImmediateMask)) { + case CCMN_w_imm: + case CCMN_x_imm: mnemonic = "ccmn"; break; + case CCMP_w_imm: + case CCMP_x_imm: mnemonic = "ccmp"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitConditionalSelect(Instruction* instr) { + bool rnm_is_zr = (RnIsZROrSP(instr) && RmIsZROrSP(instr)); + bool rn_is_rm = (instr->Rn() == instr->Rm()); + const char *mnemonic = ""; + const char *form = "'Rd, 'Rn, 'Rm, 'Cond"; + const char *form_test = "'Rd, 'CInv"; + const char *form_update = "'Rd, 'Rn, 'CInv"; + + Condition cond = static_cast<Condition>(instr->Condition()); + bool invertible_cond = (cond != al) && (cond != nv); + + switch (instr->Mask(ConditionalSelectMask)) { + case CSEL_w: + case CSEL_x: mnemonic = "csel"; break; + case CSINC_w: + case CSINC_x: { + mnemonic = "csinc"; + if (rnm_is_zr && invertible_cond) { + mnemonic = "cset"; + form = form_test; + } else if (rn_is_rm && invertible_cond) { + mnemonic = "cinc"; + form = form_update; + } + break; + } + case CSINV_w: + case CSINV_x: { + mnemonic = "csinv"; + if (rnm_is_zr && invertible_cond) { + mnemonic = "csetm"; + form = form_test; + } else if (rn_is_rm && invertible_cond) { + mnemonic = "cinv"; + form = form_update; + } + break; + } + case CSNEG_w: + case CSNEG_x: { + mnemonic = "csneg"; + if (rn_is_rm && invertible_cond) { + mnemonic = "cneg"; + form = form_update; + } + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitBitfield(Instruction* instr) { + unsigned s = instr->ImmS(); + unsigned r = instr->ImmR(); + unsigned rd_size_minus_1 = + ((instr->SixtyFourBits() == 1) ? kXRegSizeInBits : kWRegSizeInBits) - 1; + const char *mnemonic = ""; + const char *form = ""; + const char *form_shift_right = "'Rd, 'Rn, 'IBr"; + const char *form_extend = "'Rd, 'Wn"; + const char *form_bfiz = "'Rd, 'Rn, 'IBZ-r, 'IBs+1"; + const char *form_bfx = "'Rd, 'Rn, 'IBr, 'IBs-r+1"; + const char *form_lsl = "'Rd, 'Rn, 'IBZ-r"; + + switch (instr->Mask(BitfieldMask)) { + case SBFM_w: + case SBFM_x: { + mnemonic = "sbfx"; + form = form_bfx; + if (r == 0) { + form = form_extend; + if (s == 7) { + mnemonic = "sxtb"; + } else if (s == 15) { + mnemonic = "sxth"; + } else if ((s == 31) && (instr->SixtyFourBits() == 1)) { + mnemonic = "sxtw"; + } else { + form = form_bfx; + } + } else if (s == rd_size_minus_1) { + mnemonic = "asr"; + form = form_shift_right; + } else if (s < r) { + mnemonic = "sbfiz"; + form = form_bfiz; + } + break; + } + case UBFM_w: + case UBFM_x: { + mnemonic = "ubfx"; + form = form_bfx; + if (r == 0) { + form = form_extend; + if (s == 7) { + mnemonic = "uxtb"; + } else if (s == 15) { + mnemonic = "uxth"; + } else { + form = form_bfx; + } + } + if (s == rd_size_minus_1) { + mnemonic = "lsr"; + form = form_shift_right; + } else if (r == s + 1) { + mnemonic = "lsl"; + form = form_lsl; + } else if (s < r) { + mnemonic = "ubfiz"; + form = form_bfiz; + } + break; + } + case BFM_w: + case BFM_x: { + mnemonic = "bfxil"; + form = form_bfx; + if (s < r) { + mnemonic = "bfi"; + form = form_bfiz; + } + } + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitExtract(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Rd, 'Rn, 'Rm, 'IExtract"; + + switch (instr->Mask(ExtractMask)) { + case EXTR_w: + case EXTR_x: { + if (instr->Rn() == instr->Rm()) { + mnemonic = "ror"; + form = "'Rd, 'Rn, 'IExtract"; + } else { + mnemonic = "extr"; + } + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitPCRelAddressing(Instruction* instr) { + switch (instr->Mask(PCRelAddressingMask)) { + case ADR: Format(instr, "adr", "'Xd, 'AddrPCRelByte"); break; + // ADRP is not implemented. + default: Format(instr, "unimplemented", "(PCRelAddressing)"); + } +} + + +void Disassembler::VisitConditionalBranch(Instruction* instr) { + switch (instr->Mask(ConditionalBranchMask)) { + case B_cond: Format(instr, "b.'CBrn", "'BImmCond"); break; + default: UNREACHABLE(); + } +} + + +void Disassembler::VisitUnconditionalBranchToRegister(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "'Xn"; + + switch (instr->Mask(UnconditionalBranchToRegisterMask)) { + case BR: mnemonic = "br"; break; + case BLR: mnemonic = "blr"; break; + case RET: { + mnemonic = "ret"; + if (instr->Rn() == kLinkRegCode) { + form = NULL; + } + break; + } + default: form = "(UnconditionalBranchToRegister)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitUnconditionalBranch(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'BImmUncn"; + + switch (instr->Mask(UnconditionalBranchMask)) { + case B: mnemonic = "b"; break; + case BL: mnemonic = "bl"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitDataProcessing1Source(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Rd, 'Rn"; + + switch (instr->Mask(DataProcessing1SourceMask)) { + #define FORMAT(A, B) \ + case A##_w: \ + case A##_x: mnemonic = B; break; + FORMAT(RBIT, "rbit"); + FORMAT(REV16, "rev16"); + FORMAT(REV, "rev"); + FORMAT(CLZ, "clz"); + FORMAT(CLS, "cls"); + #undef FORMAT + case REV32_x: mnemonic = "rev32"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitDataProcessing2Source(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "'Rd, 'Rn, 'Rm"; + + switch (instr->Mask(DataProcessing2SourceMask)) { + #define FORMAT(A, B) \ + case A##_w: \ + case A##_x: mnemonic = B; break; + FORMAT(UDIV, "udiv"); + FORMAT(SDIV, "sdiv"); + FORMAT(LSLV, "lsl"); + FORMAT(LSRV, "lsr"); + FORMAT(ASRV, "asr"); + FORMAT(RORV, "ror"); + #undef FORMAT + default: form = "(DataProcessing2Source)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitDataProcessing3Source(Instruction* instr) { + bool ra_is_zr = RaIsZROrSP(instr); + const char *mnemonic = ""; + const char *form = "'Xd, 'Wn, 'Wm, 'Xa"; + const char *form_rrr = "'Rd, 'Rn, 'Rm"; + const char *form_rrrr = "'Rd, 'Rn, 'Rm, 'Ra"; + const char *form_xww = "'Xd, 'Wn, 'Wm"; + const char *form_xxx = "'Xd, 'Xn, 'Xm"; + + switch (instr->Mask(DataProcessing3SourceMask)) { + case MADD_w: + case MADD_x: { + mnemonic = "madd"; + form = form_rrrr; + if (ra_is_zr) { + mnemonic = "mul"; + form = form_rrr; + } + break; + } + case MSUB_w: + case MSUB_x: { + mnemonic = "msub"; + form = form_rrrr; + if (ra_is_zr) { + mnemonic = "mneg"; + form = form_rrr; + } + break; + } + case SMADDL_x: { + mnemonic = "smaddl"; + if (ra_is_zr) { + mnemonic = "smull"; + form = form_xww; + } + break; + } + case SMSUBL_x: { + mnemonic = "smsubl"; + if (ra_is_zr) { + mnemonic = "smnegl"; + form = form_xww; + } + break; + } + case UMADDL_x: { + mnemonic = "umaddl"; + if (ra_is_zr) { + mnemonic = "umull"; + form = form_xww; + } + break; + } + case UMSUBL_x: { + mnemonic = "umsubl"; + if (ra_is_zr) { + mnemonic = "umnegl"; + form = form_xww; + } + break; + } + case SMULH_x: { + mnemonic = "smulh"; + form = form_xxx; + break; + } + case UMULH_x: { + mnemonic = "umulh"; + form = form_xxx; + break; + } + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitCompareBranch(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Rt, 'BImmCmpa"; + + switch (instr->Mask(CompareBranchMask)) { + case CBZ_w: + case CBZ_x: mnemonic = "cbz"; break; + case CBNZ_w: + case CBNZ_x: mnemonic = "cbnz"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitTestBranch(Instruction* instr) { + const char *mnemonic = ""; + // If the top bit of the immediate is clear, the tested register is + // disassembled as Wt, otherwise Xt. As the top bit of the immediate is + // encoded in bit 31 of the instruction, we can reuse the Rt form, which + // uses bit 31 (normally "sf") to choose the register size. + const char *form = "'Rt, 'IS, 'BImmTest"; + + switch (instr->Mask(TestBranchMask)) { + case TBZ: mnemonic = "tbz"; break; + case TBNZ: mnemonic = "tbnz"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitMoveWideImmediate(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Rd, 'IMoveImm"; + + // Print the shift separately for movk, to make it clear which half word will + // be overwritten. Movn and movz print the computed immediate, which includes + // shift calculation. + switch (instr->Mask(MoveWideImmediateMask)) { + case MOVN_w: + case MOVN_x: mnemonic = "movn"; break; + case MOVZ_w: + case MOVZ_x: mnemonic = "movz"; break; + case MOVK_w: + case MOVK_x: mnemonic = "movk"; form = "'Rd, 'IMoveLSL"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +#define LOAD_STORE_LIST(V) \ + V(STRB_w, "strb", "'Wt") \ + V(STRH_w, "strh", "'Wt") \ + V(STR_w, "str", "'Wt") \ + V(STR_x, "str", "'Xt") \ + V(LDRB_w, "ldrb", "'Wt") \ + V(LDRH_w, "ldrh", "'Wt") \ + V(LDR_w, "ldr", "'Wt") \ + V(LDR_x, "ldr", "'Xt") \ + V(LDRSB_x, "ldrsb", "'Xt") \ + V(LDRSH_x, "ldrsh", "'Xt") \ + V(LDRSW_x, "ldrsw", "'Xt") \ + V(LDRSB_w, "ldrsb", "'Wt") \ + V(LDRSH_w, "ldrsh", "'Wt") \ + V(STR_s, "str", "'St") \ + V(STR_d, "str", "'Dt") \ + V(LDR_s, "ldr", "'St") \ + V(LDR_d, "ldr", "'Dt") + +void Disassembler::VisitLoadStorePreIndex(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(LoadStorePreIndex)"; + + switch (instr->Mask(LoadStorePreIndexMask)) { + #define LS_PREINDEX(A, B, C) \ + case A##_pre: mnemonic = B; form = C ", ['Xns'ILS]!"; break; + LOAD_STORE_LIST(LS_PREINDEX) + #undef LS_PREINDEX + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadStorePostIndex(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(LoadStorePostIndex)"; + + switch (instr->Mask(LoadStorePostIndexMask)) { + #define LS_POSTINDEX(A, B, C) \ + case A##_post: mnemonic = B; form = C ", ['Xns]'ILS"; break; + LOAD_STORE_LIST(LS_POSTINDEX) + #undef LS_POSTINDEX + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadStoreUnsignedOffset(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(LoadStoreUnsignedOffset)"; + + switch (instr->Mask(LoadStoreUnsignedOffsetMask)) { + #define LS_UNSIGNEDOFFSET(A, B, C) \ + case A##_unsigned: mnemonic = B; form = C ", ['Xns'ILU]"; break; + LOAD_STORE_LIST(LS_UNSIGNEDOFFSET) + #undef LS_UNSIGNEDOFFSET + case PRFM_unsigned: mnemonic = "prfm"; form = "'PrefOp, ['Xn'ILU]"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadStoreRegisterOffset(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(LoadStoreRegisterOffset)"; + + switch (instr->Mask(LoadStoreRegisterOffsetMask)) { + #define LS_REGISTEROFFSET(A, B, C) \ + case A##_reg: mnemonic = B; form = C ", ['Xns, 'Offsetreg]"; break; + LOAD_STORE_LIST(LS_REGISTEROFFSET) + #undef LS_REGISTEROFFSET + case PRFM_reg: mnemonic = "prfm"; form = "'PrefOp, ['Xns, 'Offsetreg]"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadStoreUnscaledOffset(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "'Wt, ['Xns'ILS]"; + const char *form_x = "'Xt, ['Xns'ILS]"; + const char *form_s = "'St, ['Xns'ILS]"; + const char *form_d = "'Dt, ['Xns'ILS]"; + + switch (instr->Mask(LoadStoreUnscaledOffsetMask)) { + case STURB_w: mnemonic = "sturb"; break; + case STURH_w: mnemonic = "sturh"; break; + case STUR_w: mnemonic = "stur"; break; + case STUR_x: mnemonic = "stur"; form = form_x; break; + case STUR_s: mnemonic = "stur"; form = form_s; break; + case STUR_d: mnemonic = "stur"; form = form_d; break; + case LDURB_w: mnemonic = "ldurb"; break; + case LDURH_w: mnemonic = "ldurh"; break; + case LDUR_w: mnemonic = "ldur"; break; + case LDUR_x: mnemonic = "ldur"; form = form_x; break; + case LDUR_s: mnemonic = "ldur"; form = form_s; break; + case LDUR_d: mnemonic = "ldur"; form = form_d; break; + case LDURSB_x: form = form_x; // Fall through. + case LDURSB_w: mnemonic = "ldursb"; break; + case LDURSH_x: form = form_x; // Fall through. + case LDURSH_w: mnemonic = "ldursh"; break; + case LDURSW_x: mnemonic = "ldursw"; form = form_x; break; + default: form = "(LoadStoreUnscaledOffset)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadLiteral(Instruction* instr) { + const char *mnemonic = "ldr"; + const char *form = "(LoadLiteral)"; + + switch (instr->Mask(LoadLiteralMask)) { + case LDR_w_lit: form = "'Wt, 'ILLiteral 'LValue"; break; + case LDR_x_lit: form = "'Xt, 'ILLiteral 'LValue"; break; + case LDR_s_lit: form = "'St, 'ILLiteral 'LValue"; break; + case LDR_d_lit: form = "'Dt, 'ILLiteral 'LValue"; break; + default: mnemonic = "unimplemented"; + } + Format(instr, mnemonic, form); +} + + +#define LOAD_STORE_PAIR_LIST(V) \ + V(STP_w, "stp", "'Wt, 'Wt2", "4") \ + V(LDP_w, "ldp", "'Wt, 'Wt2", "4") \ + V(LDPSW_x, "ldpsw", "'Xt, 'Xt2", "4") \ + V(STP_x, "stp", "'Xt, 'Xt2", "8") \ + V(LDP_x, "ldp", "'Xt, 'Xt2", "8") \ + V(STP_s, "stp", "'St, 'St2", "4") \ + V(LDP_s, "ldp", "'St, 'St2", "4") \ + V(STP_d, "stp", "'Dt, 'Dt2", "8") \ + V(LDP_d, "ldp", "'Dt, 'Dt2", "8") + +void Disassembler::VisitLoadStorePairPostIndex(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(LoadStorePairPostIndex)"; + + switch (instr->Mask(LoadStorePairPostIndexMask)) { + #define LSP_POSTINDEX(A, B, C, D) \ + case A##_post: mnemonic = B; form = C ", ['Xns]'ILP" D; break; + LOAD_STORE_PAIR_LIST(LSP_POSTINDEX) + #undef LSP_POSTINDEX + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadStorePairPreIndex(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(LoadStorePairPreIndex)"; + + switch (instr->Mask(LoadStorePairPreIndexMask)) { + #define LSP_PREINDEX(A, B, C, D) \ + case A##_pre: mnemonic = B; form = C ", ['Xns'ILP" D "]!"; break; + LOAD_STORE_PAIR_LIST(LSP_PREINDEX) + #undef LSP_PREINDEX + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadStorePairOffset(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(LoadStorePairOffset)"; + + switch (instr->Mask(LoadStorePairOffsetMask)) { + #define LSP_OFFSET(A, B, C, D) \ + case A##_off: mnemonic = B; form = C ", ['Xns'ILP" D "]"; break; + LOAD_STORE_PAIR_LIST(LSP_OFFSET) + #undef LSP_OFFSET + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitLoadStorePairNonTemporal(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form; + + switch (instr->Mask(LoadStorePairNonTemporalMask)) { + case STNP_w: mnemonic = "stnp"; form = "'Wt, 'Wt2, ['Xns'ILP4]"; break; + case LDNP_w: mnemonic = "ldnp"; form = "'Wt, 'Wt2, ['Xns'ILP4]"; break; + case STNP_x: mnemonic = "stnp"; form = "'Xt, 'Xt2, ['Xns'ILP8]"; break; + case LDNP_x: mnemonic = "ldnp"; form = "'Xt, 'Xt2, ['Xns'ILP8]"; break; + case STNP_s: mnemonic = "stnp"; form = "'St, 'St2, ['Xns'ILP4]"; break; + case LDNP_s: mnemonic = "ldnp"; form = "'St, 'St2, ['Xns'ILP4]"; break; + case STNP_d: mnemonic = "stnp"; form = "'Dt, 'Dt2, ['Xns'ILP8]"; break; + case LDNP_d: mnemonic = "ldnp"; form = "'Dt, 'Dt2, ['Xns'ILP8]"; break; + default: form = "(LoadStorePairNonTemporal)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPCompare(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "'Fn, 'Fm"; + const char *form_zero = "'Fn, #0.0"; + + switch (instr->Mask(FPCompareMask)) { + case FCMP_s_zero: + case FCMP_d_zero: form = form_zero; // Fall through. + case FCMP_s: + case FCMP_d: mnemonic = "fcmp"; break; + default: form = "(FPCompare)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPConditionalCompare(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "'Fn, 'Fm, 'INzcv, 'Cond"; + + switch (instr->Mask(FPConditionalCompareMask)) { + case FCCMP_s: + case FCCMP_d: mnemonic = "fccmp"; break; + case FCCMPE_s: + case FCCMPE_d: mnemonic = "fccmpe"; break; + default: form = "(FPConditionalCompare)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPConditionalSelect(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Fd, 'Fn, 'Fm, 'Cond"; + + switch (instr->Mask(FPConditionalSelectMask)) { + case FCSEL_s: + case FCSEL_d: mnemonic = "fcsel"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPDataProcessing1Source(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "'Fd, 'Fn"; + + switch (instr->Mask(FPDataProcessing1SourceMask)) { + #define FORMAT(A, B) \ + case A##_s: \ + case A##_d: mnemonic = B; break; + FORMAT(FMOV, "fmov"); + FORMAT(FABS, "fabs"); + FORMAT(FNEG, "fneg"); + FORMAT(FSQRT, "fsqrt"); + FORMAT(FRINTN, "frintn"); + FORMAT(FRINTP, "frintp"); + FORMAT(FRINTM, "frintm"); + FORMAT(FRINTZ, "frintz"); + FORMAT(FRINTA, "frinta"); + FORMAT(FRINTX, "frintx"); + FORMAT(FRINTI, "frinti"); + #undef FORMAT + case FCVT_ds: mnemonic = "fcvt"; form = "'Dd, 'Sn"; break; + case FCVT_sd: mnemonic = "fcvt"; form = "'Sd, 'Dn"; break; + default: form = "(FPDataProcessing1Source)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPDataProcessing2Source(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Fd, 'Fn, 'Fm"; + + switch (instr->Mask(FPDataProcessing2SourceMask)) { + #define FORMAT(A, B) \ + case A##_s: \ + case A##_d: mnemonic = B; break; + FORMAT(FMUL, "fmul"); + FORMAT(FDIV, "fdiv"); + FORMAT(FADD, "fadd"); + FORMAT(FSUB, "fsub"); + FORMAT(FMAX, "fmax"); + FORMAT(FMIN, "fmin"); + FORMAT(FMAXNM, "fmaxnm"); + FORMAT(FMINNM, "fminnm"); + FORMAT(FNMUL, "fnmul"); + #undef FORMAT + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPDataProcessing3Source(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Fd, 'Fn, 'Fm, 'Fa"; + + switch (instr->Mask(FPDataProcessing3SourceMask)) { + #define FORMAT(A, B) \ + case A##_s: \ + case A##_d: mnemonic = B; break; + FORMAT(FMADD, "fmadd"); + FORMAT(FMSUB, "fmsub"); + FORMAT(FNMADD, "fnmadd"); + FORMAT(FNMSUB, "fnmsub"); + #undef FORMAT + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPImmediate(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "(FPImmediate)"; + + switch (instr->Mask(FPImmediateMask)) { + case FMOV_s_imm: mnemonic = "fmov"; form = "'Sd, 'IFPSingle"; break; + case FMOV_d_imm: mnemonic = "fmov"; form = "'Dd, 'IFPDouble"; break; + default: UNREACHABLE(); + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPIntegerConvert(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "(FPIntegerConvert)"; + const char *form_rf = "'Rd, 'Fn"; + const char *form_fr = "'Fd, 'Rn"; + + switch (instr->Mask(FPIntegerConvertMask)) { + case FMOV_ws: + case FMOV_xd: mnemonic = "fmov"; form = form_rf; break; + case FMOV_sw: + case FMOV_dx: mnemonic = "fmov"; form = form_fr; break; + case FCVTAS_ws: + case FCVTAS_xs: + case FCVTAS_wd: + case FCVTAS_xd: mnemonic = "fcvtas"; form = form_rf; break; + case FCVTAU_ws: + case FCVTAU_xs: + case FCVTAU_wd: + case FCVTAU_xd: mnemonic = "fcvtau"; form = form_rf; break; + case FCVTMS_ws: + case FCVTMS_xs: + case FCVTMS_wd: + case FCVTMS_xd: mnemonic = "fcvtms"; form = form_rf; break; + case FCVTMU_ws: + case FCVTMU_xs: + case FCVTMU_wd: + case FCVTMU_xd: mnemonic = "fcvtmu"; form = form_rf; break; + case FCVTNS_ws: + case FCVTNS_xs: + case FCVTNS_wd: + case FCVTNS_xd: mnemonic = "fcvtns"; form = form_rf; break; + case FCVTNU_ws: + case FCVTNU_xs: + case FCVTNU_wd: + case FCVTNU_xd: mnemonic = "fcvtnu"; form = form_rf; break; + case FCVTZU_xd: + case FCVTZU_ws: + case FCVTZU_wd: + case FCVTZU_xs: mnemonic = "fcvtzu"; form = form_rf; break; + case FCVTZS_xd: + case FCVTZS_wd: + case FCVTZS_xs: + case FCVTZS_ws: mnemonic = "fcvtzs"; form = form_rf; break; + case SCVTF_sw: + case SCVTF_sx: + case SCVTF_dw: + case SCVTF_dx: mnemonic = "scvtf"; form = form_fr; break; + case UCVTF_sw: + case UCVTF_sx: + case UCVTF_dw: + case UCVTF_dx: mnemonic = "ucvtf"; form = form_fr; break; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitFPFixedPointConvert(Instruction* instr) { + const char *mnemonic = ""; + const char *form = "'Rd, 'Fn, 'IFPFBits"; + const char *form_fr = "'Fd, 'Rn, 'IFPFBits"; + + switch (instr->Mask(FPFixedPointConvertMask)) { + case FCVTZS_ws_fixed: + case FCVTZS_xs_fixed: + case FCVTZS_wd_fixed: + case FCVTZS_xd_fixed: mnemonic = "fcvtzs"; break; + case FCVTZU_ws_fixed: + case FCVTZU_xs_fixed: + case FCVTZU_wd_fixed: + case FCVTZU_xd_fixed: mnemonic = "fcvtzu"; break; + case SCVTF_sw_fixed: + case SCVTF_sx_fixed: + case SCVTF_dw_fixed: + case SCVTF_dx_fixed: mnemonic = "scvtf"; form = form_fr; break; + case UCVTF_sw_fixed: + case UCVTF_sx_fixed: + case UCVTF_dw_fixed: + case UCVTF_dx_fixed: mnemonic = "ucvtf"; form = form_fr; break; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitSystem(Instruction* instr) { + // Some system instructions hijack their Op and Cp fields to represent a + // range of immediates instead of indicating a different instruction. This + // makes the decoding tricky. + const char *mnemonic = "unimplemented"; + const char *form = "(System)"; + + if (instr->Mask(SystemSysRegFMask) == SystemSysRegFixed) { + switch (instr->Mask(SystemSysRegMask)) { + case MRS: { + mnemonic = "mrs"; + switch (instr->ImmSystemRegister()) { + case NZCV: form = "'Xt, nzcv"; break; + case FPCR: form = "'Xt, fpcr"; break; + default: form = "'Xt, (unknown)"; break; + } + break; + } + case MSR: { + mnemonic = "msr"; + switch (instr->ImmSystemRegister()) { + case NZCV: form = "nzcv, 'Xt"; break; + case FPCR: form = "fpcr, 'Xt"; break; + default: form = "(unknown), 'Xt"; break; + } + break; + } + } + } else if (instr->Mask(SystemHintFMask) == SystemHintFixed) { + ASSERT(instr->Mask(SystemHintMask) == HINT); + switch (instr->ImmHint()) { + case NOP: { + mnemonic = "nop"; + form = NULL; + break; + } + } + } else if (instr->Mask(MemBarrierFMask) == MemBarrierFixed) { + switch (instr->Mask(MemBarrierMask)) { + case DMB: { + mnemonic = "dmb"; + form = "'M"; + break; + } + case DSB: { + mnemonic = "dsb"; + form = "'M"; + break; + } + case ISB: { + mnemonic = "isb"; + form = NULL; + break; + } + } + } + + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitException(Instruction* instr) { + const char *mnemonic = "unimplemented"; + const char *form = "'IDebug"; + + switch (instr->Mask(ExceptionMask)) { + case HLT: mnemonic = "hlt"; break; + case BRK: mnemonic = "brk"; break; + case SVC: mnemonic = "svc"; break; + case HVC: mnemonic = "hvc"; break; + case SMC: mnemonic = "smc"; break; + case DCPS1: mnemonic = "dcps1"; form = "{'IDebug}"; break; + case DCPS2: mnemonic = "dcps2"; form = "{'IDebug}"; break; + case DCPS3: mnemonic = "dcps3"; form = "{'IDebug}"; break; + default: form = "(Exception)"; + } + Format(instr, mnemonic, form); +} + + +void Disassembler::VisitUnimplemented(Instruction* instr) { + Format(instr, "unimplemented", "(Unimplemented)"); +} + + +void Disassembler::VisitUnallocated(Instruction* instr) { + Format(instr, "unallocated", "(Unallocated)"); +} + + +void Disassembler::ProcessOutput(Instruction* /*instr*/) { + // The base disasm does nothing more than disassembling into a buffer. +} + + +void Disassembler::Format(Instruction* instr, const char* mnemonic, + const char* format) { + // TODO(mcapewel) don't think I can use the instr address here - there needs + // to be a base address too + ASSERT(mnemonic != NULL); + ResetOutput(); + Substitute(instr, mnemonic); + if (format != NULL) { + buffer_[buffer_pos_++] = ' '; + Substitute(instr, format); + } + buffer_[buffer_pos_] = 0; + ProcessOutput(instr); +} + + +void Disassembler::Substitute(Instruction* instr, const char* string) { + char chr = *string++; + while (chr != '\0') { + if (chr == '\'') { + string += SubstituteField(instr, string); + } else { + buffer_[buffer_pos_++] = chr; + } + chr = *string++; + } +} + + +int Disassembler::SubstituteField(Instruction* instr, const char* format) { + switch (format[0]) { + case 'R': // Register. X or W, selected by sf bit. + case 'F': // FP Register. S or D, selected by type field. + case 'W': + case 'X': + case 'S': + case 'D': return SubstituteRegisterField(instr, format); + case 'I': return SubstituteImmediateField(instr, format); + case 'L': return SubstituteLiteralField(instr, format); + case 'H': return SubstituteShiftField(instr, format); + case 'P': return SubstitutePrefetchField(instr, format); + case 'C': return SubstituteConditionField(instr, format); + case 'E': return SubstituteExtendField(instr, format); + case 'A': return SubstitutePCRelAddressField(instr, format); + case 'B': return SubstituteBranchTargetField(instr, format); + case 'O': return SubstituteLSRegOffsetField(instr, format); + case 'M': return SubstituteBarrierField(instr, format); + default: { + UNREACHABLE(); + return 1; + } + } +} + + +int Disassembler::SubstituteRegisterField(Instruction* instr, + const char* format) { + unsigned reg_num = 0; + unsigned field_len = 2; + switch (format[1]) { + case 'd': reg_num = instr->Rd(); break; + case 'n': reg_num = instr->Rn(); break; + case 'm': reg_num = instr->Rm(); break; + case 'a': reg_num = instr->Ra(); break; + case 't': { + if (format[2] == '2') { + reg_num = instr->Rt2(); + field_len = 3; + } else { + reg_num = instr->Rt(); + } + break; + } + default: UNREACHABLE(); + } + + // Increase field length for registers tagged as stack. + if (format[2] == 's') { + field_len = 3; + } + + char reg_type; + if (format[0] == 'R') { + // Register type is R: use sf bit to choose X and W. + reg_type = instr->SixtyFourBits() ? 'x' : 'w'; + } else if (format[0] == 'F') { + // Floating-point register: use type field to choose S or D. + reg_type = ((instr->FPType() & 1) == 0) ? 's' : 'd'; + } else { + // Register type is specified. Make it lower case. + reg_type = format[0] + 0x20; + } + + if ((reg_num != kZeroRegCode) || (reg_type == 's') || (reg_type == 'd')) { + // A normal register: w0 - w30, x0 - x30, s0 - s31, d0 - d31. + + // Filter special registers + if ((reg_type == 'x') && (reg_num == 27)) { + AppendToOutput("cp"); + } else if ((reg_type == 'x') && (reg_num == 28)) { + AppendToOutput("jssp"); + } else if ((reg_type == 'x') && (reg_num == 29)) { + AppendToOutput("fp"); + } else if ((reg_type == 'x') && (reg_num == 30)) { + AppendToOutput("lr"); + } else { + AppendToOutput("%c%d", reg_type, reg_num); + } + } else if (format[2] == 's') { + // Disassemble w31/x31 as stack pointer wcsp/csp. + AppendToOutput("%s", (reg_type == 'w') ? "wcsp" : "csp"); + } else { + // Disassemble w31/x31 as zero register wzr/xzr. + AppendToOutput("%czr", reg_type); + } + + return field_len; +} + + +int Disassembler::SubstituteImmediateField(Instruction* instr, + const char* format) { + ASSERT(format[0] == 'I'); + + switch (format[1]) { + case 'M': { // IMoveImm or IMoveLSL. + if (format[5] == 'I') { + uint64_t imm = instr->ImmMoveWide() << (16 * instr->ShiftMoveWide()); + AppendToOutput("#0x%" PRIx64, imm); + } else { + ASSERT(format[5] == 'L'); + AppendToOutput("#0x%" PRIx64, instr->ImmMoveWide()); + if (instr->ShiftMoveWide() > 0) { + AppendToOutput(", lsl #%d", 16 * instr->ShiftMoveWide()); + } + } + return 8; + } + case 'L': { + switch (format[2]) { + case 'L': { // ILLiteral - Immediate Load Literal. + AppendToOutput("pc%+" PRId64, + instr->ImmLLiteral() << kLoadLiteralScaleLog2); + return 9; + } + case 'S': { // ILS - Immediate Load/Store. + if (instr->ImmLS() != 0) { + AppendToOutput(", #%" PRId64, instr->ImmLS()); + } + return 3; + } + case 'P': { // ILPx - Immediate Load/Store Pair, x = access size. + if (instr->ImmLSPair() != 0) { + // format[3] is the scale value. Convert to a number. + int scale = format[3] - 0x30; + AppendToOutput(", #%" PRId64, instr->ImmLSPair() * scale); + } + return 4; + } + case 'U': { // ILU - Immediate Load/Store Unsigned. + if (instr->ImmLSUnsigned() != 0) { + AppendToOutput(", #%" PRIu64, + instr->ImmLSUnsigned() << instr->SizeLS()); + } + return 3; + } + } + } + case 'C': { // ICondB - Immediate Conditional Branch. + int64_t offset = instr->ImmCondBranch() << 2; + char sign = (offset >= 0) ? '+' : '-'; + AppendToOutput("#%c0x%" PRIx64, sign, offset); + return 6; + } + case 'A': { // IAddSub. + ASSERT(instr->ShiftAddSub() <= 1); + int64_t imm = instr->ImmAddSub() << (12 * instr->ShiftAddSub()); + AppendToOutput("#0x%" PRIx64 " (%" PRId64 ")", imm, imm); + return 7; + } + case 'F': { // IFPSingle, IFPDouble or IFPFBits. + if (format[3] == 'F') { // IFPFBits. + AppendToOutput("#%d", 64 - instr->FPScale()); + return 8; + } else { + AppendToOutput("#0x%" PRIx64 " (%.4f)", instr->ImmFP(), + format[3] == 'S' ? instr->ImmFP32() : instr->ImmFP64()); + return 9; + } + } + case 'T': { // ITri - Immediate Triangular Encoded. + AppendToOutput("#0x%" PRIx64, instr->ImmLogical()); + return 4; + } + case 'N': { // INzcv. + int nzcv = (instr->Nzcv() << Flags_offset); + AppendToOutput("#%c%c%c%c", ((nzcv & NFlag) == 0) ? 'n' : 'N', + ((nzcv & ZFlag) == 0) ? 'z' : 'Z', + ((nzcv & CFlag) == 0) ? 'c' : 'C', + ((nzcv & VFlag) == 0) ? 'v' : 'V'); + return 5; + } + case 'P': { // IP - Conditional compare. + AppendToOutput("#%d", instr->ImmCondCmp()); + return 2; + } + case 'B': { // Bitfields. + return SubstituteBitfieldImmediateField(instr, format); + } + case 'E': { // IExtract. + AppendToOutput("#%d", instr->ImmS()); + return 8; + } + case 'S': { // IS - Test and branch bit. + AppendToOutput("#%d", (instr->ImmTestBranchBit5() << 5) | + instr->ImmTestBranchBit40()); + return 2; + } + case 'D': { // IDebug - HLT and BRK instructions. + AppendToOutput("#0x%x", instr->ImmException()); + return 6; + } + default: { + UNREACHABLE(); + return 0; + } + } +} + + +int Disassembler::SubstituteBitfieldImmediateField(Instruction* instr, + const char* format) { + ASSERT((format[0] == 'I') && (format[1] == 'B')); + unsigned r = instr->ImmR(); + unsigned s = instr->ImmS(); + + switch (format[2]) { + case 'r': { // IBr. + AppendToOutput("#%d", r); + return 3; + } + case 's': { // IBs+1 or IBs-r+1. + if (format[3] == '+') { + AppendToOutput("#%d", s + 1); + return 5; + } else { + ASSERT(format[3] == '-'); + AppendToOutput("#%d", s - r + 1); + return 7; + } + } + case 'Z': { // IBZ-r. + ASSERT((format[3] == '-') && (format[4] == 'r')); + unsigned reg_size = (instr->SixtyFourBits() == 1) ? kXRegSizeInBits + : kWRegSizeInBits; + AppendToOutput("#%d", reg_size - r); + return 5; + } + default: { + UNREACHABLE(); + return 0; + } + } +} + + +int Disassembler::SubstituteLiteralField(Instruction* instr, + const char* format) { + ASSERT(strncmp(format, "LValue", 6) == 0); + USE(format); + + switch (instr->Mask(LoadLiteralMask)) { + case LDR_w_lit: + case LDR_x_lit: + case LDR_s_lit: + case LDR_d_lit: AppendToOutput("(addr %p)", instr->LiteralAddress()); break; + default: UNREACHABLE(); + } + + return 6; +} + + +int Disassembler::SubstituteShiftField(Instruction* instr, const char* format) { + ASSERT(format[0] == 'H'); + ASSERT(instr->ShiftDP() <= 0x3); + + switch (format[1]) { + case 'D': { // HDP. + ASSERT(instr->ShiftDP() != ROR); + } // Fall through. + case 'L': { // HLo. + if (instr->ImmDPShift() != 0) { + const char* shift_type[] = {"lsl", "lsr", "asr", "ror"}; + AppendToOutput(", %s #%" PRId64, shift_type[instr->ShiftDP()], + instr->ImmDPShift()); + } + return 3; + } + default: + UNREACHABLE(); + return 0; + } +} + + +int Disassembler::SubstituteConditionField(Instruction* instr, + const char* format) { + ASSERT(format[0] == 'C'); + const char* condition_code[] = { "eq", "ne", "hs", "lo", + "mi", "pl", "vs", "vc", + "hi", "ls", "ge", "lt", + "gt", "le", "al", "nv" }; + int cond; + switch (format[1]) { + case 'B': cond = instr->ConditionBranch(); break; + case 'I': { + cond = NegateCondition(static_cast<Condition>(instr->Condition())); + break; + } + default: cond = instr->Condition(); + } + AppendToOutput("%s", condition_code[cond]); + return 4; +} + + +int Disassembler::SubstitutePCRelAddressField(Instruction* instr, + const char* format) { + USE(format); + ASSERT(strncmp(format, "AddrPCRel", 9) == 0); + + int offset = instr->ImmPCRel(); + + // Only ADR (AddrPCRelByte) is supported. + ASSERT(strcmp(format, "AddrPCRelByte") == 0); + + char sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + AppendToOutput("#%c0x%x (addr %p)", sign, offset, + instr->InstructionAtOffset(offset, Instruction::NO_CHECK)); + return 13; +} + + +int Disassembler::SubstituteBranchTargetField(Instruction* instr, + const char* format) { + ASSERT(strncmp(format, "BImm", 4) == 0); + + int64_t offset = 0; + switch (format[5]) { + // BImmUncn - unconditional branch immediate. + case 'n': offset = instr->ImmUncondBranch(); break; + // BImmCond - conditional branch immediate. + case 'o': offset = instr->ImmCondBranch(); break; + // BImmCmpa - compare and branch immediate. + case 'm': offset = instr->ImmCmpBranch(); break; + // BImmTest - test and branch immediate. + case 'e': offset = instr->ImmTestBranch(); break; + default: UNREACHABLE(); + } + offset <<= kInstructionSizeLog2; + char sign = '+'; + if (offset < 0) { + sign = '-'; + } + AppendToOutput("#%c0x%" PRIx64 " (addr %p)", sign, Abs(offset), + instr->InstructionAtOffset(offset), Instruction::NO_CHECK); + return 8; +} + + +int Disassembler::SubstituteExtendField(Instruction* instr, + const char* format) { + ASSERT(strncmp(format, "Ext", 3) == 0); + ASSERT(instr->ExtendMode() <= 7); + USE(format); + + const char* extend_mode[] = { "uxtb", "uxth", "uxtw", "uxtx", + "sxtb", "sxth", "sxtw", "sxtx" }; + + // If rd or rn is SP, uxtw on 32-bit registers and uxtx on 64-bit + // registers becomes lsl. + if (((instr->Rd() == kZeroRegCode) || (instr->Rn() == kZeroRegCode)) && + (((instr->ExtendMode() == UXTW) && (instr->SixtyFourBits() == 0)) || + (instr->ExtendMode() == UXTX))) { + if (instr->ImmExtendShift() > 0) { + AppendToOutput(", lsl #%d", instr->ImmExtendShift()); + } + } else { + AppendToOutput(", %s", extend_mode[instr->ExtendMode()]); + if (instr->ImmExtendShift() > 0) { + AppendToOutput(" #%d", instr->ImmExtendShift()); + } + } + return 3; +} + + +int Disassembler::SubstituteLSRegOffsetField(Instruction* instr, + const char* format) { + ASSERT(strncmp(format, "Offsetreg", 9) == 0); + const char* extend_mode[] = { "undefined", "undefined", "uxtw", "lsl", + "undefined", "undefined", "sxtw", "sxtx" }; + USE(format); + + unsigned shift = instr->ImmShiftLS(); + Extend ext = static_cast<Extend>(instr->ExtendMode()); + char reg_type = ((ext == UXTW) || (ext == SXTW)) ? 'w' : 'x'; + + unsigned rm = instr->Rm(); + if (rm == kZeroRegCode) { + AppendToOutput("%czr", reg_type); + } else { + AppendToOutput("%c%d", reg_type, rm); + } + + // Extend mode UXTX is an alias for shift mode LSL here. + if (!((ext == UXTX) && (shift == 0))) { + AppendToOutput(", %s", extend_mode[ext]); + if (shift != 0) { + AppendToOutput(" #%d", instr->SizeLS()); + } + } + return 9; +} + + +int Disassembler::SubstitutePrefetchField(Instruction* instr, + const char* format) { + ASSERT(format[0] == 'P'); + USE(format); + + int prefetch_mode = instr->PrefetchMode(); + + const char* ls = (prefetch_mode & 0x10) ? "st" : "ld"; + int level = (prefetch_mode >> 1) + 1; + const char* ks = (prefetch_mode & 1) ? "strm" : "keep"; + + AppendToOutput("p%sl%d%s", ls, level, ks); + return 6; +} + +int Disassembler::SubstituteBarrierField(Instruction* instr, + const char* format) { + ASSERT(format[0] == 'M'); + USE(format); + + static const char* options[4][4] = { + { "sy (0b0000)", "oshld", "oshst", "osh" }, + { "sy (0b0100)", "nshld", "nshst", "nsh" }, + { "sy (0b1000)", "ishld", "ishst", "ish" }, + { "sy (0b1100)", "ld", "st", "sy" } + }; + int domain = instr->ImmBarrierDomain(); + int type = instr->ImmBarrierType(); + + AppendToOutput("%s", options[domain][type]); + return 1; +} + + +void Disassembler::ResetOutput() { + buffer_pos_ = 0; + buffer_[buffer_pos_] = 0; +} + + +void Disassembler::AppendToOutput(const char* format, ...) { + va_list args; + va_start(args, format); + buffer_pos_ += vsnprintf(&buffer_[buffer_pos_], buffer_size_, format, args); + va_end(args); +} + + +void PrintDisassembler::ProcessOutput(Instruction* instr) { + fprintf(stream_, "0x%016" PRIx64 " %08" PRIx32 "\t\t%s\n", + reinterpret_cast<uint64_t>(instr), instr->InstructionBits(), + GetOutput()); +} + +} } // namespace v8::internal + + +namespace disasm { + + +const char* NameConverter::NameOfAddress(byte* addr) const { + v8::internal::SNPrintF(tmp_buffer_, "%p", addr); + return tmp_buffer_.start(); +} + + +const char* NameConverter::NameOfConstant(byte* addr) const { + return NameOfAddress(addr); +} + + +const char* NameConverter::NameOfCPURegister(int reg) const { + unsigned ureg = reg; // Avoid warnings about signed/unsigned comparisons. + if (ureg >= v8::internal::kNumberOfRegisters) { + return "noreg"; + } + if (ureg == v8::internal::kZeroRegCode) { + return "xzr"; + } + v8::internal::SNPrintF(tmp_buffer_, "x%u", ureg); + return tmp_buffer_.start(); +} + + +const char* NameConverter::NameOfByteCPURegister(int reg) const { + UNREACHABLE(); // ARM64 does not have the concept of a byte register + return "nobytereg"; +} + + +const char* NameConverter::NameOfXMMRegister(int reg) const { + UNREACHABLE(); // ARM64 does not have any XMM registers + return "noxmmreg"; +} + + +const char* NameConverter::NameInCode(byte* addr) const { + // The default name converter is called for unknown code, so we will not try + // to access any memory. + return ""; +} + + +//------------------------------------------------------------------------------ + +class BufferDisassembler : public v8::internal::Disassembler { + public: + explicit BufferDisassembler(v8::internal::Vector<char> out_buffer) + : out_buffer_(out_buffer) { } + + ~BufferDisassembler() { } + + virtual void ProcessOutput(v8::internal::Instruction* instr) { + v8::internal::SNPrintF(out_buffer_, "%s", GetOutput()); + } + + private: + v8::internal::Vector<char> out_buffer_; +}; + +Disassembler::Disassembler(const NameConverter& converter) + : converter_(converter) {} + + +Disassembler::~Disassembler() {} + + +int Disassembler::InstructionDecode(v8::internal::Vector<char> buffer, + byte* instr) { + v8::internal::Decoder<v8::internal::DispatchingDecoderVisitor> decoder; + BufferDisassembler disasm(buffer); + decoder.AppendVisitor(&disasm); + + decoder.Decode(reinterpret_cast<v8::internal::Instruction*>(instr)); + return v8::internal::kInstructionSize; +} + + +int Disassembler::ConstantPoolSizeAt(byte* instr) { + return v8::internal::Assembler::ConstantPoolSizeAt( + reinterpret_cast<v8::internal::Instruction*>(instr)); +} + + +void Disassembler::Disassemble(FILE* file, byte* start, byte* end) { + v8::internal::Decoder<v8::internal::DispatchingDecoderVisitor> decoder; + v8::internal::PrintDisassembler disasm(file); + decoder.AppendVisitor(&disasm); + + for (byte* pc = start; pc < end; pc += v8::internal::kInstructionSize) { + decoder.Decode(reinterpret_cast<v8::internal::Instruction*>(pc)); + } +} + +} // namespace disasm + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/disasm-arm64.h b/chromium/v8/src/arm64/disasm-arm64.h new file mode 100644 index 00000000000..a7db4d2414d --- /dev/null +++ b/chromium/v8/src/arm64/disasm-arm64.h @@ -0,0 +1,92 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_DISASM_ARM64_H +#define V8_ARM64_DISASM_ARM64_H + +#include "src/v8.h" + +#include "src/globals.h" +#include "src/utils.h" +#include "src/arm64/instructions-arm64.h" +#include "src/arm64/decoder-arm64.h" + +namespace v8 { +namespace internal { + + +class Disassembler: public DecoderVisitor { + public: + Disassembler(); + Disassembler(char* text_buffer, int buffer_size); + virtual ~Disassembler(); + char* GetOutput(); + + // Declare all Visitor functions. + #define DECLARE(A) void Visit##A(Instruction* instr); + VISITOR_LIST(DECLARE) + #undef DECLARE + + protected: + virtual void ProcessOutput(Instruction* instr); + + void Format(Instruction* instr, const char* mnemonic, const char* format); + void Substitute(Instruction* instr, const char* string); + int SubstituteField(Instruction* instr, const char* format); + int SubstituteRegisterField(Instruction* instr, const char* format); + int SubstituteImmediateField(Instruction* instr, const char* format); + int SubstituteLiteralField(Instruction* instr, const char* format); + int SubstituteBitfieldImmediateField(Instruction* instr, const char* format); + int SubstituteShiftField(Instruction* instr, const char* format); + int SubstituteExtendField(Instruction* instr, const char* format); + int SubstituteConditionField(Instruction* instr, const char* format); + int SubstitutePCRelAddressField(Instruction* instr, const char* format); + int SubstituteBranchTargetField(Instruction* instr, const char* format); + int SubstituteLSRegOffsetField(Instruction* instr, const char* format); + int SubstitutePrefetchField(Instruction* instr, const char* format); + int SubstituteBarrierField(Instruction* instr, const char* format); + + bool RdIsZROrSP(Instruction* instr) const { + return (instr->Rd() == kZeroRegCode); + } + + bool RnIsZROrSP(Instruction* instr) const { + return (instr->Rn() == kZeroRegCode); + } + + bool RmIsZROrSP(Instruction* instr) const { + return (instr->Rm() == kZeroRegCode); + } + + bool RaIsZROrSP(Instruction* instr) const { + return (instr->Ra() == kZeroRegCode); + } + + bool IsMovzMovnImm(unsigned reg_size, uint64_t value); + + void ResetOutput(); + void AppendToOutput(const char* string, ...); + + char* buffer_; + uint32_t buffer_pos_; + uint32_t buffer_size_; + bool own_buffer_; +}; + + +class PrintDisassembler: public Disassembler { + public: + explicit PrintDisassembler(FILE* stream) : stream_(stream) { } + ~PrintDisassembler() { } + + virtual void ProcessOutput(Instruction* instr); + + private: + FILE *stream_; +}; + + +} } // namespace v8::internal + +#endif // V8_ARM64_DISASM_ARM64_H diff --git a/chromium/v8/src/arm64/frames-arm64.cc b/chromium/v8/src/arm64/frames-arm64.cc new file mode 100644 index 00000000000..122ac218ced --- /dev/null +++ b/chromium/v8/src/arm64/frames-arm64.cc @@ -0,0 +1,42 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/assembler.h" +#include "src/arm64/assembler-arm64.h" +#include "src/arm64/assembler-arm64-inl.h" +#include "src/frames.h" + +namespace v8 { +namespace internal { + + +Register JavaScriptFrame::fp_register() { return v8::internal::fp; } +Register JavaScriptFrame::context_register() { return cp; } +Register JavaScriptFrame::constant_pool_pointer_register() { + UNREACHABLE(); + return no_reg; +} + + +Register StubFailureTrampolineFrame::fp_register() { return v8::internal::fp; } +Register StubFailureTrampolineFrame::context_register() { return cp; } +Register StubFailureTrampolineFrame::constant_pool_pointer_register() { + UNREACHABLE(); + return no_reg; +} + + +Object*& ExitFrame::constant_pool_slot() const { + UNREACHABLE(); + return Memory::Object_at(NULL); +} + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/frames-arm64.h b/chromium/v8/src/arm64/frames-arm64.h new file mode 100644 index 00000000000..557c955fe5c --- /dev/null +++ b/chromium/v8/src/arm64/frames-arm64.h @@ -0,0 +1,109 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/arm64/constants-arm64.h" +#include "src/arm64/assembler-arm64.h" + +#ifndef V8_ARM64_FRAMES_ARM64_H_ +#define V8_ARM64_FRAMES_ARM64_H_ + +namespace v8 { +namespace internal { + +const int kNumRegs = kNumberOfRegisters; +// Registers x0-x17 are caller-saved. +const int kNumJSCallerSaved = 18; +const RegList kJSCallerSaved = 0x3ffff; + +// Number of registers for which space is reserved in safepoints. Must be a +// multiple of eight. +// TODO(all): Refine this number. +const int kNumSafepointRegisters = 32; + +// Define the list of registers actually saved at safepoints. +// Note that the number of saved registers may be smaller than the reserved +// space, i.e. kNumSafepointSavedRegisters <= kNumSafepointRegisters. +#define kSafepointSavedRegisters CPURegList::GetSafepointSavedRegisters().list() +#define kNumSafepointSavedRegisters \ + CPURegList::GetSafepointSavedRegisters().Count(); + +class EntryFrameConstants : public AllStatic { + public: + static const int kCallerFPOffset = + -(StandardFrameConstants::kFixedFrameSizeFromFp + kPointerSize); +}; + + +class ExitFrameConstants : public AllStatic { + public: + static const int kFrameSize = 2 * kPointerSize; + + static const int kCallerSPDisplacement = 2 * kPointerSize; + static const int kCallerPCOffset = 1 * kPointerSize; + static const int kCallerFPOffset = 0 * kPointerSize; // <- fp + static const int kSPOffset = -1 * kPointerSize; + static const int kCodeOffset = -2 * kPointerSize; + static const int kLastExitFrameField = kCodeOffset; + + static const int kConstantPoolOffset = 0; // Not used +}; + + +class JavaScriptFrameConstants : public AllStatic { + public: + // FP-relative. + static const int kLocal0Offset = StandardFrameConstants::kExpressionsOffset; + + // There are two words on the stack (saved fp and saved lr) between fp and + // the arguments. + static const int kLastParameterOffset = 2 * kPointerSize; + + static const int kFunctionOffset = StandardFrameConstants::kMarkerOffset; +}; + + +class ArgumentsAdaptorFrameConstants : public AllStatic { + public: + // FP-relative. + static const int kLengthOffset = StandardFrameConstants::kExpressionsOffset; + + static const int kFrameSize = + StandardFrameConstants::kFixedFrameSize + kPointerSize; +}; + + +class ConstructFrameConstants : public AllStatic { + public: + // FP-relative. + static const int kCodeOffset = StandardFrameConstants::kExpressionsOffset; + static const int kLengthOffset = -4 * kPointerSize; + static const int kConstructorOffset = -5 * kPointerSize; + static const int kImplicitReceiverOffset = -6 * kPointerSize; + + static const int kFrameSize = + StandardFrameConstants::kFixedFrameSize + 4 * kPointerSize; +}; + + +class InternalFrameConstants : public AllStatic { + public: + // FP-relative. + static const int kCodeOffset = StandardFrameConstants::kExpressionsOffset; +}; + + +inline Object* JavaScriptFrame::function_slot_object() const { + const int offset = JavaScriptFrameConstants::kFunctionOffset; + return Memory::Object_at(fp() + offset); +} + + +inline void StackHandler::SetFp(Address slot, Address fp) { + Memory::Address_at(slot) = fp; +} + + +} } // namespace v8::internal + +#endif // V8_ARM64_FRAMES_ARM64_H_ diff --git a/chromium/v8/src/arm64/full-codegen-arm64.cc b/chromium/v8/src/arm64/full-codegen-arm64.cc new file mode 100644 index 00000000000..4a44e35b05d --- /dev/null +++ b/chromium/v8/src/arm64/full-codegen-arm64.cc @@ -0,0 +1,4884 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/code-stubs.h" +#include "src/codegen.h" +#include "src/compiler.h" +#include "src/debug.h" +#include "src/full-codegen.h" +#include "src/isolate-inl.h" +#include "src/parser.h" +#include "src/scopes.h" +#include "src/stub-cache.h" + +#include "src/arm64/code-stubs-arm64.h" +#include "src/arm64/macro-assembler-arm64.h" + +namespace v8 { +namespace internal { + +#define __ ACCESS_MASM(masm_) + +class JumpPatchSite BASE_EMBEDDED { + public: + explicit JumpPatchSite(MacroAssembler* masm) : masm_(masm), reg_(NoReg) { +#ifdef DEBUG + info_emitted_ = false; +#endif + } + + ~JumpPatchSite() { + if (patch_site_.is_bound()) { + ASSERT(info_emitted_); + } else { + ASSERT(reg_.IsNone()); + } + } + + void EmitJumpIfNotSmi(Register reg, Label* target) { + // This code will be patched by PatchInlinedSmiCode, in ic-arm64.cc. + InstructionAccurateScope scope(masm_, 1); + ASSERT(!info_emitted_); + ASSERT(reg.Is64Bits()); + ASSERT(!reg.Is(csp)); + reg_ = reg; + __ bind(&patch_site_); + __ tbz(xzr, 0, target); // Always taken before patched. + } + + void EmitJumpIfSmi(Register reg, Label* target) { + // This code will be patched by PatchInlinedSmiCode, in ic-arm64.cc. + InstructionAccurateScope scope(masm_, 1); + ASSERT(!info_emitted_); + ASSERT(reg.Is64Bits()); + ASSERT(!reg.Is(csp)); + reg_ = reg; + __ bind(&patch_site_); + __ tbnz(xzr, 0, target); // Never taken before patched. + } + + void EmitJumpIfEitherNotSmi(Register reg1, Register reg2, Label* target) { + UseScratchRegisterScope temps(masm_); + Register temp = temps.AcquireX(); + __ Orr(temp, reg1, reg2); + EmitJumpIfNotSmi(temp, target); + } + + void EmitPatchInfo() { + Assembler::BlockPoolsScope scope(masm_); + InlineSmiCheckInfo::Emit(masm_, reg_, &patch_site_); +#ifdef DEBUG + info_emitted_ = true; +#endif + } + + private: + MacroAssembler* masm_; + Label patch_site_; + Register reg_; +#ifdef DEBUG + bool info_emitted_; +#endif +}; + + +// Generate code for a JS function. On entry to the function the receiver +// and arguments have been pushed on the stack left to right. The actual +// argument count matches the formal parameter count expected by the +// function. +// +// The live registers are: +// - x1: the JS function object being called (i.e. ourselves). +// - cp: our context. +// - fp: our caller's frame pointer. +// - jssp: stack pointer. +// - lr: return address. +// +// The function builds a JS frame. See JavaScriptFrameConstants in +// frames-arm.h for its layout. +void FullCodeGenerator::Generate() { + CompilationInfo* info = info_; + handler_table_ = + isolate()->factory()->NewFixedArray(function()->handler_count(), TENURED); + + profiling_counter_ = isolate()->factory()->NewCell( + Handle<Smi>(Smi::FromInt(FLAG_interrupt_budget), isolate())); + SetFunctionPosition(function()); + Comment cmnt(masm_, "[ Function compiled by full code generator"); + + ProfileEntryHookStub::MaybeCallEntryHook(masm_); + +#ifdef DEBUG + if (strlen(FLAG_stop_at) > 0 && + info->function()->name()->IsUtf8EqualTo(CStrVector(FLAG_stop_at))) { + __ Debug("stop-at", __LINE__, BREAK); + } +#endif + + // Sloppy mode functions and builtins need to replace the receiver with the + // global proxy when called as functions (without an explicit receiver + // object). + if (info->strict_mode() == SLOPPY && !info->is_native()) { + Label ok; + int receiver_offset = info->scope()->num_parameters() * kXRegSize; + __ Peek(x10, receiver_offset); + __ JumpIfNotRoot(x10, Heap::kUndefinedValueRootIndex, &ok); + + __ Ldr(x10, GlobalObjectMemOperand()); + __ Ldr(x10, FieldMemOperand(x10, GlobalObject::kGlobalReceiverOffset)); + __ Poke(x10, receiver_offset); + + __ Bind(&ok); + } + + + // Open a frame scope to indicate that there is a frame on the stack. + // The MANUAL indicates that the scope shouldn't actually generate code + // to set up the frame because we do it manually below. + FrameScope frame_scope(masm_, StackFrame::MANUAL); + + // This call emits the following sequence in a way that can be patched for + // code ageing support: + // Push(lr, fp, cp, x1); + // Add(fp, jssp, 2 * kPointerSize); + info->set_prologue_offset(masm_->pc_offset()); + __ Prologue(info->IsCodePreAgingActive()); + info->AddNoFrameRange(0, masm_->pc_offset()); + + // Reserve space on the stack for locals. + { Comment cmnt(masm_, "[ Allocate locals"); + int locals_count = info->scope()->num_stack_slots(); + // Generators allocate locals, if any, in context slots. + ASSERT(!info->function()->is_generator() || locals_count == 0); + + if (locals_count > 0) { + if (locals_count >= 128) { + Label ok; + ASSERT(jssp.Is(__ StackPointer())); + __ Sub(x10, jssp, locals_count * kPointerSize); + __ CompareRoot(x10, Heap::kRealStackLimitRootIndex); + __ B(hs, &ok); + __ InvokeBuiltin(Builtins::STACK_OVERFLOW, CALL_FUNCTION); + __ Bind(&ok); + } + __ LoadRoot(x10, Heap::kUndefinedValueRootIndex); + if (FLAG_optimize_for_size) { + __ PushMultipleTimes(x10 , locals_count); + } else { + const int kMaxPushes = 32; + if (locals_count >= kMaxPushes) { + int loop_iterations = locals_count / kMaxPushes; + __ Mov(x3, loop_iterations); + Label loop_header; + __ Bind(&loop_header); + // Do pushes. + __ PushMultipleTimes(x10 , kMaxPushes); + __ Subs(x3, x3, 1); + __ B(ne, &loop_header); + } + int remaining = locals_count % kMaxPushes; + // Emit the remaining pushes. + __ PushMultipleTimes(x10 , remaining); + } + } + } + + bool function_in_register_x1 = true; + + int heap_slots = info->scope()->num_heap_slots() - Context::MIN_CONTEXT_SLOTS; + if (heap_slots > 0) { + // Argument to NewContext is the function, which is still in x1. + Comment cmnt(masm_, "[ Allocate context"); + bool need_write_barrier = true; + if (FLAG_harmony_scoping && info->scope()->is_global_scope()) { + __ Mov(x10, Operand(info->scope()->GetScopeInfo())); + __ Push(x1, x10); + __ CallRuntime(Runtime::kHiddenNewGlobalContext, 2); + } else if (heap_slots <= FastNewContextStub::kMaximumSlots) { + FastNewContextStub stub(isolate(), heap_slots); + __ CallStub(&stub); + // Result of FastNewContextStub is always in new space. + need_write_barrier = false; + } else { + __ Push(x1); + __ CallRuntime(Runtime::kHiddenNewFunctionContext, 1); + } + function_in_register_x1 = false; + // Context is returned in x0. It replaces the context passed to us. + // It's saved in the stack and kept live in cp. + __ Mov(cp, x0); + __ Str(x0, MemOperand(fp, StandardFrameConstants::kContextOffset)); + // Copy any necessary parameters into the context. + int num_parameters = info->scope()->num_parameters(); + for (int i = 0; i < num_parameters; i++) { + Variable* var = scope()->parameter(i); + if (var->IsContextSlot()) { + int parameter_offset = StandardFrameConstants::kCallerSPOffset + + (num_parameters - 1 - i) * kPointerSize; + // Load parameter from stack. + __ Ldr(x10, MemOperand(fp, parameter_offset)); + // Store it in the context. + MemOperand target = ContextMemOperand(cp, var->index()); + __ Str(x10, target); + + // Update the write barrier. + if (need_write_barrier) { + __ RecordWriteContextSlot( + cp, target.offset(), x10, x11, kLRHasBeenSaved, kDontSaveFPRegs); + } else if (FLAG_debug_code) { + Label done; + __ JumpIfInNewSpace(cp, &done); + __ Abort(kExpectedNewSpaceObject); + __ bind(&done); + } + } + } + } + + Variable* arguments = scope()->arguments(); + if (arguments != NULL) { + // Function uses arguments object. + Comment cmnt(masm_, "[ Allocate arguments object"); + if (!function_in_register_x1) { + // Load this again, if it's used by the local context below. + __ Ldr(x3, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + } else { + __ Mov(x3, x1); + } + // Receiver is just before the parameters on the caller's stack. + int num_parameters = info->scope()->num_parameters(); + int offset = num_parameters * kPointerSize; + __ Add(x2, fp, StandardFrameConstants::kCallerSPOffset + offset); + __ Mov(x1, Smi::FromInt(num_parameters)); + __ Push(x3, x2, x1); + + // Arguments to ArgumentsAccessStub: + // function, receiver address, parameter count. + // The stub will rewrite receiver and parameter count if the previous + // stack frame was an arguments adapter frame. + ArgumentsAccessStub::Type type; + if (strict_mode() == STRICT) { + type = ArgumentsAccessStub::NEW_STRICT; + } else if (function()->has_duplicate_parameters()) { + type = ArgumentsAccessStub::NEW_SLOPPY_SLOW; + } else { + type = ArgumentsAccessStub::NEW_SLOPPY_FAST; + } + ArgumentsAccessStub stub(isolate(), type); + __ CallStub(&stub); + + SetVar(arguments, x0, x1, x2); + } + + if (FLAG_trace) { + __ CallRuntime(Runtime::kTraceEnter, 0); + } + + + // Visit the declarations and body unless there is an illegal + // redeclaration. + if (scope()->HasIllegalRedeclaration()) { + Comment cmnt(masm_, "[ Declarations"); + scope()->VisitIllegalRedeclaration(this); + + } else { + PrepareForBailoutForId(BailoutId::FunctionEntry(), NO_REGISTERS); + { Comment cmnt(masm_, "[ Declarations"); + if (scope()->is_function_scope() && scope()->function() != NULL) { + VariableDeclaration* function = scope()->function(); + ASSERT(function->proxy()->var()->mode() == CONST || + function->proxy()->var()->mode() == CONST_LEGACY); + ASSERT(function->proxy()->var()->location() != Variable::UNALLOCATED); + VisitVariableDeclaration(function); + } + VisitDeclarations(scope()->declarations()); + } + } + + { Comment cmnt(masm_, "[ Stack check"); + PrepareForBailoutForId(BailoutId::Declarations(), NO_REGISTERS); + Label ok; + ASSERT(jssp.Is(__ StackPointer())); + __ CompareRoot(jssp, Heap::kStackLimitRootIndex); + __ B(hs, &ok); + PredictableCodeSizeScope predictable(masm_, + Assembler::kCallSizeWithRelocation); + __ Call(isolate()->builtins()->StackCheck(), RelocInfo::CODE_TARGET); + __ Bind(&ok); + } + + { Comment cmnt(masm_, "[ Body"); + ASSERT(loop_depth() == 0); + VisitStatements(function()->body()); + ASSERT(loop_depth() == 0); + } + + // Always emit a 'return undefined' in case control fell off the end of + // the body. + { Comment cmnt(masm_, "[ return <undefined>;"); + __ LoadRoot(x0, Heap::kUndefinedValueRootIndex); + } + EmitReturnSequence(); + + // Force emission of the pools, so they don't get emitted in the middle + // of the back edge table. + masm()->CheckVeneerPool(true, false); + masm()->CheckConstPool(true, false); +} + + +void FullCodeGenerator::ClearAccumulator() { + __ Mov(x0, Smi::FromInt(0)); +} + + +void FullCodeGenerator::EmitProfilingCounterDecrement(int delta) { + __ Mov(x2, Operand(profiling_counter_)); + __ Ldr(x3, FieldMemOperand(x2, Cell::kValueOffset)); + __ Subs(x3, x3, Smi::FromInt(delta)); + __ Str(x3, FieldMemOperand(x2, Cell::kValueOffset)); +} + + +void FullCodeGenerator::EmitProfilingCounterReset() { + int reset_value = FLAG_interrupt_budget; + if (info_->is_debug()) { + // Detect debug break requests as soon as possible. + reset_value = FLAG_interrupt_budget >> 4; + } + __ Mov(x2, Operand(profiling_counter_)); + __ Mov(x3, Smi::FromInt(reset_value)); + __ Str(x3, FieldMemOperand(x2, Cell::kValueOffset)); +} + + +void FullCodeGenerator::EmitBackEdgeBookkeeping(IterationStatement* stmt, + Label* back_edge_target) { + ASSERT(jssp.Is(__ StackPointer())); + Comment cmnt(masm_, "[ Back edge bookkeeping"); + // Block literal pools whilst emitting back edge code. + Assembler::BlockPoolsScope block_const_pool(masm_); + Label ok; + + ASSERT(back_edge_target->is_bound()); + // We want to do a round rather than a floor of distance/kCodeSizeMultiplier + // to reduce the absolute error due to the integer division. To do that, + // we add kCodeSizeMultiplier/2 to the distance (equivalent to adding 0.5 to + // the result). + int distance = + masm_->SizeOfCodeGeneratedSince(back_edge_target) + kCodeSizeMultiplier / 2; + int weight = Min(kMaxBackEdgeWeight, + Max(1, distance / kCodeSizeMultiplier)); + EmitProfilingCounterDecrement(weight); + __ B(pl, &ok); + __ Call(isolate()->builtins()->InterruptCheck(), RelocInfo::CODE_TARGET); + + // Record a mapping of this PC offset to the OSR id. This is used to find + // the AST id from the unoptimized code in order to use it as a key into + // the deoptimization input data found in the optimized code. + RecordBackEdge(stmt->OsrEntryId()); + + EmitProfilingCounterReset(); + + __ Bind(&ok); + PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS); + // Record a mapping of the OSR id to this PC. This is used if the OSR + // entry becomes the target of a bailout. We don't expect it to be, but + // we want it to work if it is. + PrepareForBailoutForId(stmt->OsrEntryId(), NO_REGISTERS); +} + + +void FullCodeGenerator::EmitReturnSequence() { + Comment cmnt(masm_, "[ Return sequence"); + + if (return_label_.is_bound()) { + __ B(&return_label_); + + } else { + __ Bind(&return_label_); + if (FLAG_trace) { + // Push the return value on the stack as the parameter. + // Runtime::TraceExit returns its parameter in x0. + __ Push(result_register()); + __ CallRuntime(Runtime::kTraceExit, 1); + ASSERT(x0.Is(result_register())); + } + // Pretend that the exit is a backwards jump to the entry. + int weight = 1; + if (info_->ShouldSelfOptimize()) { + weight = FLAG_interrupt_budget / FLAG_self_opt_count; + } else { + int distance = masm_->pc_offset() + kCodeSizeMultiplier / 2; + weight = Min(kMaxBackEdgeWeight, + Max(1, distance / kCodeSizeMultiplier)); + } + EmitProfilingCounterDecrement(weight); + Label ok; + __ B(pl, &ok); + __ Push(x0); + __ Call(isolate()->builtins()->InterruptCheck(), + RelocInfo::CODE_TARGET); + __ Pop(x0); + EmitProfilingCounterReset(); + __ Bind(&ok); + + // Make sure that the constant pool is not emitted inside of the return + // sequence. This sequence can get patched when the debugger is used. See + // debug-arm64.cc:BreakLocationIterator::SetDebugBreakAtReturn(). + { + InstructionAccurateScope scope(masm_, + Assembler::kJSRetSequenceInstructions); + CodeGenerator::RecordPositions(masm_, function()->end_position() - 1); + __ RecordJSReturn(); + // This code is generated using Assembler methods rather than Macro + // Assembler methods because it will be patched later on, and so the size + // of the generated code must be consistent. + const Register& current_sp = __ StackPointer(); + // Nothing ensures 16 bytes alignment here. + ASSERT(!current_sp.Is(csp)); + __ mov(current_sp, fp); + int no_frame_start = masm_->pc_offset(); + __ ldp(fp, lr, MemOperand(current_sp, 2 * kXRegSize, PostIndex)); + // Drop the arguments and receiver and return. + // TODO(all): This implementation is overkill as it supports 2**31+1 + // arguments, consider how to improve it without creating a security + // hole. + __ ldr_pcrel(ip0, (3 * kInstructionSize) >> kLoadLiteralScaleLog2); + __ add(current_sp, current_sp, ip0); + __ ret(); + __ dc64(kXRegSize * (info_->scope()->num_parameters() + 1)); + info_->AddNoFrameRange(no_frame_start, masm_->pc_offset()); + } + } +} + + +void FullCodeGenerator::EffectContext::Plug(Variable* var) const { + ASSERT(var->IsStackAllocated() || var->IsContextSlot()); +} + + +void FullCodeGenerator::AccumulatorValueContext::Plug(Variable* var) const { + ASSERT(var->IsStackAllocated() || var->IsContextSlot()); + codegen()->GetVar(result_register(), var); +} + + +void FullCodeGenerator::StackValueContext::Plug(Variable* var) const { + ASSERT(var->IsStackAllocated() || var->IsContextSlot()); + codegen()->GetVar(result_register(), var); + __ Push(result_register()); +} + + +void FullCodeGenerator::TestContext::Plug(Variable* var) const { + ASSERT(var->IsStackAllocated() || var->IsContextSlot()); + // For simplicity we always test the accumulator register. + codegen()->GetVar(result_register(), var); + codegen()->PrepareForBailoutBeforeSplit(condition(), false, NULL, NULL); + codegen()->DoTest(this); +} + + +void FullCodeGenerator::EffectContext::Plug(Heap::RootListIndex index) const { + // Root values have no side effects. +} + + +void FullCodeGenerator::AccumulatorValueContext::Plug( + Heap::RootListIndex index) const { + __ LoadRoot(result_register(), index); +} + + +void FullCodeGenerator::StackValueContext::Plug( + Heap::RootListIndex index) const { + __ LoadRoot(result_register(), index); + __ Push(result_register()); +} + + +void FullCodeGenerator::TestContext::Plug(Heap::RootListIndex index) const { + codegen()->PrepareForBailoutBeforeSplit(condition(), true, true_label_, + false_label_); + if (index == Heap::kUndefinedValueRootIndex || + index == Heap::kNullValueRootIndex || + index == Heap::kFalseValueRootIndex) { + if (false_label_ != fall_through_) __ B(false_label_); + } else if (index == Heap::kTrueValueRootIndex) { + if (true_label_ != fall_through_) __ B(true_label_); + } else { + __ LoadRoot(result_register(), index); + codegen()->DoTest(this); + } +} + + +void FullCodeGenerator::EffectContext::Plug(Handle<Object> lit) const { +} + + +void FullCodeGenerator::AccumulatorValueContext::Plug( + Handle<Object> lit) const { + __ Mov(result_register(), Operand(lit)); +} + + +void FullCodeGenerator::StackValueContext::Plug(Handle<Object> lit) const { + // Immediates cannot be pushed directly. + __ Mov(result_register(), Operand(lit)); + __ Push(result_register()); +} + + +void FullCodeGenerator::TestContext::Plug(Handle<Object> lit) const { + codegen()->PrepareForBailoutBeforeSplit(condition(), + true, + true_label_, + false_label_); + ASSERT(!lit->IsUndetectableObject()); // There are no undetectable literals. + if (lit->IsUndefined() || lit->IsNull() || lit->IsFalse()) { + if (false_label_ != fall_through_) __ B(false_label_); + } else if (lit->IsTrue() || lit->IsJSObject()) { + if (true_label_ != fall_through_) __ B(true_label_); + } else if (lit->IsString()) { + if (String::cast(*lit)->length() == 0) { + if (false_label_ != fall_through_) __ B(false_label_); + } else { + if (true_label_ != fall_through_) __ B(true_label_); + } + } else if (lit->IsSmi()) { + if (Smi::cast(*lit)->value() == 0) { + if (false_label_ != fall_through_) __ B(false_label_); + } else { + if (true_label_ != fall_through_) __ B(true_label_); + } + } else { + // For simplicity we always test the accumulator register. + __ Mov(result_register(), Operand(lit)); + codegen()->DoTest(this); + } +} + + +void FullCodeGenerator::EffectContext::DropAndPlug(int count, + Register reg) const { + ASSERT(count > 0); + __ Drop(count); +} + + +void FullCodeGenerator::AccumulatorValueContext::DropAndPlug( + int count, + Register reg) const { + ASSERT(count > 0); + __ Drop(count); + __ Move(result_register(), reg); +} + + +void FullCodeGenerator::StackValueContext::DropAndPlug(int count, + Register reg) const { + ASSERT(count > 0); + if (count > 1) __ Drop(count - 1); + __ Poke(reg, 0); +} + + +void FullCodeGenerator::TestContext::DropAndPlug(int count, + Register reg) const { + ASSERT(count > 0); + // For simplicity we always test the accumulator register. + __ Drop(count); + __ Mov(result_register(), reg); + codegen()->PrepareForBailoutBeforeSplit(condition(), false, NULL, NULL); + codegen()->DoTest(this); +} + + +void FullCodeGenerator::EffectContext::Plug(Label* materialize_true, + Label* materialize_false) const { + ASSERT(materialize_true == materialize_false); + __ Bind(materialize_true); +} + + +void FullCodeGenerator::AccumulatorValueContext::Plug( + Label* materialize_true, + Label* materialize_false) const { + Label done; + __ Bind(materialize_true); + __ LoadRoot(result_register(), Heap::kTrueValueRootIndex); + __ B(&done); + __ Bind(materialize_false); + __ LoadRoot(result_register(), Heap::kFalseValueRootIndex); + __ Bind(&done); +} + + +void FullCodeGenerator::StackValueContext::Plug( + Label* materialize_true, + Label* materialize_false) const { + Label done; + __ Bind(materialize_true); + __ LoadRoot(x10, Heap::kTrueValueRootIndex); + __ B(&done); + __ Bind(materialize_false); + __ LoadRoot(x10, Heap::kFalseValueRootIndex); + __ Bind(&done); + __ Push(x10); +} + + +void FullCodeGenerator::TestContext::Plug(Label* materialize_true, + Label* materialize_false) const { + ASSERT(materialize_true == true_label_); + ASSERT(materialize_false == false_label_); +} + + +void FullCodeGenerator::EffectContext::Plug(bool flag) const { +} + + +void FullCodeGenerator::AccumulatorValueContext::Plug(bool flag) const { + Heap::RootListIndex value_root_index = + flag ? Heap::kTrueValueRootIndex : Heap::kFalseValueRootIndex; + __ LoadRoot(result_register(), value_root_index); +} + + +void FullCodeGenerator::StackValueContext::Plug(bool flag) const { + Heap::RootListIndex value_root_index = + flag ? Heap::kTrueValueRootIndex : Heap::kFalseValueRootIndex; + __ LoadRoot(x10, value_root_index); + __ Push(x10); +} + + +void FullCodeGenerator::TestContext::Plug(bool flag) const { + codegen()->PrepareForBailoutBeforeSplit(condition(), + true, + true_label_, + false_label_); + if (flag) { + if (true_label_ != fall_through_) { + __ B(true_label_); + } + } else { + if (false_label_ != fall_through_) { + __ B(false_label_); + } + } +} + + +void FullCodeGenerator::DoTest(Expression* condition, + Label* if_true, + Label* if_false, + Label* fall_through) { + Handle<Code> ic = ToBooleanStub::GetUninitialized(isolate()); + CallIC(ic, condition->test_id()); + __ CompareAndSplit(result_register(), 0, ne, if_true, if_false, fall_through); +} + + +// If (cond), branch to if_true. +// If (!cond), branch to if_false. +// fall_through is used as an optimization in cases where only one branch +// instruction is necessary. +void FullCodeGenerator::Split(Condition cond, + Label* if_true, + Label* if_false, + Label* fall_through) { + if (if_false == fall_through) { + __ B(cond, if_true); + } else if (if_true == fall_through) { + ASSERT(if_false != fall_through); + __ B(NegateCondition(cond), if_false); + } else { + __ B(cond, if_true); + __ B(if_false); + } +} + + +MemOperand FullCodeGenerator::StackOperand(Variable* var) { + // Offset is negative because higher indexes are at lower addresses. + int offset = -var->index() * kXRegSize; + // Adjust by a (parameter or local) base offset. + if (var->IsParameter()) { + offset += (info_->scope()->num_parameters() + 1) * kPointerSize; + } else { + offset += JavaScriptFrameConstants::kLocal0Offset; + } + return MemOperand(fp, offset); +} + + +MemOperand FullCodeGenerator::VarOperand(Variable* var, Register scratch) { + ASSERT(var->IsContextSlot() || var->IsStackAllocated()); + if (var->IsContextSlot()) { + int context_chain_length = scope()->ContextChainLength(var->scope()); + __ LoadContext(scratch, context_chain_length); + return ContextMemOperand(scratch, var->index()); + } else { + return StackOperand(var); + } +} + + +void FullCodeGenerator::GetVar(Register dest, Variable* var) { + // Use destination as scratch. + MemOperand location = VarOperand(var, dest); + __ Ldr(dest, location); +} + + +void FullCodeGenerator::SetVar(Variable* var, + Register src, + Register scratch0, + Register scratch1) { + ASSERT(var->IsContextSlot() || var->IsStackAllocated()); + ASSERT(!AreAliased(src, scratch0, scratch1)); + MemOperand location = VarOperand(var, scratch0); + __ Str(src, location); + + // Emit the write barrier code if the location is in the heap. + if (var->IsContextSlot()) { + // scratch0 contains the correct context. + __ RecordWriteContextSlot(scratch0, + location.offset(), + src, + scratch1, + kLRHasBeenSaved, + kDontSaveFPRegs); + } +} + + +void FullCodeGenerator::PrepareForBailoutBeforeSplit(Expression* expr, + bool should_normalize, + Label* if_true, + Label* if_false) { + // Only prepare for bailouts before splits if we're in a test + // context. Otherwise, we let the Visit function deal with the + // preparation to avoid preparing with the same AST id twice. + if (!context()->IsTest() || !info_->IsOptimizable()) return; + + // TODO(all): Investigate to see if there is something to work on here. + Label skip; + if (should_normalize) { + __ B(&skip); + } + PrepareForBailout(expr, TOS_REG); + if (should_normalize) { + __ CompareRoot(x0, Heap::kTrueValueRootIndex); + Split(eq, if_true, if_false, NULL); + __ Bind(&skip); + } +} + + +void FullCodeGenerator::EmitDebugCheckDeclarationContext(Variable* variable) { + // The variable in the declaration always resides in the current function + // context. + ASSERT_EQ(0, scope()->ContextChainLength(variable->scope())); + if (generate_debug_code_) { + // Check that we're not inside a with or catch context. + __ Ldr(x1, FieldMemOperand(cp, HeapObject::kMapOffset)); + __ CompareRoot(x1, Heap::kWithContextMapRootIndex); + __ Check(ne, kDeclarationInWithContext); + __ CompareRoot(x1, Heap::kCatchContextMapRootIndex); + __ Check(ne, kDeclarationInCatchContext); + } +} + + +void FullCodeGenerator::VisitVariableDeclaration( + VariableDeclaration* declaration) { + // If it was not possible to allocate the variable at compile time, we + // need to "declare" it at runtime to make sure it actually exists in the + // local context. + VariableProxy* proxy = declaration->proxy(); + VariableMode mode = declaration->mode(); + Variable* variable = proxy->var(); + bool hole_init = mode == LET || mode == CONST || mode == CONST_LEGACY; + + switch (variable->location()) { + case Variable::UNALLOCATED: + globals_->Add(variable->name(), zone()); + globals_->Add(variable->binding_needs_init() + ? isolate()->factory()->the_hole_value() + : isolate()->factory()->undefined_value(), + zone()); + break; + + case Variable::PARAMETER: + case Variable::LOCAL: + if (hole_init) { + Comment cmnt(masm_, "[ VariableDeclaration"); + __ LoadRoot(x10, Heap::kTheHoleValueRootIndex); + __ Str(x10, StackOperand(variable)); + } + break; + + case Variable::CONTEXT: + if (hole_init) { + Comment cmnt(masm_, "[ VariableDeclaration"); + EmitDebugCheckDeclarationContext(variable); + __ LoadRoot(x10, Heap::kTheHoleValueRootIndex); + __ Str(x10, ContextMemOperand(cp, variable->index())); + // No write barrier since the_hole_value is in old space. + PrepareForBailoutForId(proxy->id(), NO_REGISTERS); + } + break; + + case Variable::LOOKUP: { + Comment cmnt(masm_, "[ VariableDeclaration"); + __ Mov(x2, Operand(variable->name())); + // Declaration nodes are always introduced in one of four modes. + ASSERT(IsDeclaredVariableMode(mode)); + PropertyAttributes attr = IsImmutableVariableMode(mode) ? READ_ONLY + : NONE; + __ Mov(x1, Smi::FromInt(attr)); + // Push initial value, if any. + // Note: For variables we must not push an initial value (such as + // 'undefined') because we may have a (legal) redeclaration and we + // must not destroy the current value. + if (hole_init) { + __ LoadRoot(x0, Heap::kTheHoleValueRootIndex); + __ Push(cp, x2, x1, x0); + } else { + // Pushing 0 (xzr) indicates no initial value. + __ Push(cp, x2, x1, xzr); + } + __ CallRuntime(Runtime::kHiddenDeclareContextSlot, 4); + break; + } + } +} + + +void FullCodeGenerator::VisitFunctionDeclaration( + FunctionDeclaration* declaration) { + VariableProxy* proxy = declaration->proxy(); + Variable* variable = proxy->var(); + switch (variable->location()) { + case Variable::UNALLOCATED: { + globals_->Add(variable->name(), zone()); + Handle<SharedFunctionInfo> function = + Compiler::BuildFunctionInfo(declaration->fun(), script()); + // Check for stack overflow exception. + if (function.is_null()) return SetStackOverflow(); + globals_->Add(function, zone()); + break; + } + + case Variable::PARAMETER: + case Variable::LOCAL: { + Comment cmnt(masm_, "[ Function Declaration"); + VisitForAccumulatorValue(declaration->fun()); + __ Str(result_register(), StackOperand(variable)); + break; + } + + case Variable::CONTEXT: { + Comment cmnt(masm_, "[ Function Declaration"); + EmitDebugCheckDeclarationContext(variable); + VisitForAccumulatorValue(declaration->fun()); + __ Str(result_register(), ContextMemOperand(cp, variable->index())); + int offset = Context::SlotOffset(variable->index()); + // We know that we have written a function, which is not a smi. + __ RecordWriteContextSlot(cp, + offset, + result_register(), + x2, + kLRHasBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); + PrepareForBailoutForId(proxy->id(), NO_REGISTERS); + break; + } + + case Variable::LOOKUP: { + Comment cmnt(masm_, "[ Function Declaration"); + __ Mov(x2, Operand(variable->name())); + __ Mov(x1, Smi::FromInt(NONE)); + __ Push(cp, x2, x1); + // Push initial value for function declaration. + VisitForStackValue(declaration->fun()); + __ CallRuntime(Runtime::kHiddenDeclareContextSlot, 4); + break; + } + } +} + + +void FullCodeGenerator::VisitModuleDeclaration(ModuleDeclaration* declaration) { + Variable* variable = declaration->proxy()->var(); + ASSERT(variable->location() == Variable::CONTEXT); + ASSERT(variable->interface()->IsFrozen()); + + Comment cmnt(masm_, "[ ModuleDeclaration"); + EmitDebugCheckDeclarationContext(variable); + + // Load instance object. + __ LoadContext(x1, scope_->ContextChainLength(scope_->GlobalScope())); + __ Ldr(x1, ContextMemOperand(x1, variable->interface()->Index())); + __ Ldr(x1, ContextMemOperand(x1, Context::EXTENSION_INDEX)); + + // Assign it. + __ Str(x1, ContextMemOperand(cp, variable->index())); + // We know that we have written a module, which is not a smi. + __ RecordWriteContextSlot(cp, + Context::SlotOffset(variable->index()), + x1, + x3, + kLRHasBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); + PrepareForBailoutForId(declaration->proxy()->id(), NO_REGISTERS); + + // Traverse info body. + Visit(declaration->module()); +} + + +void FullCodeGenerator::VisitImportDeclaration(ImportDeclaration* declaration) { + VariableProxy* proxy = declaration->proxy(); + Variable* variable = proxy->var(); + switch (variable->location()) { + case Variable::UNALLOCATED: + // TODO(rossberg) + break; + + case Variable::CONTEXT: { + Comment cmnt(masm_, "[ ImportDeclaration"); + EmitDebugCheckDeclarationContext(variable); + // TODO(rossberg) + break; + } + + case Variable::PARAMETER: + case Variable::LOCAL: + case Variable::LOOKUP: + UNREACHABLE(); + } +} + + +void FullCodeGenerator::VisitExportDeclaration(ExportDeclaration* declaration) { + // TODO(rossberg) +} + + +void FullCodeGenerator::DeclareGlobals(Handle<FixedArray> pairs) { + // Call the runtime to declare the globals. + __ Mov(x11, Operand(pairs)); + Register flags = xzr; + if (Smi::FromInt(DeclareGlobalsFlags())) { + flags = x10; + __ Mov(flags, Smi::FromInt(DeclareGlobalsFlags())); + } + __ Push(cp, x11, flags); + __ CallRuntime(Runtime::kHiddenDeclareGlobals, 3); + // Return value is ignored. +} + + +void FullCodeGenerator::DeclareModules(Handle<FixedArray> descriptions) { + // Call the runtime to declare the modules. + __ Push(descriptions); + __ CallRuntime(Runtime::kHiddenDeclareModules, 1); + // Return value is ignored. +} + + +void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) { + ASM_LOCATION("FullCodeGenerator::VisitSwitchStatement"); + Comment cmnt(masm_, "[ SwitchStatement"); + Breakable nested_statement(this, stmt); + SetStatementPosition(stmt); + + // Keep the switch value on the stack until a case matches. + VisitForStackValue(stmt->tag()); + PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS); + + ZoneList<CaseClause*>* clauses = stmt->cases(); + CaseClause* default_clause = NULL; // Can occur anywhere in the list. + + Label next_test; // Recycled for each test. + // Compile all the tests with branches to their bodies. + for (int i = 0; i < clauses->length(); i++) { + CaseClause* clause = clauses->at(i); + clause->body_target()->Unuse(); + + // The default is not a test, but remember it as final fall through. + if (clause->is_default()) { + default_clause = clause; + continue; + } + + Comment cmnt(masm_, "[ Case comparison"); + __ Bind(&next_test); + next_test.Unuse(); + + // Compile the label expression. + VisitForAccumulatorValue(clause->label()); + + // Perform the comparison as if via '==='. + __ Peek(x1, 0); // Switch value. + + JumpPatchSite patch_site(masm_); + if (ShouldInlineSmiCase(Token::EQ_STRICT)) { + Label slow_case; + patch_site.EmitJumpIfEitherNotSmi(x0, x1, &slow_case); + __ Cmp(x1, x0); + __ B(ne, &next_test); + __ Drop(1); // Switch value is no longer needed. + __ B(clause->body_target()); + __ Bind(&slow_case); + } + + // Record position before stub call for type feedback. + SetSourcePosition(clause->position()); + Handle<Code> ic = CompareIC::GetUninitialized(isolate(), Token::EQ_STRICT); + CallIC(ic, clause->CompareId()); + patch_site.EmitPatchInfo(); + + Label skip; + __ B(&skip); + PrepareForBailout(clause, TOS_REG); + __ JumpIfNotRoot(x0, Heap::kTrueValueRootIndex, &next_test); + __ Drop(1); + __ B(clause->body_target()); + __ Bind(&skip); + + __ Cbnz(x0, &next_test); + __ Drop(1); // Switch value is no longer needed. + __ B(clause->body_target()); + } + + // Discard the test value and jump to the default if present, otherwise to + // the end of the statement. + __ Bind(&next_test); + __ Drop(1); // Switch value is no longer needed. + if (default_clause == NULL) { + __ B(nested_statement.break_label()); + } else { + __ B(default_clause->body_target()); + } + + // Compile all the case bodies. + for (int i = 0; i < clauses->length(); i++) { + Comment cmnt(masm_, "[ Case body"); + CaseClause* clause = clauses->at(i); + __ Bind(clause->body_target()); + PrepareForBailoutForId(clause->EntryId(), NO_REGISTERS); + VisitStatements(clause->statements()); + } + + __ Bind(nested_statement.break_label()); + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); +} + + +void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { + ASM_LOCATION("FullCodeGenerator::VisitForInStatement"); + Comment cmnt(masm_, "[ ForInStatement"); + int slot = stmt->ForInFeedbackSlot(); + // TODO(all): This visitor probably needs better comments and a revisit. + SetStatementPosition(stmt); + + Label loop, exit; + ForIn loop_statement(this, stmt); + increment_loop_depth(); + + // Get the object to enumerate over. If the object is null or undefined, skip + // over the loop. See ECMA-262 version 5, section 12.6.4. + VisitForAccumulatorValue(stmt->enumerable()); + __ JumpIfRoot(x0, Heap::kUndefinedValueRootIndex, &exit); + Register null_value = x15; + __ LoadRoot(null_value, Heap::kNullValueRootIndex); + __ Cmp(x0, null_value); + __ B(eq, &exit); + + PrepareForBailoutForId(stmt->PrepareId(), TOS_REG); + + // Convert the object to a JS object. + Label convert, done_convert; + __ JumpIfSmi(x0, &convert); + __ JumpIfObjectType(x0, x10, x11, FIRST_SPEC_OBJECT_TYPE, &done_convert, ge); + __ Bind(&convert); + __ Push(x0); + __ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); + __ Bind(&done_convert); + __ Push(x0); + + // Check for proxies. + Label call_runtime; + STATIC_ASSERT(FIRST_JS_PROXY_TYPE == FIRST_SPEC_OBJECT_TYPE); + __ JumpIfObjectType(x0, x10, x11, LAST_JS_PROXY_TYPE, &call_runtime, le); + + // Check cache validity in generated code. This is a fast case for + // the JSObject::IsSimpleEnum cache validity checks. If we cannot + // guarantee cache validity, call the runtime system to check cache + // validity or get the property names in a fixed array. + __ CheckEnumCache(x0, null_value, x10, x11, x12, x13, &call_runtime); + + // The enum cache is valid. Load the map of the object being + // iterated over and use the cache for the iteration. + Label use_cache; + __ Ldr(x0, FieldMemOperand(x0, HeapObject::kMapOffset)); + __ B(&use_cache); + + // Get the set of properties to enumerate. + __ Bind(&call_runtime); + __ Push(x0); // Duplicate the enumerable object on the stack. + __ CallRuntime(Runtime::kGetPropertyNamesFast, 1); + + // If we got a map from the runtime call, we can do a fast + // modification check. Otherwise, we got a fixed array, and we have + // to do a slow check. + Label fixed_array, no_descriptors; + __ Ldr(x2, FieldMemOperand(x0, HeapObject::kMapOffset)); + __ JumpIfNotRoot(x2, Heap::kMetaMapRootIndex, &fixed_array); + + // We got a map in register x0. Get the enumeration cache from it. + __ Bind(&use_cache); + + __ EnumLengthUntagged(x1, x0); + __ Cbz(x1, &no_descriptors); + + __ LoadInstanceDescriptors(x0, x2); + __ Ldr(x2, FieldMemOperand(x2, DescriptorArray::kEnumCacheOffset)); + __ Ldr(x2, + FieldMemOperand(x2, DescriptorArray::kEnumCacheBridgeCacheOffset)); + + // Set up the four remaining stack slots. + __ Push(x0, x2); // Map, enumeration cache. + __ SmiTagAndPush(x1, xzr); // Enum cache length, zero (both as smis). + __ B(&loop); + + __ Bind(&no_descriptors); + __ Drop(1); + __ B(&exit); + + // We got a fixed array in register x0. Iterate through that. + __ Bind(&fixed_array); + + __ LoadObject(x1, FeedbackVector()); + __ Mov(x10, Operand(TypeFeedbackInfo::MegamorphicSentinel(isolate()))); + __ Str(x10, FieldMemOperand(x1, FixedArray::OffsetOfElementAt(slot))); + + __ Mov(x1, Smi::FromInt(1)); // Smi indicates slow check. + __ Peek(x10, 0); // Get enumerated object. + STATIC_ASSERT(FIRST_JS_PROXY_TYPE == FIRST_SPEC_OBJECT_TYPE); + // TODO(all): similar check was done already. Can we avoid it here? + __ CompareObjectType(x10, x11, x12, LAST_JS_PROXY_TYPE); + ASSERT(Smi::FromInt(0) == 0); + __ CzeroX(x1, le); // Zero indicates proxy. + __ Push(x1, x0); // Smi and array + __ Ldr(x1, FieldMemOperand(x0, FixedArray::kLengthOffset)); + __ Push(x1, xzr); // Fixed array length (as smi) and initial index. + + // Generate code for doing the condition check. + PrepareForBailoutForId(stmt->BodyId(), NO_REGISTERS); + __ Bind(&loop); + // Load the current count to x0, load the length to x1. + __ PeekPair(x0, x1, 0); + __ Cmp(x0, x1); // Compare to the array length. + __ B(hs, loop_statement.break_label()); + + // Get the current entry of the array into register r3. + __ Peek(x10, 2 * kXRegSize); + __ Add(x10, x10, Operand::UntagSmiAndScale(x0, kPointerSizeLog2)); + __ Ldr(x3, MemOperand(x10, FixedArray::kHeaderSize - kHeapObjectTag)); + + // Get the expected map from the stack or a smi in the + // permanent slow case into register x10. + __ Peek(x2, 3 * kXRegSize); + + // Check if the expected map still matches that of the enumerable. + // If not, we may have to filter the key. + Label update_each; + __ Peek(x1, 4 * kXRegSize); + __ Ldr(x11, FieldMemOperand(x1, HeapObject::kMapOffset)); + __ Cmp(x11, x2); + __ B(eq, &update_each); + + // For proxies, no filtering is done. + // TODO(rossberg): What if only a prototype is a proxy? Not specified yet. + STATIC_ASSERT(kSmiTag == 0); + __ Cbz(x2, &update_each); + + // Convert the entry to a string or (smi) 0 if it isn't a property + // any more. If the property has been removed while iterating, we + // just skip it. + __ Push(x1, x3); + __ InvokeBuiltin(Builtins::FILTER_KEY, CALL_FUNCTION); + __ Mov(x3, x0); + __ Cbz(x0, loop_statement.continue_label()); + + // Update the 'each' property or variable from the possibly filtered + // entry in register x3. + __ Bind(&update_each); + __ Mov(result_register(), x3); + // Perform the assignment as if via '='. + { EffectContext context(this); + EmitAssignment(stmt->each()); + } + + // Generate code for the body of the loop. + Visit(stmt->body()); + + // Generate code for going to the next element by incrementing + // the index (smi) stored on top of the stack. + __ Bind(loop_statement.continue_label()); + // TODO(all): We could use a callee saved register to avoid popping. + __ Pop(x0); + __ Add(x0, x0, Smi::FromInt(1)); + __ Push(x0); + + EmitBackEdgeBookkeeping(stmt, &loop); + __ B(&loop); + + // Remove the pointers stored on the stack. + __ Bind(loop_statement.break_label()); + __ Drop(5); + + // Exit and decrement the loop depth. + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); + __ Bind(&exit); + decrement_loop_depth(); +} + + +void FullCodeGenerator::VisitForOfStatement(ForOfStatement* stmt) { + Comment cmnt(masm_, "[ ForOfStatement"); + SetStatementPosition(stmt); + + Iteration loop_statement(this, stmt); + increment_loop_depth(); + + // var iterable = subject + VisitForAccumulatorValue(stmt->assign_iterable()); + + // As with for-in, skip the loop if the iterator is null or undefined. + Register iterator = x0; + __ JumpIfRoot(iterator, Heap::kUndefinedValueRootIndex, + loop_statement.break_label()); + __ JumpIfRoot(iterator, Heap::kNullValueRootIndex, + loop_statement.break_label()); + + // var iterator = iterable[Symbol.iterator](); + VisitForEffect(stmt->assign_iterator()); + + // Loop entry. + __ Bind(loop_statement.continue_label()); + + // result = iterator.next() + VisitForEffect(stmt->next_result()); + + // if (result.done) break; + Label result_not_done; + VisitForControl(stmt->result_done(), + loop_statement.break_label(), + &result_not_done, + &result_not_done); + __ Bind(&result_not_done); + + // each = result.value + VisitForEffect(stmt->assign_each()); + + // Generate code for the body of the loop. + Visit(stmt->body()); + + // Check stack before looping. + PrepareForBailoutForId(stmt->BackEdgeId(), NO_REGISTERS); + EmitBackEdgeBookkeeping(stmt, loop_statement.continue_label()); + __ B(loop_statement.continue_label()); + + // Exit and decrement the loop depth. + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); + __ Bind(loop_statement.break_label()); + decrement_loop_depth(); +} + + +void FullCodeGenerator::EmitNewClosure(Handle<SharedFunctionInfo> info, + bool pretenure) { + // Use the fast case closure allocation code that allocates in new space for + // nested functions that don't need literals cloning. If we're running with + // the --always-opt or the --prepare-always-opt flag, we need to use the + // runtime function so that the new function we are creating here gets a + // chance to have its code optimized and doesn't just get a copy of the + // existing unoptimized code. + if (!FLAG_always_opt && + !FLAG_prepare_always_opt && + !pretenure && + scope()->is_function_scope() && + info->num_literals() == 0) { + FastNewClosureStub stub(isolate(), + info->strict_mode(), + info->is_generator()); + __ Mov(x2, Operand(info)); + __ CallStub(&stub); + } else { + __ Mov(x11, Operand(info)); + __ LoadRoot(x10, pretenure ? Heap::kTrueValueRootIndex + : Heap::kFalseValueRootIndex); + __ Push(cp, x11, x10); + __ CallRuntime(Runtime::kHiddenNewClosure, 3); + } + context()->Plug(x0); +} + + +void FullCodeGenerator::VisitVariableProxy(VariableProxy* expr) { + Comment cmnt(masm_, "[ VariableProxy"); + EmitVariableLoad(expr); +} + + +void FullCodeGenerator::EmitLoadGlobalCheckExtensions(Variable* var, + TypeofState typeof_state, + Label* slow) { + Register current = cp; + Register next = x10; + Register temp = x11; + + Scope* s = scope(); + while (s != NULL) { + if (s->num_heap_slots() > 0) { + if (s->calls_sloppy_eval()) { + // Check that extension is NULL. + __ Ldr(temp, ContextMemOperand(current, Context::EXTENSION_INDEX)); + __ Cbnz(temp, slow); + } + // Load next context in chain. + __ Ldr(next, ContextMemOperand(current, Context::PREVIOUS_INDEX)); + // Walk the rest of the chain without clobbering cp. + current = next; + } + // If no outer scope calls eval, we do not need to check more + // context extensions. + if (!s->outer_scope_calls_sloppy_eval() || s->is_eval_scope()) break; + s = s->outer_scope(); + } + + if (s->is_eval_scope()) { + Label loop, fast; + __ Mov(next, current); + + __ Bind(&loop); + // Terminate at native context. + __ Ldr(temp, FieldMemOperand(next, HeapObject::kMapOffset)); + __ JumpIfRoot(temp, Heap::kNativeContextMapRootIndex, &fast); + // Check that extension is NULL. + __ Ldr(temp, ContextMemOperand(next, Context::EXTENSION_INDEX)); + __ Cbnz(temp, slow); + // Load next context in chain. + __ Ldr(next, ContextMemOperand(next, Context::PREVIOUS_INDEX)); + __ B(&loop); + __ Bind(&fast); + } + + __ Ldr(x0, GlobalObjectMemOperand()); + __ Mov(x2, Operand(var->name())); + ContextualMode mode = (typeof_state == INSIDE_TYPEOF) ? NOT_CONTEXTUAL + : CONTEXTUAL; + CallLoadIC(mode); +} + + +MemOperand FullCodeGenerator::ContextSlotOperandCheckExtensions(Variable* var, + Label* slow) { + ASSERT(var->IsContextSlot()); + Register context = cp; + Register next = x10; + Register temp = x11; + + for (Scope* s = scope(); s != var->scope(); s = s->outer_scope()) { + if (s->num_heap_slots() > 0) { + if (s->calls_sloppy_eval()) { + // Check that extension is NULL. + __ Ldr(temp, ContextMemOperand(context, Context::EXTENSION_INDEX)); + __ Cbnz(temp, slow); + } + __ Ldr(next, ContextMemOperand(context, Context::PREVIOUS_INDEX)); + // Walk the rest of the chain without clobbering cp. + context = next; + } + } + // Check that last extension is NULL. + __ Ldr(temp, ContextMemOperand(context, Context::EXTENSION_INDEX)); + __ Cbnz(temp, slow); + + // This function is used only for loads, not stores, so it's safe to + // return an cp-based operand (the write barrier cannot be allowed to + // destroy the cp register). + return ContextMemOperand(context, var->index()); +} + + +void FullCodeGenerator::EmitDynamicLookupFastCase(Variable* var, + TypeofState typeof_state, + Label* slow, + Label* done) { + // Generate fast-case code for variables that might be shadowed by + // eval-introduced variables. Eval is used a lot without + // introducing variables. In those cases, we do not want to + // perform a runtime call for all variables in the scope + // containing the eval. + if (var->mode() == DYNAMIC_GLOBAL) { + EmitLoadGlobalCheckExtensions(var, typeof_state, slow); + __ B(done); + } else if (var->mode() == DYNAMIC_LOCAL) { + Variable* local = var->local_if_not_shadowed(); + __ Ldr(x0, ContextSlotOperandCheckExtensions(local, slow)); + if (local->mode() == LET || local->mode() == CONST || + local->mode() == CONST_LEGACY) { + __ JumpIfNotRoot(x0, Heap::kTheHoleValueRootIndex, done); + if (local->mode() == CONST_LEGACY) { + __ LoadRoot(x0, Heap::kUndefinedValueRootIndex); + } else { // LET || CONST + __ Mov(x0, Operand(var->name())); + __ Push(x0); + __ CallRuntime(Runtime::kHiddenThrowReferenceError, 1); + } + } + __ B(done); + } +} + + +void FullCodeGenerator::EmitVariableLoad(VariableProxy* proxy) { + // Record position before possible IC call. + SetSourcePosition(proxy->position()); + Variable* var = proxy->var(); + + // Three cases: global variables, lookup variables, and all other types of + // variables. + switch (var->location()) { + case Variable::UNALLOCATED: { + Comment cmnt(masm_, "Global variable"); + // Use inline caching. Variable name is passed in x2 and the global + // object (receiver) in x0. + __ Ldr(x0, GlobalObjectMemOperand()); + __ Mov(x2, Operand(var->name())); + CallLoadIC(CONTEXTUAL); + context()->Plug(x0); + break; + } + + case Variable::PARAMETER: + case Variable::LOCAL: + case Variable::CONTEXT: { + Comment cmnt(masm_, var->IsContextSlot() + ? "Context variable" + : "Stack variable"); + if (var->binding_needs_init()) { + // var->scope() may be NULL when the proxy is located in eval code and + // refers to a potential outside binding. Currently those bindings are + // always looked up dynamically, i.e. in that case + // var->location() == LOOKUP. + // always holds. + ASSERT(var->scope() != NULL); + + // Check if the binding really needs an initialization check. The check + // can be skipped in the following situation: we have a LET or CONST + // binding in harmony mode, both the Variable and the VariableProxy have + // the same declaration scope (i.e. they are both in global code, in the + // same function or in the same eval code) and the VariableProxy is in + // the source physically located after the initializer of the variable. + // + // We cannot skip any initialization checks for CONST in non-harmony + // mode because const variables may be declared but never initialized: + // if (false) { const x; }; var y = x; + // + // The condition on the declaration scopes is a conservative check for + // nested functions that access a binding and are called before the + // binding is initialized: + // function() { f(); let x = 1; function f() { x = 2; } } + // + bool skip_init_check; + if (var->scope()->DeclarationScope() != scope()->DeclarationScope()) { + skip_init_check = false; + } else { + // Check that we always have valid source position. + ASSERT(var->initializer_position() != RelocInfo::kNoPosition); + ASSERT(proxy->position() != RelocInfo::kNoPosition); + skip_init_check = var->mode() != CONST_LEGACY && + var->initializer_position() < proxy->position(); + } + + if (!skip_init_check) { + // Let and const need a read barrier. + GetVar(x0, var); + Label done; + __ JumpIfNotRoot(x0, Heap::kTheHoleValueRootIndex, &done); + if (var->mode() == LET || var->mode() == CONST) { + // Throw a reference error when using an uninitialized let/const + // binding in harmony mode. + __ Mov(x0, Operand(var->name())); + __ Push(x0); + __ CallRuntime(Runtime::kHiddenThrowReferenceError, 1); + __ Bind(&done); + } else { + // Uninitalized const bindings outside of harmony mode are unholed. + ASSERT(var->mode() == CONST_LEGACY); + __ LoadRoot(x0, Heap::kUndefinedValueRootIndex); + __ Bind(&done); + } + context()->Plug(x0); + break; + } + } + context()->Plug(var); + break; + } + + case Variable::LOOKUP: { + Label done, slow; + // Generate code for loading from variables potentially shadowed by + // eval-introduced variables. + EmitDynamicLookupFastCase(var, NOT_INSIDE_TYPEOF, &slow, &done); + __ Bind(&slow); + Comment cmnt(masm_, "Lookup variable"); + __ Mov(x1, Operand(var->name())); + __ Push(cp, x1); // Context and name. + __ CallRuntime(Runtime::kHiddenLoadContextSlot, 2); + __ Bind(&done); + context()->Plug(x0); + break; + } + } +} + + +void FullCodeGenerator::VisitRegExpLiteral(RegExpLiteral* expr) { + Comment cmnt(masm_, "[ RegExpLiteral"); + Label materialized; + // Registers will be used as follows: + // x5 = materialized value (RegExp literal) + // x4 = JS function, literals array + // x3 = literal index + // x2 = RegExp pattern + // x1 = RegExp flags + // x0 = RegExp literal clone + __ Ldr(x10, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ Ldr(x4, FieldMemOperand(x10, JSFunction::kLiteralsOffset)); + int literal_offset = + FixedArray::kHeaderSize + expr->literal_index() * kPointerSize; + __ Ldr(x5, FieldMemOperand(x4, literal_offset)); + __ JumpIfNotRoot(x5, Heap::kUndefinedValueRootIndex, &materialized); + + // Create regexp literal using runtime function. + // Result will be in x0. + __ Mov(x3, Smi::FromInt(expr->literal_index())); + __ Mov(x2, Operand(expr->pattern())); + __ Mov(x1, Operand(expr->flags())); + __ Push(x4, x3, x2, x1); + __ CallRuntime(Runtime::kHiddenMaterializeRegExpLiteral, 4); + __ Mov(x5, x0); + + __ Bind(&materialized); + int size = JSRegExp::kSize + JSRegExp::kInObjectFieldCount * kPointerSize; + Label allocated, runtime_allocate; + __ Allocate(size, x0, x2, x3, &runtime_allocate, TAG_OBJECT); + __ B(&allocated); + + __ Bind(&runtime_allocate); + __ Mov(x10, Smi::FromInt(size)); + __ Push(x5, x10); + __ CallRuntime(Runtime::kHiddenAllocateInNewSpace, 1); + __ Pop(x5); + + __ Bind(&allocated); + // After this, registers are used as follows: + // x0: Newly allocated regexp. + // x5: Materialized regexp. + // x10, x11, x12: temps. + __ CopyFields(x0, x5, CPURegList(x10, x11, x12), size / kPointerSize); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitAccessor(Expression* expression) { + if (expression == NULL) { + __ LoadRoot(x10, Heap::kNullValueRootIndex); + __ Push(x10); + } else { + VisitForStackValue(expression); + } +} + + +void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { + Comment cmnt(masm_, "[ ObjectLiteral"); + + expr->BuildConstantProperties(isolate()); + Handle<FixedArray> constant_properties = expr->constant_properties(); + __ Ldr(x3, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ Ldr(x3, FieldMemOperand(x3, JSFunction::kLiteralsOffset)); + __ Mov(x2, Smi::FromInt(expr->literal_index())); + __ Mov(x1, Operand(constant_properties)); + int flags = expr->fast_elements() + ? ObjectLiteral::kFastElements + : ObjectLiteral::kNoFlags; + flags |= expr->has_function() + ? ObjectLiteral::kHasFunction + : ObjectLiteral::kNoFlags; + __ Mov(x0, Smi::FromInt(flags)); + int properties_count = constant_properties->length() / 2; + const int max_cloned_properties = + FastCloneShallowObjectStub::kMaximumClonedProperties; + if (expr->may_store_doubles() || expr->depth() > 1 || + masm()->serializer_enabled() || flags != ObjectLiteral::kFastElements || + properties_count > max_cloned_properties) { + __ Push(x3, x2, x1, x0); + __ CallRuntime(Runtime::kHiddenCreateObjectLiteral, 4); + } else { + FastCloneShallowObjectStub stub(isolate(), properties_count); + __ CallStub(&stub); + } + + // If result_saved is true the result is on top of the stack. If + // result_saved is false the result is in x0. + bool result_saved = false; + + // Mark all computed expressions that are bound to a key that + // is shadowed by a later occurrence of the same key. For the + // marked expressions, no store code is emitted. + expr->CalculateEmitStore(zone()); + + AccessorTable accessor_table(zone()); + for (int i = 0; i < expr->properties()->length(); i++) { + ObjectLiteral::Property* property = expr->properties()->at(i); + if (property->IsCompileTimeValue()) continue; + + Literal* key = property->key(); + Expression* value = property->value(); + if (!result_saved) { + __ Push(x0); // Save result on stack + result_saved = true; + } + switch (property->kind()) { + case ObjectLiteral::Property::CONSTANT: + UNREACHABLE(); + case ObjectLiteral::Property::MATERIALIZED_LITERAL: + ASSERT(!CompileTimeValue::IsCompileTimeValue(property->value())); + // Fall through. + case ObjectLiteral::Property::COMPUTED: + if (key->value()->IsInternalizedString()) { + if (property->emit_store()) { + VisitForAccumulatorValue(value); + __ Mov(x2, Operand(key->value())); + __ Peek(x1, 0); + CallStoreIC(key->LiteralFeedbackId()); + PrepareForBailoutForId(key->id(), NO_REGISTERS); + } else { + VisitForEffect(value); + } + break; + } + if (property->emit_store()) { + // Duplicate receiver on stack. + __ Peek(x0, 0); + __ Push(x0); + VisitForStackValue(key); + VisitForStackValue(value); + __ Mov(x0, Smi::FromInt(NONE)); // PropertyAttributes + __ Push(x0); + __ CallRuntime(Runtime::kSetProperty, 4); + } else { + VisitForEffect(key); + VisitForEffect(value); + } + break; + case ObjectLiteral::Property::PROTOTYPE: + if (property->emit_store()) { + // Duplicate receiver on stack. + __ Peek(x0, 0); + __ Push(x0); + VisitForStackValue(value); + __ CallRuntime(Runtime::kSetPrototype, 2); + } else { + VisitForEffect(value); + } + break; + case ObjectLiteral::Property::GETTER: + accessor_table.lookup(key)->second->getter = value; + break; + case ObjectLiteral::Property::SETTER: + accessor_table.lookup(key)->second->setter = value; + break; + } + } + + // Emit code to define accessors, using only a single call to the runtime for + // each pair of corresponding getters and setters. + for (AccessorTable::Iterator it = accessor_table.begin(); + it != accessor_table.end(); + ++it) { + __ Peek(x10, 0); // Duplicate receiver. + __ Push(x10); + VisitForStackValue(it->first); + EmitAccessor(it->second->getter); + EmitAccessor(it->second->setter); + __ Mov(x10, Smi::FromInt(NONE)); + __ Push(x10); + __ CallRuntime(Runtime::kDefineOrRedefineAccessorProperty, 5); + } + + if (expr->has_function()) { + ASSERT(result_saved); + __ Peek(x0, 0); + __ Push(x0); + __ CallRuntime(Runtime::kToFastProperties, 1); + } + + if (result_saved) { + context()->PlugTOS(); + } else { + context()->Plug(x0); + } +} + + +void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { + Comment cmnt(masm_, "[ ArrayLiteral"); + + expr->BuildConstantElements(isolate()); + int flags = (expr->depth() == 1) ? ArrayLiteral::kShallowElements + : ArrayLiteral::kNoFlags; + + ZoneList<Expression*>* subexprs = expr->values(); + int length = subexprs->length(); + Handle<FixedArray> constant_elements = expr->constant_elements(); + ASSERT_EQ(2, constant_elements->length()); + ElementsKind constant_elements_kind = + static_cast<ElementsKind>(Smi::cast(constant_elements->get(0))->value()); + bool has_fast_elements = IsFastObjectElementsKind(constant_elements_kind); + Handle<FixedArrayBase> constant_elements_values( + FixedArrayBase::cast(constant_elements->get(1))); + + AllocationSiteMode allocation_site_mode = TRACK_ALLOCATION_SITE; + if (has_fast_elements && !FLAG_allocation_site_pretenuring) { + // If the only customer of allocation sites is transitioning, then + // we can turn it off if we don't have anywhere else to transition to. + allocation_site_mode = DONT_TRACK_ALLOCATION_SITE; + } + + __ Ldr(x3, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ Ldr(x3, FieldMemOperand(x3, JSFunction::kLiteralsOffset)); + __ Mov(x2, Smi::FromInt(expr->literal_index())); + __ Mov(x1, Operand(constant_elements)); + if (expr->depth() > 1 || length > JSObject::kInitialMaxFastElementArray) { + __ Mov(x0, Smi::FromInt(flags)); + __ Push(x3, x2, x1, x0); + __ CallRuntime(Runtime::kHiddenCreateArrayLiteral, 4); + } else { + FastCloneShallowArrayStub stub(isolate(), allocation_site_mode); + __ CallStub(&stub); + } + + bool result_saved = false; // Is the result saved to the stack? + + // Emit code to evaluate all the non-constant subexpressions and to store + // them into the newly cloned array. + for (int i = 0; i < length; i++) { + Expression* subexpr = subexprs->at(i); + // If the subexpression is a literal or a simple materialized literal it + // is already set in the cloned array. + if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue; + + if (!result_saved) { + __ Push(x0); + __ Push(Smi::FromInt(expr->literal_index())); + result_saved = true; + } + VisitForAccumulatorValue(subexpr); + + if (IsFastObjectElementsKind(constant_elements_kind)) { + int offset = FixedArray::kHeaderSize + (i * kPointerSize); + __ Peek(x6, kPointerSize); // Copy of array literal. + __ Ldr(x1, FieldMemOperand(x6, JSObject::kElementsOffset)); + __ Str(result_register(), FieldMemOperand(x1, offset)); + // Update the write barrier for the array store. + __ RecordWriteField(x1, offset, result_register(), x10, + kLRHasBeenSaved, kDontSaveFPRegs, + EMIT_REMEMBERED_SET, INLINE_SMI_CHECK); + } else { + __ Mov(x3, Smi::FromInt(i)); + StoreArrayLiteralElementStub stub(isolate()); + __ CallStub(&stub); + } + + PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS); + } + + if (result_saved) { + __ Drop(1); // literal index + context()->PlugTOS(); + } else { + context()->Plug(x0); + } +} + + +void FullCodeGenerator::VisitAssignment(Assignment* expr) { + ASSERT(expr->target()->IsValidReferenceExpression()); + + Comment cmnt(masm_, "[ Assignment"); + + // Left-hand side can only be a property, a global or a (parameter or local) + // slot. + enum LhsKind { VARIABLE, NAMED_PROPERTY, KEYED_PROPERTY }; + LhsKind assign_type = VARIABLE; + Property* property = expr->target()->AsProperty(); + if (property != NULL) { + assign_type = (property->key()->IsPropertyName()) + ? NAMED_PROPERTY + : KEYED_PROPERTY; + } + + // Evaluate LHS expression. + switch (assign_type) { + case VARIABLE: + // Nothing to do here. + break; + case NAMED_PROPERTY: + if (expr->is_compound()) { + // We need the receiver both on the stack and in the accumulator. + VisitForAccumulatorValue(property->obj()); + __ Push(result_register()); + } else { + VisitForStackValue(property->obj()); + } + break; + case KEYED_PROPERTY: + if (expr->is_compound()) { + VisitForStackValue(property->obj()); + VisitForAccumulatorValue(property->key()); + __ Peek(x1, 0); + __ Push(x0); + } else { + VisitForStackValue(property->obj()); + VisitForStackValue(property->key()); + } + break; + } + + // For compound assignments we need another deoptimization point after the + // variable/property load. + if (expr->is_compound()) { + { AccumulatorValueContext context(this); + switch (assign_type) { + case VARIABLE: + EmitVariableLoad(expr->target()->AsVariableProxy()); + PrepareForBailout(expr->target(), TOS_REG); + break; + case NAMED_PROPERTY: + EmitNamedPropertyLoad(property); + PrepareForBailoutForId(property->LoadId(), TOS_REG); + break; + case KEYED_PROPERTY: + EmitKeyedPropertyLoad(property); + PrepareForBailoutForId(property->LoadId(), TOS_REG); + break; + } + } + + Token::Value op = expr->binary_op(); + __ Push(x0); // Left operand goes on the stack. + VisitForAccumulatorValue(expr->value()); + + OverwriteMode mode = expr->value()->ResultOverwriteAllowed() + ? OVERWRITE_RIGHT + : NO_OVERWRITE; + SetSourcePosition(expr->position() + 1); + AccumulatorValueContext context(this); + if (ShouldInlineSmiCase(op)) { + EmitInlineSmiBinaryOp(expr->binary_operation(), + op, + mode, + expr->target(), + expr->value()); + } else { + EmitBinaryOp(expr->binary_operation(), op, mode); + } + + // Deoptimization point in case the binary operation may have side effects. + PrepareForBailout(expr->binary_operation(), TOS_REG); + } else { + VisitForAccumulatorValue(expr->value()); + } + + // Record source position before possible IC call. + SetSourcePosition(expr->position()); + + // Store the value. + switch (assign_type) { + case VARIABLE: + EmitVariableAssignment(expr->target()->AsVariableProxy()->var(), + expr->op()); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(x0); + break; + case NAMED_PROPERTY: + EmitNamedPropertyAssignment(expr); + break; + case KEYED_PROPERTY: + EmitKeyedPropertyAssignment(expr); + break; + } +} + + +void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) { + SetSourcePosition(prop->position()); + Literal* key = prop->key()->AsLiteral(); + __ Mov(x2, Operand(key->value())); + // Call load IC. It has arguments receiver and property name x0 and x2. + CallLoadIC(NOT_CONTEXTUAL, prop->PropertyFeedbackId()); +} + + +void FullCodeGenerator::EmitKeyedPropertyLoad(Property* prop) { + SetSourcePosition(prop->position()); + // Call keyed load IC. It has arguments key and receiver in r0 and r1. + Handle<Code> ic = isolate()->builtins()->KeyedLoadIC_Initialize(); + CallIC(ic, prop->PropertyFeedbackId()); +} + + +void FullCodeGenerator::EmitInlineSmiBinaryOp(BinaryOperation* expr, + Token::Value op, + OverwriteMode mode, + Expression* left_expr, + Expression* right_expr) { + Label done, both_smis, stub_call; + + // Get the arguments. + Register left = x1; + Register right = x0; + Register result = x0; + __ Pop(left); + + // Perform combined smi check on both operands. + __ Orr(x10, left, right); + JumpPatchSite patch_site(masm_); + patch_site.EmitJumpIfSmi(x10, &both_smis); + + __ Bind(&stub_call); + BinaryOpICStub stub(isolate(), op, mode); + { + Assembler::BlockPoolsScope scope(masm_); + CallIC(stub.GetCode(), expr->BinaryOperationFeedbackId()); + patch_site.EmitPatchInfo(); + } + __ B(&done); + + __ Bind(&both_smis); + // Smi case. This code works in the same way as the smi-smi case in the type + // recording binary operation stub, see + // BinaryOpStub::GenerateSmiSmiOperation for comments. + // TODO(all): That doesn't exist any more. Where are the comments? + // + // The set of operations that needs to be supported here is controlled by + // FullCodeGenerator::ShouldInlineSmiCase(). + switch (op) { + case Token::SAR: + __ Ubfx(right, right, kSmiShift, 5); + __ Asr(result, left, right); + __ Bic(result, result, kSmiShiftMask); + break; + case Token::SHL: + __ Ubfx(right, right, kSmiShift, 5); + __ Lsl(result, left, right); + break; + case Token::SHR: { + Label right_not_zero; + __ Cbnz(right, &right_not_zero); + __ Tbnz(left, kXSignBit, &stub_call); + __ Bind(&right_not_zero); + __ Ubfx(right, right, kSmiShift, 5); + __ Lsr(result, left, right); + __ Bic(result, result, kSmiShiftMask); + break; + } + case Token::ADD: + __ Adds(x10, left, right); + __ B(vs, &stub_call); + __ Mov(result, x10); + break; + case Token::SUB: + __ Subs(x10, left, right); + __ B(vs, &stub_call); + __ Mov(result, x10); + break; + case Token::MUL: { + Label not_minus_zero, done; + __ Smulh(x10, left, right); + __ Cbnz(x10, ¬_minus_zero); + __ Eor(x11, left, right); + __ Tbnz(x11, kXSignBit, &stub_call); + STATIC_ASSERT(kSmiTag == 0); + __ Mov(result, x10); + __ B(&done); + __ Bind(¬_minus_zero); + __ Cls(x11, x10); + __ Cmp(x11, kXRegSizeInBits - kSmiShift); + __ B(lt, &stub_call); + __ SmiTag(result, x10); + __ Bind(&done); + break; + } + case Token::BIT_OR: + __ Orr(result, left, right); + break; + case Token::BIT_AND: + __ And(result, left, right); + break; + case Token::BIT_XOR: + __ Eor(result, left, right); + break; + default: + UNREACHABLE(); + } + + __ Bind(&done); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitBinaryOp(BinaryOperation* expr, + Token::Value op, + OverwriteMode mode) { + __ Pop(x1); + BinaryOpICStub stub(isolate(), op, mode); + JumpPatchSite patch_site(masm_); // Unbound, signals no inlined smi code. + { + Assembler::BlockPoolsScope scope(masm_); + CallIC(stub.GetCode(), expr->BinaryOperationFeedbackId()); + patch_site.EmitPatchInfo(); + } + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitAssignment(Expression* expr) { + ASSERT(expr->IsValidReferenceExpression()); + + // Left-hand side can only be a property, a global or a (parameter or local) + // slot. + enum LhsKind { VARIABLE, NAMED_PROPERTY, KEYED_PROPERTY }; + LhsKind assign_type = VARIABLE; + Property* prop = expr->AsProperty(); + if (prop != NULL) { + assign_type = (prop->key()->IsPropertyName()) + ? NAMED_PROPERTY + : KEYED_PROPERTY; + } + + switch (assign_type) { + case VARIABLE: { + Variable* var = expr->AsVariableProxy()->var(); + EffectContext context(this); + EmitVariableAssignment(var, Token::ASSIGN); + break; + } + case NAMED_PROPERTY: { + __ Push(x0); // Preserve value. + VisitForAccumulatorValue(prop->obj()); + // TODO(all): We could introduce a VisitForRegValue(reg, expr) to avoid + // this copy. + __ Mov(x1, x0); + __ Pop(x0); // Restore value. + __ Mov(x2, Operand(prop->key()->AsLiteral()->value())); + CallStoreIC(); + break; + } + case KEYED_PROPERTY: { + __ Push(x0); // Preserve value. + VisitForStackValue(prop->obj()); + VisitForAccumulatorValue(prop->key()); + __ Mov(x1, x0); + __ Pop(x2, x0); + Handle<Code> ic = strict_mode() == SLOPPY + ? isolate()->builtins()->KeyedStoreIC_Initialize() + : isolate()->builtins()->KeyedStoreIC_Initialize_Strict(); + CallIC(ic); + break; + } + } + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitStoreToStackLocalOrContextSlot( + Variable* var, MemOperand location) { + __ Str(result_register(), location); + if (var->IsContextSlot()) { + // RecordWrite may destroy all its register arguments. + __ Mov(x10, result_register()); + int offset = Context::SlotOffset(var->index()); + __ RecordWriteContextSlot( + x1, offset, x10, x11, kLRHasBeenSaved, kDontSaveFPRegs); + } +} + + +void FullCodeGenerator::EmitCallStoreContextSlot( + Handle<String> name, StrictMode strict_mode) { + __ Mov(x11, Operand(name)); + __ Mov(x10, Smi::FromInt(strict_mode)); + // jssp[0] : mode. + // jssp[8] : name. + // jssp[16] : context. + // jssp[24] : value. + __ Push(x0, cp, x11, x10); + __ CallRuntime(Runtime::kHiddenStoreContextSlot, 4); +} + + +void FullCodeGenerator::EmitVariableAssignment(Variable* var, + Token::Value op) { + ASM_LOCATION("FullCodeGenerator::EmitVariableAssignment"); + if (var->IsUnallocated()) { + // Global var, const, or let. + __ Mov(x2, Operand(var->name())); + __ Ldr(x1, GlobalObjectMemOperand()); + CallStoreIC(); + + } else if (op == Token::INIT_CONST_LEGACY) { + // Const initializers need a write barrier. + ASSERT(!var->IsParameter()); // No const parameters. + if (var->IsLookupSlot()) { + __ Push(x0); + __ Mov(x0, Operand(var->name())); + __ Push(cp, x0); // Context and name. + __ CallRuntime(Runtime::kHiddenInitializeConstContextSlot, 3); + } else { + ASSERT(var->IsStackLocal() || var->IsContextSlot()); + Label skip; + MemOperand location = VarOperand(var, x1); + __ Ldr(x10, location); + __ JumpIfNotRoot(x10, Heap::kTheHoleValueRootIndex, &skip); + EmitStoreToStackLocalOrContextSlot(var, location); + __ Bind(&skip); + } + + } else if (var->mode() == LET && op != Token::INIT_LET) { + // Non-initializing assignment to let variable needs a write barrier. + if (var->IsLookupSlot()) { + EmitCallStoreContextSlot(var->name(), strict_mode()); + } else { + ASSERT(var->IsStackAllocated() || var->IsContextSlot()); + Label assign; + MemOperand location = VarOperand(var, x1); + __ Ldr(x10, location); + __ JumpIfNotRoot(x10, Heap::kTheHoleValueRootIndex, &assign); + __ Mov(x10, Operand(var->name())); + __ Push(x10); + __ CallRuntime(Runtime::kHiddenThrowReferenceError, 1); + // Perform the assignment. + __ Bind(&assign); + EmitStoreToStackLocalOrContextSlot(var, location); + } + + } else if (!var->is_const_mode() || op == Token::INIT_CONST) { + // Assignment to var or initializing assignment to let/const + // in harmony mode. + if (var->IsLookupSlot()) { + EmitCallStoreContextSlot(var->name(), strict_mode()); + } else { + ASSERT(var->IsStackAllocated() || var->IsContextSlot()); + MemOperand location = VarOperand(var, x1); + if (FLAG_debug_code && op == Token::INIT_LET) { + __ Ldr(x10, location); + __ CompareRoot(x10, Heap::kTheHoleValueRootIndex); + __ Check(eq, kLetBindingReInitialization); + } + EmitStoreToStackLocalOrContextSlot(var, location); + } + } + // Non-initializing assignments to consts are ignored. +} + + +void FullCodeGenerator::EmitNamedPropertyAssignment(Assignment* expr) { + ASM_LOCATION("FullCodeGenerator::EmitNamedPropertyAssignment"); + // Assignment to a property, using a named store IC. + Property* prop = expr->target()->AsProperty(); + ASSERT(prop != NULL); + ASSERT(prop->key()->IsLiteral()); + + // Record source code position before IC call. + SetSourcePosition(expr->position()); + __ Mov(x2, Operand(prop->key()->AsLiteral()->value())); + __ Pop(x1); + + CallStoreIC(expr->AssignmentFeedbackId()); + + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitKeyedPropertyAssignment(Assignment* expr) { + ASM_LOCATION("FullCodeGenerator::EmitKeyedPropertyAssignment"); + // Assignment to a property, using a keyed store IC. + + // Record source code position before IC call. + SetSourcePosition(expr->position()); + // TODO(all): Could we pass this in registers rather than on the stack? + __ Pop(x1, x2); // Key and object holding the property. + + Handle<Code> ic = strict_mode() == SLOPPY + ? isolate()->builtins()->KeyedStoreIC_Initialize() + : isolate()->builtins()->KeyedStoreIC_Initialize_Strict(); + CallIC(ic, expr->AssignmentFeedbackId()); + + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(x0); +} + + +void FullCodeGenerator::VisitProperty(Property* expr) { + Comment cmnt(masm_, "[ Property"); + Expression* key = expr->key(); + + if (key->IsPropertyName()) { + VisitForAccumulatorValue(expr->obj()); + EmitNamedPropertyLoad(expr); + PrepareForBailoutForId(expr->LoadId(), TOS_REG); + context()->Plug(x0); + } else { + VisitForStackValue(expr->obj()); + VisitForAccumulatorValue(expr->key()); + __ Pop(x1); + EmitKeyedPropertyLoad(expr); + context()->Plug(x0); + } +} + + +void FullCodeGenerator::CallIC(Handle<Code> code, + TypeFeedbackId ast_id) { + ic_total_count_++; + // All calls must have a predictable size in full-codegen code to ensure that + // the debugger can patch them correctly. + __ Call(code, RelocInfo::CODE_TARGET, ast_id); +} + + +// Code common for calls using the IC. +void FullCodeGenerator::EmitCallWithLoadIC(Call* expr) { + Expression* callee = expr->expression(); + + CallIC::CallType call_type = callee->IsVariableProxy() + ? CallIC::FUNCTION + : CallIC::METHOD; + + // Get the target function. + if (call_type == CallIC::FUNCTION) { + { StackValueContext context(this); + EmitVariableLoad(callee->AsVariableProxy()); + PrepareForBailout(callee, NO_REGISTERS); + } + // Push undefined as receiver. This is patched in the method prologue if it + // is a sloppy mode method. + __ Push(isolate()->factory()->undefined_value()); + } else { + // Load the function from the receiver. + ASSERT(callee->IsProperty()); + __ Peek(x0, 0); + EmitNamedPropertyLoad(callee->AsProperty()); + PrepareForBailoutForId(callee->AsProperty()->LoadId(), TOS_REG); + // Push the target function under the receiver. + __ Pop(x10); + __ Push(x0, x10); + } + + EmitCall(expr, call_type); +} + + +// Code common for calls using the IC. +void FullCodeGenerator::EmitKeyedCallWithLoadIC(Call* expr, + Expression* key) { + // Load the key. + VisitForAccumulatorValue(key); + + Expression* callee = expr->expression(); + + // Load the function from the receiver. + ASSERT(callee->IsProperty()); + __ Peek(x1, 0); + EmitKeyedPropertyLoad(callee->AsProperty()); + PrepareForBailoutForId(callee->AsProperty()->LoadId(), TOS_REG); + + // Push the target function under the receiver. + __ Pop(x10); + __ Push(x0, x10); + + EmitCall(expr, CallIC::METHOD); +} + + +void FullCodeGenerator::EmitCall(Call* expr, CallIC::CallType call_type) { + // Load the arguments. + ZoneList<Expression*>* args = expr->arguments(); + int arg_count = args->length(); + { PreservePositionScope scope(masm()->positions_recorder()); + for (int i = 0; i < arg_count; i++) { + VisitForStackValue(args->at(i)); + } + } + // Record source position of the IC call. + SetSourcePosition(expr->position()); + + Handle<Code> ic = CallIC::initialize_stub( + isolate(), arg_count, call_type); + __ Mov(x3, Smi::FromInt(expr->CallFeedbackSlot())); + __ Peek(x1, (arg_count + 1) * kXRegSize); + // Don't assign a type feedback id to the IC, since type feedback is provided + // by the vector above. + CallIC(ic); + + RecordJSReturnSite(expr); + // Restore context register. + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + context()->DropAndPlug(1, x0); +} + + +void FullCodeGenerator::EmitResolvePossiblyDirectEval(int arg_count) { + ASM_LOCATION("FullCodeGenerator::EmitResolvePossiblyDirectEval"); + // Prepare to push a copy of the first argument or undefined if it doesn't + // exist. + if (arg_count > 0) { + __ Peek(x10, arg_count * kXRegSize); + } else { + __ LoadRoot(x10, Heap::kUndefinedValueRootIndex); + } + + // Prepare to push the receiver of the enclosing function. + int receiver_offset = 2 + info_->scope()->num_parameters(); + __ Ldr(x11, MemOperand(fp, receiver_offset * kPointerSize)); + + // Push. + __ Push(x10, x11); + + // Prepare to push the language mode. + __ Mov(x10, Smi::FromInt(strict_mode())); + // Prepare to push the start position of the scope the calls resides in. + __ Mov(x11, Smi::FromInt(scope()->start_position())); + + // Push. + __ Push(x10, x11); + + // Do the runtime call. + __ CallRuntime(Runtime::kHiddenResolvePossiblyDirectEval, 5); +} + + +void FullCodeGenerator::VisitCall(Call* expr) { +#ifdef DEBUG + // We want to verify that RecordJSReturnSite gets called on all paths + // through this function. Avoid early returns. + expr->return_is_recorded_ = false; +#endif + + Comment cmnt(masm_, "[ Call"); + Expression* callee = expr->expression(); + Call::CallType call_type = expr->GetCallType(isolate()); + + if (call_type == Call::POSSIBLY_EVAL_CALL) { + // In a call to eval, we first call RuntimeHidden_ResolvePossiblyDirectEval + // to resolve the function we need to call and the receiver of the + // call. Then we call the resolved function using the given + // arguments. + ZoneList<Expression*>* args = expr->arguments(); + int arg_count = args->length(); + + { + PreservePositionScope pos_scope(masm()->positions_recorder()); + VisitForStackValue(callee); + __ LoadRoot(x10, Heap::kUndefinedValueRootIndex); + __ Push(x10); // Reserved receiver slot. + + // Push the arguments. + for (int i = 0; i < arg_count; i++) { + VisitForStackValue(args->at(i)); + } + + // Push a copy of the function (found below the arguments) and + // resolve eval. + __ Peek(x10, (arg_count + 1) * kPointerSize); + __ Push(x10); + EmitResolvePossiblyDirectEval(arg_count); + + // The runtime call returns a pair of values in x0 (function) and + // x1 (receiver). Touch up the stack with the right values. + __ PokePair(x1, x0, arg_count * kPointerSize); + } + + // Record source position for debugger. + SetSourcePosition(expr->position()); + + // Call the evaluated function. + CallFunctionStub stub(isolate(), arg_count, NO_CALL_FUNCTION_FLAGS); + __ Peek(x1, (arg_count + 1) * kXRegSize); + __ CallStub(&stub); + RecordJSReturnSite(expr); + // Restore context register. + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + context()->DropAndPlug(1, x0); + + } else if (call_type == Call::GLOBAL_CALL) { + EmitCallWithLoadIC(expr); + + } else if (call_type == Call::LOOKUP_SLOT_CALL) { + // Call to a lookup slot (dynamically introduced variable). + VariableProxy* proxy = callee->AsVariableProxy(); + Label slow, done; + + { PreservePositionScope scope(masm()->positions_recorder()); + // Generate code for loading from variables potentially shadowed + // by eval-introduced variables. + EmitDynamicLookupFastCase(proxy->var(), NOT_INSIDE_TYPEOF, &slow, &done); + } + + __ Bind(&slow); + // Call the runtime to find the function to call (returned in x0) + // and the object holding it (returned in x1). + __ Push(context_register()); + __ Mov(x10, Operand(proxy->name())); + __ Push(x10); + __ CallRuntime(Runtime::kHiddenLoadContextSlot, 2); + __ Push(x0, x1); // Receiver, function. + + // If fast case code has been generated, emit code to push the + // function and receiver and have the slow path jump around this + // code. + if (done.is_linked()) { + Label call; + __ B(&call); + __ Bind(&done); + // Push function. + __ Push(x0); + // The receiver is implicitly the global receiver. Indicate this + // by passing the undefined to the call function stub. + __ LoadRoot(x1, Heap::kUndefinedValueRootIndex); + __ Push(x1); + __ Bind(&call); + } + + // The receiver is either the global receiver or an object found + // by LoadContextSlot. + EmitCall(expr); + } else if (call_type == Call::PROPERTY_CALL) { + Property* property = callee->AsProperty(); + { PreservePositionScope scope(masm()->positions_recorder()); + VisitForStackValue(property->obj()); + } + if (property->key()->IsPropertyName()) { + EmitCallWithLoadIC(expr); + } else { + EmitKeyedCallWithLoadIC(expr, property->key()); + } + + } else { + ASSERT(call_type == Call::OTHER_CALL); + // Call to an arbitrary expression not handled specially above. + { PreservePositionScope scope(masm()->positions_recorder()); + VisitForStackValue(callee); + } + __ LoadRoot(x1, Heap::kUndefinedValueRootIndex); + __ Push(x1); + // Emit function call. + EmitCall(expr); + } + +#ifdef DEBUG + // RecordJSReturnSite should have been called. + ASSERT(expr->return_is_recorded_); +#endif +} + + +void FullCodeGenerator::VisitCallNew(CallNew* expr) { + Comment cmnt(masm_, "[ CallNew"); + // According to ECMA-262, section 11.2.2, page 44, the function + // expression in new calls must be evaluated before the + // arguments. + + // Push constructor on the stack. If it's not a function it's used as + // receiver for CALL_NON_FUNCTION, otherwise the value on the stack is + // ignored. + VisitForStackValue(expr->expression()); + + // Push the arguments ("left-to-right") on the stack. + ZoneList<Expression*>* args = expr->arguments(); + int arg_count = args->length(); + for (int i = 0; i < arg_count; i++) { + VisitForStackValue(args->at(i)); + } + + // Call the construct call builtin that handles allocation and + // constructor invocation. + SetSourcePosition(expr->position()); + + // Load function and argument count into x1 and x0. + __ Mov(x0, arg_count); + __ Peek(x1, arg_count * kXRegSize); + + // Record call targets in unoptimized code. + if (FLAG_pretenuring_call_new) { + EnsureSlotContainsAllocationSite(expr->AllocationSiteFeedbackSlot()); + ASSERT(expr->AllocationSiteFeedbackSlot() == + expr->CallNewFeedbackSlot() + 1); + } + + __ LoadObject(x2, FeedbackVector()); + __ Mov(x3, Smi::FromInt(expr->CallNewFeedbackSlot())); + + CallConstructStub stub(isolate(), RECORD_CONSTRUCTOR_TARGET); + __ Call(stub.GetCode(), RelocInfo::CONSTRUCT_CALL); + PrepareForBailoutForId(expr->ReturnId(), TOS_REG); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitIsSmi(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + __ TestAndSplit(x0, kSmiTagMask, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsNonNegativeSmi(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + __ TestAndSplit(x0, kSmiTagMask | (0x80000000UL << kSmiShift), if_true, + if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsObject(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ JumpIfSmi(x0, if_false); + __ JumpIfRoot(x0, Heap::kNullValueRootIndex, if_true); + __ Ldr(x10, FieldMemOperand(x0, HeapObject::kMapOffset)); + // Undetectable objects behave like undefined when tested with typeof. + __ Ldrb(x11, FieldMemOperand(x10, Map::kBitFieldOffset)); + __ Tbnz(x11, Map::kIsUndetectable, if_false); + __ Ldrb(x12, FieldMemOperand(x10, Map::kInstanceTypeOffset)); + __ Cmp(x12, FIRST_NONCALLABLE_SPEC_OBJECT_TYPE); + __ B(lt, if_false); + __ Cmp(x12, LAST_NONCALLABLE_SPEC_OBJECT_TYPE); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(le, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsSpecObject(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ JumpIfSmi(x0, if_false); + __ CompareObjectType(x0, x10, x11, FIRST_SPEC_OBJECT_TYPE); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(ge, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsUndetectableObject(CallRuntime* expr) { + ASM_LOCATION("FullCodeGenerator::EmitIsUndetectableObject"); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ JumpIfSmi(x0, if_false); + __ Ldr(x10, FieldMemOperand(x0, HeapObject::kMapOffset)); + __ Ldrb(x11, FieldMemOperand(x10, Map::kBitFieldOffset)); + __ Tst(x11, 1 << Map::kIsUndetectable); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(ne, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsStringWrapperSafeForDefaultValueOf( + CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false, skip_lookup; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + Register object = x0; + __ AssertNotSmi(object); + + Register map = x10; + Register bitfield2 = x11; + __ Ldr(map, FieldMemOperand(object, HeapObject::kMapOffset)); + __ Ldrb(bitfield2, FieldMemOperand(map, Map::kBitField2Offset)); + __ Tbnz(bitfield2, Map::kStringWrapperSafeForDefaultValueOf, &skip_lookup); + + // Check for fast case object. Generate false result for slow case object. + Register props = x12; + Register props_map = x12; + Register hash_table_map = x13; + __ Ldr(props, FieldMemOperand(object, JSObject::kPropertiesOffset)); + __ Ldr(props_map, FieldMemOperand(props, HeapObject::kMapOffset)); + __ LoadRoot(hash_table_map, Heap::kHashTableMapRootIndex); + __ Cmp(props_map, hash_table_map); + __ B(eq, if_false); + + // Look for valueOf name in the descriptor array, and indicate false if found. + // Since we omit an enumeration index check, if it is added via a transition + // that shares its descriptor array, this is a false positive. + Label loop, done; + + // Skip loop if no descriptors are valid. + Register descriptors = x12; + Register descriptors_length = x13; + __ NumberOfOwnDescriptors(descriptors_length, map); + __ Cbz(descriptors_length, &done); + + __ LoadInstanceDescriptors(map, descriptors); + + // Calculate the end of the descriptor array. + Register descriptors_end = x14; + __ Mov(x15, DescriptorArray::kDescriptorSize); + __ Mul(descriptors_length, descriptors_length, x15); + // Calculate location of the first key name. + __ Add(descriptors, descriptors, + DescriptorArray::kFirstOffset - kHeapObjectTag); + // Calculate the end of the descriptor array. + __ Add(descriptors_end, descriptors, + Operand(descriptors_length, LSL, kPointerSizeLog2)); + + // Loop through all the keys in the descriptor array. If one of these is the + // string "valueOf" the result is false. + Register valueof_string = x1; + int descriptor_size = DescriptorArray::kDescriptorSize * kPointerSize; + __ Mov(valueof_string, Operand(isolate()->factory()->value_of_string())); + __ Bind(&loop); + __ Ldr(x15, MemOperand(descriptors, descriptor_size, PostIndex)); + __ Cmp(x15, valueof_string); + __ B(eq, if_false); + __ Cmp(descriptors, descriptors_end); + __ B(ne, &loop); + + __ Bind(&done); + + // Set the bit in the map to indicate that there is no local valueOf field. + __ Ldrb(x2, FieldMemOperand(map, Map::kBitField2Offset)); + __ Orr(x2, x2, 1 << Map::kStringWrapperSafeForDefaultValueOf); + __ Strb(x2, FieldMemOperand(map, Map::kBitField2Offset)); + + __ Bind(&skip_lookup); + + // If a valueOf property is not found on the object check that its prototype + // is the unmodified String prototype. If not result is false. + Register prototype = x1; + Register global_idx = x2; + Register native_context = x2; + Register string_proto = x3; + Register proto_map = x4; + __ Ldr(prototype, FieldMemOperand(map, Map::kPrototypeOffset)); + __ JumpIfSmi(prototype, if_false); + __ Ldr(proto_map, FieldMemOperand(prototype, HeapObject::kMapOffset)); + __ Ldr(global_idx, GlobalObjectMemOperand()); + __ Ldr(native_context, + FieldMemOperand(global_idx, GlobalObject::kNativeContextOffset)); + __ Ldr(string_proto, + ContextMemOperand(native_context, + Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX)); + __ Cmp(proto_map, string_proto); + + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsFunction(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ JumpIfSmi(x0, if_false); + __ CompareObjectType(x0, x10, x11, JS_FUNCTION_TYPE); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsMinusZero(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + // Only a HeapNumber can be -0.0, so return false if we have something else. + __ CheckMap(x0, x1, Heap::kHeapNumberMapRootIndex, if_false, DO_SMI_CHECK); + + // Test the bit pattern. + __ Ldr(x10, FieldMemOperand(x0, HeapNumber::kValueOffset)); + __ Cmp(x10, 1); // Set V on 0x8000000000000000. + + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(vs, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsArray(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ JumpIfSmi(x0, if_false); + __ CompareObjectType(x0, x10, x11, JS_ARRAY_TYPE); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitIsRegExp(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ JumpIfSmi(x0, if_false); + __ CompareObjectType(x0, x10, x11, JS_REGEXP_TYPE); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + + +void FullCodeGenerator::EmitIsConstructCall(CallRuntime* expr) { + ASSERT(expr->arguments()->length() == 0); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + // Get the frame pointer for the calling frame. + __ Ldr(x2, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + + // Skip the arguments adaptor frame if it exists. + Label check_frame_marker; + __ Ldr(x1, MemOperand(x2, StandardFrameConstants::kContextOffset)); + __ Cmp(x1, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ B(ne, &check_frame_marker); + __ Ldr(x2, MemOperand(x2, StandardFrameConstants::kCallerFPOffset)); + + // Check the marker in the calling frame. + __ Bind(&check_frame_marker); + __ Ldr(x1, MemOperand(x2, StandardFrameConstants::kMarkerOffset)); + __ Cmp(x1, Smi::FromInt(StackFrame::CONSTRUCT)); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitObjectEquals(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 2); + + // Load the two objects into registers and perform the comparison. + VisitForStackValue(args->at(0)); + VisitForAccumulatorValue(args->at(1)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ Pop(x1); + __ Cmp(x0, x1); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitArguments(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + // ArgumentsAccessStub expects the key in x1. + VisitForAccumulatorValue(args->at(0)); + __ Mov(x1, x0); + __ Mov(x0, Smi::FromInt(info_->scope()->num_parameters())); + ArgumentsAccessStub stub(isolate(), ArgumentsAccessStub::READ_ELEMENT); + __ CallStub(&stub); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitArgumentsLength(CallRuntime* expr) { + ASSERT(expr->arguments()->length() == 0); + Label exit; + // Get the number of formal parameters. + __ Mov(x0, Smi::FromInt(info_->scope()->num_parameters())); + + // Check if the calling frame is an arguments adaptor frame. + __ Ldr(x12, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + __ Ldr(x13, MemOperand(x12, StandardFrameConstants::kContextOffset)); + __ Cmp(x13, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ B(ne, &exit); + + // Arguments adaptor case: Read the arguments length from the + // adaptor frame. + __ Ldr(x0, MemOperand(x12, ArgumentsAdaptorFrameConstants::kLengthOffset)); + + __ Bind(&exit); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitClassOf(CallRuntime* expr) { + ASM_LOCATION("FullCodeGenerator::EmitClassOf"); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + Label done, null, function, non_function_constructor; + + VisitForAccumulatorValue(args->at(0)); + + // If the object is a smi, we return null. + __ JumpIfSmi(x0, &null); + + // Check that the object is a JS object but take special care of JS + // functions to make sure they have 'Function' as their class. + // Assume that there are only two callable types, and one of them is at + // either end of the type range for JS object types. Saves extra comparisons. + STATIC_ASSERT(NUM_OF_CALLABLE_SPEC_OBJECT_TYPES == 2); + __ CompareObjectType(x0, x10, x11, FIRST_SPEC_OBJECT_TYPE); + // x10: object's map. + // x11: object's type. + __ B(lt, &null); + STATIC_ASSERT(FIRST_NONCALLABLE_SPEC_OBJECT_TYPE == + FIRST_SPEC_OBJECT_TYPE + 1); + __ B(eq, &function); + + __ Cmp(x11, LAST_SPEC_OBJECT_TYPE); + STATIC_ASSERT(LAST_NONCALLABLE_SPEC_OBJECT_TYPE == + LAST_SPEC_OBJECT_TYPE - 1); + __ B(eq, &function); + // Assume that there is no larger type. + STATIC_ASSERT(LAST_NONCALLABLE_SPEC_OBJECT_TYPE == LAST_TYPE - 1); + + // Check if the constructor in the map is a JS function. + __ Ldr(x12, FieldMemOperand(x10, Map::kConstructorOffset)); + __ JumpIfNotObjectType(x12, x13, x14, JS_FUNCTION_TYPE, + &non_function_constructor); + + // x12 now contains the constructor function. Grab the + // instance class name from there. + __ Ldr(x13, FieldMemOperand(x12, JSFunction::kSharedFunctionInfoOffset)); + __ Ldr(x0, + FieldMemOperand(x13, SharedFunctionInfo::kInstanceClassNameOffset)); + __ B(&done); + + // Functions have class 'Function'. + __ Bind(&function); + __ LoadRoot(x0, Heap::kfunction_class_stringRootIndex); + __ B(&done); + + // Objects with a non-function constructor have class 'Object'. + __ Bind(&non_function_constructor); + __ LoadRoot(x0, Heap::kObject_stringRootIndex); + __ B(&done); + + // Non-JS objects have class null. + __ Bind(&null); + __ LoadRoot(x0, Heap::kNullValueRootIndex); + + // All done. + __ Bind(&done); + + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitSubString(CallRuntime* expr) { + // Load the arguments on the stack and call the stub. + SubStringStub stub(isolate()); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 3); + VisitForStackValue(args->at(0)); + VisitForStackValue(args->at(1)); + VisitForStackValue(args->at(2)); + __ CallStub(&stub); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitRegExpExec(CallRuntime* expr) { + // Load the arguments on the stack and call the stub. + RegExpExecStub stub(isolate()); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 4); + VisitForStackValue(args->at(0)); + VisitForStackValue(args->at(1)); + VisitForStackValue(args->at(2)); + VisitForStackValue(args->at(3)); + __ CallStub(&stub); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitValueOf(CallRuntime* expr) { + ASM_LOCATION("FullCodeGenerator::EmitValueOf"); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + VisitForAccumulatorValue(args->at(0)); // Load the object. + + Label done; + // If the object is a smi return the object. + __ JumpIfSmi(x0, &done); + // If the object is not a value type, return the object. + __ JumpIfNotObjectType(x0, x10, x11, JS_VALUE_TYPE, &done); + __ Ldr(x0, FieldMemOperand(x0, JSValue::kValueOffset)); + + __ Bind(&done); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitDateField(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 2); + ASSERT_NE(NULL, args->at(1)->AsLiteral()); + Smi* index = Smi::cast(*(args->at(1)->AsLiteral()->value())); + + VisitForAccumulatorValue(args->at(0)); // Load the object. + + Label runtime, done, not_date_object; + Register object = x0; + Register result = x0; + Register stamp_addr = x10; + Register stamp_cache = x11; + + __ JumpIfSmi(object, ¬_date_object); + __ JumpIfNotObjectType(object, x10, x10, JS_DATE_TYPE, ¬_date_object); + + if (index->value() == 0) { + __ Ldr(result, FieldMemOperand(object, JSDate::kValueOffset)); + __ B(&done); + } else { + if (index->value() < JSDate::kFirstUncachedField) { + ExternalReference stamp = ExternalReference::date_cache_stamp(isolate()); + __ Mov(x10, stamp); + __ Ldr(stamp_addr, MemOperand(x10)); + __ Ldr(stamp_cache, FieldMemOperand(object, JSDate::kCacheStampOffset)); + __ Cmp(stamp_addr, stamp_cache); + __ B(ne, &runtime); + __ Ldr(result, FieldMemOperand(object, JSDate::kValueOffset + + kPointerSize * index->value())); + __ B(&done); + } + + __ Bind(&runtime); + __ Mov(x1, index); + __ CallCFunction(ExternalReference::get_date_field_function(isolate()), 2); + __ B(&done); + } + + __ Bind(¬_date_object); + __ CallRuntime(Runtime::kHiddenThrowNotDateError, 0); + __ Bind(&done); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitOneByteSeqStringSetChar(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT_EQ(3, args->length()); + + Register string = x0; + Register index = x1; + Register value = x2; + Register scratch = x10; + + VisitForStackValue(args->at(1)); // index + VisitForStackValue(args->at(2)); // value + VisitForAccumulatorValue(args->at(0)); // string + __ Pop(value, index); + + if (FLAG_debug_code) { + __ AssertSmi(value, kNonSmiValue); + __ AssertSmi(index, kNonSmiIndex); + static const uint32_t one_byte_seq_type = kSeqStringTag | kOneByteStringTag; + __ EmitSeqStringSetCharCheck(string, index, kIndexIsSmi, scratch, + one_byte_seq_type); + } + + __ Add(scratch, string, SeqOneByteString::kHeaderSize - kHeapObjectTag); + __ SmiUntag(value); + __ SmiUntag(index); + __ Strb(value, MemOperand(scratch, index)); + context()->Plug(string); +} + + +void FullCodeGenerator::EmitTwoByteSeqStringSetChar(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT_EQ(3, args->length()); + + Register string = x0; + Register index = x1; + Register value = x2; + Register scratch = x10; + + VisitForStackValue(args->at(1)); // index + VisitForStackValue(args->at(2)); // value + VisitForAccumulatorValue(args->at(0)); // string + __ Pop(value, index); + + if (FLAG_debug_code) { + __ AssertSmi(value, kNonSmiValue); + __ AssertSmi(index, kNonSmiIndex); + static const uint32_t two_byte_seq_type = kSeqStringTag | kTwoByteStringTag; + __ EmitSeqStringSetCharCheck(string, index, kIndexIsSmi, scratch, + two_byte_seq_type); + } + + __ Add(scratch, string, SeqTwoByteString::kHeaderSize - kHeapObjectTag); + __ SmiUntag(value); + __ SmiUntag(index); + __ Strh(value, MemOperand(scratch, index, LSL, 1)); + context()->Plug(string); +} + + +void FullCodeGenerator::EmitMathPow(CallRuntime* expr) { + // Load the arguments on the stack and call the MathPow stub. + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 2); + VisitForStackValue(args->at(0)); + VisitForStackValue(args->at(1)); + MathPowStub stub(isolate(), MathPowStub::ON_STACK); + __ CallStub(&stub); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitSetValueOf(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 2); + VisitForStackValue(args->at(0)); // Load the object. + VisitForAccumulatorValue(args->at(1)); // Load the value. + __ Pop(x1); + // x0 = value. + // x1 = object. + + Label done; + // If the object is a smi, return the value. + __ JumpIfSmi(x1, &done); + + // If the object is not a value type, return the value. + __ JumpIfNotObjectType(x1, x10, x11, JS_VALUE_TYPE, &done); + + // Store the value. + __ Str(x0, FieldMemOperand(x1, JSValue::kValueOffset)); + // Update the write barrier. Save the value as it will be + // overwritten by the write barrier code and is needed afterward. + __ Mov(x10, x0); + __ RecordWriteField( + x1, JSValue::kValueOffset, x10, x11, kLRHasBeenSaved, kDontSaveFPRegs); + + __ Bind(&done); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitNumberToString(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT_EQ(args->length(), 1); + + // Load the argument into x0 and call the stub. + VisitForAccumulatorValue(args->at(0)); + + NumberToStringStub stub(isolate()); + __ CallStub(&stub); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitStringCharFromCode(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + + VisitForAccumulatorValue(args->at(0)); + + Label done; + Register code = x0; + Register result = x1; + + StringCharFromCodeGenerator generator(code, result); + generator.GenerateFast(masm_); + __ B(&done); + + NopRuntimeCallHelper call_helper; + generator.GenerateSlow(masm_, call_helper); + + __ Bind(&done); + context()->Plug(result); +} + + +void FullCodeGenerator::EmitStringCharCodeAt(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 2); + + VisitForStackValue(args->at(0)); + VisitForAccumulatorValue(args->at(1)); + + Register object = x1; + Register index = x0; + Register result = x3; + + __ Pop(object); + + Label need_conversion; + Label index_out_of_range; + Label done; + StringCharCodeAtGenerator generator(object, + index, + result, + &need_conversion, + &need_conversion, + &index_out_of_range, + STRING_INDEX_IS_NUMBER); + generator.GenerateFast(masm_); + __ B(&done); + + __ Bind(&index_out_of_range); + // When the index is out of range, the spec requires us to return NaN. + __ LoadRoot(result, Heap::kNanValueRootIndex); + __ B(&done); + + __ Bind(&need_conversion); + // Load the undefined value into the result register, which will + // trigger conversion. + __ LoadRoot(result, Heap::kUndefinedValueRootIndex); + __ B(&done); + + NopRuntimeCallHelper call_helper; + generator.GenerateSlow(masm_, call_helper); + + __ Bind(&done); + context()->Plug(result); +} + + +void FullCodeGenerator::EmitStringCharAt(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 2); + + VisitForStackValue(args->at(0)); + VisitForAccumulatorValue(args->at(1)); + + Register object = x1; + Register index = x0; + Register result = x0; + + __ Pop(object); + + Label need_conversion; + Label index_out_of_range; + Label done; + StringCharAtGenerator generator(object, + index, + x3, + result, + &need_conversion, + &need_conversion, + &index_out_of_range, + STRING_INDEX_IS_NUMBER); + generator.GenerateFast(masm_); + __ B(&done); + + __ Bind(&index_out_of_range); + // When the index is out of range, the spec requires us to return + // the empty string. + __ LoadRoot(result, Heap::kempty_stringRootIndex); + __ B(&done); + + __ Bind(&need_conversion); + // Move smi zero into the result register, which will trigger conversion. + __ Mov(result, Smi::FromInt(0)); + __ B(&done); + + NopRuntimeCallHelper call_helper; + generator.GenerateSlow(masm_, call_helper); + + __ Bind(&done); + context()->Plug(result); +} + + +void FullCodeGenerator::EmitStringAdd(CallRuntime* expr) { + ASM_LOCATION("FullCodeGenerator::EmitStringAdd"); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT_EQ(2, args->length()); + + VisitForStackValue(args->at(0)); + VisitForAccumulatorValue(args->at(1)); + + __ Pop(x1); + StringAddStub stub(isolate(), STRING_ADD_CHECK_BOTH, NOT_TENURED); + __ CallStub(&stub); + + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitStringCompare(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT_EQ(2, args->length()); + VisitForStackValue(args->at(0)); + VisitForStackValue(args->at(1)); + + StringCompareStub stub(isolate()); + __ CallStub(&stub); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitCallFunction(CallRuntime* expr) { + ASM_LOCATION("FullCodeGenerator::EmitCallFunction"); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() >= 2); + + int arg_count = args->length() - 2; // 2 ~ receiver and function. + for (int i = 0; i < arg_count + 1; i++) { + VisitForStackValue(args->at(i)); + } + VisitForAccumulatorValue(args->last()); // Function. + + Label runtime, done; + // Check for non-function argument (including proxy). + __ JumpIfSmi(x0, &runtime); + __ JumpIfNotObjectType(x0, x1, x1, JS_FUNCTION_TYPE, &runtime); + + // InvokeFunction requires the function in x1. Move it in there. + __ Mov(x1, x0); + ParameterCount count(arg_count); + __ InvokeFunction(x1, count, CALL_FUNCTION, NullCallWrapper()); + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ B(&done); + + __ Bind(&runtime); + __ Push(x0); + __ CallRuntime(Runtime::kCall, args->length()); + __ Bind(&done); + + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitRegExpConstructResult(CallRuntime* expr) { + RegExpConstructResultStub stub(isolate()); + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 3); + VisitForStackValue(args->at(0)); + VisitForStackValue(args->at(1)); + VisitForAccumulatorValue(args->at(2)); + __ Pop(x1, x2); + __ CallStub(&stub); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitGetFromCache(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT_EQ(2, args->length()); + ASSERT_NE(NULL, args->at(0)->AsLiteral()); + int cache_id = Smi::cast(*(args->at(0)->AsLiteral()->value()))->value(); + + Handle<FixedArray> jsfunction_result_caches( + isolate()->native_context()->jsfunction_result_caches()); + if (jsfunction_result_caches->length() <= cache_id) { + __ Abort(kAttemptToUseUndefinedCache); + __ LoadRoot(x0, Heap::kUndefinedValueRootIndex); + context()->Plug(x0); + return; + } + + VisitForAccumulatorValue(args->at(1)); + + Register key = x0; + Register cache = x1; + __ Ldr(cache, GlobalObjectMemOperand()); + __ Ldr(cache, FieldMemOperand(cache, GlobalObject::kNativeContextOffset)); + __ Ldr(cache, ContextMemOperand(cache, + Context::JSFUNCTION_RESULT_CACHES_INDEX)); + __ Ldr(cache, + FieldMemOperand(cache, FixedArray::OffsetOfElementAt(cache_id))); + + Label done; + __ Ldrsw(x2, UntagSmiFieldMemOperand(cache, + JSFunctionResultCache::kFingerOffset)); + __ Add(x3, cache, FixedArray::kHeaderSize - kHeapObjectTag); + __ Add(x3, x3, Operand(x2, LSL, kPointerSizeLog2)); + + // Load the key and data from the cache. + __ Ldp(x2, x3, MemOperand(x3)); + + __ Cmp(key, x2); + __ CmovX(x0, x3, eq); + __ B(eq, &done); + + // Call runtime to perform the lookup. + __ Push(cache, key); + __ CallRuntime(Runtime::kHiddenGetFromCache, 2); + + __ Bind(&done); + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitHasCachedArrayIndex(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + VisitForAccumulatorValue(args->at(0)); + + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + __ Ldr(x10, FieldMemOperand(x0, String::kHashFieldOffset)); + __ Tst(x10, String::kContainsCachedArrayIndexMask); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitGetCachedArrayIndex(CallRuntime* expr) { + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 1); + VisitForAccumulatorValue(args->at(0)); + + __ AssertString(x0); + + __ Ldr(x10, FieldMemOperand(x0, String::kHashFieldOffset)); + __ IndexFromHash(x10, x0); + + context()->Plug(x0); +} + + +void FullCodeGenerator::EmitFastAsciiArrayJoin(CallRuntime* expr) { + ASM_LOCATION("FullCodeGenerator::EmitFastAsciiArrayJoin"); + + ZoneList<Expression*>* args = expr->arguments(); + ASSERT(args->length() == 2); + VisitForStackValue(args->at(1)); + VisitForAccumulatorValue(args->at(0)); + + Register array = x0; + Register result = x0; + Register elements = x1; + Register element = x2; + Register separator = x3; + Register array_length = x4; + Register result_pos = x5; + Register map = x6; + Register string_length = x10; + Register elements_end = x11; + Register string = x12; + Register scratch1 = x13; + Register scratch2 = x14; + Register scratch3 = x7; + Register separator_length = x15; + + Label bailout, done, one_char_separator, long_separator, + non_trivial_array, not_size_one_array, loop, + empty_separator_loop, one_char_separator_loop, + one_char_separator_loop_entry, long_separator_loop; + + // The separator operand is on the stack. + __ Pop(separator); + + // Check that the array is a JSArray. + __ JumpIfSmi(array, &bailout); + __ JumpIfNotObjectType(array, map, scratch1, JS_ARRAY_TYPE, &bailout); + + // Check that the array has fast elements. + __ CheckFastElements(map, scratch1, &bailout); + + // If the array has length zero, return the empty string. + // Load and untag the length of the array. + // It is an unsigned value, so we can skip sign extension. + // We assume little endianness. + __ Ldrsw(array_length, + UntagSmiFieldMemOperand(array, JSArray::kLengthOffset)); + __ Cbnz(array_length, &non_trivial_array); + __ LoadRoot(result, Heap::kempty_stringRootIndex); + __ B(&done); + + __ Bind(&non_trivial_array); + // Get the FixedArray containing array's elements. + __ Ldr(elements, FieldMemOperand(array, JSArray::kElementsOffset)); + + // Check that all array elements are sequential ASCII strings, and + // accumulate the sum of their lengths. + __ Mov(string_length, 0); + __ Add(element, elements, FixedArray::kHeaderSize - kHeapObjectTag); + __ Add(elements_end, element, Operand(array_length, LSL, kPointerSizeLog2)); + // Loop condition: while (element < elements_end). + // Live values in registers: + // elements: Fixed array of strings. + // array_length: Length of the fixed array of strings (not smi) + // separator: Separator string + // string_length: Accumulated sum of string lengths (not smi). + // element: Current array element. + // elements_end: Array end. + if (FLAG_debug_code) { + __ Cmp(array_length, 0); + __ Assert(gt, kNoEmptyArraysHereInEmitFastAsciiArrayJoin); + } + __ Bind(&loop); + __ Ldr(string, MemOperand(element, kPointerSize, PostIndex)); + __ JumpIfSmi(string, &bailout); + __ Ldr(scratch1, FieldMemOperand(string, HeapObject::kMapOffset)); + __ Ldrb(scratch1, FieldMemOperand(scratch1, Map::kInstanceTypeOffset)); + __ JumpIfInstanceTypeIsNotSequentialAscii(scratch1, scratch2, &bailout); + __ Ldrsw(scratch1, + UntagSmiFieldMemOperand(string, SeqOneByteString::kLengthOffset)); + __ Adds(string_length, string_length, scratch1); + __ B(vs, &bailout); + __ Cmp(element, elements_end); + __ B(lt, &loop); + + // If array_length is 1, return elements[0], a string. + __ Cmp(array_length, 1); + __ B(ne, ¬_size_one_array); + __ Ldr(result, FieldMemOperand(elements, FixedArray::kHeaderSize)); + __ B(&done); + + __ Bind(¬_size_one_array); + + // Live values in registers: + // separator: Separator string + // array_length: Length of the array (not smi). + // string_length: Sum of string lengths (not smi). + // elements: FixedArray of strings. + + // Check that the separator is a flat ASCII string. + __ JumpIfSmi(separator, &bailout); + __ Ldr(scratch1, FieldMemOperand(separator, HeapObject::kMapOffset)); + __ Ldrb(scratch1, FieldMemOperand(scratch1, Map::kInstanceTypeOffset)); + __ JumpIfInstanceTypeIsNotSequentialAscii(scratch1, scratch2, &bailout); + + // Add (separator length times array_length) - separator length to the + // string_length to get the length of the result string. + // Load the separator length as untagged. + // We assume little endianness, and that the length is positive. + __ Ldrsw(separator_length, + UntagSmiFieldMemOperand(separator, + SeqOneByteString::kLengthOffset)); + __ Sub(string_length, string_length, separator_length); + __ Umaddl(string_length, array_length.W(), separator_length.W(), + string_length); + + // Get first element in the array. + __ Add(element, elements, FixedArray::kHeaderSize - kHeapObjectTag); + // Live values in registers: + // element: First array element + // separator: Separator string + // string_length: Length of result string (not smi) + // array_length: Length of the array (not smi). + __ AllocateAsciiString(result, string_length, scratch1, scratch2, scratch3, + &bailout); + + // Prepare for looping. Set up elements_end to end of the array. Set + // result_pos to the position of the result where to write the first + // character. + // TODO(all): useless unless AllocateAsciiString trashes the register. + __ Add(elements_end, element, Operand(array_length, LSL, kPointerSizeLog2)); + __ Add(result_pos, result, SeqOneByteString::kHeaderSize - kHeapObjectTag); + + // Check the length of the separator. + __ Cmp(separator_length, 1); + __ B(eq, &one_char_separator); + __ B(gt, &long_separator); + + // Empty separator case + __ Bind(&empty_separator_loop); + // Live values in registers: + // result_pos: the position to which we are currently copying characters. + // element: Current array element. + // elements_end: Array end. + + // Copy next array element to the result. + __ Ldr(string, MemOperand(element, kPointerSize, PostIndex)); + __ Ldrsw(string_length, + UntagSmiFieldMemOperand(string, String::kLengthOffset)); + __ Add(string, string, SeqOneByteString::kHeaderSize - kHeapObjectTag); + __ CopyBytes(result_pos, string, string_length, scratch1); + __ Cmp(element, elements_end); + __ B(lt, &empty_separator_loop); // End while (element < elements_end). + __ B(&done); + + // One-character separator case + __ Bind(&one_char_separator); + // Replace separator with its ASCII character value. + __ Ldrb(separator, FieldMemOperand(separator, SeqOneByteString::kHeaderSize)); + // Jump into the loop after the code that copies the separator, so the first + // element is not preceded by a separator + __ B(&one_char_separator_loop_entry); + + __ Bind(&one_char_separator_loop); + // Live values in registers: + // result_pos: the position to which we are currently copying characters. + // element: Current array element. + // elements_end: Array end. + // separator: Single separator ASCII char (in lower byte). + + // Copy the separator character to the result. + __ Strb(separator, MemOperand(result_pos, 1, PostIndex)); + + // Copy next array element to the result. + __ Bind(&one_char_separator_loop_entry); + __ Ldr(string, MemOperand(element, kPointerSize, PostIndex)); + __ Ldrsw(string_length, + UntagSmiFieldMemOperand(string, String::kLengthOffset)); + __ Add(string, string, SeqOneByteString::kHeaderSize - kHeapObjectTag); + __ CopyBytes(result_pos, string, string_length, scratch1); + __ Cmp(element, elements_end); + __ B(lt, &one_char_separator_loop); // End while (element < elements_end). + __ B(&done); + + // Long separator case (separator is more than one character). Entry is at the + // label long_separator below. + __ Bind(&long_separator_loop); + // Live values in registers: + // result_pos: the position to which we are currently copying characters. + // element: Current array element. + // elements_end: Array end. + // separator: Separator string. + + // Copy the separator to the result. + // TODO(all): hoist next two instructions. + __ Ldrsw(string_length, + UntagSmiFieldMemOperand(separator, String::kLengthOffset)); + __ Add(string, separator, SeqOneByteString::kHeaderSize - kHeapObjectTag); + __ CopyBytes(result_pos, string, string_length, scratch1); + + __ Bind(&long_separator); + __ Ldr(string, MemOperand(element, kPointerSize, PostIndex)); + __ Ldrsw(string_length, + UntagSmiFieldMemOperand(string, String::kLengthOffset)); + __ Add(string, string, SeqOneByteString::kHeaderSize - kHeapObjectTag); + __ CopyBytes(result_pos, string, string_length, scratch1); + __ Cmp(element, elements_end); + __ B(lt, &long_separator_loop); // End while (element < elements_end). + __ B(&done); + + __ Bind(&bailout); + // Returning undefined will force slower code to handle it. + __ LoadRoot(result, Heap::kUndefinedValueRootIndex); + __ Bind(&done); + context()->Plug(result); +} + + +void FullCodeGenerator::VisitCallRuntime(CallRuntime* expr) { + if (expr->function() != NULL && + expr->function()->intrinsic_type == Runtime::INLINE) { + Comment cmnt(masm_, "[ InlineRuntimeCall"); + EmitInlineRuntimeCall(expr); + return; + } + + Comment cmnt(masm_, "[ CallRunTime"); + ZoneList<Expression*>* args = expr->arguments(); + int arg_count = args->length(); + + if (expr->is_jsruntime()) { + // Push the builtins object as the receiver. + __ Ldr(x10, GlobalObjectMemOperand()); + __ Ldr(x0, FieldMemOperand(x10, GlobalObject::kBuiltinsOffset)); + __ Push(x0); + + // Load the function from the receiver. + Handle<String> name = expr->name(); + __ Mov(x2, Operand(name)); + CallLoadIC(NOT_CONTEXTUAL, expr->CallRuntimeFeedbackId()); + + // Push the target function under the receiver. + __ Pop(x10); + __ Push(x0, x10); + + int arg_count = args->length(); + for (int i = 0; i < arg_count; i++) { + VisitForStackValue(args->at(i)); + } + + // Record source position of the IC call. + SetSourcePosition(expr->position()); + CallFunctionStub stub(isolate(), arg_count, NO_CALL_FUNCTION_FLAGS); + __ Peek(x1, (arg_count + 1) * kPointerSize); + __ CallStub(&stub); + + // Restore context register. + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + + context()->DropAndPlug(1, x0); + } else { + // Push the arguments ("left-to-right"). + for (int i = 0; i < arg_count; i++) { + VisitForStackValue(args->at(i)); + } + + // Call the C runtime function. + __ CallRuntime(expr->function(), arg_count); + context()->Plug(x0); + } +} + + +void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) { + switch (expr->op()) { + case Token::DELETE: { + Comment cmnt(masm_, "[ UnaryOperation (DELETE)"); + Property* property = expr->expression()->AsProperty(); + VariableProxy* proxy = expr->expression()->AsVariableProxy(); + + if (property != NULL) { + VisitForStackValue(property->obj()); + VisitForStackValue(property->key()); + __ Mov(x10, Smi::FromInt(strict_mode())); + __ Push(x10); + __ InvokeBuiltin(Builtins::DELETE, CALL_FUNCTION); + context()->Plug(x0); + } else if (proxy != NULL) { + Variable* var = proxy->var(); + // Delete of an unqualified identifier is disallowed in strict mode + // but "delete this" is allowed. + ASSERT(strict_mode() == SLOPPY || var->is_this()); + if (var->IsUnallocated()) { + __ Ldr(x12, GlobalObjectMemOperand()); + __ Mov(x11, Operand(var->name())); + __ Mov(x10, Smi::FromInt(SLOPPY)); + __ Push(x12, x11, x10); + __ InvokeBuiltin(Builtins::DELETE, CALL_FUNCTION); + context()->Plug(x0); + } else if (var->IsStackAllocated() || var->IsContextSlot()) { + // Result of deleting non-global, non-dynamic variables is false. + // The subexpression does not have side effects. + context()->Plug(var->is_this()); + } else { + // Non-global variable. Call the runtime to try to delete from the + // context where the variable was introduced. + __ Mov(x2, Operand(var->name())); + __ Push(context_register(), x2); + __ CallRuntime(Runtime::kHiddenDeleteContextSlot, 2); + context()->Plug(x0); + } + } else { + // Result of deleting non-property, non-variable reference is true. + // The subexpression may have side effects. + VisitForEffect(expr->expression()); + context()->Plug(true); + } + break; + break; + } + case Token::VOID: { + Comment cmnt(masm_, "[ UnaryOperation (VOID)"); + VisitForEffect(expr->expression()); + context()->Plug(Heap::kUndefinedValueRootIndex); + break; + } + case Token::NOT: { + Comment cmnt(masm_, "[ UnaryOperation (NOT)"); + if (context()->IsEffect()) { + // Unary NOT has no side effects so it's only necessary to visit the + // subexpression. Match the optimizing compiler by not branching. + VisitForEffect(expr->expression()); + } else if (context()->IsTest()) { + const TestContext* test = TestContext::cast(context()); + // The labels are swapped for the recursive call. + VisitForControl(expr->expression(), + test->false_label(), + test->true_label(), + test->fall_through()); + context()->Plug(test->true_label(), test->false_label()); + } else { + ASSERT(context()->IsAccumulatorValue() || context()->IsStackValue()); + // TODO(jbramley): This could be much more efficient using (for + // example) the CSEL instruction. + Label materialize_true, materialize_false, done; + VisitForControl(expr->expression(), + &materialize_false, + &materialize_true, + &materialize_true); + + __ Bind(&materialize_true); + PrepareForBailoutForId(expr->MaterializeTrueId(), NO_REGISTERS); + __ LoadRoot(result_register(), Heap::kTrueValueRootIndex); + __ B(&done); + + __ Bind(&materialize_false); + PrepareForBailoutForId(expr->MaterializeFalseId(), NO_REGISTERS); + __ LoadRoot(result_register(), Heap::kFalseValueRootIndex); + __ B(&done); + + __ Bind(&done); + if (context()->IsStackValue()) { + __ Push(result_register()); + } + } + break; + } + case Token::TYPEOF: { + Comment cmnt(masm_, "[ UnaryOperation (TYPEOF)"); + { + StackValueContext context(this); + VisitForTypeofValue(expr->expression()); + } + __ CallRuntime(Runtime::kTypeof, 1); + context()->Plug(x0); + break; + } + default: + UNREACHABLE(); + } +} + + +void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { + ASSERT(expr->expression()->IsValidReferenceExpression()); + + Comment cmnt(masm_, "[ CountOperation"); + SetSourcePosition(expr->position()); + + // Expression can only be a property, a global or a (parameter or local) + // slot. + enum LhsKind { VARIABLE, NAMED_PROPERTY, KEYED_PROPERTY }; + LhsKind assign_type = VARIABLE; + Property* prop = expr->expression()->AsProperty(); + // In case of a property we use the uninitialized expression context + // of the key to detect a named property. + if (prop != NULL) { + assign_type = + (prop->key()->IsPropertyName()) ? NAMED_PROPERTY : KEYED_PROPERTY; + } + + // Evaluate expression and get value. + if (assign_type == VARIABLE) { + ASSERT(expr->expression()->AsVariableProxy()->var() != NULL); + AccumulatorValueContext context(this); + EmitVariableLoad(expr->expression()->AsVariableProxy()); + } else { + // Reserve space for result of postfix operation. + if (expr->is_postfix() && !context()->IsEffect()) { + __ Push(xzr); + } + if (assign_type == NAMED_PROPERTY) { + // Put the object both on the stack and in the accumulator. + VisitForAccumulatorValue(prop->obj()); + __ Push(x0); + EmitNamedPropertyLoad(prop); + } else { + // KEYED_PROPERTY + VisitForStackValue(prop->obj()); + VisitForAccumulatorValue(prop->key()); + __ Peek(x1, 0); + __ Push(x0); + EmitKeyedPropertyLoad(prop); + } + } + + // We need a second deoptimization point after loading the value + // in case evaluating the property load my have a side effect. + if (assign_type == VARIABLE) { + PrepareForBailout(expr->expression(), TOS_REG); + } else { + PrepareForBailoutForId(prop->LoadId(), TOS_REG); + } + + // Inline smi case if we are in a loop. + Label stub_call, done; + JumpPatchSite patch_site(masm_); + + int count_value = expr->op() == Token::INC ? 1 : -1; + if (ShouldInlineSmiCase(expr->op())) { + Label slow; + patch_site.EmitJumpIfNotSmi(x0, &slow); + + // Save result for postfix expressions. + if (expr->is_postfix()) { + if (!context()->IsEffect()) { + // Save the result on the stack. If we have a named or keyed property we + // store the result under the receiver that is currently on top of the + // stack. + switch (assign_type) { + case VARIABLE: + __ Push(x0); + break; + case NAMED_PROPERTY: + __ Poke(x0, kPointerSize); + break; + case KEYED_PROPERTY: + __ Poke(x0, kPointerSize * 2); + break; + } + } + } + + __ Adds(x0, x0, Smi::FromInt(count_value)); + __ B(vc, &done); + // Call stub. Undo operation first. + __ Sub(x0, x0, Smi::FromInt(count_value)); + __ B(&stub_call); + __ Bind(&slow); + } + ToNumberStub convert_stub(isolate()); + __ CallStub(&convert_stub); + + // Save result for postfix expressions. + if (expr->is_postfix()) { + if (!context()->IsEffect()) { + // Save the result on the stack. If we have a named or keyed property + // we store the result under the receiver that is currently on top + // of the stack. + switch (assign_type) { + case VARIABLE: + __ Push(x0); + break; + case NAMED_PROPERTY: + __ Poke(x0, kXRegSize); + break; + case KEYED_PROPERTY: + __ Poke(x0, 2 * kXRegSize); + break; + } + } + } + + __ Bind(&stub_call); + __ Mov(x1, x0); + __ Mov(x0, Smi::FromInt(count_value)); + + // Record position before stub call. + SetSourcePosition(expr->position()); + + { + Assembler::BlockPoolsScope scope(masm_); + BinaryOpICStub stub(isolate(), Token::ADD, NO_OVERWRITE); + CallIC(stub.GetCode(), expr->CountBinOpFeedbackId()); + patch_site.EmitPatchInfo(); + } + __ Bind(&done); + + // Store the value returned in x0. + switch (assign_type) { + case VARIABLE: + if (expr->is_postfix()) { + { EffectContext context(this); + EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), + Token::ASSIGN); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context.Plug(x0); + } + // For all contexts except EffectConstant We have the result on + // top of the stack. + if (!context()->IsEffect()) { + context()->PlugTOS(); + } + } else { + EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), + Token::ASSIGN); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(x0); + } + break; + case NAMED_PROPERTY: { + __ Mov(x2, Operand(prop->key()->AsLiteral()->value())); + __ Pop(x1); + CallStoreIC(expr->CountStoreFeedbackId()); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + if (expr->is_postfix()) { + if (!context()->IsEffect()) { + context()->PlugTOS(); + } + } else { + context()->Plug(x0); + } + break; + } + case KEYED_PROPERTY: { + __ Pop(x1); // Key. + __ Pop(x2); // Receiver. + Handle<Code> ic = strict_mode() == SLOPPY + ? isolate()->builtins()->KeyedStoreIC_Initialize() + : isolate()->builtins()->KeyedStoreIC_Initialize_Strict(); + CallIC(ic, expr->CountStoreFeedbackId()); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + if (expr->is_postfix()) { + if (!context()->IsEffect()) { + context()->PlugTOS(); + } + } else { + context()->Plug(x0); + } + break; + } + } +} + + +void FullCodeGenerator::VisitForTypeofValue(Expression* expr) { + ASSERT(!context()->IsEffect()); + ASSERT(!context()->IsTest()); + VariableProxy* proxy = expr->AsVariableProxy(); + if (proxy != NULL && proxy->var()->IsUnallocated()) { + Comment cmnt(masm_, "Global variable"); + __ Ldr(x0, GlobalObjectMemOperand()); + __ Mov(x2, Operand(proxy->name())); + // Use a regular load, not a contextual load, to avoid a reference + // error. + CallLoadIC(NOT_CONTEXTUAL); + PrepareForBailout(expr, TOS_REG); + context()->Plug(x0); + } else if (proxy != NULL && proxy->var()->IsLookupSlot()) { + Label done, slow; + + // Generate code for loading from variables potentially shadowed + // by eval-introduced variables. + EmitDynamicLookupFastCase(proxy->var(), INSIDE_TYPEOF, &slow, &done); + + __ Bind(&slow); + __ Mov(x0, Operand(proxy->name())); + __ Push(cp, x0); + __ CallRuntime(Runtime::kHiddenLoadContextSlotNoReferenceError, 2); + PrepareForBailout(expr, TOS_REG); + __ Bind(&done); + + context()->Plug(x0); + } else { + // This expression cannot throw a reference error at the top level. + VisitInDuplicateContext(expr); + } +} + + +void FullCodeGenerator::EmitLiteralCompareTypeof(Expression* expr, + Expression* sub_expr, + Handle<String> check) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof"); + Comment cmnt(masm_, "[ EmitLiteralCompareTypeof"); + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + { AccumulatorValueContext context(this); + VisitForTypeofValue(sub_expr); + } + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + + Factory* factory = isolate()->factory(); + if (String::Equals(check, factory->number_string())) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof number_string"); + __ JumpIfSmi(x0, if_true); + __ Ldr(x0, FieldMemOperand(x0, HeapObject::kMapOffset)); + __ CompareRoot(x0, Heap::kHeapNumberMapRootIndex); + Split(eq, if_true, if_false, fall_through); + } else if (String::Equals(check, factory->string_string())) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof string_string"); + __ JumpIfSmi(x0, if_false); + // Check for undetectable objects => false. + __ JumpIfObjectType(x0, x0, x1, FIRST_NONSTRING_TYPE, if_false, ge); + __ Ldrb(x1, FieldMemOperand(x0, Map::kBitFieldOffset)); + __ TestAndSplit(x1, 1 << Map::kIsUndetectable, if_true, if_false, + fall_through); + } else if (String::Equals(check, factory->symbol_string())) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof symbol_string"); + __ JumpIfSmi(x0, if_false); + __ CompareObjectType(x0, x0, x1, SYMBOL_TYPE); + Split(eq, if_true, if_false, fall_through); + } else if (String::Equals(check, factory->boolean_string())) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof boolean_string"); + __ JumpIfRoot(x0, Heap::kTrueValueRootIndex, if_true); + __ CompareRoot(x0, Heap::kFalseValueRootIndex); + Split(eq, if_true, if_false, fall_through); + } else if (FLAG_harmony_typeof && + String::Equals(check, factory->null_string())) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof null_string"); + __ CompareRoot(x0, Heap::kNullValueRootIndex); + Split(eq, if_true, if_false, fall_through); + } else if (String::Equals(check, factory->undefined_string())) { + ASM_LOCATION( + "FullCodeGenerator::EmitLiteralCompareTypeof undefined_string"); + __ JumpIfRoot(x0, Heap::kUndefinedValueRootIndex, if_true); + __ JumpIfSmi(x0, if_false); + // Check for undetectable objects => true. + __ Ldr(x0, FieldMemOperand(x0, HeapObject::kMapOffset)); + __ Ldrb(x1, FieldMemOperand(x0, Map::kBitFieldOffset)); + __ TestAndSplit(x1, 1 << Map::kIsUndetectable, if_false, if_true, + fall_through); + } else if (String::Equals(check, factory->function_string())) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof function_string"); + __ JumpIfSmi(x0, if_false); + STATIC_ASSERT(NUM_OF_CALLABLE_SPEC_OBJECT_TYPES == 2); + __ JumpIfObjectType(x0, x10, x11, JS_FUNCTION_TYPE, if_true); + __ CompareAndSplit(x11, JS_FUNCTION_PROXY_TYPE, eq, if_true, if_false, + fall_through); + + } else if (String::Equals(check, factory->object_string())) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof object_string"); + __ JumpIfSmi(x0, if_false); + if (!FLAG_harmony_typeof) { + __ JumpIfRoot(x0, Heap::kNullValueRootIndex, if_true); + } + // Check for JS objects => true. + Register map = x10; + __ JumpIfObjectType(x0, map, x11, FIRST_NONCALLABLE_SPEC_OBJECT_TYPE, + if_false, lt); + __ CompareInstanceType(map, x11, LAST_NONCALLABLE_SPEC_OBJECT_TYPE); + __ B(gt, if_false); + // Check for undetectable objects => false. + __ Ldrb(x10, FieldMemOperand(map, Map::kBitFieldOffset)); + + __ TestAndSplit(x10, 1 << Map::kIsUndetectable, if_true, if_false, + fall_through); + + } else { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareTypeof other"); + if (if_false != fall_through) __ B(if_false); + } + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) { + Comment cmnt(masm_, "[ CompareOperation"); + SetSourcePosition(expr->position()); + + // Try to generate an optimized comparison with a literal value. + // TODO(jbramley): This only checks common values like NaN or undefined. + // Should it also handle ARM64 immediate operands? + if (TryLiteralCompare(expr)) { + return; + } + + // Assign labels according to context()->PrepareTest. + Label materialize_true; + Label materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + Token::Value op = expr->op(); + VisitForStackValue(expr->left()); + switch (op) { + case Token::IN: + VisitForStackValue(expr->right()); + __ InvokeBuiltin(Builtins::IN, CALL_FUNCTION); + PrepareForBailoutBeforeSplit(expr, false, NULL, NULL); + __ CompareRoot(x0, Heap::kTrueValueRootIndex); + Split(eq, if_true, if_false, fall_through); + break; + + case Token::INSTANCEOF: { + VisitForStackValue(expr->right()); + InstanceofStub stub(isolate(), InstanceofStub::kNoFlags); + __ CallStub(&stub); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + // The stub returns 0 for true. + __ CompareAndSplit(x0, 0, eq, if_true, if_false, fall_through); + break; + } + + default: { + VisitForAccumulatorValue(expr->right()); + Condition cond = CompareIC::ComputeCondition(op); + + // Pop the stack value. + __ Pop(x1); + + JumpPatchSite patch_site(masm_); + if (ShouldInlineSmiCase(op)) { + Label slow_case; + patch_site.EmitJumpIfEitherNotSmi(x0, x1, &slow_case); + __ Cmp(x1, x0); + Split(cond, if_true, if_false, NULL); + __ Bind(&slow_case); + } + + // Record position and call the compare IC. + SetSourcePosition(expr->position()); + Handle<Code> ic = CompareIC::GetUninitialized(isolate(), op); + CallIC(ic, expr->CompareOperationFeedbackId()); + patch_site.EmitPatchInfo(); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + __ CompareAndSplit(x0, 0, cond, if_true, if_false, fall_through); + } + } + + // Convert the result of the comparison into one expected for this + // expression's context. + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::EmitLiteralCompareNil(CompareOperation* expr, + Expression* sub_expr, + NilValue nil) { + ASM_LOCATION("FullCodeGenerator::EmitLiteralCompareNil"); + Label materialize_true, materialize_false; + Label* if_true = NULL; + Label* if_false = NULL; + Label* fall_through = NULL; + context()->PrepareTest(&materialize_true, &materialize_false, + &if_true, &if_false, &fall_through); + + VisitForAccumulatorValue(sub_expr); + PrepareForBailoutBeforeSplit(expr, true, if_true, if_false); + + if (expr->op() == Token::EQ_STRICT) { + Heap::RootListIndex nil_value = nil == kNullValue ? + Heap::kNullValueRootIndex : + Heap::kUndefinedValueRootIndex; + __ CompareRoot(x0, nil_value); + Split(eq, if_true, if_false, fall_through); + } else { + Handle<Code> ic = CompareNilICStub::GetUninitialized(isolate(), nil); + CallIC(ic, expr->CompareOperationFeedbackId()); + __ CompareAndSplit(x0, 0, ne, if_true, if_false, fall_through); + } + + context()->Plug(if_true, if_false); +} + + +void FullCodeGenerator::VisitThisFunction(ThisFunction* expr) { + __ Ldr(x0, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + context()->Plug(x0); +} + + +void FullCodeGenerator::VisitYield(Yield* expr) { + Comment cmnt(masm_, "[ Yield"); + // Evaluate yielded value first; the initial iterator definition depends on + // this. It stays on the stack while we update the iterator. + VisitForStackValue(expr->expression()); + + // TODO(jbramley): Tidy this up once the merge is done, using named registers + // and suchlike. The implementation changes a little by bleeding_edge so I + // don't want to spend too much time on it now. + + switch (expr->yield_kind()) { + case Yield::SUSPEND: + // Pop value from top-of-stack slot; box result into result register. + EmitCreateIteratorResult(false); + __ Push(result_register()); + // Fall through. + case Yield::INITIAL: { + Label suspend, continuation, post_runtime, resume; + + __ B(&suspend); + + // TODO(jbramley): This label is bound here because the following code + // looks at its pos(). Is it possible to do something more efficient here, + // perhaps using Adr? + __ Bind(&continuation); + __ B(&resume); + + __ Bind(&suspend); + VisitForAccumulatorValue(expr->generator_object()); + ASSERT((continuation.pos() > 0) && Smi::IsValid(continuation.pos())); + __ Mov(x1, Smi::FromInt(continuation.pos())); + __ Str(x1, FieldMemOperand(x0, JSGeneratorObject::kContinuationOffset)); + __ Str(cp, FieldMemOperand(x0, JSGeneratorObject::kContextOffset)); + __ Mov(x1, cp); + __ RecordWriteField(x0, JSGeneratorObject::kContextOffset, x1, x2, + kLRHasBeenSaved, kDontSaveFPRegs); + __ Add(x1, fp, StandardFrameConstants::kExpressionsOffset); + __ Cmp(__ StackPointer(), x1); + __ B(eq, &post_runtime); + __ Push(x0); // generator object + __ CallRuntime(Runtime::kHiddenSuspendJSGeneratorObject, 1); + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ Bind(&post_runtime); + __ Pop(result_register()); + EmitReturnSequence(); + + __ Bind(&resume); + context()->Plug(result_register()); + break; + } + + case Yield::FINAL: { + VisitForAccumulatorValue(expr->generator_object()); + __ Mov(x1, Smi::FromInt(JSGeneratorObject::kGeneratorClosed)); + __ Str(x1, FieldMemOperand(result_register(), + JSGeneratorObject::kContinuationOffset)); + // Pop value from top-of-stack slot, box result into result register. + EmitCreateIteratorResult(true); + EmitUnwindBeforeReturn(); + EmitReturnSequence(); + break; + } + + case Yield::DELEGATING: { + VisitForStackValue(expr->generator_object()); + + // Initial stack layout is as follows: + // [sp + 1 * kPointerSize] iter + // [sp + 0 * kPointerSize] g + + Label l_catch, l_try, l_suspend, l_continuation, l_resume; + Label l_next, l_call, l_loop; + // Initial send value is undefined. + __ LoadRoot(x0, Heap::kUndefinedValueRootIndex); + __ B(&l_next); + + // catch (e) { receiver = iter; f = 'throw'; arg = e; goto l_call; } + __ Bind(&l_catch); + handler_table()->set(expr->index(), Smi::FromInt(l_catch.pos())); + __ LoadRoot(x2, Heap::kthrow_stringRootIndex); // "throw" + __ Peek(x3, 1 * kPointerSize); // iter + __ Push(x2, x3, x0); // "throw", iter, except + __ B(&l_call); + + // try { received = %yield result } + // Shuffle the received result above a try handler and yield it without + // re-boxing. + __ Bind(&l_try); + __ Pop(x0); // result + __ PushTryHandler(StackHandler::CATCH, expr->index()); + const int handler_size = StackHandlerConstants::kSize; + __ Push(x0); // result + __ B(&l_suspend); + + // TODO(jbramley): This label is bound here because the following code + // looks at its pos(). Is it possible to do something more efficient here, + // perhaps using Adr? + __ Bind(&l_continuation); + __ B(&l_resume); + + __ Bind(&l_suspend); + const int generator_object_depth = kPointerSize + handler_size; + __ Peek(x0, generator_object_depth); + __ Push(x0); // g + ASSERT((l_continuation.pos() > 0) && Smi::IsValid(l_continuation.pos())); + __ Mov(x1, Smi::FromInt(l_continuation.pos())); + __ Str(x1, FieldMemOperand(x0, JSGeneratorObject::kContinuationOffset)); + __ Str(cp, FieldMemOperand(x0, JSGeneratorObject::kContextOffset)); + __ Mov(x1, cp); + __ RecordWriteField(x0, JSGeneratorObject::kContextOffset, x1, x2, + kLRHasBeenSaved, kDontSaveFPRegs); + __ CallRuntime(Runtime::kHiddenSuspendJSGeneratorObject, 1); + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ Pop(x0); // result + EmitReturnSequence(); + __ Bind(&l_resume); // received in x0 + __ PopTryHandler(); + + // receiver = iter; f = 'next'; arg = received; + __ Bind(&l_next); + __ LoadRoot(x2, Heap::knext_stringRootIndex); // "next" + __ Peek(x3, 1 * kPointerSize); // iter + __ Push(x2, x3, x0); // "next", iter, received + + // result = receiver[f](arg); + __ Bind(&l_call); + __ Peek(x1, 1 * kPointerSize); + __ Peek(x0, 2 * kPointerSize); + Handle<Code> ic = isolate()->builtins()->KeyedLoadIC_Initialize(); + CallIC(ic, TypeFeedbackId::None()); + __ Mov(x1, x0); + __ Poke(x1, 2 * kPointerSize); + CallFunctionStub stub(isolate(), 1, CALL_AS_METHOD); + __ CallStub(&stub); + + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ Drop(1); // The function is still on the stack; drop it. + + // if (!result.done) goto l_try; + __ Bind(&l_loop); + __ Push(x0); // save result + __ LoadRoot(x2, Heap::kdone_stringRootIndex); // "done" + CallLoadIC(NOT_CONTEXTUAL); // result.done in x0 + // The ToBooleanStub argument (result.done) is in x0. + Handle<Code> bool_ic = ToBooleanStub::GetUninitialized(isolate()); + CallIC(bool_ic); + __ Cbz(x0, &l_try); + + // result.value + __ Pop(x0); // result + __ LoadRoot(x2, Heap::kvalue_stringRootIndex); // "value" + CallLoadIC(NOT_CONTEXTUAL); // result.value in x0 + context()->DropAndPlug(2, x0); // drop iter and g + break; + } + } +} + + +void FullCodeGenerator::EmitGeneratorResume(Expression *generator, + Expression *value, + JSGeneratorObject::ResumeMode resume_mode) { + ASM_LOCATION("FullCodeGenerator::EmitGeneratorResume"); + Register value_reg = x0; + Register generator_object = x1; + Register the_hole = x2; + Register operand_stack_size = w3; + Register function = x4; + + // The value stays in x0, and is ultimately read by the resumed generator, as + // if CallRuntime(Runtime::kHiddenSuspendJSGeneratorObject) returned it. Or it + // is read to throw the value when the resumed generator is already closed. r1 + // will hold the generator object until the activation has been resumed. + VisitForStackValue(generator); + VisitForAccumulatorValue(value); + __ Pop(generator_object); + + // Check generator state. + Label wrong_state, closed_state, done; + __ Ldr(x10, FieldMemOperand(generator_object, + JSGeneratorObject::kContinuationOffset)); + STATIC_ASSERT(JSGeneratorObject::kGeneratorExecuting < 0); + STATIC_ASSERT(JSGeneratorObject::kGeneratorClosed == 0); + __ CompareAndBranch(x10, Smi::FromInt(0), eq, &closed_state); + __ CompareAndBranch(x10, Smi::FromInt(0), lt, &wrong_state); + + // Load suspended function and context. + __ Ldr(cp, FieldMemOperand(generator_object, + JSGeneratorObject::kContextOffset)); + __ Ldr(function, FieldMemOperand(generator_object, + JSGeneratorObject::kFunctionOffset)); + + // Load receiver and store as the first argument. + __ Ldr(x10, FieldMemOperand(generator_object, + JSGeneratorObject::kReceiverOffset)); + __ Push(x10); + + // Push holes for the rest of the arguments to the generator function. + __ Ldr(x10, FieldMemOperand(function, JSFunction::kSharedFunctionInfoOffset)); + + // The number of arguments is stored as an int32_t, and -1 is a marker + // (SharedFunctionInfo::kDontAdaptArgumentsSentinel), so we need sign + // extension to correctly handle it. However, in this case, we operate on + // 32-bit W registers, so extension isn't required. + __ Ldr(w10, FieldMemOperand(x10, + SharedFunctionInfo::kFormalParameterCountOffset)); + __ LoadRoot(the_hole, Heap::kTheHoleValueRootIndex); + __ PushMultipleTimes(the_hole, w10); + + // Enter a new JavaScript frame, and initialize its slots as they were when + // the generator was suspended. + Label resume_frame; + __ Bl(&resume_frame); + __ B(&done); + + __ Bind(&resume_frame); + __ Push(lr, // Return address. + fp, // Caller's frame pointer. + cp, // Callee's context. + function); // Callee's JS Function. + __ Add(fp, __ StackPointer(), kPointerSize * 2); + + // Load and untag the operand stack size. + __ Ldr(x10, FieldMemOperand(generator_object, + JSGeneratorObject::kOperandStackOffset)); + __ Ldr(operand_stack_size, + UntagSmiFieldMemOperand(x10, FixedArray::kLengthOffset)); + + // If we are sending a value and there is no operand stack, we can jump back + // in directly. + if (resume_mode == JSGeneratorObject::NEXT) { + Label slow_resume; + __ Cbnz(operand_stack_size, &slow_resume); + __ Ldr(x10, FieldMemOperand(function, JSFunction::kCodeEntryOffset)); + __ Ldrsw(x11, + UntagSmiFieldMemOperand(generator_object, + JSGeneratorObject::kContinuationOffset)); + __ Add(x10, x10, x11); + __ Mov(x12, Smi::FromInt(JSGeneratorObject::kGeneratorExecuting)); + __ Str(x12, FieldMemOperand(generator_object, + JSGeneratorObject::kContinuationOffset)); + __ Br(x10); + + __ Bind(&slow_resume); + } + + // Otherwise, we push holes for the operand stack and call the runtime to fix + // up the stack and the handlers. + __ PushMultipleTimes(the_hole, operand_stack_size); + + __ Mov(x10, Smi::FromInt(resume_mode)); + __ Push(generator_object, result_register(), x10); + __ CallRuntime(Runtime::kHiddenResumeJSGeneratorObject, 3); + // Not reached: the runtime call returns elsewhere. + __ Unreachable(); + + // Reach here when generator is closed. + __ Bind(&closed_state); + if (resume_mode == JSGeneratorObject::NEXT) { + // Return completed iterator result when generator is closed. + __ LoadRoot(x10, Heap::kUndefinedValueRootIndex); + __ Push(x10); + // Pop value from top-of-stack slot; box result into result register. + EmitCreateIteratorResult(true); + } else { + // Throw the provided value. + __ Push(value_reg); + __ CallRuntime(Runtime::kHiddenThrow, 1); + } + __ B(&done); + + // Throw error if we attempt to operate on a running generator. + __ Bind(&wrong_state); + __ Push(generator_object); + __ CallRuntime(Runtime::kHiddenThrowGeneratorStateError, 1); + + __ Bind(&done); + context()->Plug(result_register()); +} + + +void FullCodeGenerator::EmitCreateIteratorResult(bool done) { + Label gc_required; + Label allocated; + + Handle<Map> map(isolate()->native_context()->iterator_result_map()); + + // Allocate and populate an object with this form: { value: VAL, done: DONE } + + Register result = x0; + __ Allocate(map->instance_size(), result, x10, x11, &gc_required, TAG_OBJECT); + __ B(&allocated); + + __ Bind(&gc_required); + __ Push(Smi::FromInt(map->instance_size())); + __ CallRuntime(Runtime::kHiddenAllocateInNewSpace, 1); + __ Ldr(context_register(), + MemOperand(fp, StandardFrameConstants::kContextOffset)); + + __ Bind(&allocated); + Register map_reg = x1; + Register result_value = x2; + Register boolean_done = x3; + Register empty_fixed_array = x4; + Register untagged_result = x5; + __ Mov(map_reg, Operand(map)); + __ Pop(result_value); + __ Mov(boolean_done, Operand(isolate()->factory()->ToBoolean(done))); + __ Mov(empty_fixed_array, Operand(isolate()->factory()->empty_fixed_array())); + ASSERT_EQ(map->instance_size(), 5 * kPointerSize); + STATIC_ASSERT(JSObject::kPropertiesOffset + kPointerSize == + JSObject::kElementsOffset); + STATIC_ASSERT(JSGeneratorObject::kResultValuePropertyOffset + kPointerSize == + JSGeneratorObject::kResultDonePropertyOffset); + __ ObjectUntag(untagged_result, result); + __ Str(map_reg, MemOperand(untagged_result, HeapObject::kMapOffset)); + __ Stp(empty_fixed_array, empty_fixed_array, + MemOperand(untagged_result, JSObject::kPropertiesOffset)); + __ Stp(result_value, boolean_done, + MemOperand(untagged_result, + JSGeneratorObject::kResultValuePropertyOffset)); + + // Only the value field needs a write barrier, as the other values are in the + // root set. + __ RecordWriteField(result, JSGeneratorObject::kResultValuePropertyOffset, + x10, x11, kLRHasBeenSaved, kDontSaveFPRegs); +} + + +// TODO(all): I don't like this method. +// It seems to me that in too many places x0 is used in place of this. +// Also, this function is not suitable for all places where x0 should be +// abstracted (eg. when used as an argument). But some places assume that the +// first argument register is x0, and use this function instead. +// Considering that most of the register allocation is hard-coded in the +// FullCodeGen, that it is unlikely we will need to change it extensively, and +// that abstracting the allocation through functions would not yield any +// performance benefit, I think the existence of this function is debatable. +Register FullCodeGenerator::result_register() { + return x0; +} + + +Register FullCodeGenerator::context_register() { + return cp; +} + + +void FullCodeGenerator::StoreToFrameField(int frame_offset, Register value) { + ASSERT(POINTER_SIZE_ALIGN(frame_offset) == frame_offset); + __ Str(value, MemOperand(fp, frame_offset)); +} + + +void FullCodeGenerator::LoadContextField(Register dst, int context_index) { + __ Ldr(dst, ContextMemOperand(cp, context_index)); +} + + +void FullCodeGenerator::PushFunctionArgumentForContextAllocation() { + Scope* declaration_scope = scope()->DeclarationScope(); + if (declaration_scope->is_global_scope() || + declaration_scope->is_module_scope()) { + // Contexts nested in the native context have a canonical empty function + // as their closure, not the anonymous closure containing the global + // code. Pass a smi sentinel and let the runtime look up the empty + // function. + ASSERT(kSmiTag == 0); + __ Push(xzr); + } else if (declaration_scope->is_eval_scope()) { + // Contexts created by a call to eval have the same closure as the + // context calling eval, not the anonymous closure containing the eval + // code. Fetch it from the context. + __ Ldr(x10, ContextMemOperand(cp, Context::CLOSURE_INDEX)); + __ Push(x10); + } else { + ASSERT(declaration_scope->is_function_scope()); + __ Ldr(x10, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ Push(x10); + } +} + + +void FullCodeGenerator::EnterFinallyBlock() { + ASM_LOCATION("FullCodeGenerator::EnterFinallyBlock"); + ASSERT(!result_register().is(x10)); + // Preserve the result register while executing finally block. + // Also cook the return address in lr to the stack (smi encoded Code* delta). + __ Sub(x10, lr, Operand(masm_->CodeObject())); + __ SmiTag(x10); + __ Push(result_register(), x10); + + // Store pending message while executing finally block. + ExternalReference pending_message_obj = + ExternalReference::address_of_pending_message_obj(isolate()); + __ Mov(x10, pending_message_obj); + __ Ldr(x10, MemOperand(x10)); + + ExternalReference has_pending_message = + ExternalReference::address_of_has_pending_message(isolate()); + STATIC_ASSERT(sizeof(bool) == 1); // NOLINT(runtime/sizeof) + __ Mov(x11, has_pending_message); + __ Ldrb(x11, MemOperand(x11)); + __ SmiTag(x11); + + __ Push(x10, x11); + + ExternalReference pending_message_script = + ExternalReference::address_of_pending_message_script(isolate()); + __ Mov(x10, pending_message_script); + __ Ldr(x10, MemOperand(x10)); + __ Push(x10); +} + + +void FullCodeGenerator::ExitFinallyBlock() { + ASM_LOCATION("FullCodeGenerator::ExitFinallyBlock"); + ASSERT(!result_register().is(x10)); + + // Restore pending message from stack. + __ Pop(x10, x11, x12); + ExternalReference pending_message_script = + ExternalReference::address_of_pending_message_script(isolate()); + __ Mov(x13, pending_message_script); + __ Str(x10, MemOperand(x13)); + + __ SmiUntag(x11); + ExternalReference has_pending_message = + ExternalReference::address_of_has_pending_message(isolate()); + __ Mov(x13, has_pending_message); + STATIC_ASSERT(sizeof(bool) == 1); // NOLINT(runtime/sizeof) + __ Strb(x11, MemOperand(x13)); + + ExternalReference pending_message_obj = + ExternalReference::address_of_pending_message_obj(isolate()); + __ Mov(x13, pending_message_obj); + __ Str(x12, MemOperand(x13)); + + // Restore result register and cooked return address from the stack. + __ Pop(x10, result_register()); + + // Uncook the return address (see EnterFinallyBlock). + __ SmiUntag(x10); + __ Add(x11, x10, Operand(masm_->CodeObject())); + __ Br(x11); +} + + +#undef __ + + +void BackEdgeTable::PatchAt(Code* unoptimized_code, + Address pc, + BackEdgeState target_state, + Code* replacement_code) { + // Turn the jump into a nop. + Address branch_address = pc - 3 * kInstructionSize; + PatchingAssembler patcher(branch_address, 1); + + ASSERT(Instruction::Cast(branch_address) + ->IsNop(Assembler::INTERRUPT_CODE_NOP) || + (Instruction::Cast(branch_address)->IsCondBranchImm() && + Instruction::Cast(branch_address)->ImmPCOffset() == + 6 * kInstructionSize)); + + switch (target_state) { + case INTERRUPT: + // <decrement profiling counter> + // .. .. .. .. b.pl ok + // .. .. .. .. ldr x16, pc+<interrupt stub address> + // .. .. .. .. blr x16 + // ... more instructions. + // ok-label + // Jump offset is 6 instructions. + patcher.b(6, pl); + break; + case ON_STACK_REPLACEMENT: + case OSR_AFTER_STACK_CHECK: + // <decrement profiling counter> + // .. .. .. .. mov x0, x0 (NOP) + // .. .. .. .. ldr x16, pc+<on-stack replacement address> + // .. .. .. .. blr x16 + patcher.nop(Assembler::INTERRUPT_CODE_NOP); + break; + } + + // Replace the call address. + Instruction* load = Instruction::Cast(pc)->preceding(2); + Address interrupt_address_pointer = + reinterpret_cast<Address>(load) + load->ImmPCOffset(); + ASSERT((Memory::uint64_at(interrupt_address_pointer) == + reinterpret_cast<uint64_t>(unoptimized_code->GetIsolate() + ->builtins() + ->OnStackReplacement() + ->entry())) || + (Memory::uint64_at(interrupt_address_pointer) == + reinterpret_cast<uint64_t>(unoptimized_code->GetIsolate() + ->builtins() + ->InterruptCheck() + ->entry())) || + (Memory::uint64_at(interrupt_address_pointer) == + reinterpret_cast<uint64_t>(unoptimized_code->GetIsolate() + ->builtins() + ->OsrAfterStackCheck() + ->entry())) || + (Memory::uint64_at(interrupt_address_pointer) == + reinterpret_cast<uint64_t>(unoptimized_code->GetIsolate() + ->builtins() + ->OnStackReplacement() + ->entry()))); + Memory::uint64_at(interrupt_address_pointer) = + reinterpret_cast<uint64_t>(replacement_code->entry()); + + unoptimized_code->GetHeap()->incremental_marking()->RecordCodeTargetPatch( + unoptimized_code, reinterpret_cast<Address>(load), replacement_code); +} + + +BackEdgeTable::BackEdgeState BackEdgeTable::GetBackEdgeState( + Isolate* isolate, + Code* unoptimized_code, + Address pc) { + // TODO(jbramley): There should be some extra assertions here (as in the ARM + // back-end), but this function is gone in bleeding_edge so it might not + // matter anyway. + Instruction* jump_or_nop = Instruction::Cast(pc)->preceding(3); + + if (jump_or_nop->IsNop(Assembler::INTERRUPT_CODE_NOP)) { + Instruction* load = Instruction::Cast(pc)->preceding(2); + uint64_t entry = Memory::uint64_at(reinterpret_cast<Address>(load) + + load->ImmPCOffset()); + if (entry == reinterpret_cast<uint64_t>( + isolate->builtins()->OnStackReplacement()->entry())) { + return ON_STACK_REPLACEMENT; + } else if (entry == reinterpret_cast<uint64_t>( + isolate->builtins()->OsrAfterStackCheck()->entry())) { + return OSR_AFTER_STACK_CHECK; + } else { + UNREACHABLE(); + } + } + + return INTERRUPT; +} + + +#define __ ACCESS_MASM(masm()) + + +FullCodeGenerator::NestedStatement* FullCodeGenerator::TryFinally::Exit( + int* stack_depth, + int* context_length) { + ASM_LOCATION("FullCodeGenerator::TryFinally::Exit"); + // The macros used here must preserve the result register. + + // Because the handler block contains the context of the finally + // code, we can restore it directly from there for the finally code + // rather than iteratively unwinding contexts via their previous + // links. + __ Drop(*stack_depth); // Down to the handler block. + if (*context_length > 0) { + // Restore the context to its dedicated register and the stack. + __ Peek(cp, StackHandlerConstants::kContextOffset); + __ Str(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + } + __ PopTryHandler(); + __ Bl(finally_entry_); + + *stack_depth = 0; + *context_length = 0; + return previous_; +} + + +#undef __ + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/ic-arm64.cc b/chromium/v8/src/arm64/ic-arm64.cc new file mode 100644 index 00000000000..842b3e75dcb --- /dev/null +++ b/chromium/v8/src/arm64/ic-arm64.cc @@ -0,0 +1,1387 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/arm64/assembler-arm64.h" +#include "src/code-stubs.h" +#include "src/codegen.h" +#include "src/disasm.h" +#include "src/ic-inl.h" +#include "src/runtime.h" +#include "src/stub-cache.h" + +namespace v8 { +namespace internal { + + +#define __ ACCESS_MASM(masm) + + +// "type" holds an instance type on entry and is not clobbered. +// Generated code branch on "global_object" if type is any kind of global +// JS object. +static void GenerateGlobalInstanceTypeCheck(MacroAssembler* masm, + Register type, + Label* global_object) { + __ Cmp(type, JS_GLOBAL_OBJECT_TYPE); + __ Ccmp(type, JS_BUILTINS_OBJECT_TYPE, ZFlag, ne); + __ Ccmp(type, JS_GLOBAL_PROXY_TYPE, ZFlag, ne); + __ B(eq, global_object); +} + + +// Generated code falls through if the receiver is a regular non-global +// JS object with slow properties and no interceptors. +// +// "receiver" holds the receiver on entry and is unchanged. +// "elements" holds the property dictionary on fall through. +static void GenerateNameDictionaryReceiverCheck(MacroAssembler* masm, + Register receiver, + Register elements, + Register scratch0, + Register scratch1, + Label* miss) { + ASSERT(!AreAliased(receiver, elements, scratch0, scratch1)); + + // Check that the receiver isn't a smi. + __ JumpIfSmi(receiver, miss); + + // Check that the receiver is a valid JS object. + // Let t be the object instance type, we want: + // FIRST_SPEC_OBJECT_TYPE <= t <= LAST_SPEC_OBJECT_TYPE. + // Since LAST_SPEC_OBJECT_TYPE is the last possible instance type we only + // check the lower bound. + STATIC_ASSERT(LAST_TYPE == LAST_SPEC_OBJECT_TYPE); + + __ JumpIfObjectType(receiver, scratch0, scratch1, FIRST_SPEC_OBJECT_TYPE, + miss, lt); + + // scratch0 now contains the map of the receiver and scratch1 the object type. + Register map = scratch0; + Register type = scratch1; + + // Check if the receiver is a global JS object. + GenerateGlobalInstanceTypeCheck(masm, type, miss); + + // Check that the object does not require access checks. + __ Ldrb(scratch1, FieldMemOperand(map, Map::kBitFieldOffset)); + __ Tbnz(scratch1, Map::kIsAccessCheckNeeded, miss); + __ Tbnz(scratch1, Map::kHasNamedInterceptor, miss); + + // Check that the properties dictionary is valid. + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kPropertiesOffset)); + __ Ldr(scratch1, FieldMemOperand(elements, HeapObject::kMapOffset)); + __ JumpIfNotRoot(scratch1, Heap::kHashTableMapRootIndex, miss); +} + + +// Helper function used from LoadIC GenerateNormal. +// +// elements: Property dictionary. It is not clobbered if a jump to the miss +// label is done. +// name: Property name. It is not clobbered if a jump to the miss label is +// done +// result: Register for the result. It is only updated if a jump to the miss +// label is not done. +// The scratch registers need to be different from elements, name and result. +// The generated code assumes that the receiver has slow properties, +// is not a global object and does not have interceptors. +static void GenerateDictionaryLoad(MacroAssembler* masm, + Label* miss, + Register elements, + Register name, + Register result, + Register scratch1, + Register scratch2) { + ASSERT(!AreAliased(elements, name, scratch1, scratch2)); + ASSERT(!AreAliased(result, scratch1, scratch2)); + + Label done; + + // Probe the dictionary. + NameDictionaryLookupStub::GeneratePositiveLookup(masm, + miss, + &done, + elements, + name, + scratch1, + scratch2); + + // If probing finds an entry check that the value is a normal property. + __ Bind(&done); + + static const int kElementsStartOffset = NameDictionary::kHeaderSize + + NameDictionary::kElementsStartIndex * kPointerSize; + static const int kDetailsOffset = kElementsStartOffset + 2 * kPointerSize; + __ Ldr(scratch1, FieldMemOperand(scratch2, kDetailsOffset)); + __ Tst(scratch1, Smi::FromInt(PropertyDetails::TypeField::kMask)); + __ B(ne, miss); + + // Get the value at the masked, scaled index and return. + __ Ldr(result, + FieldMemOperand(scratch2, kElementsStartOffset + 1 * kPointerSize)); +} + + +// Helper function used from StoreIC::GenerateNormal. +// +// elements: Property dictionary. It is not clobbered if a jump to the miss +// label is done. +// name: Property name. It is not clobbered if a jump to the miss label is +// done +// value: The value to store (never clobbered). +// +// The generated code assumes that the receiver has slow properties, +// is not a global object and does not have interceptors. +static void GenerateDictionaryStore(MacroAssembler* masm, + Label* miss, + Register elements, + Register name, + Register value, + Register scratch1, + Register scratch2) { + ASSERT(!AreAliased(elements, name, value, scratch1, scratch2)); + + Label done; + + // Probe the dictionary. + NameDictionaryLookupStub::GeneratePositiveLookup(masm, + miss, + &done, + elements, + name, + scratch1, + scratch2); + + // If probing finds an entry in the dictionary check that the value + // is a normal property that is not read only. + __ Bind(&done); + + static const int kElementsStartOffset = NameDictionary::kHeaderSize + + NameDictionary::kElementsStartIndex * kPointerSize; + static const int kDetailsOffset = kElementsStartOffset + 2 * kPointerSize; + static const int kTypeAndReadOnlyMask = + PropertyDetails::TypeField::kMask | + PropertyDetails::AttributesField::encode(READ_ONLY); + __ Ldrsw(scratch1, UntagSmiFieldMemOperand(scratch2, kDetailsOffset)); + __ Tst(scratch1, kTypeAndReadOnlyMask); + __ B(ne, miss); + + // Store the value at the masked, scaled index and return. + static const int kValueOffset = kElementsStartOffset + kPointerSize; + __ Add(scratch2, scratch2, kValueOffset - kHeapObjectTag); + __ Str(value, MemOperand(scratch2)); + + // Update the write barrier. Make sure not to clobber the value. + __ Mov(scratch1, value); + __ RecordWrite( + elements, scratch2, scratch1, kLRHasNotBeenSaved, kDontSaveFPRegs); +} + + +// Checks the receiver for special cases (value type, slow case bits). +// Falls through for regular JS object and return the map of the +// receiver in 'map_scratch' if the receiver is not a SMI. +static void GenerateKeyedLoadReceiverCheck(MacroAssembler* masm, + Register receiver, + Register map_scratch, + Register scratch, + int interceptor_bit, + Label* slow) { + ASSERT(!AreAliased(map_scratch, scratch)); + + // Check that the object isn't a smi. + __ JumpIfSmi(receiver, slow); + // Get the map of the receiver. + __ Ldr(map_scratch, FieldMemOperand(receiver, HeapObject::kMapOffset)); + // Check bit field. + __ Ldrb(scratch, FieldMemOperand(map_scratch, Map::kBitFieldOffset)); + __ Tbnz(scratch, Map::kIsAccessCheckNeeded, slow); + __ Tbnz(scratch, interceptor_bit, slow); + + // Check that the object is some kind of JS object EXCEPT JS Value type. + // In the case that the object is a value-wrapper object, we enter the + // runtime system to make sure that indexing into string objects work + // as intended. + STATIC_ASSERT(JS_OBJECT_TYPE > JS_VALUE_TYPE); + __ Ldrb(scratch, FieldMemOperand(map_scratch, Map::kInstanceTypeOffset)); + __ Cmp(scratch, JS_OBJECT_TYPE); + __ B(lt, slow); +} + + +// Loads an indexed element from a fast case array. +// If not_fast_array is NULL, doesn't perform the elements map check. +// +// receiver - holds the receiver on entry. +// Unchanged unless 'result' is the same register. +// +// key - holds the smi key on entry. +// Unchanged unless 'result' is the same register. +// +// elements - holds the elements of the receiver on exit. +// +// elements_map - holds the elements map on exit if the not_fast_array branch is +// taken. Otherwise, this is used as a scratch register. +// +// result - holds the result on exit if the load succeeded. +// Allowed to be the the same as 'receiver' or 'key'. +// Unchanged on bailout so 'receiver' and 'key' can be safely +// used by further computation. +static void GenerateFastArrayLoad(MacroAssembler* masm, + Register receiver, + Register key, + Register elements, + Register elements_map, + Register scratch2, + Register result, + Label* not_fast_array, + Label* slow) { + ASSERT(!AreAliased(receiver, key, elements, elements_map, scratch2)); + + // Check for fast array. + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + if (not_fast_array != NULL) { + // Check that the object is in fast mode and writable. + __ Ldr(elements_map, FieldMemOperand(elements, HeapObject::kMapOffset)); + __ JumpIfNotRoot(elements_map, Heap::kFixedArrayMapRootIndex, + not_fast_array); + } else { + __ AssertFastElements(elements); + } + + // The elements_map register is only used for the not_fast_array path, which + // was handled above. From this point onward it is a scratch register. + Register scratch1 = elements_map; + + // Check that the key (index) is within bounds. + __ Ldr(scratch1, FieldMemOperand(elements, FixedArray::kLengthOffset)); + __ Cmp(key, scratch1); + __ B(hs, slow); + + // Fast case: Do the load. + __ Add(scratch1, elements, FixedArray::kHeaderSize - kHeapObjectTag); + __ SmiUntag(scratch2, key); + __ Ldr(scratch2, MemOperand(scratch1, scratch2, LSL, kPointerSizeLog2)); + + // In case the loaded value is the_hole we have to consult GetProperty + // to ensure the prototype chain is searched. + __ JumpIfRoot(scratch2, Heap::kTheHoleValueRootIndex, slow); + + // Move the value to the result register. + // 'result' can alias with 'receiver' or 'key' but these two must be + // preserved if we jump to 'slow'. + __ Mov(result, scratch2); +} + + +// Checks whether a key is an array index string or a unique name. +// Falls through if a key is a unique name. +// The map of the key is returned in 'map_scratch'. +// If the jump to 'index_string' is done the hash of the key is left +// in 'hash_scratch'. +static void GenerateKeyNameCheck(MacroAssembler* masm, + Register key, + Register map_scratch, + Register hash_scratch, + Label* index_string, + Label* not_unique) { + ASSERT(!AreAliased(key, map_scratch, hash_scratch)); + + // Is the key a name? + Label unique; + __ JumpIfObjectType(key, map_scratch, hash_scratch, LAST_UNIQUE_NAME_TYPE, + not_unique, hi); + STATIC_ASSERT(LAST_UNIQUE_NAME_TYPE == FIRST_NONSTRING_TYPE); + __ B(eq, &unique); + + // Is the string an array index with cached numeric value? + __ Ldr(hash_scratch.W(), FieldMemOperand(key, Name::kHashFieldOffset)); + __ TestAndBranchIfAllClear(hash_scratch, + Name::kContainsCachedArrayIndexMask, + index_string); + + // Is the string internalized? We know it's a string, so a single bit test is + // enough. + __ Ldrb(hash_scratch, FieldMemOperand(map_scratch, Map::kInstanceTypeOffset)); + STATIC_ASSERT(kInternalizedTag == 0); + __ TestAndBranchIfAnySet(hash_scratch, kIsNotInternalizedMask, not_unique); + + __ Bind(&unique); + // Fall through if the key is a unique name. +} + + +// Neither 'object' nor 'key' are modified by this function. +// +// If the 'unmapped_case' or 'slow_case' exit is taken, the 'map' register is +// left with the object's elements map. Otherwise, it is used as a scratch +// register. +static MemOperand GenerateMappedArgumentsLookup(MacroAssembler* masm, + Register object, + Register key, + Register map, + Register scratch1, + Register scratch2, + Label* unmapped_case, + Label* slow_case) { + ASSERT(!AreAliased(object, key, map, scratch1, scratch2)); + + Heap* heap = masm->isolate()->heap(); + + // Check that the receiver is a JSObject. Because of the elements + // map check later, we do not need to check for interceptors or + // whether it requires access checks. + __ JumpIfSmi(object, slow_case); + // Check that the object is some kind of JSObject. + __ JumpIfObjectType(object, map, scratch1, FIRST_JS_RECEIVER_TYPE, + slow_case, lt); + + // Check that the key is a positive smi. + __ JumpIfNotSmi(key, slow_case); + __ Tbnz(key, kXSignBit, slow_case); + + // Load the elements object and check its map. + Handle<Map> arguments_map(heap->sloppy_arguments_elements_map()); + __ Ldr(map, FieldMemOperand(object, JSObject::kElementsOffset)); + __ CheckMap(map, scratch1, arguments_map, slow_case, DONT_DO_SMI_CHECK); + + // Check if element is in the range of mapped arguments. If not, jump + // to the unmapped lookup. + __ Ldr(scratch1, FieldMemOperand(map, FixedArray::kLengthOffset)); + __ Sub(scratch1, scratch1, Smi::FromInt(2)); + __ Cmp(key, scratch1); + __ B(hs, unmapped_case); + + // Load element index and check whether it is the hole. + static const int offset = + FixedArray::kHeaderSize + 2 * kPointerSize - kHeapObjectTag; + + __ Add(scratch1, map, offset); + __ SmiUntag(scratch2, key); + __ Ldr(scratch1, MemOperand(scratch1, scratch2, LSL, kPointerSizeLog2)); + __ JumpIfRoot(scratch1, Heap::kTheHoleValueRootIndex, unmapped_case); + + // Load value from context and return it. + __ Ldr(scratch2, FieldMemOperand(map, FixedArray::kHeaderSize)); + __ SmiUntag(scratch1); + __ Lsl(scratch1, scratch1, kPointerSizeLog2); + __ Add(scratch1, scratch1, Context::kHeaderSize - kHeapObjectTag); + // The base of the result (scratch2) is passed to RecordWrite in + // KeyedStoreIC::GenerateSloppyArguments and it must be a HeapObject. + return MemOperand(scratch2, scratch1); +} + + +// The 'parameter_map' register must be loaded with the parameter map of the +// arguments object and is overwritten. +static MemOperand GenerateUnmappedArgumentsLookup(MacroAssembler* masm, + Register key, + Register parameter_map, + Register scratch, + Label* slow_case) { + ASSERT(!AreAliased(key, parameter_map, scratch)); + + // Element is in arguments backing store, which is referenced by the + // second element of the parameter_map. + const int kBackingStoreOffset = FixedArray::kHeaderSize + kPointerSize; + Register backing_store = parameter_map; + __ Ldr(backing_store, FieldMemOperand(parameter_map, kBackingStoreOffset)); + Handle<Map> fixed_array_map(masm->isolate()->heap()->fixed_array_map()); + __ CheckMap( + backing_store, scratch, fixed_array_map, slow_case, DONT_DO_SMI_CHECK); + __ Ldr(scratch, FieldMemOperand(backing_store, FixedArray::kLengthOffset)); + __ Cmp(key, scratch); + __ B(hs, slow_case); + + __ Add(backing_store, + backing_store, + FixedArray::kHeaderSize - kHeapObjectTag); + __ SmiUntag(scratch, key); + return MemOperand(backing_store, scratch, LSL, kPointerSizeLog2); +} + + +void LoadIC::GenerateMegamorphic(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x2 : name + // -- lr : return address + // -- x0 : receiver + // ----------------------------------- + + // Probe the stub cache. + Code::Flags flags = Code::ComputeHandlerFlags(Code::LOAD_IC); + masm->isolate()->stub_cache()->GenerateProbe( + masm, flags, x0, x2, x3, x4, x5, x6); + + // Cache miss: Jump to runtime. + GenerateMiss(masm); +} + + +void LoadIC::GenerateNormal(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x2 : name + // -- lr : return address + // -- x0 : receiver + // ----------------------------------- + Label miss, slow; + + GenerateNameDictionaryReceiverCheck(masm, x0, x1, x3, x4, &miss); + + // x1 now holds the property dictionary. + GenerateDictionaryLoad(masm, &slow, x1, x2, x0, x3, x4); + __ Ret(); + + // Dictionary load failed, go slow (but don't miss). + __ Bind(&slow); + GenerateRuntimeGetProperty(masm); + + // Cache miss: Jump to runtime. + __ Bind(&miss); + GenerateMiss(masm); +} + + +void LoadIC::GenerateMiss(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x2 : name + // -- lr : return address + // -- x0 : receiver + // ----------------------------------- + Isolate* isolate = masm->isolate(); + ASM_LOCATION("LoadIC::GenerateMiss"); + + __ IncrementCounter(isolate->counters()->load_miss(), 1, x3, x4); + + // Perform tail call to the entry. + __ Push(x0, x2); + ExternalReference ref = + ExternalReference(IC_Utility(kLoadIC_Miss), isolate); + __ TailCallExternalReference(ref, 2, 1); +} + + +void LoadIC::GenerateRuntimeGetProperty(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- x2 : name + // -- lr : return address + // -- x0 : receiver + // ----------------------------------- + + __ Push(x0, x2); + __ TailCallRuntime(Runtime::kGetProperty, 2, 1); +} + + +void KeyedLoadIC::GenerateSloppyArguments(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key + // -- x1 : receiver + // ----------------------------------- + Register result = x0; + Register key = x0; + Register receiver = x1; + Label miss, unmapped; + + Register map_scratch = x2; + MemOperand mapped_location = GenerateMappedArgumentsLookup( + masm, receiver, key, map_scratch, x3, x4, &unmapped, &miss); + __ Ldr(result, mapped_location); + __ Ret(); + + __ Bind(&unmapped); + // Parameter map is left in map_scratch when a jump on unmapped is done. + MemOperand unmapped_location = + GenerateUnmappedArgumentsLookup(masm, key, map_scratch, x3, &miss); + __ Ldr(x2, unmapped_location); + __ JumpIfRoot(x2, Heap::kTheHoleValueRootIndex, &miss); + // Move the result in x0. x0 must be preserved on miss. + __ Mov(result, x2); + __ Ret(); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void KeyedStoreIC::GenerateSloppyArguments(MacroAssembler* masm) { + ASM_LOCATION("KeyedStoreIC::GenerateSloppyArguments"); + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // ----------------------------------- + + Label slow, notin; + + Register value = x0; + Register key = x1; + Register receiver = x2; + Register map = x3; + + // These registers are used by GenerateMappedArgumentsLookup to build a + // MemOperand. They are live for as long as the MemOperand is live. + Register mapped1 = x4; + Register mapped2 = x5; + + MemOperand mapped = + GenerateMappedArgumentsLookup(masm, receiver, key, map, + mapped1, mapped2, + ¬in, &slow); + Operand mapped_offset = mapped.OffsetAsOperand(); + __ Str(value, mapped); + __ Add(x10, mapped.base(), mapped_offset); + __ Mov(x11, value); + __ RecordWrite(mapped.base(), x10, x11, kLRHasNotBeenSaved, kDontSaveFPRegs); + __ Ret(); + + __ Bind(¬in); + + // These registers are used by GenerateMappedArgumentsLookup to build a + // MemOperand. They are live for as long as the MemOperand is live. + Register unmapped1 = map; // This is assumed to alias 'map'. + Register unmapped2 = x4; + MemOperand unmapped = + GenerateUnmappedArgumentsLookup(masm, key, unmapped1, unmapped2, &slow); + Operand unmapped_offset = unmapped.OffsetAsOperand(); + __ Str(value, unmapped); + __ Add(x10, unmapped.base(), unmapped_offset); + __ Mov(x11, value); + __ RecordWrite(unmapped.base(), x10, x11, + kLRHasNotBeenSaved, kDontSaveFPRegs); + __ Ret(); + __ Bind(&slow); + GenerateMiss(masm); +} + + +void KeyedLoadIC::GenerateMiss(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key + // -- x1 : receiver + // ----------------------------------- + Isolate* isolate = masm->isolate(); + + __ IncrementCounter(isolate->counters()->keyed_load_miss(), 1, x10, x11); + + __ Push(x1, x0); + + // Perform tail call to the entry. + ExternalReference ref = + ExternalReference(IC_Utility(kKeyedLoadIC_Miss), isolate); + + __ TailCallExternalReference(ref, 2, 1); +} + + +void KeyedLoadIC::GenerateRuntimeGetProperty(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key + // -- x1 : receiver + // ----------------------------------- + Register key = x0; + Register receiver = x1; + + __ Push(receiver, key); + __ TailCallRuntime(Runtime::kKeyedGetProperty, 2, 1); +} + + +static void GenerateKeyedLoadWithSmiKey(MacroAssembler* masm, + Register key, + Register receiver, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4, + Register scratch5, + Label *slow) { + ASSERT(!AreAliased( + key, receiver, scratch1, scratch2, scratch3, scratch4, scratch5)); + + Isolate* isolate = masm->isolate(); + Label check_number_dictionary; + // If we can load the value, it should be returned in x0. + Register result = x0; + + GenerateKeyedLoadReceiverCheck( + masm, receiver, scratch1, scratch2, Map::kHasIndexedInterceptor, slow); + + // Check the receiver's map to see if it has fast elements. + __ CheckFastElements(scratch1, scratch2, &check_number_dictionary); + + GenerateFastArrayLoad( + masm, receiver, key, scratch3, scratch2, scratch1, result, NULL, slow); + __ IncrementCounter( + isolate->counters()->keyed_load_generic_smi(), 1, scratch1, scratch2); + __ Ret(); + + __ Bind(&check_number_dictionary); + __ Ldr(scratch3, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ Ldr(scratch2, FieldMemOperand(scratch3, JSObject::kMapOffset)); + + // Check whether we have a number dictionary. + __ JumpIfNotRoot(scratch2, Heap::kHashTableMapRootIndex, slow); + + __ LoadFromNumberDictionary( + slow, scratch3, key, result, scratch1, scratch2, scratch4, scratch5); + __ Ret(); +} + +static void GenerateKeyedLoadWithNameKey(MacroAssembler* masm, + Register key, + Register receiver, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4, + Register scratch5, + Label *slow) { + ASSERT(!AreAliased( + key, receiver, scratch1, scratch2, scratch3, scratch4, scratch5)); + + Isolate* isolate = masm->isolate(); + Label probe_dictionary, property_array_property; + // If we can load the value, it should be returned in x0. + Register result = x0; + + GenerateKeyedLoadReceiverCheck( + masm, receiver, scratch1, scratch2, Map::kHasNamedInterceptor, slow); + + // If the receiver is a fast-case object, check the keyed lookup cache. + // Otherwise probe the dictionary. + __ Ldr(scratch2, FieldMemOperand(receiver, JSObject::kPropertiesOffset)); + __ Ldr(scratch3, FieldMemOperand(scratch2, HeapObject::kMapOffset)); + __ JumpIfRoot(scratch3, Heap::kHashTableMapRootIndex, &probe_dictionary); + + // We keep the map of the receiver in scratch1. + Register receiver_map = scratch1; + + // Load the map of the receiver, compute the keyed lookup cache hash + // based on 32 bits of the map pointer and the name hash. + __ Ldr(receiver_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ Mov(scratch2, Operand(receiver_map, ASR, KeyedLookupCache::kMapHashShift)); + __ Ldr(scratch3.W(), FieldMemOperand(key, Name::kHashFieldOffset)); + __ Eor(scratch2, scratch2, Operand(scratch3, ASR, Name::kHashShift)); + int mask = KeyedLookupCache::kCapacityMask & KeyedLookupCache::kHashMask; + __ And(scratch2, scratch2, mask); + + // Load the key (consisting of map and unique name) from the cache and + // check for match. + Label load_in_object_property; + static const int kEntriesPerBucket = KeyedLookupCache::kEntriesPerBucket; + Label hit_on_nth_entry[kEntriesPerBucket]; + ExternalReference cache_keys = + ExternalReference::keyed_lookup_cache_keys(isolate); + + __ Mov(scratch3, cache_keys); + __ Add(scratch3, scratch3, Operand(scratch2, LSL, kPointerSizeLog2 + 1)); + + for (int i = 0; i < kEntriesPerBucket - 1; i++) { + Label try_next_entry; + // Load map and make scratch3 pointing to the next entry. + __ Ldr(scratch4, MemOperand(scratch3, kPointerSize * 2, PostIndex)); + __ Cmp(receiver_map, scratch4); + __ B(ne, &try_next_entry); + __ Ldr(scratch4, MemOperand(scratch3, -kPointerSize)); // Load name + __ Cmp(key, scratch4); + __ B(eq, &hit_on_nth_entry[i]); + __ Bind(&try_next_entry); + } + + // Last entry. + __ Ldr(scratch4, MemOperand(scratch3, kPointerSize, PostIndex)); + __ Cmp(receiver_map, scratch4); + __ B(ne, slow); + __ Ldr(scratch4, MemOperand(scratch3)); + __ Cmp(key, scratch4); + __ B(ne, slow); + + // Get field offset. + ExternalReference cache_field_offsets = + ExternalReference::keyed_lookup_cache_field_offsets(isolate); + + // Hit on nth entry. + for (int i = kEntriesPerBucket - 1; i >= 0; i--) { + __ Bind(&hit_on_nth_entry[i]); + __ Mov(scratch3, cache_field_offsets); + if (i != 0) { + __ Add(scratch2, scratch2, i); + } + __ Ldr(scratch4.W(), MemOperand(scratch3, scratch2, LSL, 2)); + __ Ldrb(scratch5, + FieldMemOperand(receiver_map, Map::kInObjectPropertiesOffset)); + __ Subs(scratch4, scratch4, scratch5); + __ B(ge, &property_array_property); + if (i != 0) { + __ B(&load_in_object_property); + } + } + + // Load in-object property. + __ Bind(&load_in_object_property); + __ Ldrb(scratch5, FieldMemOperand(receiver_map, Map::kInstanceSizeOffset)); + __ Add(scratch5, scratch5, scratch4); // Index from start of object. + __ Sub(receiver, receiver, kHeapObjectTag); // Remove the heap tag. + __ Ldr(result, MemOperand(receiver, scratch5, LSL, kPointerSizeLog2)); + __ IncrementCounter(isolate->counters()->keyed_load_generic_lookup_cache(), + 1, scratch1, scratch2); + __ Ret(); + + // Load property array property. + __ Bind(&property_array_property); + __ Ldr(scratch1, FieldMemOperand(receiver, JSObject::kPropertiesOffset)); + __ Add(scratch1, scratch1, FixedArray::kHeaderSize - kHeapObjectTag); + __ Ldr(result, MemOperand(scratch1, scratch4, LSL, kPointerSizeLog2)); + __ IncrementCounter(isolate->counters()->keyed_load_generic_lookup_cache(), + 1, scratch1, scratch2); + __ Ret(); + + // Do a quick inline probe of the receiver's dictionary, if it exists. + __ Bind(&probe_dictionary); + __ Ldr(scratch1, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ Ldrb(scratch1, FieldMemOperand(scratch1, Map::kInstanceTypeOffset)); + GenerateGlobalInstanceTypeCheck(masm, scratch1, slow); + // Load the property. + GenerateDictionaryLoad(masm, slow, scratch2, key, result, scratch1, scratch3); + __ IncrementCounter(isolate->counters()->keyed_load_generic_symbol(), + 1, scratch1, scratch2); + __ Ret(); +} + + +void KeyedLoadIC::GenerateGeneric(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key + // -- x1 : receiver + // ----------------------------------- + Label slow, check_name, index_smi, index_name; + + Register key = x0; + Register receiver = x1; + + __ JumpIfNotSmi(key, &check_name); + __ Bind(&index_smi); + // Now the key is known to be a smi. This place is also jumped to from below + // where a numeric string is converted to a smi. + GenerateKeyedLoadWithSmiKey(masm, key, receiver, x2, x3, x4, x5, x6, &slow); + + // Slow case, key and receiver still in x0 and x1. + __ Bind(&slow); + __ IncrementCounter( + masm->isolate()->counters()->keyed_load_generic_slow(), 1, x2, x3); + GenerateRuntimeGetProperty(masm); + + __ Bind(&check_name); + GenerateKeyNameCheck(masm, key, x2, x3, &index_name, &slow); + + GenerateKeyedLoadWithNameKey(masm, key, receiver, x2, x3, x4, x5, x6, &slow); + + __ Bind(&index_name); + __ IndexFromHash(x3, key); + // Now jump to the place where smi keys are handled. + __ B(&index_smi); +} + + +void KeyedLoadIC::GenerateString(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key (index) + // -- x1 : receiver + // ----------------------------------- + Label miss; + + Register index = x0; + Register receiver = x1; + Register result = x0; + Register scratch = x3; + + StringCharAtGenerator char_at_generator(receiver, + index, + scratch, + result, + &miss, // When not a string. + &miss, // When not a number. + &miss, // When index out of range. + STRING_INDEX_IS_ARRAY_INDEX); + char_at_generator.GenerateFast(masm); + __ Ret(); + + StubRuntimeCallHelper call_helper; + char_at_generator.GenerateSlow(masm, call_helper); + + __ Bind(&miss); + GenerateMiss(masm); +} + + +void KeyedLoadIC::GenerateIndexedInterceptor(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key + // -- x1 : receiver + // ----------------------------------- + Label slow; + Register key = x0; + Register receiver = x1; + + // Check that the receiver isn't a smi. + __ JumpIfSmi(receiver, &slow); + + // Check that the key is an array index, that is Uint32. + __ TestAndBranchIfAnySet(key, kSmiTagMask | kSmiSignMask, &slow); + + // Get the map of the receiver. + Register map = x2; + __ Ldr(map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + + // Check that it has indexed interceptor and access checks + // are not enabled for this object. + __ Ldrb(x3, FieldMemOperand(map, Map::kBitFieldOffset)); + ASSERT(kSlowCaseBitFieldMask == + ((1 << Map::kIsAccessCheckNeeded) | (1 << Map::kHasIndexedInterceptor))); + __ Tbnz(x3, Map::kIsAccessCheckNeeded, &slow); + __ Tbz(x3, Map::kHasIndexedInterceptor, &slow); + + // Everything is fine, call runtime. + __ Push(receiver, key); + __ TailCallExternalReference( + ExternalReference(IC_Utility(kKeyedLoadPropertyWithInterceptor), + masm->isolate()), + 2, + 1); + + __ Bind(&slow); + GenerateMiss(masm); +} + + +void KeyedStoreIC::GenerateMiss(MacroAssembler* masm) { + ASM_LOCATION("KeyedStoreIC::GenerateMiss"); + // ---------- S t a t e -------------- + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // -- lr : return address + // ----------------------------------- + + // Push receiver, key and value for runtime call. + __ Push(x2, x1, x0); + + ExternalReference ref = + ExternalReference(IC_Utility(kKeyedStoreIC_Miss), masm->isolate()); + __ TailCallExternalReference(ref, 3, 1); +} + + +void KeyedStoreIC::GenerateSlow(MacroAssembler* masm) { + ASM_LOCATION("KeyedStoreIC::GenerateSlow"); + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // ----------------------------------- + + // Push receiver, key and value for runtime call. + __ Push(x2, x1, x0); + + // The slow case calls into the runtime to complete the store without causing + // an IC miss that would otherwise cause a transition to the generic stub. + ExternalReference ref = + ExternalReference(IC_Utility(kKeyedStoreIC_Slow), masm->isolate()); + __ TailCallExternalReference(ref, 3, 1); +} + + +void KeyedStoreIC::GenerateRuntimeSetProperty(MacroAssembler* masm, + StrictMode strict_mode) { + ASM_LOCATION("KeyedStoreIC::GenerateRuntimeSetProperty"); + // ---------- S t a t e -------------- + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // -- lr : return address + // ----------------------------------- + + // Push receiver, key and value for runtime call. + __ Push(x2, x1, x0); + + // Push PropertyAttributes(NONE) and strict_mode for runtime call. + STATIC_ASSERT(NONE == 0); + __ Mov(x10, Smi::FromInt(strict_mode)); + __ Push(xzr, x10); + + __ TailCallRuntime(Runtime::kSetProperty, 5, 1); +} + + +static void KeyedStoreGenerateGenericHelper( + MacroAssembler* masm, + Label* fast_object, + Label* fast_double, + Label* slow, + KeyedStoreCheckMap check_map, + KeyedStoreIncrementLength increment_length, + Register value, + Register key, + Register receiver, + Register receiver_map, + Register elements_map, + Register elements) { + ASSERT(!AreAliased( + value, key, receiver, receiver_map, elements_map, elements, x10, x11)); + + Label transition_smi_elements; + Label transition_double_elements; + Label fast_double_without_map_check; + Label non_double_value; + Label finish_store; + + __ Bind(fast_object); + if (check_map == kCheckMap) { + __ Ldr(elements_map, FieldMemOperand(elements, HeapObject::kMapOffset)); + __ Cmp(elements_map, + Operand(masm->isolate()->factory()->fixed_array_map())); + __ B(ne, fast_double); + } + + // HOLECHECK: guards "A[i] = V" + // We have to go to the runtime if the current value is the hole because there + // may be a callback on the element. + Label holecheck_passed; + __ Add(x10, elements, FixedArray::kHeaderSize - kHeapObjectTag); + __ Add(x10, x10, Operand::UntagSmiAndScale(key, kPointerSizeLog2)); + __ Ldr(x11, MemOperand(x10)); + __ JumpIfNotRoot(x11, Heap::kTheHoleValueRootIndex, &holecheck_passed); + __ JumpIfDictionaryInPrototypeChain(receiver, elements_map, x10, slow); + __ bind(&holecheck_passed); + + // Smi stores don't require further checks. + __ JumpIfSmi(value, &finish_store); + + // Escape to elements kind transition case. + __ CheckFastObjectElements(receiver_map, x10, &transition_smi_elements); + + __ Bind(&finish_store); + if (increment_length == kIncrementLength) { + // Add 1 to receiver->length. + __ Add(x10, key, Smi::FromInt(1)); + __ Str(x10, FieldMemOperand(receiver, JSArray::kLengthOffset)); + } + + Register address = x11; + __ Add(address, elements, FixedArray::kHeaderSize - kHeapObjectTag); + __ Add(address, address, Operand::UntagSmiAndScale(key, kPointerSizeLog2)); + __ Str(value, MemOperand(address)); + + Label dont_record_write; + __ JumpIfSmi(value, &dont_record_write); + + // Update write barrier for the elements array address. + __ Mov(x10, value); // Preserve the value which is returned. + __ RecordWrite(elements, + address, + x10, + kLRHasNotBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); + + __ Bind(&dont_record_write); + __ Ret(); + + + __ Bind(fast_double); + if (check_map == kCheckMap) { + // Check for fast double array case. If this fails, call through to the + // runtime. + __ JumpIfNotRoot(elements_map, Heap::kFixedDoubleArrayMapRootIndex, slow); + } + + // HOLECHECK: guards "A[i] double hole?" + // We have to see if the double version of the hole is present. If so go to + // the runtime. + __ Add(x10, elements, FixedDoubleArray::kHeaderSize - kHeapObjectTag); + __ Add(x10, x10, Operand::UntagSmiAndScale(key, kPointerSizeLog2)); + __ Ldr(x11, MemOperand(x10)); + __ CompareAndBranch(x11, kHoleNanInt64, ne, &fast_double_without_map_check); + __ JumpIfDictionaryInPrototypeChain(receiver, elements_map, x10, slow); + + __ Bind(&fast_double_without_map_check); + __ StoreNumberToDoubleElements(value, + key, + elements, + x10, + d0, + &transition_double_elements); + if (increment_length == kIncrementLength) { + // Add 1 to receiver->length. + __ Add(x10, key, Smi::FromInt(1)); + __ Str(x10, FieldMemOperand(receiver, JSArray::kLengthOffset)); + } + __ Ret(); + + + __ Bind(&transition_smi_elements); + // Transition the array appropriately depending on the value type. + __ Ldr(x10, FieldMemOperand(value, HeapObject::kMapOffset)); + __ JumpIfNotRoot(x10, Heap::kHeapNumberMapRootIndex, &non_double_value); + + // Value is a double. Transition FAST_SMI_ELEMENTS -> + // FAST_DOUBLE_ELEMENTS and complete the store. + __ LoadTransitionedArrayMapConditional(FAST_SMI_ELEMENTS, + FAST_DOUBLE_ELEMENTS, + receiver_map, + x10, + x11, + slow); + ASSERT(receiver_map.Is(x3)); // Transition code expects map in x3. + AllocationSiteMode mode = AllocationSite::GetMode(FAST_SMI_ELEMENTS, + FAST_DOUBLE_ELEMENTS); + ElementsTransitionGenerator::GenerateSmiToDouble(masm, mode, slow); + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ B(&fast_double_without_map_check); + + __ Bind(&non_double_value); + // Value is not a double, FAST_SMI_ELEMENTS -> FAST_ELEMENTS. + __ LoadTransitionedArrayMapConditional(FAST_SMI_ELEMENTS, + FAST_ELEMENTS, + receiver_map, + x10, + x11, + slow); + ASSERT(receiver_map.Is(x3)); // Transition code expects map in x3. + mode = AllocationSite::GetMode(FAST_SMI_ELEMENTS, FAST_ELEMENTS); + ElementsTransitionGenerator::GenerateMapChangeElementsTransition(masm, mode, + slow); + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ B(&finish_store); + + __ Bind(&transition_double_elements); + // Elements are FAST_DOUBLE_ELEMENTS, but value is an Object that's not a + // HeapNumber. Make sure that the receiver is a Array with FAST_ELEMENTS and + // transition array from FAST_DOUBLE_ELEMENTS to FAST_ELEMENTS + __ LoadTransitionedArrayMapConditional(FAST_DOUBLE_ELEMENTS, + FAST_ELEMENTS, + receiver_map, + x10, + x11, + slow); + ASSERT(receiver_map.Is(x3)); // Transition code expects map in x3. + mode = AllocationSite::GetMode(FAST_DOUBLE_ELEMENTS, FAST_ELEMENTS); + ElementsTransitionGenerator::GenerateDoubleToObject(masm, mode, slow); + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ B(&finish_store); +} + + +void KeyedStoreIC::GenerateGeneric(MacroAssembler* masm, + StrictMode strict_mode) { + ASM_LOCATION("KeyedStoreIC::GenerateGeneric"); + // ---------- S t a t e -------------- + // -- x0 : value + // -- x1 : key + // -- x2 : receiver + // -- lr : return address + // ----------------------------------- + Label slow; + Label array; + Label fast_object; + Label extra; + Label fast_object_grow; + Label fast_double_grow; + Label fast_double; + + Register value = x0; + Register key = x1; + Register receiver = x2; + Register receiver_map = x3; + Register elements = x4; + Register elements_map = x5; + + __ JumpIfNotSmi(key, &slow); + __ JumpIfSmi(receiver, &slow); + __ Ldr(receiver_map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + + // Check that the receiver does not require access checks and is not observed. + // The generic stub does not perform map checks or handle observed objects. + __ Ldrb(x10, FieldMemOperand(receiver_map, Map::kBitFieldOffset)); + __ TestAndBranchIfAnySet( + x10, (1 << Map::kIsAccessCheckNeeded) | (1 << Map::kIsObserved), &slow); + + // Check if the object is a JS array or not. + Register instance_type = x10; + __ CompareInstanceType(receiver_map, instance_type, JS_ARRAY_TYPE); + __ B(eq, &array); + // Check that the object is some kind of JSObject. + __ Cmp(instance_type, FIRST_JS_OBJECT_TYPE); + __ B(lt, &slow); + + // Object case: Check key against length in the elements array. + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + // Check array bounds. Both the key and the length of FixedArray are smis. + __ Ldrsw(x10, UntagSmiFieldMemOperand(elements, FixedArray::kLengthOffset)); + __ Cmp(x10, Operand::UntagSmi(key)); + __ B(hi, &fast_object); + + + __ Bind(&slow); + // Slow case, handle jump to runtime. + // Live values: + // x0: value + // x1: key + // x2: receiver + GenerateRuntimeSetProperty(masm, strict_mode); + + + __ Bind(&extra); + // Extra capacity case: Check if there is extra capacity to + // perform the store and update the length. Used for adding one + // element to the array by writing to array[array.length]. + + // Check for room in the elements backing store. + // Both the key and the length of FixedArray are smis. + __ Ldrsw(x10, UntagSmiFieldMemOperand(elements, FixedArray::kLengthOffset)); + __ Cmp(x10, Operand::UntagSmi(key)); + __ B(ls, &slow); + + __ Ldr(elements_map, FieldMemOperand(elements, HeapObject::kMapOffset)); + __ Cmp(elements_map, Operand(masm->isolate()->factory()->fixed_array_map())); + __ B(eq, &fast_object_grow); + __ Cmp(elements_map, + Operand(masm->isolate()->factory()->fixed_double_array_map())); + __ B(eq, &fast_double_grow); + __ B(&slow); + + + __ Bind(&array); + // Array case: Get the length and the elements array from the JS + // array. Check that the array is in fast mode (and writable); if it + // is the length is always a smi. + + __ Ldr(elements, FieldMemOperand(receiver, JSObject::kElementsOffset)); + + // Check the key against the length in the array. + __ Ldrsw(x10, UntagSmiFieldMemOperand(receiver, JSArray::kLengthOffset)); + __ Cmp(x10, Operand::UntagSmi(key)); + __ B(eq, &extra); // We can handle the case where we are appending 1 element. + __ B(lo, &slow); + + KeyedStoreGenerateGenericHelper(masm, &fast_object, &fast_double, + &slow, kCheckMap, kDontIncrementLength, + value, key, receiver, receiver_map, + elements_map, elements); + KeyedStoreGenerateGenericHelper(masm, &fast_object_grow, &fast_double_grow, + &slow, kDontCheckMap, kIncrementLength, + value, key, receiver, receiver_map, + elements_map, elements); +} + + +void StoreIC::GenerateMegamorphic(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : value + // -- x1 : receiver + // -- x2 : name + // -- lr : return address + // ----------------------------------- + + // Probe the stub cache. + Code::Flags flags = Code::ComputeHandlerFlags(Code::STORE_IC); + masm->isolate()->stub_cache()->GenerateProbe( + masm, flags, x1, x2, x3, x4, x5, x6); + + // Cache miss: Jump to runtime. + GenerateMiss(masm); +} + + +void StoreIC::GenerateMiss(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : value + // -- x1 : receiver + // -- x2 : name + // -- lr : return address + // ----------------------------------- + + __ Push(x1, x2, x0); + + // Tail call to the entry. + ExternalReference ref = + ExternalReference(IC_Utility(kStoreIC_Miss), masm->isolate()); + __ TailCallExternalReference(ref, 3, 1); +} + + +void StoreIC::GenerateNormal(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- x0 : value + // -- x1 : receiver + // -- x2 : name + // -- lr : return address + // ----------------------------------- + Label miss; + Register value = x0; + Register receiver = x1; + Register name = x2; + Register dictionary = x3; + + GenerateNameDictionaryReceiverCheck( + masm, receiver, dictionary, x4, x5, &miss); + + GenerateDictionaryStore(masm, &miss, dictionary, name, value, x4, x5); + Counters* counters = masm->isolate()->counters(); + __ IncrementCounter(counters->store_normal_hit(), 1, x4, x5); + __ Ret(); + + // Cache miss: Jump to runtime. + __ Bind(&miss); + __ IncrementCounter(counters->store_normal_miss(), 1, x4, x5); + GenerateMiss(masm); +} + + +void StoreIC::GenerateRuntimeSetProperty(MacroAssembler* masm, + StrictMode strict_mode) { + ASM_LOCATION("StoreIC::GenerateRuntimeSetProperty"); + // ----------- S t a t e ------------- + // -- x0 : value + // -- x1 : receiver + // -- x2 : name + // -- lr : return address + // ----------------------------------- + + __ Push(x1, x2, x0); + + __ Mov(x11, Smi::FromInt(NONE)); // PropertyAttributes + __ Mov(x10, Smi::FromInt(strict_mode)); + __ Push(x11, x10); + + // Do tail-call to runtime routine. + __ TailCallRuntime(Runtime::kSetProperty, 5, 1); +} + + +void StoreIC::GenerateSlow(MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- x0 : value + // -- x1 : receiver + // -- x2 : name + // -- lr : return address + // ----------------------------------- + + // Push receiver, name and value for runtime call. + __ Push(x1, x2, x0); + + // The slow case calls into the runtime to complete the store without causing + // an IC miss that would otherwise cause a transition to the generic stub. + ExternalReference ref = + ExternalReference(IC_Utility(kStoreIC_Slow), masm->isolate()); + __ TailCallExternalReference(ref, 3, 1); +} + + +Condition CompareIC::ComputeCondition(Token::Value op) { + switch (op) { + case Token::EQ_STRICT: + case Token::EQ: + return eq; + case Token::LT: + return lt; + case Token::GT: + return gt; + case Token::LTE: + return le; + case Token::GTE: + return ge; + default: + UNREACHABLE(); + return al; + } +} + + +bool CompareIC::HasInlinedSmiCode(Address address) { + // The address of the instruction following the call. + Address info_address = + Assembler::return_address_from_call_start(address); + + InstructionSequence* patch_info = InstructionSequence::At(info_address); + return patch_info->IsInlineData(); +} + + +// Activate a SMI fast-path by patching the instructions generated by +// JumpPatchSite::EmitJumpIf(Not)Smi(), using the information encoded by +// JumpPatchSite::EmitPatchInfo(). +void PatchInlinedSmiCode(Address address, InlinedSmiCheck check) { + // The patch information is encoded in the instruction stream using + // instructions which have no side effects, so we can safely execute them. + // The patch information is encoded directly after the call to the helper + // function which is requesting this patch operation. + Address info_address = + Assembler::return_address_from_call_start(address); + InlineSmiCheckInfo info(info_address); + + // Check and decode the patch information instruction. + if (!info.HasSmiCheck()) { + return; + } + + if (FLAG_trace_ic) { + PrintF("[ Patching ic at %p, marker=%p, SMI check=%p\n", + address, info_address, reinterpret_cast<void*>(info.SmiCheck())); + } + + // Patch and activate code generated by JumpPatchSite::EmitJumpIfNotSmi() + // and JumpPatchSite::EmitJumpIfSmi(). + // Changing + // tb(n)z xzr, #0, <target> + // to + // tb(!n)z test_reg, #0, <target> + Instruction* to_patch = info.SmiCheck(); + PatchingAssembler patcher(to_patch, 1); + ASSERT(to_patch->IsTestBranch()); + ASSERT(to_patch->ImmTestBranchBit5() == 0); + ASSERT(to_patch->ImmTestBranchBit40() == 0); + + STATIC_ASSERT(kSmiTag == 0); + STATIC_ASSERT(kSmiTagMask == 1); + + int branch_imm = to_patch->ImmTestBranch(); + Register smi_reg; + if (check == ENABLE_INLINED_SMI_CHECK) { + ASSERT(to_patch->Rt() == xzr.code()); + smi_reg = info.SmiRegister(); + } else { + ASSERT(check == DISABLE_INLINED_SMI_CHECK); + ASSERT(to_patch->Rt() != xzr.code()); + smi_reg = xzr; + } + + if (to_patch->Mask(TestBranchMask) == TBZ) { + // This is JumpIfNotSmi(smi_reg, branch_imm). + patcher.tbnz(smi_reg, 0, branch_imm); + } else { + ASSERT(to_patch->Mask(TestBranchMask) == TBNZ); + // This is JumpIfSmi(smi_reg, branch_imm). + patcher.tbz(smi_reg, 0, branch_imm); + } +} + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/instructions-arm64.cc b/chromium/v8/src/arm64/instructions-arm64.cc new file mode 100644 index 00000000000..c7334ed5cfc --- /dev/null +++ b/chromium/v8/src/arm64/instructions-arm64.cc @@ -0,0 +1,317 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#define ARM64_DEFINE_FP_STATICS + +#include "src/arm64/instructions-arm64.h" +#include "src/arm64/assembler-arm64-inl.h" + +namespace v8 { +namespace internal { + + +bool Instruction::IsLoad() const { + if (Mask(LoadStoreAnyFMask) != LoadStoreAnyFixed) { + return false; + } + + if (Mask(LoadStorePairAnyFMask) == LoadStorePairAnyFixed) { + return Mask(LoadStorePairLBit) != 0; + } else { + LoadStoreOp op = static_cast<LoadStoreOp>(Mask(LoadStoreOpMask)); + switch (op) { + case LDRB_w: + case LDRH_w: + case LDR_w: + case LDR_x: + case LDRSB_w: + case LDRSB_x: + case LDRSH_w: + case LDRSH_x: + case LDRSW_x: + case LDR_s: + case LDR_d: return true; + default: return false; + } + } +} + + +bool Instruction::IsStore() const { + if (Mask(LoadStoreAnyFMask) != LoadStoreAnyFixed) { + return false; + } + + if (Mask(LoadStorePairAnyFMask) == LoadStorePairAnyFixed) { + return Mask(LoadStorePairLBit) == 0; + } else { + LoadStoreOp op = static_cast<LoadStoreOp>(Mask(LoadStoreOpMask)); + switch (op) { + case STRB_w: + case STRH_w: + case STR_w: + case STR_x: + case STR_s: + case STR_d: return true; + default: return false; + } + } +} + + +static uint64_t RotateRight(uint64_t value, + unsigned int rotate, + unsigned int width) { + ASSERT(width <= 64); + rotate &= 63; + return ((value & ((1UL << rotate) - 1UL)) << (width - rotate)) | + (value >> rotate); +} + + +static uint64_t RepeatBitsAcrossReg(unsigned reg_size, + uint64_t value, + unsigned width) { + ASSERT((width == 2) || (width == 4) || (width == 8) || (width == 16) || + (width == 32)); + ASSERT((reg_size == kWRegSizeInBits) || (reg_size == kXRegSizeInBits)); + uint64_t result = value & ((1UL << width) - 1UL); + for (unsigned i = width; i < reg_size; i *= 2) { + result |= (result << i); + } + return result; +} + + +// Logical immediates can't encode zero, so a return value of zero is used to +// indicate a failure case. Specifically, where the constraints on imm_s are not +// met. +uint64_t Instruction::ImmLogical() { + unsigned reg_size = SixtyFourBits() ? kXRegSizeInBits : kWRegSizeInBits; + int64_t n = BitN(); + int64_t imm_s = ImmSetBits(); + int64_t imm_r = ImmRotate(); + + // An integer is constructed from the n, imm_s and imm_r bits according to + // 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. + // + + if (n == 1) { + if (imm_s == 0x3F) { + return 0; + } + uint64_t bits = (1UL << (imm_s + 1)) - 1; + return RotateRight(bits, imm_r, 64); + } else { + if ((imm_s >> 1) == 0x1F) { + return 0; + } + for (int width = 0x20; width >= 0x2; width >>= 1) { + if ((imm_s & width) == 0) { + int mask = width - 1; + if ((imm_s & mask) == mask) { + return 0; + } + uint64_t bits = (1UL << ((imm_s & mask) + 1)) - 1; + return RepeatBitsAcrossReg(reg_size, + RotateRight(bits, imm_r & mask, width), + width); + } + } + } + UNREACHABLE(); + return 0; +} + + +float Instruction::ImmFP32() { + // ImmFP: abcdefgh (8 bits) + // Single: aBbb.bbbc.defg.h000.0000.0000.0000.0000 (32 bits) + // where B is b ^ 1 + uint32_t bits = ImmFP(); + uint32_t bit7 = (bits >> 7) & 0x1; + uint32_t bit6 = (bits >> 6) & 0x1; + uint32_t bit5_to_0 = bits & 0x3f; + uint32_t result = (bit7 << 31) | ((32 - bit6) << 25) | (bit5_to_0 << 19); + + return rawbits_to_float(result); +} + + +double Instruction::ImmFP64() { + // ImmFP: abcdefgh (8 bits) + // Double: aBbb.bbbb.bbcd.efgh.0000.0000.0000.0000 + // 0000.0000.0000.0000.0000.0000.0000.0000 (64 bits) + // where B is b ^ 1 + uint32_t bits = ImmFP(); + uint64_t bit7 = (bits >> 7) & 0x1; + uint64_t bit6 = (bits >> 6) & 0x1; + uint64_t bit5_to_0 = bits & 0x3f; + uint64_t result = (bit7 << 63) | ((256 - bit6) << 54) | (bit5_to_0 << 48); + + return rawbits_to_double(result); +} + + +LSDataSize CalcLSPairDataSize(LoadStorePairOp op) { + switch (op) { + case STP_x: + case LDP_x: + case STP_d: + case LDP_d: return LSDoubleWord; + default: return LSWord; + } +} + + +ptrdiff_t Instruction::ImmPCOffset() { + ptrdiff_t offset; + if (IsPCRelAddressing()) { + // PC-relative addressing. Only ADR is supported. + offset = ImmPCRel(); + } else if (BranchType() != UnknownBranchType) { + // All PC-relative branches. + // Relative branch offsets are instruction-size-aligned. + offset = ImmBranch() << kInstructionSizeLog2; + } else { + // Load literal (offset from PC). + ASSERT(IsLdrLiteral()); + // The offset is always shifted by 2 bits, even for loads to 64-bits + // registers. + offset = ImmLLiteral() << kInstructionSizeLog2; + } + return offset; +} + + +Instruction* Instruction::ImmPCOffsetTarget() { + return InstructionAtOffset(ImmPCOffset()); +} + + +bool Instruction::IsValidImmPCOffset(ImmBranchType branch_type, + int32_t offset) { + return is_intn(offset, ImmBranchRangeBitwidth(branch_type)); +} + + +bool Instruction::IsTargetInImmPCOffsetRange(Instruction* target) { + return IsValidImmPCOffset(BranchType(), DistanceTo(target)); +} + + +void Instruction::SetImmPCOffsetTarget(Instruction* target) { + if (IsPCRelAddressing()) { + SetPCRelImmTarget(target); + } else if (BranchType() != UnknownBranchType) { + SetBranchImmTarget(target); + } else { + SetImmLLiteral(target); + } +} + + +void Instruction::SetPCRelImmTarget(Instruction* target) { + // ADRP is not supported, so 'this' must point to an ADR instruction. + ASSERT(IsAdr()); + + int target_offset = DistanceTo(target); + Instr imm; + if (Instruction::IsValidPCRelOffset(target_offset)) { + imm = Assembler::ImmPCRelAddress(target_offset); + SetInstructionBits(Mask(~ImmPCRel_mask) | imm); + } else { + PatchingAssembler patcher(this, + PatchingAssembler::kAdrFarPatchableNInstrs); + patcher.PatchAdrFar(target); + } +} + + +void Instruction::SetBranchImmTarget(Instruction* target) { + ASSERT(IsAligned(DistanceTo(target), kInstructionSize)); + Instr branch_imm = 0; + uint32_t imm_mask = 0; + ptrdiff_t offset = DistanceTo(target) >> kInstructionSizeLog2; + switch (BranchType()) { + case CondBranchType: { + branch_imm = Assembler::ImmCondBranch(offset); + imm_mask = ImmCondBranch_mask; + break; + } + case UncondBranchType: { + branch_imm = Assembler::ImmUncondBranch(offset); + imm_mask = ImmUncondBranch_mask; + break; + } + case CompareBranchType: { + branch_imm = Assembler::ImmCmpBranch(offset); + imm_mask = ImmCmpBranch_mask; + break; + } + case TestBranchType: { + branch_imm = Assembler::ImmTestBranch(offset); + imm_mask = ImmTestBranch_mask; + break; + } + default: UNREACHABLE(); + } + SetInstructionBits(Mask(~imm_mask) | branch_imm); +} + + +void Instruction::SetImmLLiteral(Instruction* source) { + ASSERT(IsAligned(DistanceTo(source), kInstructionSize)); + ptrdiff_t offset = DistanceTo(source) >> kLoadLiteralScaleLog2; + Instr imm = Assembler::ImmLLiteral(offset); + Instr mask = ImmLLiteral_mask; + + SetInstructionBits(Mask(~mask) | imm); +} + + +// TODO(jbramley): We can't put this inline in the class because things like +// xzr and Register are not defined in that header. Consider adding +// instructions-arm64-inl.h to work around this. +bool InstructionSequence::IsInlineData() const { + // Inline data is encoded as a single movz instruction which writes to xzr + // (x31). + return IsMovz() && SixtyFourBits() && (Rd() == xzr.code()); + // TODO(all): If we extend ::InlineData() to support bigger data, we need + // to update this method too. +} + + +// TODO(jbramley): We can't put this inline in the class because things like +// xzr and Register are not defined in that header. Consider adding +// instructions-arm64-inl.h to work around this. +uint64_t InstructionSequence::InlineData() const { + ASSERT(IsInlineData()); + uint64_t payload = ImmMoveWide(); + // TODO(all): If we extend ::InlineData() to support bigger data, we need + // to update this method too. + return payload; +} + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/instructions-arm64.h b/chromium/v8/src/arm64/instructions-arm64.h new file mode 100644 index 00000000000..b3d9b794acb --- /dev/null +++ b/chromium/v8/src/arm64/instructions-arm64.h @@ -0,0 +1,509 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_INSTRUCTIONS_ARM64_H_ +#define V8_ARM64_INSTRUCTIONS_ARM64_H_ + +#include "src/globals.h" +#include "src/utils.h" +#include "src/arm64/constants-arm64.h" +#include "src/arm64/utils-arm64.h" + +namespace v8 { +namespace internal { + + +// ISA constants. -------------------------------------------------------------- + +typedef uint32_t Instr; + +// The following macros initialize a float/double variable with a bit pattern +// without using static initializers: If ARM64_DEFINE_FP_STATICS is defined, the +// symbol is defined as uint32_t/uint64_t initialized with the desired bit +// pattern. Otherwise, the same symbol is declared as an external float/double. +#if defined(ARM64_DEFINE_FP_STATICS) +#define DEFINE_FLOAT(name, value) extern const uint32_t name = value +#define DEFINE_DOUBLE(name, value) extern const uint64_t name = value +#else +#define DEFINE_FLOAT(name, value) extern const float name +#define DEFINE_DOUBLE(name, value) extern const double name +#endif // defined(ARM64_DEFINE_FP_STATICS) + +DEFINE_FLOAT(kFP32PositiveInfinity, 0x7f800000); +DEFINE_FLOAT(kFP32NegativeInfinity, 0xff800000); +DEFINE_DOUBLE(kFP64PositiveInfinity, 0x7ff0000000000000UL); +DEFINE_DOUBLE(kFP64NegativeInfinity, 0xfff0000000000000UL); + +// This value is a signalling NaN as both a double and as a float (taking the +// least-significant word). +DEFINE_DOUBLE(kFP64SignallingNaN, 0x7ff000007f800001); +DEFINE_FLOAT(kFP32SignallingNaN, 0x7f800001); + +// A similar value, but as a quiet NaN. +DEFINE_DOUBLE(kFP64QuietNaN, 0x7ff800007fc00001); +DEFINE_FLOAT(kFP32QuietNaN, 0x7fc00001); + +// The default NaN values (for FPCR.DN=1). +DEFINE_DOUBLE(kFP64DefaultNaN, 0x7ff8000000000000UL); +DEFINE_FLOAT(kFP32DefaultNaN, 0x7fc00000); + +#undef DEFINE_FLOAT +#undef DEFINE_DOUBLE + + +enum LSDataSize { + LSByte = 0, + LSHalfword = 1, + LSWord = 2, + LSDoubleWord = 3 +}; + +LSDataSize CalcLSPairDataSize(LoadStorePairOp op); + +enum ImmBranchType { + UnknownBranchType = 0, + CondBranchType = 1, + UncondBranchType = 2, + CompareBranchType = 3, + TestBranchType = 4 +}; + +enum AddrMode { + Offset, + PreIndex, + PostIndex +}; + +enum FPRounding { + // The first four values are encodable directly by FPCR<RMode>. + FPTieEven = 0x0, + FPPositiveInfinity = 0x1, + FPNegativeInfinity = 0x2, + FPZero = 0x3, + + // The final rounding mode is only available when explicitly specified by the + // instruction (such as with fcvta). It cannot be set in FPCR. + FPTieAway +}; + +enum Reg31Mode { + Reg31IsStackPointer, + Reg31IsZeroRegister +}; + +// Instructions. --------------------------------------------------------------- + +class Instruction { + public: + V8_INLINE Instr InstructionBits() const { + return *reinterpret_cast<const Instr*>(this); + } + + V8_INLINE void SetInstructionBits(Instr new_instr) { + *reinterpret_cast<Instr*>(this) = new_instr; + } + + int Bit(int pos) const { + return (InstructionBits() >> pos) & 1; + } + + uint32_t Bits(int msb, int lsb) const { + return unsigned_bitextract_32(msb, lsb, InstructionBits()); + } + + int32_t SignedBits(int msb, int lsb) const { + int32_t bits = *(reinterpret_cast<const int32_t*>(this)); + return signed_bitextract_32(msb, lsb, bits); + } + + Instr Mask(uint32_t mask) const { + return InstructionBits() & mask; + } + + V8_INLINE Instruction* following(int count = 1) { + return InstructionAtOffset(count * static_cast<int>(kInstructionSize)); + } + + V8_INLINE Instruction* preceding(int count = 1) { + return following(-count); + } + + #define DEFINE_GETTER(Name, HighBit, LowBit, Func) \ + int64_t Name() const { return Func(HighBit, LowBit); } + INSTRUCTION_FIELDS_LIST(DEFINE_GETTER) + #undef DEFINE_GETTER + + // ImmPCRel is a compound field (not present in INSTRUCTION_FIELDS_LIST), + // formed from ImmPCRelLo and ImmPCRelHi. + int ImmPCRel() const { + ASSERT(IsPCRelAddressing()); + int const offset = ((ImmPCRelHi() << ImmPCRelLo_width) | ImmPCRelLo()); + int const width = ImmPCRelLo_width + ImmPCRelHi_width; + return signed_bitextract_32(width - 1, 0, offset); + } + + uint64_t ImmLogical(); + float ImmFP32(); + double ImmFP64(); + + LSDataSize SizeLSPair() const { + return CalcLSPairDataSize( + static_cast<LoadStorePairOp>(Mask(LoadStorePairMask))); + } + + // Helpers. + bool IsCondBranchImm() const { + return Mask(ConditionalBranchFMask) == ConditionalBranchFixed; + } + + bool IsUncondBranchImm() const { + return Mask(UnconditionalBranchFMask) == UnconditionalBranchFixed; + } + + bool IsCompareBranch() const { + return Mask(CompareBranchFMask) == CompareBranchFixed; + } + + bool IsTestBranch() const { + return Mask(TestBranchFMask) == TestBranchFixed; + } + + bool IsImmBranch() const { + return BranchType() != UnknownBranchType; + } + + bool IsLdrLiteral() const { + return Mask(LoadLiteralFMask) == LoadLiteralFixed; + } + + bool IsLdrLiteralX() const { + return Mask(LoadLiteralMask) == LDR_x_lit; + } + + bool IsPCRelAddressing() const { + return Mask(PCRelAddressingFMask) == PCRelAddressingFixed; + } + + bool IsAdr() const { + return Mask(PCRelAddressingMask) == ADR; + } + + bool IsLogicalImmediate() const { + return Mask(LogicalImmediateFMask) == LogicalImmediateFixed; + } + + bool IsAddSubImmediate() const { + return Mask(AddSubImmediateFMask) == AddSubImmediateFixed; + } + + bool IsAddSubShifted() const { + return Mask(AddSubShiftedFMask) == AddSubShiftedFixed; + } + + bool IsAddSubExtended() const { + return Mask(AddSubExtendedFMask) == AddSubExtendedFixed; + } + + // Match any loads or stores, including pairs. + bool IsLoadOrStore() const { + return Mask(LoadStoreAnyFMask) == LoadStoreAnyFixed; + } + + // Match any loads, including pairs. + bool IsLoad() const; + // Match any stores, including pairs. + bool IsStore() const; + + // Indicate whether Rd can be the stack pointer or the zero register. This + // does not check that the instruction actually has an Rd field. + Reg31Mode RdMode() const { + // The following instructions use csp or wsp as Rd: + // Add/sub (immediate) when not setting the flags. + // Add/sub (extended) when not setting the flags. + // Logical (immediate) when not setting the flags. + // Otherwise, r31 is the zero register. + if (IsAddSubImmediate() || IsAddSubExtended()) { + if (Mask(AddSubSetFlagsBit)) { + return Reg31IsZeroRegister; + } else { + return Reg31IsStackPointer; + } + } + if (IsLogicalImmediate()) { + // Of the logical (immediate) instructions, only ANDS (and its aliases) + // can set the flags. The others can all write into csp. + // Note that some logical operations are not available to + // immediate-operand instructions, so we have to combine two masks here. + if (Mask(LogicalImmediateMask & LogicalOpMask) == ANDS) { + return Reg31IsZeroRegister; + } else { + return Reg31IsStackPointer; + } + } + return Reg31IsZeroRegister; + } + + // Indicate whether Rn can be the stack pointer or the zero register. This + // does not check that the instruction actually has an Rn field. + Reg31Mode RnMode() const { + // The following instructions use csp or wsp as Rn: + // All loads and stores. + // Add/sub (immediate). + // Add/sub (extended). + // Otherwise, r31 is the zero register. + if (IsLoadOrStore() || IsAddSubImmediate() || IsAddSubExtended()) { + return Reg31IsStackPointer; + } + return Reg31IsZeroRegister; + } + + ImmBranchType BranchType() const { + if (IsCondBranchImm()) { + return CondBranchType; + } else if (IsUncondBranchImm()) { + return UncondBranchType; + } else if (IsCompareBranch()) { + return CompareBranchType; + } else if (IsTestBranch()) { + return TestBranchType; + } else { + return UnknownBranchType; + } + } + + static int ImmBranchRangeBitwidth(ImmBranchType branch_type) { + switch (branch_type) { + case UncondBranchType: + return ImmUncondBranch_width; + case CondBranchType: + return ImmCondBranch_width; + case CompareBranchType: + return ImmCmpBranch_width; + case TestBranchType: + return ImmTestBranch_width; + default: + UNREACHABLE(); + return 0; + } + } + + // The range of the branch instruction, expressed as 'instr +- range'. + static int32_t ImmBranchRange(ImmBranchType branch_type) { + return + (1 << (ImmBranchRangeBitwidth(branch_type) + kInstructionSizeLog2)) / 2 - + kInstructionSize; + } + + int ImmBranch() const { + switch (BranchType()) { + case CondBranchType: return ImmCondBranch(); + case UncondBranchType: return ImmUncondBranch(); + case CompareBranchType: return ImmCmpBranch(); + case TestBranchType: return ImmTestBranch(); + default: UNREACHABLE(); + } + return 0; + } + + bool IsBranchAndLinkToRegister() const { + return Mask(UnconditionalBranchToRegisterMask) == BLR; + } + + bool IsMovz() const { + return (Mask(MoveWideImmediateMask) == MOVZ_x) || + (Mask(MoveWideImmediateMask) == MOVZ_w); + } + + bool IsMovk() const { + return (Mask(MoveWideImmediateMask) == MOVK_x) || + (Mask(MoveWideImmediateMask) == MOVK_w); + } + + bool IsMovn() const { + return (Mask(MoveWideImmediateMask) == MOVN_x) || + (Mask(MoveWideImmediateMask) == MOVN_w); + } + + bool IsNop(int n) { + // A marking nop is an instruction + // mov r<n>, r<n> + // which is encoded as + // orr r<n>, xzr, r<n> + return (Mask(LogicalShiftedMask) == ORR_x) && + (Rd() == Rm()) && + (Rd() == n); + } + + // Find the PC offset encoded in this instruction. 'this' may be a branch or + // a PC-relative addressing instruction. + // The offset returned is unscaled. + ptrdiff_t ImmPCOffset(); + + // Find the target of this instruction. 'this' may be a branch or a + // PC-relative addressing instruction. + Instruction* ImmPCOffsetTarget(); + + static bool IsValidImmPCOffset(ImmBranchType branch_type, int32_t offset); + bool IsTargetInImmPCOffsetRange(Instruction* target); + // Patch a PC-relative offset to refer to 'target'. 'this' may be a branch or + // a PC-relative addressing instruction. + void SetImmPCOffsetTarget(Instruction* target); + // Patch a literal load instruction to load from 'source'. + void SetImmLLiteral(Instruction* source); + + uint8_t* LiteralAddress() { + int offset = ImmLLiteral() << kLoadLiteralScaleLog2; + return reinterpret_cast<uint8_t*>(this) + offset; + } + + enum CheckAlignment { NO_CHECK, CHECK_ALIGNMENT }; + + V8_INLINE Instruction* InstructionAtOffset( + int64_t offset, + CheckAlignment check = CHECK_ALIGNMENT) { + Address addr = reinterpret_cast<Address>(this) + offset; + // The FUZZ_disasm test relies on no check being done. + ASSERT(check == NO_CHECK || IsAddressAligned(addr, kInstructionSize)); + return Cast(addr); + } + + template<typename T> V8_INLINE static Instruction* Cast(T src) { + return reinterpret_cast<Instruction*>(src); + } + + V8_INLINE ptrdiff_t DistanceTo(Instruction* target) { + return reinterpret_cast<Address>(target) - reinterpret_cast<Address>(this); + } + + + static const int ImmPCRelRangeBitwidth = 21; + static bool IsValidPCRelOffset(int offset) { + return is_int21(offset); + } + void SetPCRelImmTarget(Instruction* target); + void SetBranchImmTarget(Instruction* target); +}; + + +// Where Instruction looks at instructions generated by the Assembler, +// InstructionSequence looks at instructions sequences generated by the +// MacroAssembler. +class InstructionSequence : public Instruction { + public: + static InstructionSequence* At(Address address) { + return reinterpret_cast<InstructionSequence*>(address); + } + + // Sequences generated by MacroAssembler::InlineData(). + bool IsInlineData() const; + uint64_t InlineData() const; +}; + + +// Simulator/Debugger debug instructions --------------------------------------- +// Each debug marker is represented by a HLT instruction. The immediate comment +// field in the instruction is used to identify the type of debug marker. Each +// marker encodes arguments in a different way, as described below. + +// Indicate to the Debugger that the instruction is a redirected call. +const Instr kImmExceptionIsRedirectedCall = 0xca11; + +// Represent unreachable code. This is used as a guard in parts of the code that +// should not be reachable, such as in data encoded inline in the instructions. +const Instr kImmExceptionIsUnreachable = 0xdebf; + +// A pseudo 'printf' instruction. The arguments will be passed to the platform +// printf method. +const Instr kImmExceptionIsPrintf = 0xdeb1; +// Most parameters are stored in ARM64 registers as if the printf +// pseudo-instruction was a call to the real printf method: +// x0: The format string. +// x1-x7: Optional arguments. +// d0-d7: Optional arguments. +// +// Also, the argument layout is described inline in the instructions: +// - arg_count: The number of arguments. +// - arg_pattern: A set of PrintfArgPattern values, packed into two-bit fields. +// +// Floating-point and integer arguments are passed in separate sets of registers +// in AAPCS64 (even for varargs functions), so it is not possible to determine +// the type of each argument without some information about the values that were +// passed in. This information could be retrieved from the printf format string, +// but the format string is not trivial to parse so we encode the relevant +// information with the HLT instruction. +const unsigned kPrintfArgCountOffset = 1 * kInstructionSize; +const unsigned kPrintfArgPatternListOffset = 2 * kInstructionSize; +const unsigned kPrintfLength = 3 * kInstructionSize; + +const unsigned kPrintfMaxArgCount = 4; + +// The argument pattern is a set of two-bit-fields, each with one of the +// following values: +enum PrintfArgPattern { + kPrintfArgW = 1, + kPrintfArgX = 2, + // There is no kPrintfArgS because floats are always converted to doubles in C + // varargs calls. + kPrintfArgD = 3 +}; +static const unsigned kPrintfArgPatternBits = 2; + +// A pseudo 'debug' instruction. +const Instr kImmExceptionIsDebug = 0xdeb0; +// Parameters are inlined in the code after a debug pseudo-instruction: +// - Debug code. +// - Debug parameters. +// - Debug message string. This is a NULL-terminated ASCII string, padded to +// kInstructionSize so that subsequent instructions are correctly aligned. +// - A kImmExceptionIsUnreachable marker, to catch accidental execution of the +// string data. +const unsigned kDebugCodeOffset = 1 * kInstructionSize; +const unsigned kDebugParamsOffset = 2 * kInstructionSize; +const unsigned kDebugMessageOffset = 3 * kInstructionSize; + +// Debug parameters. +// Used without a TRACE_ option, the Debugger will print the arguments only +// once. Otherwise TRACE_ENABLE and TRACE_DISABLE will enable or disable tracing +// before every instruction for the specified LOG_ parameters. +// +// TRACE_OVERRIDE enables the specified LOG_ parameters, and disabled any +// others that were not specified. +// +// For example: +// +// __ debug("print registers and fp registers", 0, LOG_REGS | LOG_FP_REGS); +// will print the registers and fp registers only once. +// +// __ debug("trace disasm", 1, TRACE_ENABLE | LOG_DISASM); +// starts disassembling the code. +// +// __ debug("trace rets", 2, TRACE_ENABLE | LOG_REGS); +// adds the general purpose registers to the trace. +// +// __ debug("stop regs", 3, TRACE_DISABLE | LOG_REGS); +// stops tracing the registers. +const unsigned kDebuggerTracingDirectivesMask = 3 << 6; +enum DebugParameters { + NO_PARAM = 0, + BREAK = 1 << 0, + LOG_DISASM = 1 << 1, // Use only with TRACE. Disassemble the code. + LOG_REGS = 1 << 2, // Log general purpose registers. + LOG_FP_REGS = 1 << 3, // Log floating-point registers. + LOG_SYS_REGS = 1 << 4, // Log the status flags. + LOG_WRITE = 1 << 5, // Log any memory write. + + LOG_STATE = LOG_REGS | LOG_FP_REGS | LOG_SYS_REGS, + LOG_ALL = LOG_DISASM | LOG_STATE | LOG_WRITE, + + // Trace control. + TRACE_ENABLE = 1 << 6, + TRACE_DISABLE = 2 << 6, + TRACE_OVERRIDE = 3 << 6 +}; + + +} } // namespace v8::internal + + +#endif // V8_ARM64_INSTRUCTIONS_ARM64_H_ diff --git a/chromium/v8/src/arm64/instrument-arm64.cc b/chromium/v8/src/arm64/instrument-arm64.cc new file mode 100644 index 00000000000..631556f7241 --- /dev/null +++ b/chromium/v8/src/arm64/instrument-arm64.cc @@ -0,0 +1,595 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/arm64/instrument-arm64.h" + +namespace v8 { +namespace internal { + +Counter::Counter(const char* name, CounterType type) + : count_(0), enabled_(false), type_(type) { + ASSERT(name != NULL); + strncpy(name_, name, kCounterNameMaxLength); +} + + +void Counter::Enable() { + enabled_ = true; +} + + +void Counter::Disable() { + enabled_ = false; +} + + +bool Counter::IsEnabled() { + return enabled_; +} + + +void Counter::Increment() { + if (enabled_) { + count_++; + } +} + + +uint64_t Counter::count() { + uint64_t result = count_; + if (type_ == Gauge) { + // If the counter is a Gauge, reset the count after reading. + count_ = 0; + } + return result; +} + + +const char* Counter::name() { + return name_; +} + + +CounterType Counter::type() { + return type_; +} + + +typedef struct { + const char* name; + CounterType type; +} CounterDescriptor; + + +static const CounterDescriptor kCounterList[] = { + {"Instruction", Cumulative}, + + {"Move Immediate", Gauge}, + {"Add/Sub DP", Gauge}, + {"Logical DP", Gauge}, + {"Other Int DP", Gauge}, + {"FP DP", Gauge}, + + {"Conditional Select", Gauge}, + {"Conditional Compare", Gauge}, + + {"Unconditional Branch", Gauge}, + {"Compare and Branch", Gauge}, + {"Test and Branch", Gauge}, + {"Conditional Branch", Gauge}, + + {"Load Integer", Gauge}, + {"Load FP", Gauge}, + {"Load Pair", Gauge}, + {"Load Literal", Gauge}, + + {"Store Integer", Gauge}, + {"Store FP", Gauge}, + {"Store Pair", Gauge}, + + {"PC Addressing", Gauge}, + {"Other", Gauge}, + {"SP Adjust", Gauge}, +}; + + +Instrument::Instrument(const char* datafile, uint64_t sample_period) + : output_stream_(stderr), sample_period_(sample_period) { + + // Set up the output stream. If datafile is non-NULL, use that file. If it + // can't be opened, or datafile is NULL, use stderr. + if (datafile != NULL) { + output_stream_ = fopen(datafile, "w"); + if (output_stream_ == NULL) { + fprintf(stderr, "Can't open output file %s. Using stderr.\n", datafile); + output_stream_ = stderr; + } + } + + static const int num_counters = + sizeof(kCounterList) / sizeof(CounterDescriptor); + + // Dump an instrumentation description comment at the top of the file. + fprintf(output_stream_, "# counters=%d\n", num_counters); + fprintf(output_stream_, "# sample_period=%" PRIu64 "\n", sample_period_); + + // Construct Counter objects from counter description array. + for (int i = 0; i < num_counters; i++) { + Counter* counter = new Counter(kCounterList[i].name, kCounterList[i].type); + counters_.push_back(counter); + } + + DumpCounterNames(); +} + + +Instrument::~Instrument() { + // Dump any remaining instruction data to the output file. + DumpCounters(); + + // Free all the counter objects. + std::list<Counter*>::iterator it; + for (it = counters_.begin(); it != counters_.end(); it++) { + delete *it; + } + + if (output_stream_ != stderr) { + fclose(output_stream_); + } +} + + +void Instrument::Update() { + // Increment the instruction counter, and dump all counters if a sample period + // has elapsed. + static Counter* counter = GetCounter("Instruction"); + ASSERT(counter->type() == Cumulative); + counter->Increment(); + + if (counter->IsEnabled() && (counter->count() % sample_period_) == 0) { + DumpCounters(); + } +} + + +void Instrument::DumpCounters() { + // Iterate through the counter objects, dumping their values to the output + // stream. + std::list<Counter*>::const_iterator it; + for (it = counters_.begin(); it != counters_.end(); it++) { + fprintf(output_stream_, "%" PRIu64 ",", (*it)->count()); + } + fprintf(output_stream_, "\n"); + fflush(output_stream_); +} + + +void Instrument::DumpCounterNames() { + // Iterate through the counter objects, dumping the counter names to the + // output stream. + std::list<Counter*>::const_iterator it; + for (it = counters_.begin(); it != counters_.end(); it++) { + fprintf(output_stream_, "%s,", (*it)->name()); + } + fprintf(output_stream_, "\n"); + fflush(output_stream_); +} + + +void Instrument::HandleInstrumentationEvent(unsigned event) { + switch (event) { + case InstrumentStateEnable: Enable(); break; + case InstrumentStateDisable: Disable(); break; + default: DumpEventMarker(event); + } +} + + +void Instrument::DumpEventMarker(unsigned marker) { + // Dumpan event marker to the output stream as a specially formatted comment + // line. + static Counter* counter = GetCounter("Instruction"); + + fprintf(output_stream_, "# %c%c @ %" PRId64 "\n", marker & 0xff, + (marker >> 8) & 0xff, counter->count()); +} + + +Counter* Instrument::GetCounter(const char* name) { + // Get a Counter object by name from the counter list. + std::list<Counter*>::const_iterator it; + for (it = counters_.begin(); it != counters_.end(); it++) { + if (strcmp((*it)->name(), name) == 0) { + return *it; + } + } + + // A Counter by that name does not exist: print an error message to stderr + // and the output file, and exit. + static const char* error_message = + "# Error: Unknown counter \"%s\". Exiting.\n"; + fprintf(stderr, error_message, name); + fprintf(output_stream_, error_message, name); + exit(1); +} + + +void Instrument::Enable() { + std::list<Counter*>::iterator it; + for (it = counters_.begin(); it != counters_.end(); it++) { + (*it)->Enable(); + } +} + + +void Instrument::Disable() { + std::list<Counter*>::iterator it; + for (it = counters_.begin(); it != counters_.end(); it++) { + (*it)->Disable(); + } +} + + +void Instrument::VisitPCRelAddressing(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("PC Addressing"); + counter->Increment(); +} + + +void Instrument::VisitAddSubImmediate(Instruction* instr) { + Update(); + static Counter* sp_counter = GetCounter("SP Adjust"); + static Counter* add_sub_counter = GetCounter("Add/Sub DP"); + if (((instr->Mask(AddSubOpMask) == SUB) || + (instr->Mask(AddSubOpMask) == ADD)) && + (instr->Rd() == 31) && (instr->Rn() == 31)) { + // Count adjustments to the C stack pointer caused by V8 needing two SPs. + sp_counter->Increment(); + } else { + add_sub_counter->Increment(); + } +} + + +void Instrument::VisitLogicalImmediate(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Logical DP"); + counter->Increment(); +} + + +void Instrument::VisitMoveWideImmediate(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Move Immediate"); + + if (instr->IsMovn() && (instr->Rd() == kZeroRegCode)) { + unsigned imm = instr->ImmMoveWide(); + HandleInstrumentationEvent(imm); + } else { + counter->Increment(); + } +} + + +void Instrument::VisitBitfield(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other Int DP"); + counter->Increment(); +} + + +void Instrument::VisitExtract(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other Int DP"); + counter->Increment(); +} + + +void Instrument::VisitUnconditionalBranch(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Unconditional Branch"); + counter->Increment(); +} + + +void Instrument::VisitUnconditionalBranchToRegister(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Unconditional Branch"); + counter->Increment(); +} + + +void Instrument::VisitCompareBranch(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Compare and Branch"); + counter->Increment(); +} + + +void Instrument::VisitTestBranch(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Test and Branch"); + counter->Increment(); +} + + +void Instrument::VisitConditionalBranch(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Conditional Branch"); + counter->Increment(); +} + + +void Instrument::VisitSystem(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other"); + counter->Increment(); +} + + +void Instrument::VisitException(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other"); + counter->Increment(); +} + + +void Instrument::InstrumentLoadStorePair(Instruction* instr) { + static Counter* load_pair_counter = GetCounter("Load Pair"); + static Counter* store_pair_counter = GetCounter("Store Pair"); + if (instr->Mask(LoadStorePairLBit) != 0) { + load_pair_counter->Increment(); + } else { + store_pair_counter->Increment(); + } +} + + +void Instrument::VisitLoadStorePairPostIndex(Instruction* instr) { + Update(); + InstrumentLoadStorePair(instr); +} + + +void Instrument::VisitLoadStorePairOffset(Instruction* instr) { + Update(); + InstrumentLoadStorePair(instr); +} + + +void Instrument::VisitLoadStorePairPreIndex(Instruction* instr) { + Update(); + InstrumentLoadStorePair(instr); +} + + +void Instrument::VisitLoadStorePairNonTemporal(Instruction* instr) { + Update(); + InstrumentLoadStorePair(instr); +} + + +void Instrument::VisitLoadLiteral(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Load Literal"); + counter->Increment(); +} + + +void Instrument::InstrumentLoadStore(Instruction* instr) { + static Counter* load_int_counter = GetCounter("Load Integer"); + static Counter* store_int_counter = GetCounter("Store Integer"); + static Counter* load_fp_counter = GetCounter("Load FP"); + static Counter* store_fp_counter = GetCounter("Store FP"); + + switch (instr->Mask(LoadStoreOpMask)) { + case STRB_w: // Fall through. + case STRH_w: // Fall through. + case STR_w: // Fall through. + case STR_x: store_int_counter->Increment(); break; + case STR_s: // Fall through. + case STR_d: store_fp_counter->Increment(); break; + case LDRB_w: // Fall through. + case LDRH_w: // Fall through. + case LDR_w: // Fall through. + case LDR_x: // Fall through. + case LDRSB_x: // Fall through. + case LDRSH_x: // Fall through. + case LDRSW_x: // Fall through. + case LDRSB_w: // Fall through. + case LDRSH_w: load_int_counter->Increment(); break; + case LDR_s: // Fall through. + case LDR_d: load_fp_counter->Increment(); break; + default: UNREACHABLE(); + } +} + + +void Instrument::VisitLoadStoreUnscaledOffset(Instruction* instr) { + Update(); + InstrumentLoadStore(instr); +} + + +void Instrument::VisitLoadStorePostIndex(Instruction* instr) { + Update(); + InstrumentLoadStore(instr); +} + + +void Instrument::VisitLoadStorePreIndex(Instruction* instr) { + Update(); + InstrumentLoadStore(instr); +} + + +void Instrument::VisitLoadStoreRegisterOffset(Instruction* instr) { + Update(); + InstrumentLoadStore(instr); +} + + +void Instrument::VisitLoadStoreUnsignedOffset(Instruction* instr) { + Update(); + InstrumentLoadStore(instr); +} + + +void Instrument::VisitLogicalShifted(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Logical DP"); + counter->Increment(); +} + + +void Instrument::VisitAddSubShifted(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Add/Sub DP"); + counter->Increment(); +} + + +void Instrument::VisitAddSubExtended(Instruction* instr) { + Update(); + static Counter* sp_counter = GetCounter("SP Adjust"); + static Counter* add_sub_counter = GetCounter("Add/Sub DP"); + if (((instr->Mask(AddSubOpMask) == SUB) || + (instr->Mask(AddSubOpMask) == ADD)) && + (instr->Rd() == 31) && (instr->Rn() == 31)) { + // Count adjustments to the C stack pointer caused by V8 needing two SPs. + sp_counter->Increment(); + } else { + add_sub_counter->Increment(); + } +} + + +void Instrument::VisitAddSubWithCarry(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Add/Sub DP"); + counter->Increment(); +} + + +void Instrument::VisitConditionalCompareRegister(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Conditional Compare"); + counter->Increment(); +} + + +void Instrument::VisitConditionalCompareImmediate(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Conditional Compare"); + counter->Increment(); +} + + +void Instrument::VisitConditionalSelect(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Conditional Select"); + counter->Increment(); +} + + +void Instrument::VisitDataProcessing1Source(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other Int DP"); + counter->Increment(); +} + + +void Instrument::VisitDataProcessing2Source(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other Int DP"); + counter->Increment(); +} + + +void Instrument::VisitDataProcessing3Source(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other Int DP"); + counter->Increment(); +} + + +void Instrument::VisitFPCompare(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("FP DP"); + counter->Increment(); +} + + +void Instrument::VisitFPConditionalCompare(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Conditional Compare"); + counter->Increment(); +} + + +void Instrument::VisitFPConditionalSelect(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Conditional Select"); + counter->Increment(); +} + + +void Instrument::VisitFPImmediate(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("FP DP"); + counter->Increment(); +} + + +void Instrument::VisitFPDataProcessing1Source(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("FP DP"); + counter->Increment(); +} + + +void Instrument::VisitFPDataProcessing2Source(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("FP DP"); + counter->Increment(); +} + + +void Instrument::VisitFPDataProcessing3Source(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("FP DP"); + counter->Increment(); +} + + +void Instrument::VisitFPIntegerConvert(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("FP DP"); + counter->Increment(); +} + + +void Instrument::VisitFPFixedPointConvert(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("FP DP"); + counter->Increment(); +} + + +void Instrument::VisitUnallocated(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other"); + counter->Increment(); +} + + +void Instrument::VisitUnimplemented(Instruction* instr) { + Update(); + static Counter* counter = GetCounter("Other"); + counter->Increment(); +} + + +} } // namespace v8::internal diff --git a/chromium/v8/src/arm64/instrument-arm64.h b/chromium/v8/src/arm64/instrument-arm64.h new file mode 100644 index 00000000000..74583108542 --- /dev/null +++ b/chromium/v8/src/arm64/instrument-arm64.h @@ -0,0 +1,84 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_INSTRUMENT_ARM64_H_ +#define V8_ARM64_INSTRUMENT_ARM64_H_ + +#include "src/globals.h" +#include "src/utils.h" +#include "src/arm64/decoder-arm64.h" +#include "src/arm64/constants-arm64.h" + +namespace v8 { +namespace internal { + +const int kCounterNameMaxLength = 256; +const uint64_t kDefaultInstrumentationSamplingPeriod = 1 << 22; + + +enum InstrumentState { + InstrumentStateDisable = 0, + InstrumentStateEnable = 1 +}; + + +enum CounterType { + Gauge = 0, // Gauge counters reset themselves after reading. + Cumulative = 1 // Cumulative counters keep their value after reading. +}; + + +class Counter { + public: + Counter(const char* name, CounterType type = Gauge); + + void Increment(); + void Enable(); + void Disable(); + bool IsEnabled(); + uint64_t count(); + const char* name(); + CounterType type(); + + private: + char name_[kCounterNameMaxLength]; + uint64_t count_; + bool enabled_; + CounterType type_; +}; + + +class Instrument: public DecoderVisitor { + public: + explicit Instrument(const char* datafile = NULL, + uint64_t sample_period = kDefaultInstrumentationSamplingPeriod); + ~Instrument(); + + // Declare all Visitor functions. + #define DECLARE(A) void Visit##A(Instruction* instr); + VISITOR_LIST(DECLARE) + #undef DECLARE + + private: + void Update(); + void Enable(); + void Disable(); + void DumpCounters(); + void DumpCounterNames(); + void DumpEventMarker(unsigned marker); + void HandleInstrumentationEvent(unsigned event); + Counter* GetCounter(const char* name); + + void InstrumentLoadStore(Instruction* instr); + void InstrumentLoadStorePair(Instruction* instr); + + std::list<Counter*> counters_; + + FILE *output_stream_; + uint64_t sample_period_; +}; + +} } // namespace v8::internal + +#endif // V8_ARM64_INSTRUMENT_ARM64_H_ diff --git a/chromium/v8/src/arm64/lithium-arm64.cc b/chromium/v8/src/arm64/lithium-arm64.cc new file mode 100644 index 00000000000..8446edfae79 --- /dev/null +++ b/chromium/v8/src/arm64/lithium-arm64.cc @@ -0,0 +1,2725 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#include "src/lithium-allocator-inl.h" +#include "src/arm64/lithium-arm64.h" +#include "src/arm64/lithium-codegen-arm64.h" +#include "src/hydrogen-osr.h" + +namespace v8 { +namespace internal { + + +#define DEFINE_COMPILE(type) \ + void L##type::CompileToNative(LCodeGen* generator) { \ + generator->Do##type(this); \ + } +LITHIUM_CONCRETE_INSTRUCTION_LIST(DEFINE_COMPILE) +#undef DEFINE_COMPILE + +#ifdef DEBUG +void LInstruction::VerifyCall() { + // Call instructions can use only fixed registers as temporaries and + // outputs because all registers are blocked by the calling convention. + // Inputs operands must use a fixed register or use-at-start policy or + // a non-register policy. + ASSERT(Output() == NULL || + LUnallocated::cast(Output())->HasFixedPolicy() || + !LUnallocated::cast(Output())->HasRegisterPolicy()); + for (UseIterator it(this); !it.Done(); it.Advance()) { + LUnallocated* operand = LUnallocated::cast(it.Current()); + ASSERT(operand->HasFixedPolicy() || + operand->IsUsedAtStart()); + } + for (TempIterator it(this); !it.Done(); it.Advance()) { + LUnallocated* operand = LUnallocated::cast(it.Current()); + ASSERT(operand->HasFixedPolicy() ||!operand->HasRegisterPolicy()); + } +} +#endif + + +void LLabel::PrintDataTo(StringStream* stream) { + LGap::PrintDataTo(stream); + LLabel* rep = replacement(); + if (rep != NULL) { + stream->Add(" Dead block replaced with B%d", rep->block_id()); + } +} + + +void LAccessArgumentsAt::PrintDataTo(StringStream* stream) { + arguments()->PrintTo(stream); + stream->Add(" length "); + length()->PrintTo(stream); + stream->Add(" index "); + index()->PrintTo(stream); +} + + +void LBranch::PrintDataTo(StringStream* stream) { + stream->Add("B%d | B%d on ", true_block_id(), false_block_id()); + value()->PrintTo(stream); +} + + +void LCallJSFunction::PrintDataTo(StringStream* stream) { + stream->Add("= "); + function()->PrintTo(stream); + stream->Add("#%d / ", arity()); +} + + +void LCallWithDescriptor::PrintDataTo(StringStream* stream) { + for (int i = 0; i < InputCount(); i++) { + InputAt(i)->PrintTo(stream); + stream->Add(" "); + } + stream->Add("#%d / ", arity()); +} + + +void LCallNew::PrintDataTo(StringStream* stream) { + stream->Add("= "); + constructor()->PrintTo(stream); + stream->Add(" #%d / ", arity()); +} + + +void LCallNewArray::PrintDataTo(StringStream* stream) { + stream->Add("= "); + constructor()->PrintTo(stream); + stream->Add(" #%d / ", arity()); + ElementsKind kind = hydrogen()->elements_kind(); + stream->Add(" (%s) ", ElementsKindToString(kind)); +} + + +void LClassOfTestAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if class_of_test("); + value()->PrintTo(stream); + stream->Add(", \"%o\") then B%d else B%d", + *hydrogen()->class_name(), + true_block_id(), + false_block_id()); +} + + +void LCompareNumericAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if "); + left()->PrintTo(stream); + stream->Add(" %s ", Token::String(op())); + right()->PrintTo(stream); + stream->Add(" then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LHasCachedArrayIndexAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if has_cached_array_index("); + value()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +bool LGoto::HasInterestingComment(LCodeGen* gen) const { + return !gen->IsNextEmittedBlock(block_id()); +} + + +void LGoto::PrintDataTo(StringStream* stream) { + stream->Add("B%d", block_id()); +} + + +void LInnerAllocatedObject::PrintDataTo(StringStream* stream) { + stream->Add(" = "); + base_object()->PrintTo(stream); + stream->Add(" + "); + offset()->PrintTo(stream); +} + + +void LInvokeFunction::PrintDataTo(StringStream* stream) { + stream->Add("= "); + function()->PrintTo(stream); + stream->Add(" #%d / ", arity()); +} + + +void LInstruction::PrintTo(StringStream* stream) { + stream->Add("%s ", this->Mnemonic()); + + PrintOutputOperandTo(stream); + + PrintDataTo(stream); + + if (HasEnvironment()) { + stream->Add(" "); + environment()->PrintTo(stream); + } + + if (HasPointerMap()) { + stream->Add(" "); + pointer_map()->PrintTo(stream); + } +} + + +void LInstruction::PrintDataTo(StringStream* stream) { + stream->Add("= "); + for (int i = 0; i < InputCount(); i++) { + if (i > 0) stream->Add(" "); + if (InputAt(i) == NULL) { + stream->Add("NULL"); + } else { + InputAt(i)->PrintTo(stream); + } + } +} + + +void LInstruction::PrintOutputOperandTo(StringStream* stream) { + if (HasResult()) result()->PrintTo(stream); +} + + +void LHasInstanceTypeAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if has_instance_type("); + value()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsObjectAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if is_object("); + value()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsStringAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if is_string("); + value()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsSmiAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if is_smi("); + value()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LTypeofIsAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if typeof "); + value()->PrintTo(stream); + stream->Add(" == \"%s\" then B%d else B%d", + hydrogen()->type_literal()->ToCString().get(), + true_block_id(), false_block_id()); +} + + +void LIsUndetectableAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if is_undetectable("); + value()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +bool LGap::IsRedundant() const { + for (int i = 0; i < 4; i++) { + if ((parallel_moves_[i] != NULL) && !parallel_moves_[i]->IsRedundant()) { + return false; + } + } + + return true; +} + + +void LGap::PrintDataTo(StringStream* stream) { + for (int i = 0; i < 4; i++) { + stream->Add("("); + if (parallel_moves_[i] != NULL) { + parallel_moves_[i]->PrintDataTo(stream); + } + stream->Add(") "); + } +} + + +void LLoadContextSlot::PrintDataTo(StringStream* stream) { + context()->PrintTo(stream); + stream->Add("[%d]", slot_index()); +} + + +void LStoreCodeEntry::PrintDataTo(StringStream* stream) { + stream->Add(" = "); + function()->PrintTo(stream); + stream->Add(".code_entry = "); + code_object()->PrintTo(stream); +} + + +void LStoreContextSlot::PrintDataTo(StringStream* stream) { + context()->PrintTo(stream); + stream->Add("[%d] <- ", slot_index()); + value()->PrintTo(stream); +} + + +void LStoreKeyedGeneric::PrintDataTo(StringStream* stream) { + object()->PrintTo(stream); + stream->Add("["); + key()->PrintTo(stream); + stream->Add("] <- "); + value()->PrintTo(stream); +} + + +void LStoreNamedField::PrintDataTo(StringStream* stream) { + object()->PrintTo(stream); + hydrogen()->access().PrintTo(stream); + stream->Add(" <- "); + value()->PrintTo(stream); +} + + +void LStoreNamedGeneric::PrintDataTo(StringStream* stream) { + object()->PrintTo(stream); + stream->Add("."); + stream->Add(String::cast(*name())->ToCString().get()); + stream->Add(" <- "); + value()->PrintTo(stream); +} + + +void LStringCompareAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if string_compare("); + left()->PrintTo(stream); + right()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LTransitionElementsKind::PrintDataTo(StringStream* stream) { + object()->PrintTo(stream); + stream->Add("%p -> %p", *original_map(), *transitioned_map()); +} + + +template<int T> +void LUnaryMathOperation<T>::PrintDataTo(StringStream* stream) { + value()->PrintTo(stream); +} + + +const char* LArithmeticD::Mnemonic() const { + switch (op()) { + case Token::ADD: return "add-d"; + case Token::SUB: return "sub-d"; + case Token::MUL: return "mul-d"; + case Token::DIV: return "div-d"; + case Token::MOD: return "mod-d"; + default: + UNREACHABLE(); + return NULL; + } +} + + +const char* LArithmeticT::Mnemonic() const { + switch (op()) { + case Token::ADD: return "add-t"; + case Token::SUB: return "sub-t"; + case Token::MUL: return "mul-t"; + case Token::MOD: return "mod-t"; + case Token::DIV: return "div-t"; + case Token::BIT_AND: return "bit-and-t"; + case Token::BIT_OR: return "bit-or-t"; + case Token::BIT_XOR: return "bit-xor-t"; + case Token::ROR: return "ror-t"; + case Token::SHL: return "shl-t"; + case Token::SAR: return "sar-t"; + case Token::SHR: return "shr-t"; + default: + UNREACHABLE(); + return NULL; + } +} + + +void LChunkBuilder::Abort(BailoutReason reason) { + info()->set_bailout_reason(reason); + status_ = ABORTED; +} + + +LUnallocated* LChunkBuilder::ToUnallocated(Register reg) { + return new(zone()) LUnallocated(LUnallocated::FIXED_REGISTER, + Register::ToAllocationIndex(reg)); +} + + +LUnallocated* LChunkBuilder::ToUnallocated(DoubleRegister reg) { + return new(zone()) LUnallocated(LUnallocated::FIXED_DOUBLE_REGISTER, + DoubleRegister::ToAllocationIndex(reg)); +} + + +LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) { + if (value->EmitAtUses()) { + HInstruction* instr = HInstruction::cast(value); + VisitInstruction(instr); + } + operand->set_virtual_register(value->id()); + return operand; +} + + +LOperand* LChunkBuilder::UseFixed(HValue* value, Register fixed_register) { + return Use(value, ToUnallocated(fixed_register)); +} + + +LOperand* LChunkBuilder::UseFixedDouble(HValue* value, + DoubleRegister fixed_register) { + return Use(value, ToUnallocated(fixed_register)); +} + + +LOperand* LChunkBuilder::UseRegister(HValue* value) { + return Use(value, new(zone()) LUnallocated(LUnallocated::MUST_HAVE_REGISTER)); +} + + +LOperand* LChunkBuilder::UseRegisterAndClobber(HValue* value) { + return Use(value, new(zone()) LUnallocated(LUnallocated::WRITABLE_REGISTER)); +} + + +LOperand* LChunkBuilder::UseRegisterAtStart(HValue* value) { + return Use(value, + new(zone()) LUnallocated(LUnallocated::MUST_HAVE_REGISTER, + LUnallocated::USED_AT_START)); +} + + +LOperand* LChunkBuilder::UseRegisterOrConstant(HValue* value) { + return value->IsConstant() ? UseConstant(value) : UseRegister(value); +} + + +LOperand* LChunkBuilder::UseRegisterOrConstantAtStart(HValue* value) { + return value->IsConstant() ? UseConstant(value) : UseRegisterAtStart(value); +} + + +LConstantOperand* LChunkBuilder::UseConstant(HValue* value) { + return chunk_->DefineConstantOperand(HConstant::cast(value)); +} + + +LOperand* LChunkBuilder::UseAny(HValue* value) { + return value->IsConstant() + ? UseConstant(value) + : Use(value, new(zone()) LUnallocated(LUnallocated::ANY)); +} + + +LInstruction* LChunkBuilder::Define(LTemplateResultInstruction<1>* instr, + LUnallocated* result) { + result->set_virtual_register(current_instruction_->id()); + instr->set_result(result); + return instr; +} + + +LInstruction* LChunkBuilder::DefineAsRegister( + LTemplateResultInstruction<1>* instr) { + return Define(instr, + new(zone()) LUnallocated(LUnallocated::MUST_HAVE_REGISTER)); +} + + +LInstruction* LChunkBuilder::DefineAsSpilled( + LTemplateResultInstruction<1>* instr, int index) { + return Define(instr, + new(zone()) LUnallocated(LUnallocated::FIXED_SLOT, index)); +} + + +LInstruction* LChunkBuilder::DefineSameAsFirst( + LTemplateResultInstruction<1>* instr) { + return Define(instr, + new(zone()) LUnallocated(LUnallocated::SAME_AS_FIRST_INPUT)); +} + + +LInstruction* LChunkBuilder::DefineFixed( + LTemplateResultInstruction<1>* instr, Register reg) { + return Define(instr, ToUnallocated(reg)); +} + + +LInstruction* LChunkBuilder::DefineFixedDouble( + LTemplateResultInstruction<1>* instr, DoubleRegister reg) { + return Define(instr, ToUnallocated(reg)); +} + + +LInstruction* LChunkBuilder::MarkAsCall(LInstruction* instr, + HInstruction* hinstr, + CanDeoptimize can_deoptimize) { + info()->MarkAsNonDeferredCalling(); +#ifdef DEBUG + instr->VerifyCall(); +#endif + instr->MarkAsCall(); + instr = AssignPointerMap(instr); + + // If instruction does not have side-effects lazy deoptimization + // after the call will try to deoptimize to the point before the call. + // Thus we still need to attach environment to this call even if + // call sequence can not deoptimize eagerly. + bool needs_environment = + (can_deoptimize == CAN_DEOPTIMIZE_EAGERLY) || + !hinstr->HasObservableSideEffects(); + if (needs_environment && !instr->HasEnvironment()) { + instr = AssignEnvironment(instr); + // We can't really figure out if the environment is needed or not. + instr->environment()->set_has_been_used(); + } + + return instr; +} + + +LInstruction* LChunkBuilder::AssignPointerMap(LInstruction* instr) { + ASSERT(!instr->HasPointerMap()); + instr->set_pointer_map(new(zone()) LPointerMap(zone())); + return instr; +} + + +LUnallocated* LChunkBuilder::TempRegister() { + LUnallocated* operand = + new(zone()) LUnallocated(LUnallocated::MUST_HAVE_REGISTER); + int vreg = allocator_->GetVirtualRegister(); + if (!allocator_->AllocationOk()) { + Abort(kOutOfVirtualRegistersWhileTryingToAllocateTempRegister); + vreg = 0; + } + operand->set_virtual_register(vreg); + return operand; +} + + +LUnallocated* LChunkBuilder::TempDoubleRegister() { + LUnallocated* operand = + new(zone()) LUnallocated(LUnallocated::MUST_HAVE_DOUBLE_REGISTER); + int vreg = allocator_->GetVirtualRegister(); + if (!allocator_->AllocationOk()) { + Abort(kOutOfVirtualRegistersWhileTryingToAllocateTempRegister); + vreg = 0; + } + operand->set_virtual_register(vreg); + return operand; +} + + +int LPlatformChunk::GetNextSpillIndex() { + return spill_slot_count_++; +} + + +LOperand* LPlatformChunk::GetNextSpillSlot(RegisterKind kind) { + int index = GetNextSpillIndex(); + if (kind == DOUBLE_REGISTERS) { + return LDoubleStackSlot::Create(index, zone()); + } else { + ASSERT(kind == GENERAL_REGISTERS); + return LStackSlot::Create(index, zone()); + } +} + + +LOperand* LChunkBuilder::FixedTemp(DoubleRegister reg) { + LUnallocated* operand = ToUnallocated(reg); + ASSERT(operand->HasFixedPolicy()); + return operand; +} + + +LPlatformChunk* LChunkBuilder::Build() { + ASSERT(is_unused()); + chunk_ = new(zone()) LPlatformChunk(info_, graph_); + LPhase phase("L_Building chunk", chunk_); + status_ = BUILDING; + + // If compiling for OSR, reserve space for the unoptimized frame, + // which will be subsumed into this frame. + if (graph()->has_osr()) { + // TODO(all): GetNextSpillIndex just increments a field. It has no other + // side effects, so we should get rid of this loop. + for (int i = graph()->osr()->UnoptimizedFrameSlots(); i > 0; i--) { + chunk_->GetNextSpillIndex(); + } + } + + const ZoneList<HBasicBlock*>* blocks = graph_->blocks(); + for (int i = 0; i < blocks->length(); i++) { + DoBasicBlock(blocks->at(i)); + if (is_aborted()) return NULL; + } + status_ = DONE; + return chunk_; +} + + +void LChunkBuilder::DoBasicBlock(HBasicBlock* block) { + ASSERT(is_building()); + current_block_ = block; + + if (block->IsStartBlock()) { + block->UpdateEnvironment(graph_->start_environment()); + argument_count_ = 0; + } else if (block->predecessors()->length() == 1) { + // We have a single predecessor => copy environment and outgoing + // argument count from the predecessor. + ASSERT(block->phis()->length() == 0); + HBasicBlock* pred = block->predecessors()->at(0); + HEnvironment* last_environment = pred->last_environment(); + ASSERT(last_environment != NULL); + + // Only copy the environment, if it is later used again. + if (pred->end()->SecondSuccessor() == NULL) { + ASSERT(pred->end()->FirstSuccessor() == block); + } else { + if ((pred->end()->FirstSuccessor()->block_id() > block->block_id()) || + (pred->end()->SecondSuccessor()->block_id() > block->block_id())) { + last_environment = last_environment->Copy(); + } + } + block->UpdateEnvironment(last_environment); + ASSERT(pred->argument_count() >= 0); + argument_count_ = pred->argument_count(); + } else { + // We are at a state join => process phis. + HBasicBlock* pred = block->predecessors()->at(0); + // No need to copy the environment, it cannot be used later. + HEnvironment* last_environment = pred->last_environment(); + for (int i = 0; i < block->phis()->length(); ++i) { + HPhi* phi = block->phis()->at(i); + if (phi->HasMergedIndex()) { + last_environment->SetValueAt(phi->merged_index(), phi); + } + } + for (int i = 0; i < block->deleted_phis()->length(); ++i) { + if (block->deleted_phis()->at(i) < last_environment->length()) { + last_environment->SetValueAt(block->deleted_phis()->at(i), + graph_->GetConstantUndefined()); + } + } + block->UpdateEnvironment(last_environment); + // Pick up the outgoing argument count of one of the predecessors. + argument_count_ = pred->argument_count(); + } + + // Translate hydrogen instructions to lithium ones for the current block. + HInstruction* current = block->first(); + int start = chunk_->instructions()->length(); + while ((current != NULL) && !is_aborted()) { + // Code for constants in registers is generated lazily. + if (!current->EmitAtUses()) { + VisitInstruction(current); + } + current = current->next(); + } + int end = chunk_->instructions()->length() - 1; + if (end >= start) { + block->set_first_instruction_index(start); + block->set_last_instruction_index(end); + } + block->set_argument_count(argument_count_); + current_block_ = NULL; +} + + +void LChunkBuilder::VisitInstruction(HInstruction* current) { + HInstruction* old_current = current_instruction_; + current_instruction_ = current; + + LInstruction* instr = NULL; + if (current->CanReplaceWithDummyUses()) { + if (current->OperandCount() == 0) { + instr = DefineAsRegister(new(zone()) LDummy()); + } else { + ASSERT(!current->OperandAt(0)->IsControlInstruction()); + instr = DefineAsRegister(new(zone()) + LDummyUse(UseAny(current->OperandAt(0)))); + } + for (int i = 1; i < current->OperandCount(); ++i) { + if (current->OperandAt(i)->IsControlInstruction()) continue; + LInstruction* dummy = + new(zone()) LDummyUse(UseAny(current->OperandAt(i))); + dummy->set_hydrogen_value(current); + chunk_->AddInstruction(dummy, current_block_); + } + } else { + HBasicBlock* successor; + if (current->IsControlInstruction() && + HControlInstruction::cast(current)->KnownSuccessorBlock(&successor) && + successor != NULL) { + instr = new(zone()) LGoto(successor); + } else { + instr = current->CompileToLithium(this); + } + } + + argument_count_ += current->argument_delta(); + ASSERT(argument_count_ >= 0); + + if (instr != NULL) { + AddInstruction(instr, current); + } + + current_instruction_ = old_current; +} + + +void LChunkBuilder::AddInstruction(LInstruction* instr, + HInstruction* hydrogen_val) { + // Associate the hydrogen instruction first, since we may need it for + // the ClobbersRegisters() or ClobbersDoubleRegisters() calls below. + instr->set_hydrogen_value(hydrogen_val); + +#if DEBUG + // Make sure that the lithium instruction has either no fixed register + // constraints in temps or the result OR no uses that are only used at + // start. If this invariant doesn't hold, the register allocator can decide + // to insert a split of a range immediately before the instruction due to an + // already allocated register needing to be used for the instruction's fixed + // register constraint. In this case, the register allocator won't see an + // interference between the split child and the use-at-start (it would if + // the it was just a plain use), so it is free to move the split child into + // the same register that is used for the use-at-start. + // See https://code.google.com/p/chromium/issues/detail?id=201590 + if (!(instr->ClobbersRegisters() && + instr->ClobbersDoubleRegisters(isolate()))) { + int fixed = 0; + int used_at_start = 0; + for (UseIterator it(instr); !it.Done(); it.Advance()) { + LUnallocated* operand = LUnallocated::cast(it.Current()); + if (operand->IsUsedAtStart()) ++used_at_start; + } + if (instr->Output() != NULL) { + if (LUnallocated::cast(instr->Output())->HasFixedPolicy()) ++fixed; + } + for (TempIterator it(instr); !it.Done(); it.Advance()) { + LUnallocated* operand = LUnallocated::cast(it.Current()); + if (operand->HasFixedPolicy()) ++fixed; + } + ASSERT(fixed == 0 || used_at_start == 0); + } +#endif + + if (FLAG_stress_pointer_maps && !instr->HasPointerMap()) { + instr = AssignPointerMap(instr); + } + if (FLAG_stress_environments && !instr->HasEnvironment()) { + instr = AssignEnvironment(instr); + } + chunk_->AddInstruction(instr, current_block_); + + if (instr->IsCall()) { + HValue* hydrogen_value_for_lazy_bailout = hydrogen_val; + LInstruction* instruction_needing_environment = NULL; + if (hydrogen_val->HasObservableSideEffects()) { + HSimulate* sim = HSimulate::cast(hydrogen_val->next()); + instruction_needing_environment = instr; + sim->ReplayEnvironment(current_block_->last_environment()); + hydrogen_value_for_lazy_bailout = sim; + } + LInstruction* bailout = AssignEnvironment(new(zone()) LLazyBailout()); + bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout); + chunk_->AddInstruction(bailout, current_block_); + if (instruction_needing_environment != NULL) { + // Store the lazy deopt environment with the instruction if needed. + // Right now it is only used for LInstanceOfKnownGlobal. + instruction_needing_environment-> + SetDeferredLazyDeoptimizationEnvironment(bailout->environment()); + } + } +} + + +LInstruction* LChunkBuilder::AssignEnvironment(LInstruction* instr) { + HEnvironment* hydrogen_env = current_block_->last_environment(); + int argument_index_accumulator = 0; + ZoneList<HValue*> objects_to_materialize(0, zone()); + instr->set_environment(CreateEnvironment(hydrogen_env, + &argument_index_accumulator, + &objects_to_materialize)); + return instr; +} + + +LInstruction* LChunkBuilder::DoAbnormalExit(HAbnormalExit* instr) { + // The control instruction marking the end of a block that completed + // abruptly (e.g., threw an exception). There is nothing specific to do. + return NULL; +} + + +LInstruction* LChunkBuilder::DoArithmeticD(Token::Value op, + HArithmeticBinaryOperation* instr) { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->left()->representation().IsDouble()); + ASSERT(instr->right()->representation().IsDouble()); + + if (op == Token::MOD) { + LOperand* left = UseFixedDouble(instr->left(), d0); + LOperand* right = UseFixedDouble(instr->right(), d1); + LArithmeticD* result = new(zone()) LArithmeticD(Token::MOD, left, right); + return MarkAsCall(DefineFixedDouble(result, d0), instr); + } else { + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + LArithmeticD* result = new(zone()) LArithmeticD(op, left, right); + return DefineAsRegister(result); + } +} + + +LInstruction* LChunkBuilder::DoArithmeticT(Token::Value op, + HBinaryOperation* instr) { + ASSERT((op == Token::ADD) || (op == Token::SUB) || (op == Token::MUL) || + (op == Token::DIV) || (op == Token::MOD) || (op == Token::SHR) || + (op == Token::SHL) || (op == Token::SAR) || (op == Token::ROR) || + (op == Token::BIT_OR) || (op == Token::BIT_AND) || + (op == Token::BIT_XOR)); + HValue* left = instr->left(); + HValue* right = instr->right(); + + // TODO(jbramley): Once we've implemented smi support for all arithmetic + // operations, these assertions should check IsTagged(). + ASSERT(instr->representation().IsSmiOrTagged()); + ASSERT(left->representation().IsSmiOrTagged()); + ASSERT(right->representation().IsSmiOrTagged()); + + LOperand* context = UseFixed(instr->context(), cp); + LOperand* left_operand = UseFixed(left, x1); + LOperand* right_operand = UseFixed(right, x0); + LArithmeticT* result = + new(zone()) LArithmeticT(op, context, left_operand, right_operand); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoBoundsCheckBaseIndexInformation( + HBoundsCheckBaseIndexInformation* instr) { + UNREACHABLE(); + return NULL; +} + + +LInstruction* LChunkBuilder::DoAccessArgumentsAt(HAccessArgumentsAt* instr) { + info()->MarkAsRequiresFrame(); + LOperand* args = NULL; + LOperand* length = NULL; + LOperand* index = NULL; + + if (instr->length()->IsConstant() && instr->index()->IsConstant()) { + args = UseRegisterAtStart(instr->arguments()); + length = UseConstant(instr->length()); + index = UseConstant(instr->index()); + } else { + args = UseRegister(instr->arguments()); + length = UseRegisterAtStart(instr->length()); + index = UseRegisterOrConstantAtStart(instr->index()); + } + + return DefineAsRegister(new(zone()) LAccessArgumentsAt(args, length, index)); +} + + +LInstruction* LChunkBuilder::DoAdd(HAdd* instr) { + if (instr->representation().IsSmiOrInteger32()) { + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + + LInstruction* shifted_operation = TryDoOpWithShiftedRightOperand(instr); + if (shifted_operation != NULL) { + return shifted_operation; + } + + LOperand* left = UseRegisterAtStart(instr->BetterLeftOperand()); + LOperand* right = + UseRegisterOrConstantAtStart(instr->BetterRightOperand()); + LInstruction* result = instr->representation().IsSmi() ? + DefineAsRegister(new(zone()) LAddS(left, right)) : + DefineAsRegister(new(zone()) LAddI(left, right)); + if (instr->CheckFlag(HValue::kCanOverflow)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsExternal()) { + ASSERT(instr->left()->representation().IsExternal()); + ASSERT(instr->right()->representation().IsInteger32()); + ASSERT(!instr->CheckFlag(HValue::kCanOverflow)); + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterOrConstantAtStart(instr->right()); + return DefineAsRegister(new(zone()) LAddE(left, right)); + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::ADD, instr); + } else { + ASSERT(instr->representation().IsTagged()); + return DoArithmeticT(Token::ADD, instr); + } +} + + +LInstruction* LChunkBuilder::DoAllocate(HAllocate* instr) { + info()->MarkAsDeferredCalling(); + LOperand* context = UseAny(instr->context()); + LOperand* size = UseRegisterOrConstant(instr->size()); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + LOperand* temp3 = instr->MustPrefillWithFiller() ? TempRegister() : NULL; + LAllocate* result = new(zone()) LAllocate(context, size, temp1, temp2, temp3); + return AssignPointerMap(DefineAsRegister(result)); +} + + +LInstruction* LChunkBuilder::DoApplyArguments(HApplyArguments* instr) { + LOperand* function = UseFixed(instr->function(), x1); + LOperand* receiver = UseFixed(instr->receiver(), x0); + LOperand* length = UseFixed(instr->length(), x2); + LOperand* elements = UseFixed(instr->elements(), x3); + LApplyArguments* result = new(zone()) LApplyArguments(function, + receiver, + length, + elements); + return MarkAsCall(DefineFixed(result, x0), instr, CAN_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoArgumentsElements(HArgumentsElements* instr) { + info()->MarkAsRequiresFrame(); + LOperand* temp = instr->from_inlined() ? NULL : TempRegister(); + return DefineAsRegister(new(zone()) LArgumentsElements(temp)); +} + + +LInstruction* LChunkBuilder::DoArgumentsLength(HArgumentsLength* instr) { + info()->MarkAsRequiresFrame(); + LOperand* value = UseRegisterAtStart(instr->value()); + return DefineAsRegister(new(zone()) LArgumentsLength(value)); +} + + +LInstruction* LChunkBuilder::DoArgumentsObject(HArgumentsObject* instr) { + // There are no real uses of the arguments object. + // arguments.length and element access are supported directly on + // stack arguments, and any real arguments object use causes a bailout. + // So this value is never used. + return NULL; +} + + +LInstruction* LChunkBuilder::DoBitwise(HBitwise* instr) { + if (instr->representation().IsSmiOrInteger32()) { + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + ASSERT(instr->CheckFlag(HValue::kTruncatingToInt32)); + + LInstruction* shifted_operation = TryDoOpWithShiftedRightOperand(instr); + if (shifted_operation != NULL) { + return shifted_operation; + } + + LOperand* left = UseRegisterAtStart(instr->BetterLeftOperand()); + LOperand* right = + UseRegisterOrConstantAtStart(instr->BetterRightOperand()); + return instr->representation().IsSmi() ? + DefineAsRegister(new(zone()) LBitS(left, right)) : + DefineAsRegister(new(zone()) LBitI(left, right)); + } else { + return DoArithmeticT(instr->op(), instr); + } +} + + +LInstruction* LChunkBuilder::DoBlockEntry(HBlockEntry* instr) { + // V8 expects a label to be generated for each basic block. + // This is used in some places like LAllocator::IsBlockBoundary + // in lithium-allocator.cc + return new(zone()) LLabel(instr->block()); +} + + +LInstruction* LChunkBuilder::DoBoundsCheck(HBoundsCheck* instr) { + if (!FLAG_debug_code && instr->skip_check()) return NULL; + LOperand* index = UseRegisterOrConstantAtStart(instr->index()); + LOperand* length = !index->IsConstantOperand() + ? UseRegisterOrConstantAtStart(instr->length()) + : UseRegisterAtStart(instr->length()); + LInstruction* result = new(zone()) LBoundsCheck(index, length); + if (!FLAG_debug_code || !instr->skip_check()) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoBranch(HBranch* instr) { + HValue* value = instr->value(); + Representation r = value->representation(); + HType type = value->type(); + + if (r.IsInteger32() || r.IsSmi() || r.IsDouble()) { + // These representations have simple checks that cannot deoptimize. + return new(zone()) LBranch(UseRegister(value), NULL, NULL); + } else { + ASSERT(r.IsTagged()); + if (type.IsBoolean() || type.IsSmi() || type.IsJSArray() || + type.IsHeapNumber()) { + // These types have simple checks that cannot deoptimize. + return new(zone()) LBranch(UseRegister(value), NULL, NULL); + } + + if (type.IsString()) { + // This type cannot deoptimize, but needs a scratch register. + return new(zone()) LBranch(UseRegister(value), TempRegister(), NULL); + } + + ToBooleanStub::Types expected = instr->expected_input_types(); + bool needs_temps = expected.NeedsMap() || expected.IsEmpty(); + LOperand* temp1 = needs_temps ? TempRegister() : NULL; + LOperand* temp2 = needs_temps ? TempRegister() : NULL; + + if (expected.IsGeneric() || expected.IsEmpty()) { + // The generic case cannot deoptimize because it already supports every + // possible input type. + ASSERT(needs_temps); + return new(zone()) LBranch(UseRegister(value), temp1, temp2); + } else { + return AssignEnvironment( + new(zone()) LBranch(UseRegister(value), temp1, temp2)); + } + } +} + + +LInstruction* LChunkBuilder::DoCallJSFunction( + HCallJSFunction* instr) { + LOperand* function = UseFixed(instr->function(), x1); + + LCallJSFunction* result = new(zone()) LCallJSFunction(function); + + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoCallWithDescriptor( + HCallWithDescriptor* instr) { + const CallInterfaceDescriptor* descriptor = instr->descriptor(); + + LOperand* target = UseRegisterOrConstantAtStart(instr->target()); + ZoneList<LOperand*> ops(instr->OperandCount(), zone()); + ops.Add(target, zone()); + for (int i = 1; i < instr->OperandCount(); i++) { + LOperand* op = UseFixed(instr->OperandAt(i), + descriptor->GetParameterRegister(i - 1)); + ops.Add(op, zone()); + } + + LCallWithDescriptor* result = new(zone()) LCallWithDescriptor(descriptor, + ops, + zone()); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoCallFunction(HCallFunction* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* function = UseFixed(instr->function(), x1); + LCallFunction* call = new(zone()) LCallFunction(context, function); + return MarkAsCall(DefineFixed(call, x0), instr); +} + + +LInstruction* LChunkBuilder::DoCallNew(HCallNew* instr) { + LOperand* context = UseFixed(instr->context(), cp); + // The call to CallConstructStub will expect the constructor to be in x1. + LOperand* constructor = UseFixed(instr->constructor(), x1); + LCallNew* result = new(zone()) LCallNew(context, constructor); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoCallNewArray(HCallNewArray* instr) { + LOperand* context = UseFixed(instr->context(), cp); + // The call to ArrayConstructCode will expect the constructor to be in x1. + LOperand* constructor = UseFixed(instr->constructor(), x1); + LCallNewArray* result = new(zone()) LCallNewArray(context, constructor); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoCallRuntime(HCallRuntime* instr) { + LOperand* context = UseFixed(instr->context(), cp); + return MarkAsCall(DefineFixed(new(zone()) LCallRuntime(context), x0), instr); +} + + +LInstruction* LChunkBuilder::DoCallStub(HCallStub* instr) { + LOperand* context = UseFixed(instr->context(), cp); + return MarkAsCall(DefineFixed(new(zone()) LCallStub(context), x0), instr); +} + + +LInstruction* LChunkBuilder::DoCapturedObject(HCapturedObject* instr) { + instr->ReplayEnvironment(current_block_->last_environment()); + + // There are no real uses of a captured object. + return NULL; +} + + +LInstruction* LChunkBuilder::DoChange(HChange* instr) { + Representation from = instr->from(); + Representation to = instr->to(); + HValue* val = instr->value(); + if (from.IsSmi()) { + if (to.IsTagged()) { + LOperand* value = UseRegister(val); + return DefineSameAsFirst(new(zone()) LDummyUse(value)); + } + from = Representation::Tagged(); + } + if (from.IsTagged()) { + if (to.IsDouble()) { + LOperand* value = UseRegister(val); + LOperand* temp = TempRegister(); + LInstruction* result = + DefineAsRegister(new(zone()) LNumberUntagD(value, temp)); + if (!val->representation().IsSmi()) result = AssignEnvironment(result); + return result; + } else if (to.IsSmi()) { + LOperand* value = UseRegister(val); + if (val->type().IsSmi()) { + return DefineSameAsFirst(new(zone()) LDummyUse(value)); + } + return AssignEnvironment(DefineSameAsFirst(new(zone()) LCheckSmi(value))); + } else { + ASSERT(to.IsInteger32()); + if (val->type().IsSmi() || val->representation().IsSmi()) { + LOperand* value = UseRegisterAtStart(val); + return DefineAsRegister(new(zone()) LSmiUntag(value, false)); + } else { + LOperand* value = UseRegister(val); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = instr->CanTruncateToInt32() + ? NULL : TempDoubleRegister(); + LInstruction* result = + DefineAsRegister(new(zone()) LTaggedToI(value, temp1, temp2)); + if (!val->representation().IsSmi()) result = AssignEnvironment(result); + return result; + } + } + } else if (from.IsDouble()) { + if (to.IsTagged()) { + info()->MarkAsDeferredCalling(); + LOperand* value = UseRegister(val); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + LNumberTagD* result = new(zone()) LNumberTagD(value, temp1, temp2); + return AssignPointerMap(DefineAsRegister(result)); + } else { + ASSERT(to.IsSmi() || to.IsInteger32()); + if (instr->CanTruncateToInt32()) { + LOperand* value = UseRegister(val); + return DefineAsRegister(new(zone()) LTruncateDoubleToIntOrSmi(value)); + } else { + LOperand* value = UseRegister(val); + LDoubleToIntOrSmi* result = new(zone()) LDoubleToIntOrSmi(value); + return AssignEnvironment(DefineAsRegister(result)); + } + } + } else if (from.IsInteger32()) { + info()->MarkAsDeferredCalling(); + if (to.IsTagged()) { + if (val->CheckFlag(HInstruction::kUint32)) { + LOperand* value = UseRegister(val); + LNumberTagU* result = + new(zone()) LNumberTagU(value, TempRegister(), TempRegister()); + return AssignPointerMap(DefineAsRegister(result)); + } else { + STATIC_ASSERT((kMinInt == Smi::kMinValue) && + (kMaxInt == Smi::kMaxValue)); + LOperand* value = UseRegisterAtStart(val); + return DefineAsRegister(new(zone()) LSmiTag(value)); + } + } else if (to.IsSmi()) { + LOperand* value = UseRegisterAtStart(val); + LInstruction* result = DefineAsRegister(new(zone()) LSmiTag(value)); + if (val->CheckFlag(HInstruction::kUint32)) { + result = AssignEnvironment(result); + } + return result; + } else { + ASSERT(to.IsDouble()); + if (val->CheckFlag(HInstruction::kUint32)) { + return DefineAsRegister( + new(zone()) LUint32ToDouble(UseRegisterAtStart(val))); + } else { + return DefineAsRegister( + new(zone()) LInteger32ToDouble(UseRegisterAtStart(val))); + } + } + } + UNREACHABLE(); + return NULL; +} + + +LInstruction* LChunkBuilder::DoCheckValue(HCheckValue* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new(zone()) LCheckValue(value)); +} + + +LInstruction* LChunkBuilder::DoCheckInstanceType(HCheckInstanceType* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* temp = TempRegister(); + LInstruction* result = new(zone()) LCheckInstanceType(value, temp); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoCheckMaps(HCheckMaps* instr) { + if (instr->IsStabilityCheck()) return new(zone()) LCheckMaps; + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* temp = TempRegister(); + LInstruction* result = AssignEnvironment(new(zone()) LCheckMaps(value, temp)); + if (instr->HasMigrationTarget()) { + info()->MarkAsDeferredCalling(); + result = AssignPointerMap(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoCheckHeapObject(HCheckHeapObject* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + LInstruction* result = new(zone()) LCheckNonSmi(value); + if (!instr->value()->type().IsHeapObject()) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoCheckSmi(HCheckSmi* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new(zone()) LCheckSmi(value)); +} + + +LInstruction* LChunkBuilder::DoClampToUint8(HClampToUint8* instr) { + HValue* value = instr->value(); + Representation input_rep = value->representation(); + LOperand* reg = UseRegister(value); + if (input_rep.IsDouble()) { + return DefineAsRegister(new(zone()) LClampDToUint8(reg)); + } else if (input_rep.IsInteger32()) { + return DefineAsRegister(new(zone()) LClampIToUint8(reg)); + } else { + ASSERT(input_rep.IsSmiOrTagged()); + return AssignEnvironment( + DefineAsRegister(new(zone()) LClampTToUint8(reg, + TempRegister(), + TempDoubleRegister()))); + } +} + + +LInstruction* LChunkBuilder::DoClassOfTestAndBranch( + HClassOfTestAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + return new(zone()) LClassOfTestAndBranch(value, + TempRegister(), + TempRegister()); +} + + +LInstruction* LChunkBuilder::DoCompareNumericAndBranch( + HCompareNumericAndBranch* instr) { + Representation r = instr->representation(); + if (r.IsSmiOrInteger32()) { + ASSERT(instr->left()->representation().Equals(r)); + ASSERT(instr->right()->representation().Equals(r)); + LOperand* left = UseRegisterOrConstantAtStart(instr->left()); + LOperand* right = UseRegisterOrConstantAtStart(instr->right()); + return new(zone()) LCompareNumericAndBranch(left, right); + } else { + ASSERT(r.IsDouble()); + ASSERT(instr->left()->representation().IsDouble()); + ASSERT(instr->right()->representation().IsDouble()); + // TODO(all): In fact the only case that we can handle more efficiently is + // when one of the operand is the constant 0. Currently the MacroAssembler + // will be able to cope with any constant by loading it into an internal + // scratch register. This means that if the constant is used more that once, + // it will be loaded multiple times. Unfortunatly crankshaft already + // duplicates constant loads, but we should modify the code below once this + // issue has been addressed in crankshaft. + LOperand* left = UseRegisterOrConstantAtStart(instr->left()); + LOperand* right = UseRegisterOrConstantAtStart(instr->right()); + return new(zone()) LCompareNumericAndBranch(left, right); + } +} + + +LInstruction* LChunkBuilder::DoCompareGeneric(HCompareGeneric* instr) { + ASSERT(instr->left()->representation().IsTagged()); + ASSERT(instr->right()->representation().IsTagged()); + LOperand* context = UseFixed(instr->context(), cp); + LOperand* left = UseFixed(instr->left(), x1); + LOperand* right = UseFixed(instr->right(), x0); + LCmpT* result = new(zone()) LCmpT(context, left, right); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoCompareHoleAndBranch( + HCompareHoleAndBranch* instr) { + LOperand* value = UseRegister(instr->value()); + if (instr->representation().IsTagged()) { + return new(zone()) LCmpHoleAndBranchT(value); + } else { + LOperand* temp = TempRegister(); + return new(zone()) LCmpHoleAndBranchD(value, temp); + } +} + + +LInstruction* LChunkBuilder::DoCompareObjectEqAndBranch( + HCompareObjectEqAndBranch* instr) { + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + return new(zone()) LCmpObjectEqAndBranch(left, right); +} + + +LInstruction* LChunkBuilder::DoCompareMap(HCompareMap* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* temp = TempRegister(); + return new(zone()) LCmpMapAndBranch(value, temp); +} + + +LInstruction* LChunkBuilder::DoConstant(HConstant* instr) { + Representation r = instr->representation(); + if (r.IsSmi()) { + return DefineAsRegister(new(zone()) LConstantS); + } else if (r.IsInteger32()) { + return DefineAsRegister(new(zone()) LConstantI); + } else if (r.IsDouble()) { + return DefineAsRegister(new(zone()) LConstantD); + } else if (r.IsExternal()) { + return DefineAsRegister(new(zone()) LConstantE); + } else if (r.IsTagged()) { + return DefineAsRegister(new(zone()) LConstantT); + } else { + UNREACHABLE(); + return NULL; + } +} + + +LInstruction* LChunkBuilder::DoContext(HContext* instr) { + if (instr->HasNoUses()) return NULL; + + if (info()->IsStub()) { + return DefineFixed(new(zone()) LContext, cp); + } + + return DefineAsRegister(new(zone()) LContext); +} + + +LInstruction* LChunkBuilder::DoDateField(HDateField* instr) { + LOperand* object = UseFixed(instr->value(), x0); + LDateField* result = new(zone()) LDateField(object, instr->index()); + return MarkAsCall(DefineFixed(result, x0), instr, CAN_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoDebugBreak(HDebugBreak* instr) { + return new(zone()) LDebugBreak(); +} + + +LInstruction* LChunkBuilder::DoDeclareGlobals(HDeclareGlobals* instr) { + LOperand* context = UseFixed(instr->context(), cp); + return MarkAsCall(new(zone()) LDeclareGlobals(context), instr); +} + + +LInstruction* LChunkBuilder::DoDeoptimize(HDeoptimize* instr) { + return AssignEnvironment(new(zone()) LDeoptimize); +} + + +LInstruction* LChunkBuilder::DoDivByPowerOf2I(HDiv* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegister(instr->left()); + int32_t divisor = instr->right()->GetInteger32Constant(); + LInstruction* result = DefineAsRegister(new(zone()) LDivByPowerOf2I( + dividend, divisor)); + if ((instr->CheckFlag(HValue::kBailoutOnMinusZero) && divisor < 0) || + (instr->CheckFlag(HValue::kCanOverflow) && divisor == -1) || + (!instr->CheckFlag(HInstruction::kAllUsesTruncatingToInt32) && + divisor != 1 && divisor != -1)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoDivByConstI(HDiv* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegister(instr->left()); + int32_t divisor = instr->right()->GetInteger32Constant(); + LOperand* temp = instr->CheckFlag(HInstruction::kAllUsesTruncatingToInt32) + ? NULL : TempRegister(); + LInstruction* result = DefineAsRegister(new(zone()) LDivByConstI( + dividend, divisor, temp)); + if (divisor == 0 || + (instr->CheckFlag(HValue::kBailoutOnMinusZero) && divisor < 0) || + !instr->CheckFlag(HInstruction::kAllUsesTruncatingToInt32)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoDivI(HBinaryOperation* instr) { + ASSERT(instr->representation().IsSmiOrInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegister(instr->left()); + LOperand* divisor = UseRegister(instr->right()); + LOperand* temp = instr->CheckFlag(HInstruction::kAllUsesTruncatingToInt32) + ? NULL : TempRegister(); + LInstruction* result = + DefineAsRegister(new(zone()) LDivI(dividend, divisor, temp)); + if (!instr->CheckFlag(HValue::kAllUsesTruncatingToInt32)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoDiv(HDiv* instr) { + if (instr->representation().IsSmiOrInteger32()) { + if (instr->RightIsPowerOf2()) { + return DoDivByPowerOf2I(instr); + } else if (instr->right()->IsConstant()) { + return DoDivByConstI(instr); + } else { + return DoDivI(instr); + } + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::DIV, instr); + } else { + return DoArithmeticT(Token::DIV, instr); + } +} + + +LInstruction* LChunkBuilder::DoDummyUse(HDummyUse* instr) { + return DefineAsRegister(new(zone()) LDummyUse(UseAny(instr->value()))); +} + + +LInstruction* LChunkBuilder::DoEnterInlined(HEnterInlined* instr) { + HEnvironment* outer = current_block_->last_environment(); + outer->set_ast_id(instr->ReturnId()); + HConstant* undefined = graph()->GetConstantUndefined(); + HEnvironment* inner = outer->CopyForInlining(instr->closure(), + instr->arguments_count(), + instr->function(), + undefined, + instr->inlining_kind()); + // Only replay binding of arguments object if it wasn't removed from graph. + if ((instr->arguments_var() != NULL) && + instr->arguments_object()->IsLinked()) { + inner->Bind(instr->arguments_var(), instr->arguments_object()); + } + inner->set_entry(instr); + current_block_->UpdateEnvironment(inner); + chunk_->AddInlinedClosure(instr->closure()); + return NULL; +} + + +LInstruction* LChunkBuilder::DoEnvironmentMarker(HEnvironmentMarker* instr) { + UNREACHABLE(); + return NULL; +} + + +LInstruction* LChunkBuilder::DoForceRepresentation( + HForceRepresentation* instr) { + // All HForceRepresentation instructions should be eliminated in the + // representation change phase of Hydrogen. + UNREACHABLE(); + return NULL; +} + + +LInstruction* LChunkBuilder::DoFunctionLiteral(HFunctionLiteral* instr) { + LOperand* context = UseFixed(instr->context(), cp); + return MarkAsCall( + DefineFixed(new(zone()) LFunctionLiteral(context), x0), instr); +} + + +LInstruction* LChunkBuilder::DoGetCachedArrayIndex( + HGetCachedArrayIndex* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + return DefineAsRegister(new(zone()) LGetCachedArrayIndex(value)); +} + + +LInstruction* LChunkBuilder::DoGoto(HGoto* instr) { + return new(zone()) LGoto(instr->FirstSuccessor()); +} + + +LInstruction* LChunkBuilder::DoHasCachedArrayIndexAndBranch( + HHasCachedArrayIndexAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + return new(zone()) LHasCachedArrayIndexAndBranch( + UseRegisterAtStart(instr->value()), TempRegister()); +} + + +LInstruction* LChunkBuilder::DoHasInstanceTypeAndBranch( + HHasInstanceTypeAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + return new(zone()) LHasInstanceTypeAndBranch(value, TempRegister()); +} + + +LInstruction* LChunkBuilder::DoInnerAllocatedObject( + HInnerAllocatedObject* instr) { + LOperand* base_object = UseRegisterAtStart(instr->base_object()); + LOperand* offset = UseRegisterOrConstantAtStart(instr->offset()); + return DefineAsRegister( + new(zone()) LInnerAllocatedObject(base_object, offset)); +} + + +LInstruction* LChunkBuilder::DoInstanceOf(HInstanceOf* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LInstanceOf* result = new(zone()) LInstanceOf( + context, + UseFixed(instr->left(), InstanceofStub::left()), + UseFixed(instr->right(), InstanceofStub::right())); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoInstanceOfKnownGlobal( + HInstanceOfKnownGlobal* instr) { + LInstanceOfKnownGlobal* result = new(zone()) LInstanceOfKnownGlobal( + UseFixed(instr->context(), cp), + UseFixed(instr->left(), InstanceofStub::left())); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoInvokeFunction(HInvokeFunction* instr) { + LOperand* context = UseFixed(instr->context(), cp); + // The function is required (by MacroAssembler::InvokeFunction) to be in x1. + LOperand* function = UseFixed(instr->function(), x1); + LInvokeFunction* result = new(zone()) LInvokeFunction(context, function); + return MarkAsCall(DefineFixed(result, x0), instr, CANNOT_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoIsConstructCallAndBranch( + HIsConstructCallAndBranch* instr) { + return new(zone()) LIsConstructCallAndBranch(TempRegister(), TempRegister()); +} + + +LInstruction* LChunkBuilder::DoCompareMinusZeroAndBranch( + HCompareMinusZeroAndBranch* instr) { + LOperand* value = UseRegister(instr->value()); + LOperand* scratch = TempRegister(); + return new(zone()) LCompareMinusZeroAndBranch(value, scratch); +} + + +LInstruction* LChunkBuilder::DoIsObjectAndBranch(HIsObjectAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + return new(zone()) LIsObjectAndBranch(value, temp1, temp2); +} + + +LInstruction* LChunkBuilder::DoIsStringAndBranch(HIsStringAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* temp = TempRegister(); + return new(zone()) LIsStringAndBranch(value, temp); +} + + +LInstruction* LChunkBuilder::DoIsSmiAndBranch(HIsSmiAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + return new(zone()) LIsSmiAndBranch(UseRegisterAtStart(instr->value())); +} + + +LInstruction* LChunkBuilder::DoIsUndetectableAndBranch( + HIsUndetectableAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + return new(zone()) LIsUndetectableAndBranch(value, TempRegister()); +} + + +LInstruction* LChunkBuilder::DoLeaveInlined(HLeaveInlined* instr) { + LInstruction* pop = NULL; + HEnvironment* env = current_block_->last_environment(); + + if (env->entry()->arguments_pushed()) { + int argument_count = env->arguments_environment()->parameter_count(); + pop = new(zone()) LDrop(argument_count); + ASSERT(instr->argument_delta() == -argument_count); + } + + HEnvironment* outer = + current_block_->last_environment()->DiscardInlined(false); + current_block_->UpdateEnvironment(outer); + + return pop; +} + + +LInstruction* LChunkBuilder::DoLoadContextSlot(HLoadContextSlot* instr) { + LOperand* context = UseRegisterAtStart(instr->value()); + LInstruction* result = + DefineAsRegister(new(zone()) LLoadContextSlot(context)); + if (instr->RequiresHoleCheck() && instr->DeoptimizesOnHole()) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoLoadFunctionPrototype( + HLoadFunctionPrototype* instr) { + LOperand* function = UseRegister(instr->function()); + LOperand* temp = TempRegister(); + return AssignEnvironment(DefineAsRegister( + new(zone()) LLoadFunctionPrototype(function, temp))); +} + + +LInstruction* LChunkBuilder::DoLoadGlobalCell(HLoadGlobalCell* instr) { + LLoadGlobalCell* result = new(zone()) LLoadGlobalCell(); + return instr->RequiresHoleCheck() + ? AssignEnvironment(DefineAsRegister(result)) + : DefineAsRegister(result); +} + + +LInstruction* LChunkBuilder::DoLoadGlobalGeneric(HLoadGlobalGeneric* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* global_object = UseFixed(instr->global_object(), x0); + LLoadGlobalGeneric* result = + new(zone()) LLoadGlobalGeneric(context, global_object); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoLoadKeyed(HLoadKeyed* instr) { + ASSERT(instr->key()->representation().IsSmiOrInteger32()); + ElementsKind elements_kind = instr->elements_kind(); + LOperand* elements = UseRegister(instr->elements()); + LOperand* key = UseRegisterOrConstant(instr->key()); + + if (!instr->is_typed_elements()) { + if (instr->representation().IsDouble()) { + LOperand* temp = (!instr->key()->IsConstant() || + instr->RequiresHoleCheck()) + ? TempRegister() + : NULL; + + LLoadKeyedFixedDouble* result = + new(zone()) LLoadKeyedFixedDouble(elements, key, temp); + return instr->RequiresHoleCheck() + ? AssignEnvironment(DefineAsRegister(result)) + : DefineAsRegister(result); + } else { + ASSERT(instr->representation().IsSmiOrTagged() || + instr->representation().IsInteger32()); + LOperand* temp = instr->key()->IsConstant() ? NULL : TempRegister(); + LLoadKeyedFixed* result = + new(zone()) LLoadKeyedFixed(elements, key, temp); + return instr->RequiresHoleCheck() + ? AssignEnvironment(DefineAsRegister(result)) + : DefineAsRegister(result); + } + } else { + ASSERT((instr->representation().IsInteger32() && + !IsDoubleOrFloatElementsKind(instr->elements_kind())) || + (instr->representation().IsDouble() && + IsDoubleOrFloatElementsKind(instr->elements_kind()))); + + LOperand* temp = instr->key()->IsConstant() ? NULL : TempRegister(); + LInstruction* result = DefineAsRegister( + new(zone()) LLoadKeyedExternal(elements, key, temp)); + if ((elements_kind == EXTERNAL_UINT32_ELEMENTS || + elements_kind == UINT32_ELEMENTS) && + !instr->CheckFlag(HInstruction::kUint32)) { + result = AssignEnvironment(result); + } + return result; + } +} + + +LInstruction* LChunkBuilder::DoLoadKeyedGeneric(HLoadKeyedGeneric* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* object = UseFixed(instr->object(), x1); + LOperand* key = UseFixed(instr->key(), x0); + + LInstruction* result = + DefineFixed(new(zone()) LLoadKeyedGeneric(context, object, key), x0); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoLoadNamedField(HLoadNamedField* instr) { + LOperand* object = UseRegisterAtStart(instr->object()); + return DefineAsRegister(new(zone()) LLoadNamedField(object)); +} + + +LInstruction* LChunkBuilder::DoLoadNamedGeneric(HLoadNamedGeneric* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* object = UseFixed(instr->object(), x0); + LInstruction* result = + DefineFixed(new(zone()) LLoadNamedGeneric(context, object), x0); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoLoadRoot(HLoadRoot* instr) { + return DefineAsRegister(new(zone()) LLoadRoot); +} + + +LInstruction* LChunkBuilder::DoMapEnumLength(HMapEnumLength* instr) { + LOperand* map = UseRegisterAtStart(instr->value()); + return DefineAsRegister(new(zone()) LMapEnumLength(map)); +} + + +LInstruction* LChunkBuilder::DoFlooringDivByPowerOf2I(HMathFloorOfDiv* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegisterAtStart(instr->left()); + int32_t divisor = instr->right()->GetInteger32Constant(); + LInstruction* result = DefineAsRegister(new(zone()) LFlooringDivByPowerOf2I( + dividend, divisor)); + if ((instr->CheckFlag(HValue::kBailoutOnMinusZero) && divisor < 0) || + (instr->CheckFlag(HValue::kLeftCanBeMinInt) && divisor == -1)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoFlooringDivByConstI(HMathFloorOfDiv* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegister(instr->left()); + int32_t divisor = instr->right()->GetInteger32Constant(); + LOperand* temp = + ((divisor > 0 && !instr->CheckFlag(HValue::kLeftCanBeNegative)) || + (divisor < 0 && !instr->CheckFlag(HValue::kLeftCanBePositive))) ? + NULL : TempRegister(); + LInstruction* result = DefineAsRegister( + new(zone()) LFlooringDivByConstI(dividend, divisor, temp)); + if (divisor == 0 || + (instr->CheckFlag(HValue::kBailoutOnMinusZero) && divisor < 0)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoFlooringDivI(HMathFloorOfDiv* instr) { + LOperand* dividend = UseRegister(instr->left()); + LOperand* divisor = UseRegister(instr->right()); + LOperand* remainder = TempRegister(); + LInstruction* result = + DefineAsRegister(new(zone()) LFlooringDivI(dividend, divisor, remainder)); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoMathFloorOfDiv(HMathFloorOfDiv* instr) { + if (instr->RightIsPowerOf2()) { + return DoFlooringDivByPowerOf2I(instr); + } else if (instr->right()->IsConstant()) { + return DoFlooringDivByConstI(instr); + } else { + return DoFlooringDivI(instr); + } +} + + +LInstruction* LChunkBuilder::DoMathMinMax(HMathMinMax* instr) { + LOperand* left = NULL; + LOperand* right = NULL; + if (instr->representation().IsSmiOrInteger32()) { + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + left = UseRegisterAtStart(instr->BetterLeftOperand()); + right = UseRegisterOrConstantAtStart(instr->BetterRightOperand()); + } else { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->left()->representation().IsDouble()); + ASSERT(instr->right()->representation().IsDouble()); + left = UseRegisterAtStart(instr->left()); + right = UseRegisterAtStart(instr->right()); + } + return DefineAsRegister(new(zone()) LMathMinMax(left, right)); +} + + +LInstruction* LChunkBuilder::DoModByPowerOf2I(HMod* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegisterAtStart(instr->left()); + int32_t divisor = instr->right()->GetInteger32Constant(); + LInstruction* result = DefineSameAsFirst(new(zone()) LModByPowerOf2I( + dividend, divisor)); + if (instr->CheckFlag(HValue::kBailoutOnMinusZero)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoModByConstI(HMod* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegister(instr->left()); + int32_t divisor = instr->right()->GetInteger32Constant(); + LOperand* temp = TempRegister(); + LInstruction* result = DefineAsRegister(new(zone()) LModByConstI( + dividend, divisor, temp)); + if (divisor == 0 || instr->CheckFlag(HValue::kBailoutOnMinusZero)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoModI(HMod* instr) { + ASSERT(instr->representation().IsSmiOrInteger32()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + LOperand* dividend = UseRegister(instr->left()); + LOperand* divisor = UseRegister(instr->right()); + LInstruction* result = DefineAsRegister(new(zone()) LModI(dividend, divisor)); + if (instr->CheckFlag(HValue::kCanBeDivByZero) || + instr->CheckFlag(HValue::kBailoutOnMinusZero)) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoMod(HMod* instr) { + if (instr->representation().IsSmiOrInteger32()) { + if (instr->RightIsPowerOf2()) { + return DoModByPowerOf2I(instr); + } else if (instr->right()->IsConstant()) { + return DoModByConstI(instr); + } else { + return DoModI(instr); + } + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::MOD, instr); + } else { + return DoArithmeticT(Token::MOD, instr); + } +} + + +LInstruction* LChunkBuilder::DoMul(HMul* instr) { + if (instr->representation().IsSmiOrInteger32()) { + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + + bool can_overflow = instr->CheckFlag(HValue::kCanOverflow); + bool bailout_on_minus_zero = instr->CheckFlag(HValue::kBailoutOnMinusZero); + + HValue* least_const = instr->BetterLeftOperand(); + HValue* most_const = instr->BetterRightOperand(); + + // LMulConstI can handle a subset of constants: + // With support for overflow detection: + // -1, 0, 1, 2 + // 2^n, -(2^n) + // Without support for overflow detection: + // 2^n + 1, -(2^n - 1) + if (most_const->IsConstant()) { + int32_t constant = HConstant::cast(most_const)->Integer32Value(); + bool small_constant = (constant >= -1) && (constant <= 2); + bool end_range_constant = (constant <= -kMaxInt) || (constant == kMaxInt); + int32_t constant_abs = Abs(constant); + + if (!end_range_constant && + (small_constant || + (IsPowerOf2(constant_abs)) || + (!can_overflow && (IsPowerOf2(constant_abs + 1) || + IsPowerOf2(constant_abs - 1))))) { + LConstantOperand* right = UseConstant(most_const); + bool need_register = IsPowerOf2(constant_abs) && !small_constant; + LOperand* left = need_register ? UseRegister(least_const) + : UseRegisterAtStart(least_const); + LInstruction* result = + DefineAsRegister(new(zone()) LMulConstIS(left, right)); + if ((bailout_on_minus_zero && constant <= 0) || can_overflow) { + result = AssignEnvironment(result); + } + return result; + } + } + + // LMulI/S can handle all cases, but it requires that a register is + // allocated for the second operand. + LOperand* left = UseRegisterAtStart(least_const); + LOperand* right = UseRegisterAtStart(most_const); + LInstruction* result = instr->representation().IsSmi() + ? DefineAsRegister(new(zone()) LMulS(left, right)) + : DefineAsRegister(new(zone()) LMulI(left, right)); + if ((bailout_on_minus_zero && least_const != most_const) || can_overflow) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::MUL, instr); + } else { + return DoArithmeticT(Token::MUL, instr); + } +} + + +LInstruction* LChunkBuilder::DoOsrEntry(HOsrEntry* instr) { + ASSERT(argument_count_ == 0); + allocator_->MarkAsOsrEntry(); + current_block_->last_environment()->set_ast_id(instr->ast_id()); + return AssignEnvironment(new(zone()) LOsrEntry); +} + + +LInstruction* LChunkBuilder::DoParameter(HParameter* instr) { + LParameter* result = new(zone()) LParameter; + if (instr->kind() == HParameter::STACK_PARAMETER) { + int spill_index = chunk_->GetParameterStackSlot(instr->index()); + return DefineAsSpilled(result, spill_index); + } else { + ASSERT(info()->IsStub()); + CodeStubInterfaceDescriptor* descriptor = + info()->code_stub()->GetInterfaceDescriptor(); + int index = static_cast<int>(instr->index()); + Register reg = descriptor->GetParameterRegister(index); + return DefineFixed(result, reg); + } +} + + +LInstruction* LChunkBuilder::DoPower(HPower* instr) { + ASSERT(instr->representation().IsDouble()); + // We call a C function for double power. It can't trigger a GC. + // We need to use fixed result register for the call. + Representation exponent_type = instr->right()->representation(); + ASSERT(instr->left()->representation().IsDouble()); + LOperand* left = UseFixedDouble(instr->left(), d0); + LOperand* right = exponent_type.IsInteger32() + ? UseFixed(instr->right(), x12) + : exponent_type.IsDouble() + ? UseFixedDouble(instr->right(), d1) + : UseFixed(instr->right(), x11); + LPower* result = new(zone()) LPower(left, right); + return MarkAsCall(DefineFixedDouble(result, d0), + instr, + CAN_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoPushArguments(HPushArguments* instr) { + int argc = instr->OperandCount(); + AddInstruction(new(zone()) LPreparePushArguments(argc), instr); + + LPushArguments* push_args = new(zone()) LPushArguments(zone()); + + for (int i = 0; i < argc; ++i) { + if (push_args->ShouldSplitPush()) { + AddInstruction(push_args, instr); + push_args = new(zone()) LPushArguments(zone()); + } + push_args->AddArgument(UseRegister(instr->argument(i))); + } + + return push_args; +} + + +LInstruction* LChunkBuilder::DoRegExpLiteral(HRegExpLiteral* instr) { + LOperand* context = UseFixed(instr->context(), cp); + return MarkAsCall( + DefineFixed(new(zone()) LRegExpLiteral(context), x0), instr); +} + + +LInstruction* LChunkBuilder::DoDoubleBits(HDoubleBits* instr) { + HValue* value = instr->value(); + ASSERT(value->representation().IsDouble()); + return DefineAsRegister(new(zone()) LDoubleBits(UseRegister(value))); +} + + +LInstruction* LChunkBuilder::DoConstructDouble(HConstructDouble* instr) { + LOperand* lo = UseRegisterAndClobber(instr->lo()); + LOperand* hi = UseRegister(instr->hi()); + return DefineAsRegister(new(zone()) LConstructDouble(hi, lo)); +} + + +LInstruction* LChunkBuilder::DoReturn(HReturn* instr) { + LOperand* context = info()->IsStub() + ? UseFixed(instr->context(), cp) + : NULL; + LOperand* parameter_count = UseRegisterOrConstant(instr->parameter_count()); + return new(zone()) LReturn(UseFixed(instr->value(), x0), context, + parameter_count); +} + + +LInstruction* LChunkBuilder::DoSeqStringGetChar(HSeqStringGetChar* instr) { + LOperand* string = UseRegisterAtStart(instr->string()); + LOperand* index = UseRegisterOrConstantAtStart(instr->index()); + LOperand* temp = TempRegister(); + LSeqStringGetChar* result = + new(zone()) LSeqStringGetChar(string, index, temp); + return DefineAsRegister(result); +} + + +LInstruction* LChunkBuilder::DoSeqStringSetChar(HSeqStringSetChar* instr) { + LOperand* string = UseRegister(instr->string()); + LOperand* index = FLAG_debug_code + ? UseRegister(instr->index()) + : UseRegisterOrConstant(instr->index()); + LOperand* value = UseRegister(instr->value()); + LOperand* context = FLAG_debug_code ? UseFixed(instr->context(), cp) : NULL; + LOperand* temp = TempRegister(); + LSeqStringSetChar* result = + new(zone()) LSeqStringSetChar(context, string, index, value, temp); + return DefineAsRegister(result); +} + + +HBitwiseBinaryOperation* LChunkBuilder::CanTransformToShiftedOp(HValue* val, + HValue** left) { + if (!val->representation().IsInteger32()) return NULL; + if (!(val->IsBitwise() || val->IsAdd() || val->IsSub())) return NULL; + + HBinaryOperation* hinstr = HBinaryOperation::cast(val); + HValue* hleft = hinstr->left(); + HValue* hright = hinstr->right(); + ASSERT(hleft->representation().Equals(hinstr->representation())); + ASSERT(hright->representation().Equals(hinstr->representation())); + + if ((hright->IsConstant() && + LikelyFitsImmField(hinstr, HConstant::cast(hright)->Integer32Value())) || + (hinstr->IsCommutative() && hleft->IsConstant() && + LikelyFitsImmField(hinstr, HConstant::cast(hleft)->Integer32Value()))) { + // The constant operand will likely fit in the immediate field. We are + // better off with + // lsl x8, x9, #imm + // add x0, x8, #imm2 + // than with + // mov x16, #imm2 + // add x0, x16, x9 LSL #imm + return NULL; + } + + HBitwiseBinaryOperation* shift = NULL; + // TODO(aleram): We will miss situations where a shift operation is used by + // different instructions both as a left and right operands. + if (hright->IsBitwiseBinaryShift() && + HBitwiseBinaryOperation::cast(hright)->right()->IsConstant()) { + shift = HBitwiseBinaryOperation::cast(hright); + if (left != NULL) { + *left = hleft; + } + } else if (hinstr->IsCommutative() && + hleft->IsBitwiseBinaryShift() && + HBitwiseBinaryOperation::cast(hleft)->right()->IsConstant()) { + shift = HBitwiseBinaryOperation::cast(hleft); + if (left != NULL) { + *left = hright; + } + } else { + return NULL; + } + + if ((JSShiftAmountFromHConstant(shift->right()) == 0) && shift->IsShr()) { + // Shifts right by zero can deoptimize. + return NULL; + } + + return shift; +} + + +bool LChunkBuilder::ShiftCanBeOptimizedAway(HBitwiseBinaryOperation* shift) { + if (!shift->representation().IsInteger32()) { + return false; + } + for (HUseIterator it(shift->uses()); !it.Done(); it.Advance()) { + if (shift != CanTransformToShiftedOp(it.value())) { + return false; + } + } + return true; +} + + +LInstruction* LChunkBuilder::TryDoOpWithShiftedRightOperand( + HBinaryOperation* instr) { + HValue* left; + HBitwiseBinaryOperation* shift = CanTransformToShiftedOp(instr, &left); + + if ((shift != NULL) && ShiftCanBeOptimizedAway(shift)) { + return DoShiftedBinaryOp(instr, left, shift); + } + return NULL; +} + + +LInstruction* LChunkBuilder::DoShiftedBinaryOp( + HBinaryOperation* hinstr, HValue* hleft, HBitwiseBinaryOperation* hshift) { + ASSERT(hshift->IsBitwiseBinaryShift()); + ASSERT(!hshift->IsShr() || (JSShiftAmountFromHConstant(hshift->right()) > 0)); + + LTemplateResultInstruction<1>* res; + LOperand* left = UseRegisterAtStart(hleft); + LOperand* right = UseRegisterAtStart(hshift->left()); + LOperand* shift_amount = UseConstant(hshift->right()); + Shift shift_op; + switch (hshift->opcode()) { + case HValue::kShl: shift_op = LSL; break; + case HValue::kShr: shift_op = LSR; break; + case HValue::kSar: shift_op = ASR; break; + default: UNREACHABLE(); shift_op = NO_SHIFT; + } + + if (hinstr->IsBitwise()) { + res = new(zone()) LBitI(left, right, shift_op, shift_amount); + } else if (hinstr->IsAdd()) { + res = new(zone()) LAddI(left, right, shift_op, shift_amount); + } else { + ASSERT(hinstr->IsSub()); + res = new(zone()) LSubI(left, right, shift_op, shift_amount); + } + if (hinstr->CheckFlag(HValue::kCanOverflow)) { + AssignEnvironment(res); + } + return DefineAsRegister(res); +} + + +LInstruction* LChunkBuilder::DoShift(Token::Value op, + HBitwiseBinaryOperation* instr) { + if (instr->representation().IsTagged()) { + return DoArithmeticT(op, instr); + } + + ASSERT(instr->representation().IsInteger32() || + instr->representation().IsSmi()); + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + + if (ShiftCanBeOptimizedAway(instr)) { + return NULL; + } + + LOperand* left = instr->representation().IsSmi() + ? UseRegister(instr->left()) + : UseRegisterAtStart(instr->left()); + + HValue* right_value = instr->right(); + LOperand* right = NULL; + LOperand* temp = NULL; + int constant_value = 0; + if (right_value->IsConstant()) { + right = UseConstant(right_value); + constant_value = JSShiftAmountFromHConstant(right_value); + } else { + right = UseRegisterAtStart(right_value); + if (op == Token::ROR) { + temp = TempRegister(); + } + } + + // Shift operations can only deoptimize if we do a logical shift by 0 and the + // result cannot be truncated to int32. + bool does_deopt = false; + if ((op == Token::SHR) && (constant_value == 0)) { + if (FLAG_opt_safe_uint32_operations) { + does_deopt = !instr->CheckFlag(HInstruction::kUint32); + } else { + does_deopt = !instr->CheckUsesForFlag(HValue::kTruncatingToInt32); + } + } + + LInstruction* result; + if (instr->representation().IsInteger32()) { + result = DefineAsRegister(new(zone()) LShiftI(op, left, right, does_deopt)); + } else { + ASSERT(instr->representation().IsSmi()); + result = DefineAsRegister( + new(zone()) LShiftS(op, left, right, temp, does_deopt)); + } + + return does_deopt ? AssignEnvironment(result) : result; +} + + +LInstruction* LChunkBuilder::DoRor(HRor* instr) { + return DoShift(Token::ROR, instr); +} + + +LInstruction* LChunkBuilder::DoSar(HSar* instr) { + return DoShift(Token::SAR, instr); +} + + +LInstruction* LChunkBuilder::DoShl(HShl* instr) { + return DoShift(Token::SHL, instr); +} + + +LInstruction* LChunkBuilder::DoShr(HShr* instr) { + return DoShift(Token::SHR, instr); +} + + +LInstruction* LChunkBuilder::DoSimulate(HSimulate* instr) { + instr->ReplayEnvironment(current_block_->last_environment()); + return NULL; +} + + +LInstruction* LChunkBuilder::DoStackCheck(HStackCheck* instr) { + if (instr->is_function_entry()) { + LOperand* context = UseFixed(instr->context(), cp); + return MarkAsCall(new(zone()) LStackCheck(context), instr); + } else { + ASSERT(instr->is_backwards_branch()); + LOperand* context = UseAny(instr->context()); + return AssignEnvironment( + AssignPointerMap(new(zone()) LStackCheck(context))); + } +} + + +LInstruction* LChunkBuilder::DoStoreCodeEntry(HStoreCodeEntry* instr) { + LOperand* function = UseRegister(instr->function()); + LOperand* code_object = UseRegisterAtStart(instr->code_object()); + LOperand* temp = TempRegister(); + return new(zone()) LStoreCodeEntry(function, code_object, temp); +} + + +LInstruction* LChunkBuilder::DoStoreContextSlot(HStoreContextSlot* instr) { + LOperand* temp = TempRegister(); + LOperand* context; + LOperand* value; + if (instr->NeedsWriteBarrier()) { + // TODO(all): Replace these constraints when RecordWriteStub has been + // rewritten. + context = UseRegisterAndClobber(instr->context()); + value = UseRegisterAndClobber(instr->value()); + } else { + context = UseRegister(instr->context()); + value = UseRegister(instr->value()); + } + LInstruction* result = new(zone()) LStoreContextSlot(context, value, temp); + if (instr->RequiresHoleCheck() && instr->DeoptimizesOnHole()) { + result = AssignEnvironment(result); + } + return result; +} + + +LInstruction* LChunkBuilder::DoStoreGlobalCell(HStoreGlobalCell* instr) { + LOperand* value = UseRegister(instr->value()); + if (instr->RequiresHoleCheck()) { + return AssignEnvironment(new(zone()) LStoreGlobalCell(value, + TempRegister(), + TempRegister())); + } else { + return new(zone()) LStoreGlobalCell(value, TempRegister(), NULL); + } +} + + +LInstruction* LChunkBuilder::DoStoreKeyed(HStoreKeyed* instr) { + LOperand* key = UseRegisterOrConstant(instr->key()); + LOperand* temp = NULL; + LOperand* elements = NULL; + LOperand* val = NULL; + + if (!instr->is_typed_elements() && + instr->value()->representation().IsTagged() && + instr->NeedsWriteBarrier()) { + // RecordWrite() will clobber all registers. + elements = UseRegisterAndClobber(instr->elements()); + val = UseRegisterAndClobber(instr->value()); + temp = TempRegister(); + } else { + elements = UseRegister(instr->elements()); + val = UseRegister(instr->value()); + temp = instr->key()->IsConstant() ? NULL : TempRegister(); + } + + if (instr->is_typed_elements()) { + ASSERT((instr->value()->representation().IsInteger32() && + !IsDoubleOrFloatElementsKind(instr->elements_kind())) || + (instr->value()->representation().IsDouble() && + IsDoubleOrFloatElementsKind(instr->elements_kind()))); + ASSERT((instr->is_fixed_typed_array() && + instr->elements()->representation().IsTagged()) || + (instr->is_external() && + instr->elements()->representation().IsExternal())); + return new(zone()) LStoreKeyedExternal(elements, key, val, temp); + + } else if (instr->value()->representation().IsDouble()) { + ASSERT(instr->elements()->representation().IsTagged()); + return new(zone()) LStoreKeyedFixedDouble(elements, key, val, temp); + + } else { + ASSERT(instr->elements()->representation().IsTagged()); + ASSERT(instr->value()->representation().IsSmiOrTagged() || + instr->value()->representation().IsInteger32()); + return new(zone()) LStoreKeyedFixed(elements, key, val, temp); + } +} + + +LInstruction* LChunkBuilder::DoStoreKeyedGeneric(HStoreKeyedGeneric* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* object = UseFixed(instr->object(), x2); + LOperand* key = UseFixed(instr->key(), x1); + LOperand* value = UseFixed(instr->value(), x0); + + ASSERT(instr->object()->representation().IsTagged()); + ASSERT(instr->key()->representation().IsTagged()); + ASSERT(instr->value()->representation().IsTagged()); + + return MarkAsCall( + new(zone()) LStoreKeyedGeneric(context, object, key, value), instr); +} + + +LInstruction* LChunkBuilder::DoStoreNamedField(HStoreNamedField* instr) { + // TODO(jbramley): It might be beneficial to allow value to be a constant in + // some cases. x64 makes use of this with FLAG_track_fields, for example. + + LOperand* object = UseRegister(instr->object()); + LOperand* value; + LOperand* temp0 = NULL; + LOperand* temp1 = NULL; + + if (instr->access().IsExternalMemory() || + instr->field_representation().IsDouble()) { + value = UseRegister(instr->value()); + } else if (instr->NeedsWriteBarrier()) { + value = UseRegisterAndClobber(instr->value()); + temp0 = TempRegister(); + temp1 = TempRegister(); + } else if (instr->NeedsWriteBarrierForMap()) { + value = UseRegister(instr->value()); + temp0 = TempRegister(); + temp1 = TempRegister(); + } else { + value = UseRegister(instr->value()); + temp0 = TempRegister(); + } + + return new(zone()) LStoreNamedField(object, value, temp0, temp1); +} + + +LInstruction* LChunkBuilder::DoStoreNamedGeneric(HStoreNamedGeneric* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* object = UseFixed(instr->object(), x1); + LOperand* value = UseFixed(instr->value(), x0); + LInstruction* result = new(zone()) LStoreNamedGeneric(context, object, value); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoStringAdd(HStringAdd* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* left = UseFixed(instr->left(), x1); + LOperand* right = UseFixed(instr->right(), x0); + + LStringAdd* result = new(zone()) LStringAdd(context, left, right); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoStringCharCodeAt(HStringCharCodeAt* instr) { + LOperand* string = UseRegisterAndClobber(instr->string()); + LOperand* index = UseRegisterAndClobber(instr->index()); + LOperand* context = UseAny(instr->context()); + LStringCharCodeAt* result = + new(zone()) LStringCharCodeAt(context, string, index); + return AssignPointerMap(DefineAsRegister(result)); +} + + +LInstruction* LChunkBuilder::DoStringCharFromCode(HStringCharFromCode* instr) { + LOperand* char_code = UseRegister(instr->value()); + LOperand* context = UseAny(instr->context()); + LStringCharFromCode* result = + new(zone()) LStringCharFromCode(context, char_code); + return AssignPointerMap(DefineAsRegister(result)); +} + + +LInstruction* LChunkBuilder::DoStringCompareAndBranch( + HStringCompareAndBranch* instr) { + ASSERT(instr->left()->representation().IsTagged()); + ASSERT(instr->right()->representation().IsTagged()); + LOperand* context = UseFixed(instr->context(), cp); + LOperand* left = UseFixed(instr->left(), x1); + LOperand* right = UseFixed(instr->right(), x0); + LStringCompareAndBranch* result = + new(zone()) LStringCompareAndBranch(context, left, right); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoSub(HSub* instr) { + if (instr->representation().IsSmiOrInteger32()) { + ASSERT(instr->left()->representation().Equals(instr->representation())); + ASSERT(instr->right()->representation().Equals(instr->representation())); + + LInstruction* shifted_operation = TryDoOpWithShiftedRightOperand(instr); + if (shifted_operation != NULL) { + return shifted_operation; + } + + LOperand *left; + if (instr->left()->IsConstant() && + (HConstant::cast(instr->left())->Integer32Value() == 0)) { + left = UseConstant(instr->left()); + } else { + left = UseRegisterAtStart(instr->left()); + } + LOperand* right = UseRegisterOrConstantAtStart(instr->right()); + LInstruction* result = instr->representation().IsSmi() ? + DefineAsRegister(new(zone()) LSubS(left, right)) : + DefineAsRegister(new(zone()) LSubI(left, right)); + if (instr->CheckFlag(HValue::kCanOverflow)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::SUB, instr); + } else { + return DoArithmeticT(Token::SUB, instr); + } +} + + +LInstruction* LChunkBuilder::DoThisFunction(HThisFunction* instr) { + if (instr->HasNoUses()) { + return NULL; + } else { + return DefineAsRegister(new(zone()) LThisFunction); + } +} + + +LInstruction* LChunkBuilder::DoToFastProperties(HToFastProperties* instr) { + LOperand* object = UseFixed(instr->value(), x0); + LToFastProperties* result = new(zone()) LToFastProperties(object); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoTransitionElementsKind( + HTransitionElementsKind* instr) { + if (IsSimpleMapChangeTransition(instr->from_kind(), instr->to_kind())) { + LOperand* object = UseRegister(instr->object()); + LTransitionElementsKind* result = + new(zone()) LTransitionElementsKind(object, NULL, + TempRegister(), TempRegister()); + return result; + } else { + LOperand* object = UseFixed(instr->object(), x0); + LOperand* context = UseFixed(instr->context(), cp); + LTransitionElementsKind* result = + new(zone()) LTransitionElementsKind(object, context, NULL, NULL); + return MarkAsCall(result, instr); + } +} + + +LInstruction* LChunkBuilder::DoTrapAllocationMemento( + HTrapAllocationMemento* instr) { + LOperand* object = UseRegister(instr->object()); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + LTrapAllocationMemento* result = + new(zone()) LTrapAllocationMemento(object, temp1, temp2); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoTypeof(HTypeof* instr) { + LOperand* context = UseFixed(instr->context(), cp); + // TODO(jbramley): In ARM, this uses UseFixed to force the input to x0. + // However, LCodeGen::DoTypeof just pushes it to the stack (for CallRuntime) + // anyway, so the input doesn't have to be in x0. We might be able to improve + // the ARM back-end a little by relaxing this restriction. + LTypeof* result = + new(zone()) LTypeof(context, UseRegisterAtStart(instr->value())); + return MarkAsCall(DefineFixed(result, x0), instr); +} + + +LInstruction* LChunkBuilder::DoTypeofIsAndBranch(HTypeofIsAndBranch* instr) { + // We only need temp registers in some cases, but we can't dereference the + // instr->type_literal() handle to test that here. + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + + return new(zone()) LTypeofIsAndBranch( + UseRegister(instr->value()), temp1, temp2); +} + + +LInstruction* LChunkBuilder::DoUnaryMathOperation(HUnaryMathOperation* instr) { + switch (instr->op()) { + case kMathAbs: { + Representation r = instr->representation(); + if (r.IsTagged()) { + // The tagged case might need to allocate a HeapNumber for the result, + // so it is handled by a separate LInstruction. + LOperand* context = UseFixed(instr->context(), cp); + LOperand* input = UseRegister(instr->value()); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + LOperand* temp3 = TempRegister(); + LInstruction* result = DefineAsRegister( + new(zone()) LMathAbsTagged(context, input, temp1, temp2, temp3)); + return AssignEnvironment(AssignPointerMap(result)); + } else { + LOperand* input = UseRegisterAtStart(instr->value()); + LInstruction* result = DefineAsRegister(new(zone()) LMathAbs(input)); + if (!r.IsDouble()) result = AssignEnvironment(result); + return result; + } + } + case kMathExp: { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->value()->representation().IsDouble()); + LOperand* input = UseRegister(instr->value()); + LOperand* double_temp1 = TempDoubleRegister(); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + LOperand* temp3 = TempRegister(); + LMathExp* result = new(zone()) LMathExp(input, double_temp1, + temp1, temp2, temp3); + return DefineAsRegister(result); + } + case kMathFloor: { + ASSERT(instr->value()->representation().IsDouble()); + LOperand* input = UseRegisterAtStart(instr->value()); + if (instr->representation().IsInteger32()) { + LMathFloorI* result = new(zone()) LMathFloorI(input); + return AssignEnvironment(AssignPointerMap(DefineAsRegister(result))); + } else { + ASSERT(instr->representation().IsDouble()); + LMathFloorD* result = new(zone()) LMathFloorD(input); + return DefineAsRegister(result); + } + } + case kMathLog: { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->value()->representation().IsDouble()); + LOperand* input = UseFixedDouble(instr->value(), d0); + LMathLog* result = new(zone()) LMathLog(input); + return MarkAsCall(DefineFixedDouble(result, d0), instr); + } + case kMathPowHalf: { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->value()->representation().IsDouble()); + LOperand* input = UseRegister(instr->value()); + return DefineAsRegister(new(zone()) LMathPowHalf(input)); + } + case kMathRound: { + ASSERT(instr->value()->representation().IsDouble()); + LOperand* input = UseRegister(instr->value()); + if (instr->representation().IsInteger32()) { + LOperand* temp = TempDoubleRegister(); + LMathRoundI* result = new(zone()) LMathRoundI(input, temp); + return AssignEnvironment(DefineAsRegister(result)); + } else { + ASSERT(instr->representation().IsDouble()); + LMathRoundD* result = new(zone()) LMathRoundD(input); + return DefineAsRegister(result); + } + } + case kMathSqrt: { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->value()->representation().IsDouble()); + LOperand* input = UseRegisterAtStart(instr->value()); + return DefineAsRegister(new(zone()) LMathSqrt(input)); + } + case kMathClz32: { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->value()->representation().IsInteger32()); + LOperand* input = UseRegisterAtStart(instr->value()); + return DefineAsRegister(new(zone()) LMathClz32(input)); + } + default: + UNREACHABLE(); + return NULL; + } +} + + +LInstruction* LChunkBuilder::DoUnknownOSRValue(HUnknownOSRValue* instr) { + // Use an index that corresponds to the location in the unoptimized frame, + // which the optimized frame will subsume. + int env_index = instr->index(); + int spill_index = 0; + if (instr->environment()->is_parameter_index(env_index)) { + spill_index = chunk_->GetParameterStackSlot(env_index); + } else { + spill_index = env_index - instr->environment()->first_local_index(); + if (spill_index > LUnallocated::kMaxFixedSlotIndex) { + Abort(kTooManySpillSlotsNeededForOSR); + spill_index = 0; + } + } + return DefineAsSpilled(new(zone()) LUnknownOSRValue, spill_index); +} + + +LInstruction* LChunkBuilder::DoUseConst(HUseConst* instr) { + return NULL; +} + + +LInstruction* LChunkBuilder::DoForInPrepareMap(HForInPrepareMap* instr) { + LOperand* context = UseFixed(instr->context(), cp); + // Assign object to a fixed register different from those already used in + // LForInPrepareMap. + LOperand* object = UseFixed(instr->enumerable(), x0); + LForInPrepareMap* result = new(zone()) LForInPrepareMap(context, object); + return MarkAsCall(DefineFixed(result, x0), instr, CAN_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoForInCacheArray(HForInCacheArray* instr) { + LOperand* map = UseRegister(instr->map()); + return AssignEnvironment(DefineAsRegister(new(zone()) LForInCacheArray(map))); +} + + +LInstruction* LChunkBuilder::DoCheckMapValue(HCheckMapValue* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* map = UseRegister(instr->map()); + LOperand* temp = TempRegister(); + return AssignEnvironment(new(zone()) LCheckMapValue(value, map, temp)); +} + + +LInstruction* LChunkBuilder::DoLoadFieldByIndex(HLoadFieldByIndex* instr) { + LOperand* object = UseRegisterAtStart(instr->object()); + LOperand* index = UseRegisterAndClobber(instr->index()); + LLoadFieldByIndex* load = new(zone()) LLoadFieldByIndex(object, index); + LInstruction* result = DefineSameAsFirst(load); + return AssignPointerMap(result); +} + + +LInstruction* LChunkBuilder::DoWrapReceiver(HWrapReceiver* instr) { + LOperand* receiver = UseRegister(instr->receiver()); + LOperand* function = UseRegister(instr->function()); + LWrapReceiver* result = new(zone()) LWrapReceiver(receiver, function); + return AssignEnvironment(DefineAsRegister(result)); +} + + +LInstruction* LChunkBuilder::DoStoreFrameContext(HStoreFrameContext* instr) { + LOperand* context = UseRegisterAtStart(instr->context()); + return new(zone()) LStoreFrameContext(context); +} + + +LInstruction* LChunkBuilder::DoAllocateBlockContext( + HAllocateBlockContext* instr) { + LOperand* context = UseFixed(instr->context(), cp); + LOperand* function = UseRegisterAtStart(instr->function()); + LAllocateBlockContext* result = + new(zone()) LAllocateBlockContext(context, function); + return MarkAsCall(DefineFixed(result, cp), instr); +} + + +} } // namespace v8::internal diff --git a/chromium/v8/src/arm64/lithium-arm64.h b/chromium/v8/src/arm64/lithium-arm64.h new file mode 100644 index 00000000000..18dd927d815 --- /dev/null +++ b/chromium/v8/src/arm64/lithium-arm64.h @@ -0,0 +1,3248 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_LITHIUM_ARM64_H_ +#define V8_ARM64_LITHIUM_ARM64_H_ + +#include "src/hydrogen.h" +#include "src/lithium-allocator.h" +#include "src/lithium.h" +#include "src/safepoint-table.h" +#include "src/utils.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LCodeGen; + +#define LITHIUM_CONCRETE_INSTRUCTION_LIST(V) \ + V(AccessArgumentsAt) \ + V(AddE) \ + V(AddI) \ + V(AddS) \ + V(Allocate) \ + V(AllocateBlockContext) \ + V(ApplyArguments) \ + V(ArgumentsElements) \ + V(ArgumentsLength) \ + V(ArithmeticD) \ + V(ArithmeticT) \ + V(BitI) \ + V(BitS) \ + V(BoundsCheck) \ + V(Branch) \ + V(CallFunction) \ + V(CallJSFunction) \ + V(CallNew) \ + V(CallNewArray) \ + V(CallRuntime) \ + V(CallStub) \ + V(CallWithDescriptor) \ + V(CheckInstanceType) \ + V(CheckMapValue) \ + V(CheckMaps) \ + V(CheckNonSmi) \ + V(CheckSmi) \ + V(CheckValue) \ + V(ClampDToUint8) \ + V(ClampIToUint8) \ + V(ClampTToUint8) \ + V(ClassOfTestAndBranch) \ + V(CmpHoleAndBranchD) \ + V(CmpHoleAndBranchT) \ + V(CmpMapAndBranch) \ + V(CmpObjectEqAndBranch) \ + V(CmpT) \ + V(CompareMinusZeroAndBranch) \ + V(CompareNumericAndBranch) \ + V(ConstantD) \ + V(ConstantE) \ + V(ConstantI) \ + V(ConstantS) \ + V(ConstantT) \ + V(ConstructDouble) \ + V(Context) \ + V(DateField) \ + V(DebugBreak) \ + V(DeclareGlobals) \ + V(Deoptimize) \ + V(DivByConstI) \ + V(DivByPowerOf2I) \ + V(DivI) \ + V(DoubleBits) \ + V(DoubleToIntOrSmi) \ + V(Drop) \ + V(Dummy) \ + V(DummyUse) \ + V(FlooringDivByConstI) \ + V(FlooringDivByPowerOf2I) \ + V(FlooringDivI) \ + V(ForInCacheArray) \ + V(ForInPrepareMap) \ + V(FunctionLiteral) \ + V(GetCachedArrayIndex) \ + V(Goto) \ + V(HasCachedArrayIndexAndBranch) \ + V(HasInstanceTypeAndBranch) \ + V(InnerAllocatedObject) \ + V(InstanceOf) \ + V(InstanceOfKnownGlobal) \ + V(InstructionGap) \ + V(Integer32ToDouble) \ + V(InvokeFunction) \ + V(IsConstructCallAndBranch) \ + V(IsObjectAndBranch) \ + V(IsSmiAndBranch) \ + V(IsStringAndBranch) \ + V(IsUndetectableAndBranch) \ + V(Label) \ + V(LazyBailout) \ + V(LoadContextSlot) \ + V(LoadFieldByIndex) \ + V(LoadFunctionPrototype) \ + V(LoadGlobalCell) \ + V(LoadGlobalGeneric) \ + V(LoadKeyedExternal) \ + V(LoadKeyedFixed) \ + V(LoadKeyedFixedDouble) \ + V(LoadKeyedGeneric) \ + V(LoadNamedField) \ + V(LoadNamedGeneric) \ + V(LoadRoot) \ + V(MapEnumLength) \ + V(MathAbs) \ + V(MathAbsTagged) \ + V(MathClz32) \ + V(MathExp) \ + V(MathFloorD) \ + V(MathFloorI) \ + V(MathLog) \ + V(MathMinMax) \ + V(MathPowHalf) \ + V(MathRoundD) \ + V(MathRoundI) \ + V(MathSqrt) \ + V(ModByConstI) \ + V(ModByPowerOf2I) \ + V(ModI) \ + V(MulConstIS) \ + V(MulI) \ + V(MulS) \ + V(NumberTagD) \ + V(NumberTagU) \ + V(NumberUntagD) \ + V(OsrEntry) \ + V(Parameter) \ + V(Power) \ + V(PreparePushArguments) \ + V(PushArguments) \ + V(RegExpLiteral) \ + V(Return) \ + V(SeqStringGetChar) \ + V(SeqStringSetChar) \ + V(ShiftI) \ + V(ShiftS) \ + V(SmiTag) \ + V(SmiUntag) \ + V(StackCheck) \ + V(StoreCodeEntry) \ + V(StoreContextSlot) \ + V(StoreFrameContext) \ + V(StoreGlobalCell) \ + V(StoreKeyedExternal) \ + V(StoreKeyedFixed) \ + V(StoreKeyedFixedDouble) \ + V(StoreKeyedGeneric) \ + V(StoreNamedField) \ + V(StoreNamedGeneric) \ + V(StringAdd) \ + V(StringCharCodeAt) \ + V(StringCharFromCode) \ + V(StringCompareAndBranch) \ + V(SubI) \ + V(SubS) \ + V(TaggedToI) \ + V(ThisFunction) \ + V(ToFastProperties) \ + V(TransitionElementsKind) \ + V(TrapAllocationMemento) \ + V(TruncateDoubleToIntOrSmi) \ + V(Typeof) \ + V(TypeofIsAndBranch) \ + V(Uint32ToDouble) \ + V(UnknownOSRValue) \ + V(WrapReceiver) + + +#define DECLARE_CONCRETE_INSTRUCTION(type, mnemonic) \ + virtual Opcode opcode() const V8_FINAL V8_OVERRIDE { \ + return LInstruction::k##type; \ + } \ + virtual void CompileToNative(LCodeGen* generator) V8_FINAL V8_OVERRIDE; \ + virtual const char* Mnemonic() const V8_FINAL V8_OVERRIDE { \ + return mnemonic; \ + } \ + static L##type* cast(LInstruction* instr) { \ + ASSERT(instr->Is##type()); \ + return reinterpret_cast<L##type*>(instr); \ + } + + +#define DECLARE_HYDROGEN_ACCESSOR(type) \ + H##type* hydrogen() const { \ + return H##type::cast(this->hydrogen_value()); \ + } + + +class LInstruction : public ZoneObject { + public: + LInstruction() + : environment_(NULL), + hydrogen_value_(NULL), + bit_field_(IsCallBits::encode(false)) { } + + virtual ~LInstruction() { } + + virtual void CompileToNative(LCodeGen* generator) = 0; + virtual const char* Mnemonic() const = 0; + virtual void PrintTo(StringStream* stream); + virtual void PrintDataTo(StringStream* stream); + virtual void PrintOutputOperandTo(StringStream* stream); + + enum Opcode { + // Declare a unique enum value for each instruction. +#define DECLARE_OPCODE(type) k##type, + LITHIUM_CONCRETE_INSTRUCTION_LIST(DECLARE_OPCODE) + kNumberOfInstructions +#undef DECLARE_OPCODE + }; + + virtual Opcode opcode() const = 0; + + // Declare non-virtual type testers for all leaf IR classes. +#define DECLARE_PREDICATE(type) \ + bool Is##type() const { return opcode() == k##type; } + LITHIUM_CONCRETE_INSTRUCTION_LIST(DECLARE_PREDICATE) +#undef DECLARE_PREDICATE + + // Declare virtual predicates for instructions that don't have + // an opcode. + virtual bool IsGap() const { return false; } + + virtual bool IsControl() const { return false; } + + void set_environment(LEnvironment* env) { environment_ = env; } + LEnvironment* environment() const { return environment_; } + bool HasEnvironment() const { return environment_ != NULL; } + + void set_pointer_map(LPointerMap* p) { pointer_map_.set(p); } + LPointerMap* pointer_map() const { return pointer_map_.get(); } + bool HasPointerMap() const { return pointer_map_.is_set(); } + + void set_hydrogen_value(HValue* value) { hydrogen_value_ = value; } + HValue* hydrogen_value() const { return hydrogen_value_; } + + virtual void SetDeferredLazyDeoptimizationEnvironment(LEnvironment* env) { } + + void MarkAsCall() { bit_field_ = IsCallBits::update(bit_field_, true); } + bool IsCall() const { return IsCallBits::decode(bit_field_); } + + // Interface to the register allocator and iterators. + bool ClobbersTemps() const { return IsCall(); } + bool ClobbersRegisters() const { return IsCall(); } + virtual bool ClobbersDoubleRegisters(Isolate* isolate) const { + return IsCall(); + } + bool IsMarkedAsCall() const { return IsCall(); } + + virtual bool HasResult() const = 0; + virtual LOperand* result() const = 0; + + virtual int InputCount() = 0; + virtual LOperand* InputAt(int i) = 0; + virtual int TempCount() = 0; + virtual LOperand* TempAt(int i) = 0; + + LOperand* FirstInput() { return InputAt(0); } + LOperand* Output() { return HasResult() ? result() : NULL; } + + virtual bool HasInterestingComment(LCodeGen* gen) const { return true; } + +#ifdef DEBUG + void VerifyCall(); +#endif + + private: + class IsCallBits: public BitField<bool, 0, 1> {}; + + LEnvironment* environment_; + SetOncePointer<LPointerMap> pointer_map_; + HValue* hydrogen_value_; + int32_t bit_field_; +}; + + +// R = number of result operands (0 or 1). +template<int R> +class LTemplateResultInstruction : public LInstruction { + public: + // Allow 0 or 1 output operands. + STATIC_ASSERT(R == 0 || R == 1); + virtual bool HasResult() const V8_FINAL V8_OVERRIDE { + return (R != 0) && (result() != NULL); + } + void set_result(LOperand* operand) { results_[0] = operand; } + LOperand* result() const { return results_[0]; } + + protected: + EmbeddedContainer<LOperand*, R> results_; +}; + + +// R = number of result operands (0 or 1). +// I = number of input operands. +// T = number of temporary operands. +template<int R, int I, int T> +class LTemplateInstruction : public LTemplateResultInstruction<R> { + protected: + EmbeddedContainer<LOperand*, I> inputs_; + EmbeddedContainer<LOperand*, T> temps_; + + private: + // Iterator support. + virtual int InputCount() V8_FINAL V8_OVERRIDE { return I; } + virtual LOperand* InputAt(int i) V8_FINAL V8_OVERRIDE { return inputs_[i]; } + + virtual int TempCount() V8_FINAL V8_OVERRIDE { return T; } + virtual LOperand* TempAt(int i) V8_FINAL V8_OVERRIDE { return temps_[i]; } +}; + + +class LUnknownOSRValue V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + virtual bool HasInterestingComment(LCodeGen* gen) const V8_OVERRIDE { + return false; + } + DECLARE_CONCRETE_INSTRUCTION(UnknownOSRValue, "unknown-osr-value") +}; + + +template<int I, int T> +class LControlInstruction : public LTemplateInstruction<0, I, T> { + public: + LControlInstruction() : false_label_(NULL), true_label_(NULL) { } + + virtual bool IsControl() const V8_FINAL V8_OVERRIDE { return true; } + + int SuccessorCount() { return hydrogen()->SuccessorCount(); } + HBasicBlock* SuccessorAt(int i) { return hydrogen()->SuccessorAt(i); } + + int TrueDestination(LChunk* chunk) { + return chunk->LookupDestination(true_block_id()); + } + + int FalseDestination(LChunk* chunk) { + return chunk->LookupDestination(false_block_id()); + } + + Label* TrueLabel(LChunk* chunk) { + if (true_label_ == NULL) { + true_label_ = chunk->GetAssemblyLabel(TrueDestination(chunk)); + } + return true_label_; + } + + Label* FalseLabel(LChunk* chunk) { + if (false_label_ == NULL) { + false_label_ = chunk->GetAssemblyLabel(FalseDestination(chunk)); + } + return false_label_; + } + + protected: + int true_block_id() { return SuccessorAt(0)->block_id(); } + int false_block_id() { return SuccessorAt(1)->block_id(); } + + private: + DECLARE_HYDROGEN_ACCESSOR(ControlInstruction); + + Label* false_label_; + Label* true_label_; +}; + + +class LGap : public LTemplateInstruction<0, 0, 0> { + public: + explicit LGap(HBasicBlock* block) + : block_(block) { + parallel_moves_[BEFORE] = NULL; + parallel_moves_[START] = NULL; + parallel_moves_[END] = NULL; + parallel_moves_[AFTER] = NULL; + } + + // Can't use the DECLARE-macro here because of sub-classes. + virtual bool IsGap() const V8_OVERRIDE { return true; } + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + static LGap* cast(LInstruction* instr) { + ASSERT(instr->IsGap()); + return reinterpret_cast<LGap*>(instr); + } + + bool IsRedundant() const; + + HBasicBlock* block() const { return block_; } + + enum InnerPosition { + BEFORE, + START, + END, + AFTER, + FIRST_INNER_POSITION = BEFORE, + LAST_INNER_POSITION = AFTER + }; + + LParallelMove* GetOrCreateParallelMove(InnerPosition pos, Zone* zone) { + if (parallel_moves_[pos] == NULL) { + parallel_moves_[pos] = new(zone) LParallelMove(zone); + } + return parallel_moves_[pos]; + } + + LParallelMove* GetParallelMove(InnerPosition pos) { + return parallel_moves_[pos]; + } + + private: + LParallelMove* parallel_moves_[LAST_INNER_POSITION + 1]; + HBasicBlock* block_; +}; + + +class LInstructionGap V8_FINAL : public LGap { + public: + explicit LInstructionGap(HBasicBlock* block) : LGap(block) { } + + virtual bool HasInterestingComment(LCodeGen* gen) const V8_OVERRIDE { + return !IsRedundant(); + } + + DECLARE_CONCRETE_INSTRUCTION(InstructionGap, "gap") +}; + + +class LDrop V8_FINAL : public LTemplateInstruction<0, 0, 0> { + public: + explicit LDrop(int count) : count_(count) { } + + int count() const { return count_; } + + DECLARE_CONCRETE_INSTRUCTION(Drop, "drop") + + private: + int count_; +}; + + +class LDummy V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + explicit LDummy() { } + DECLARE_CONCRETE_INSTRUCTION(Dummy, "dummy") +}; + + +class LDummyUse V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LDummyUse(LOperand* value) { + inputs_[0] = value; + } + DECLARE_CONCRETE_INSTRUCTION(DummyUse, "dummy-use") +}; + + +class LGoto V8_FINAL : public LTemplateInstruction<0, 0, 0> { + public: + explicit LGoto(HBasicBlock* block) : block_(block) { } + + virtual bool HasInterestingComment(LCodeGen* gen) const V8_OVERRIDE; + DECLARE_CONCRETE_INSTRUCTION(Goto, "goto") + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + virtual bool IsControl() const V8_OVERRIDE { return true; } + + int block_id() const { return block_->block_id(); } + + private: + HBasicBlock* block_; +}; + + +class LLazyBailout V8_FINAL : public LTemplateInstruction<0, 0, 0> { + public: + LLazyBailout() : gap_instructions_size_(0) { } + + DECLARE_CONCRETE_INSTRUCTION(LazyBailout, "lazy-bailout") + + void set_gap_instructions_size(int gap_instructions_size) { + gap_instructions_size_ = gap_instructions_size; + } + int gap_instructions_size() { return gap_instructions_size_; } + + private: + int gap_instructions_size_; +}; + + +class LLabel V8_FINAL : public LGap { + public: + explicit LLabel(HBasicBlock* block) + : LGap(block), replacement_(NULL) { } + + virtual bool HasInterestingComment(LCodeGen* gen) const V8_OVERRIDE { + return false; + } + DECLARE_CONCRETE_INSTRUCTION(Label, "label") + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + int block_id() const { return block()->block_id(); } + bool is_loop_header() const { return block()->IsLoopHeader(); } + bool is_osr_entry() const { return block()->is_osr_entry(); } + Label* label() { return &label_; } + LLabel* replacement() const { return replacement_; } + void set_replacement(LLabel* label) { replacement_ = label; } + bool HasReplacement() const { return replacement_ != NULL; } + + private: + Label label_; + LLabel* replacement_; +}; + + +class LOsrEntry V8_FINAL : public LTemplateInstruction<0, 0, 0> { + public: + LOsrEntry() {} + + virtual bool HasInterestingComment(LCodeGen* gen) const V8_OVERRIDE { + return false; + } + DECLARE_CONCRETE_INSTRUCTION(OsrEntry, "osr-entry") +}; + + +class LAccessArgumentsAt V8_FINAL : public LTemplateInstruction<1, 3, 0> { + public: + LAccessArgumentsAt(LOperand* arguments, + LOperand* length, + LOperand* index) { + inputs_[0] = arguments; + inputs_[1] = length; + inputs_[2] = index; + } + + DECLARE_CONCRETE_INSTRUCTION(AccessArgumentsAt, "access-arguments-at") + + LOperand* arguments() { return inputs_[0]; } + LOperand* length() { return inputs_[1]; } + LOperand* index() { return inputs_[2]; } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LAddE V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LAddE(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(AddE, "add-e") + DECLARE_HYDROGEN_ACCESSOR(Add) +}; + + +class LAddI V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LAddI(LOperand* left, LOperand* right) + : shift_(NO_SHIFT), shift_amount_(0) { + inputs_[0] = left; + inputs_[1] = right; + } + + LAddI(LOperand* left, LOperand* right, Shift shift, LOperand* shift_amount) + : shift_(shift), shift_amount_(shift_amount) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + Shift shift() const { return shift_; } + LOperand* shift_amount() const { return shift_amount_; } + + DECLARE_CONCRETE_INSTRUCTION(AddI, "add-i") + DECLARE_HYDROGEN_ACCESSOR(Add) + + protected: + Shift shift_; + LOperand* shift_amount_; +}; + + +class LAddS V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LAddS(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(AddS, "add-s") + DECLARE_HYDROGEN_ACCESSOR(Add) +}; + + +class LAllocate V8_FINAL : public LTemplateInstruction<1, 2, 3> { + public: + LAllocate(LOperand* context, + LOperand* size, + LOperand* temp1, + LOperand* temp2, + LOperand* temp3) { + inputs_[0] = context; + inputs_[1] = size; + temps_[0] = temp1; + temps_[1] = temp2; + temps_[2] = temp3; + } + + LOperand* context() { return inputs_[0]; } + LOperand* size() { return inputs_[1]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + LOperand* temp3() { return temps_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(Allocate, "allocate") + DECLARE_HYDROGEN_ACCESSOR(Allocate) +}; + + +class LApplyArguments V8_FINAL : public LTemplateInstruction<1, 4, 0> { + public: + LApplyArguments(LOperand* function, + LOperand* receiver, + LOperand* length, + LOperand* elements) { + inputs_[0] = function; + inputs_[1] = receiver; + inputs_[2] = length; + inputs_[3] = elements; + } + + DECLARE_CONCRETE_INSTRUCTION(ApplyArguments, "apply-arguments") + + LOperand* function() { return inputs_[0]; } + LOperand* receiver() { return inputs_[1]; } + LOperand* length() { return inputs_[2]; } + LOperand* elements() { return inputs_[3]; } +}; + + +class LArgumentsElements V8_FINAL : public LTemplateInstruction<1, 0, 1> { + public: + explicit LArgumentsElements(LOperand* temp) { + temps_[0] = temp; + } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsElements, "arguments-elements") + DECLARE_HYDROGEN_ACCESSOR(ArgumentsElements) +}; + + +class LArgumentsLength V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LArgumentsLength(LOperand* elements) { + inputs_[0] = elements; + } + + LOperand* elements() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsLength, "arguments-length") +}; + + +class LArithmeticD V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LArithmeticD(Token::Value op, + LOperand* left, + LOperand* right) + : op_(op) { + inputs_[0] = left; + inputs_[1] = right; + } + + Token::Value op() const { return op_; } + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + virtual Opcode opcode() const V8_OVERRIDE { + return LInstruction::kArithmeticD; + } + virtual void CompileToNative(LCodeGen* generator) V8_OVERRIDE; + virtual const char* Mnemonic() const V8_OVERRIDE; + + private: + Token::Value op_; +}; + + +class LArithmeticT V8_FINAL : public LTemplateInstruction<1, 3, 0> { + public: + LArithmeticT(Token::Value op, + LOperand* context, + LOperand* left, + LOperand* right) + : op_(op) { + inputs_[0] = context; + inputs_[1] = left; + inputs_[2] = right; + } + + LOperand* context() { return inputs_[0]; } + LOperand* left() { return inputs_[1]; } + LOperand* right() { return inputs_[2]; } + Token::Value op() const { return op_; } + + virtual Opcode opcode() const V8_OVERRIDE { + return LInstruction::kArithmeticT; + } + virtual void CompileToNative(LCodeGen* generator) V8_OVERRIDE; + virtual const char* Mnemonic() const V8_OVERRIDE; + + private: + Token::Value op_; +}; + + +class LBoundsCheck V8_FINAL : public LTemplateInstruction<0, 2, 0> { + public: + explicit LBoundsCheck(LOperand* index, LOperand* length) { + inputs_[0] = index; + inputs_[1] = length; + } + + LOperand* index() { return inputs_[0]; } + LOperand* length() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(BoundsCheck, "bounds-check") + DECLARE_HYDROGEN_ACCESSOR(BoundsCheck) +}; + + +class LBitI V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LBitI(LOperand* left, LOperand* right) + : shift_(NO_SHIFT), shift_amount_(0) { + inputs_[0] = left; + inputs_[1] = right; + } + + LBitI(LOperand* left, LOperand* right, Shift shift, LOperand* shift_amount) + : shift_(shift), shift_amount_(shift_amount) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + Shift shift() const { return shift_; } + LOperand* shift_amount() const { return shift_amount_; } + + Token::Value op() const { return hydrogen()->op(); } + + DECLARE_CONCRETE_INSTRUCTION(BitI, "bit-i") + DECLARE_HYDROGEN_ACCESSOR(Bitwise) + + protected: + Shift shift_; + LOperand* shift_amount_; +}; + + +class LBitS V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LBitS(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + Token::Value op() const { return hydrogen()->op(); } + + DECLARE_CONCRETE_INSTRUCTION(BitS, "bit-s") + DECLARE_HYDROGEN_ACCESSOR(Bitwise) +}; + + +class LBranch V8_FINAL : public LControlInstruction<1, 2> { + public: + explicit LBranch(LOperand* value, LOperand *temp1, LOperand *temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(Branch, "branch") + DECLARE_HYDROGEN_ACCESSOR(Branch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LCallJSFunction V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LCallJSFunction(LOperand* function) { + inputs_[0] = function; + } + + LOperand* function() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CallJSFunction, "call-js-function") + DECLARE_HYDROGEN_ACCESSOR(CallJSFunction) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallFunction V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LCallFunction(LOperand* context, LOperand* function) { + inputs_[0] = context; + inputs_[1] = function; + } + + LOperand* context() { return inputs_[0]; } + LOperand* function() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(CallFunction, "call-function") + DECLARE_HYDROGEN_ACCESSOR(CallFunction) + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallNew V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LCallNew(LOperand* context, LOperand* constructor) { + inputs_[0] = context; + inputs_[1] = constructor; + } + + LOperand* context() { return inputs_[0]; } + LOperand* constructor() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(CallNew, "call-new") + DECLARE_HYDROGEN_ACCESSOR(CallNew) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallNewArray V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LCallNewArray(LOperand* context, LOperand* constructor) { + inputs_[0] = context; + inputs_[1] = constructor; + } + + LOperand* context() { return inputs_[0]; } + LOperand* constructor() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(CallNewArray, "call-new-array") + DECLARE_HYDROGEN_ACCESSOR(CallNewArray) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallRuntime V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LCallRuntime(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CallRuntime, "call-runtime") + DECLARE_HYDROGEN_ACCESSOR(CallRuntime) + + virtual bool ClobbersDoubleRegisters(Isolate* isolate) const V8_OVERRIDE { + return save_doubles() == kDontSaveFPRegs; + } + + const Runtime::Function* function() const { return hydrogen()->function(); } + int arity() const { return hydrogen()->argument_count(); } + SaveFPRegsMode save_doubles() const { return hydrogen()->save_doubles(); } +}; + + +class LCallStub V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LCallStub(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CallStub, "call-stub") + DECLARE_HYDROGEN_ACCESSOR(CallStub) +}; + + +class LCheckInstanceType V8_FINAL : public LTemplateInstruction<0, 1, 1> { + public: + explicit LCheckInstanceType(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CheckInstanceType, "check-instance-type") + DECLARE_HYDROGEN_ACCESSOR(CheckInstanceType) +}; + + +class LCheckMaps V8_FINAL : public LTemplateInstruction<0, 1, 1> { + public: + LCheckMaps(LOperand* value = NULL, LOperand* temp = NULL) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CheckMaps, "check-maps") + DECLARE_HYDROGEN_ACCESSOR(CheckMaps) +}; + + +class LCheckNonSmi V8_FINAL : public LTemplateInstruction<0, 1, 0> { + public: + explicit LCheckNonSmi(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CheckNonSmi, "check-non-smi") + DECLARE_HYDROGEN_ACCESSOR(CheckHeapObject) +}; + + +class LCheckSmi V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LCheckSmi(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CheckSmi, "check-smi") +}; + + +class LCheckValue V8_FINAL : public LTemplateInstruction<0, 1, 0> { + public: + explicit LCheckValue(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CheckValue, "check-value") + DECLARE_HYDROGEN_ACCESSOR(CheckValue) +}; + + +class LClampDToUint8 V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LClampDToUint8(LOperand* unclamped) { + inputs_[0] = unclamped; + } + + LOperand* unclamped() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(ClampDToUint8, "clamp-d-to-uint8") +}; + + +class LClampIToUint8 V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LClampIToUint8(LOperand* unclamped) { + inputs_[0] = unclamped; + } + + LOperand* unclamped() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(ClampIToUint8, "clamp-i-to-uint8") +}; + + +class LClampTToUint8 V8_FINAL : public LTemplateInstruction<1, 1, 2> { + public: + LClampTToUint8(LOperand* unclamped, LOperand* temp1, LOperand* temp2) { + inputs_[0] = unclamped; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* unclamped() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(ClampTToUint8, "clamp-t-to-uint8") +}; + + +class LDoubleBits V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LDoubleBits(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(DoubleBits, "double-bits") + DECLARE_HYDROGEN_ACCESSOR(DoubleBits) +}; + + +class LConstructDouble V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LConstructDouble(LOperand* hi, LOperand* lo) { + inputs_[0] = hi; + inputs_[1] = lo; + } + + LOperand* hi() { return inputs_[0]; } + LOperand* lo() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(ConstructDouble, "construct-double") +}; + + +class LClassOfTestAndBranch V8_FINAL : public LControlInstruction<1, 2> { + public: + LClassOfTestAndBranch(LOperand* value, LOperand* temp1, LOperand* temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(ClassOfTestAndBranch, + "class-of-test-and-branch") + DECLARE_HYDROGEN_ACCESSOR(ClassOfTestAndBranch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LCmpHoleAndBranchD V8_FINAL : public LControlInstruction<1, 1> { + public: + explicit LCmpHoleAndBranchD(LOperand* object, LOperand* temp) { + inputs_[0] = object; + temps_[0] = temp; + } + + LOperand* object() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CmpHoleAndBranchD, "cmp-hole-and-branch-d") + DECLARE_HYDROGEN_ACCESSOR(CompareHoleAndBranch) +}; + + +class LCmpHoleAndBranchT V8_FINAL : public LControlInstruction<1, 0> { + public: + explicit LCmpHoleAndBranchT(LOperand* object) { + inputs_[0] = object; + } + + LOperand* object() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CmpHoleAndBranchT, "cmp-hole-and-branch-t") + DECLARE_HYDROGEN_ACCESSOR(CompareHoleAndBranch) +}; + + +class LCmpMapAndBranch V8_FINAL : public LControlInstruction<1, 1> { + public: + LCmpMapAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CmpMapAndBranch, "cmp-map-and-branch") + DECLARE_HYDROGEN_ACCESSOR(CompareMap) + + Handle<Map> map() const { return hydrogen()->map().handle(); } +}; + + +class LCmpObjectEqAndBranch V8_FINAL : public LControlInstruction<2, 0> { + public: + LCmpObjectEqAndBranch(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(CmpObjectEqAndBranch, "cmp-object-eq-and-branch") + DECLARE_HYDROGEN_ACCESSOR(CompareObjectEqAndBranch) +}; + + +class LCmpT V8_FINAL : public LTemplateInstruction<1, 3, 0> { + public: + LCmpT(LOperand* context, LOperand* left, LOperand* right) { + inputs_[0] = context; + inputs_[1] = left; + inputs_[2] = right; + } + + LOperand* context() { return inputs_[0]; } + LOperand* left() { return inputs_[1]; } + LOperand* right() { return inputs_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t") + DECLARE_HYDROGEN_ACCESSOR(CompareGeneric) + + Token::Value op() const { return hydrogen()->token(); } +}; + + +class LCompareMinusZeroAndBranch V8_FINAL : public LControlInstruction<1, 1> { + public: + LCompareMinusZeroAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CompareMinusZeroAndBranch, + "cmp-minus-zero-and-branch") + DECLARE_HYDROGEN_ACCESSOR(CompareMinusZeroAndBranch) +}; + + +class LCompareNumericAndBranch V8_FINAL : public LControlInstruction<2, 0> { + public: + LCompareNumericAndBranch(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(CompareNumericAndBranch, + "compare-numeric-and-branch") + DECLARE_HYDROGEN_ACCESSOR(CompareNumericAndBranch) + + Token::Value op() const { return hydrogen()->token(); } + bool is_double() const { + return hydrogen()->representation().IsDouble(); + } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LConstantD V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(ConstantD, "constant-d") + DECLARE_HYDROGEN_ACCESSOR(Constant) + + double value() const { return hydrogen()->DoubleValue(); } +}; + + +class LConstantE V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(ConstantE, "constant-e") + DECLARE_HYDROGEN_ACCESSOR(Constant) + + ExternalReference value() const { + return hydrogen()->ExternalReferenceValue(); + } +}; + + +class LConstantI V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(ConstantI, "constant-i") + DECLARE_HYDROGEN_ACCESSOR(Constant) + + int32_t value() const { return hydrogen()->Integer32Value(); } +}; + + +class LConstantS V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(ConstantS, "constant-s") + DECLARE_HYDROGEN_ACCESSOR(Constant) + + Smi* value() const { return Smi::FromInt(hydrogen()->Integer32Value()); } +}; + + +class LConstantT V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(ConstantT, "constant-t") + DECLARE_HYDROGEN_ACCESSOR(Constant) + + Handle<Object> value(Isolate* isolate) const { + return hydrogen()->handle(isolate); + } +}; + + +class LContext V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(Context, "context") + DECLARE_HYDROGEN_ACCESSOR(Context) +}; + + +class LDateField V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + LDateField(LOperand* date, Smi* index) : index_(index) { + inputs_[0] = date; + } + + LOperand* date() { return inputs_[0]; } + Smi* index() const { return index_; } + + DECLARE_CONCRETE_INSTRUCTION(DateField, "date-field") + DECLARE_HYDROGEN_ACCESSOR(DateField) + + private: + Smi* index_; +}; + + +class LDebugBreak V8_FINAL : public LTemplateInstruction<0, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(DebugBreak, "break") +}; + + +class LDeclareGlobals V8_FINAL : public LTemplateInstruction<0, 1, 0> { + public: + explicit LDeclareGlobals(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(DeclareGlobals, "declare-globals") + DECLARE_HYDROGEN_ACCESSOR(DeclareGlobals) +}; + + +class LDeoptimize V8_FINAL : public LTemplateInstruction<0, 0, 0> { + public: + virtual bool IsControl() const V8_OVERRIDE { return true; } + DECLARE_CONCRETE_INSTRUCTION(Deoptimize, "deoptimize") + DECLARE_HYDROGEN_ACCESSOR(Deoptimize) +}; + + +class LDivByPowerOf2I V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + LDivByPowerOf2I(LOperand* dividend, int32_t divisor) { + inputs_[0] = dividend; + divisor_ = divisor; + } + + LOperand* dividend() { return inputs_[0]; } + int32_t divisor() const { return divisor_; } + + DECLARE_CONCRETE_INSTRUCTION(DivByPowerOf2I, "div-by-power-of-2-i") + DECLARE_HYDROGEN_ACCESSOR(Div) + + private: + int32_t divisor_; +}; + + +class LDivByConstI V8_FINAL : public LTemplateInstruction<1, 1, 1> { + public: + LDivByConstI(LOperand* dividend, int32_t divisor, LOperand* temp) { + inputs_[0] = dividend; + divisor_ = divisor; + temps_[0] = temp; + } + + LOperand* dividend() { return inputs_[0]; } + int32_t divisor() const { return divisor_; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(DivByConstI, "div-by-const-i") + DECLARE_HYDROGEN_ACCESSOR(Div) + + private: + int32_t divisor_; +}; + + +class LDivI V8_FINAL : public LTemplateInstruction<1, 2, 1> { + public: + LDivI(LOperand* dividend, LOperand* divisor, LOperand* temp) { + inputs_[0] = dividend; + inputs_[1] = divisor; + temps_[0] = temp; + } + + LOperand* dividend() { return inputs_[0]; } + LOperand* divisor() { return inputs_[1]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(DivI, "div-i") + DECLARE_HYDROGEN_ACCESSOR(BinaryOperation) +}; + + +class LDoubleToIntOrSmi V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LDoubleToIntOrSmi(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(DoubleToIntOrSmi, "double-to-int-or-smi") + DECLARE_HYDROGEN_ACCESSOR(UnaryOperation) + + bool tag_result() { return hydrogen()->representation().IsSmi(); } +}; + + +class LForInCacheArray V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LForInCacheArray(LOperand* map) { + inputs_[0] = map; + } + + LOperand* map() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(ForInCacheArray, "for-in-cache-array") + + int idx() { + return HForInCacheArray::cast(this->hydrogen_value())->idx(); + } +}; + + +class LForInPrepareMap V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LForInPrepareMap(LOperand* context, LOperand* object) { + inputs_[0] = context; + inputs_[1] = object; + } + + LOperand* context() { return inputs_[0]; } + LOperand* object() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(ForInPrepareMap, "for-in-prepare-map") +}; + + +class LGetCachedArrayIndex V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LGetCachedArrayIndex(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(GetCachedArrayIndex, "get-cached-array-index") + DECLARE_HYDROGEN_ACCESSOR(GetCachedArrayIndex) +}; + + +class LHasCachedArrayIndexAndBranch V8_FINAL + : public LControlInstruction<1, 1> { + public: + LHasCachedArrayIndexAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(HasCachedArrayIndexAndBranch, + "has-cached-array-index-and-branch") + DECLARE_HYDROGEN_ACCESSOR(HasCachedArrayIndexAndBranch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LHasInstanceTypeAndBranch V8_FINAL : public LControlInstruction<1, 1> { + public: + LHasInstanceTypeAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(HasInstanceTypeAndBranch, + "has-instance-type-and-branch") + DECLARE_HYDROGEN_ACCESSOR(HasInstanceTypeAndBranch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LInnerAllocatedObject V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LInnerAllocatedObject(LOperand* base_object, LOperand* offset) { + inputs_[0] = base_object; + inputs_[1] = offset; + } + + LOperand* base_object() const { return inputs_[0]; } + LOperand* offset() const { return inputs_[1]; } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + DECLARE_CONCRETE_INSTRUCTION(InnerAllocatedObject, "inner-allocated-object") +}; + + +class LInstanceOf V8_FINAL : public LTemplateInstruction<1, 3, 0> { + public: + LInstanceOf(LOperand* context, LOperand* left, LOperand* right) { + inputs_[0] = context; + inputs_[1] = left; + inputs_[2] = right; + } + + LOperand* context() { return inputs_[0]; } + LOperand* left() { return inputs_[1]; } + LOperand* right() { return inputs_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(InstanceOf, "instance-of") +}; + + +class LInstanceOfKnownGlobal V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LInstanceOfKnownGlobal(LOperand* context, LOperand* value) { + inputs_[0] = context; + inputs_[1] = value; + } + + LOperand* context() { return inputs_[0]; } + LOperand* value() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(InstanceOfKnownGlobal, + "instance-of-known-global") + DECLARE_HYDROGEN_ACCESSOR(InstanceOfKnownGlobal) + + Handle<JSFunction> function() const { return hydrogen()->function(); } + LEnvironment* GetDeferredLazyDeoptimizationEnvironment() { + return lazy_deopt_env_; + } + virtual void SetDeferredLazyDeoptimizationEnvironment( + LEnvironment* env) V8_OVERRIDE { + lazy_deopt_env_ = env; + } + + private: + LEnvironment* lazy_deopt_env_; +}; + + +class LInteger32ToDouble V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LInteger32ToDouble(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(Integer32ToDouble, "int32-to-double") +}; + + +class LCallWithDescriptor V8_FINAL : public LTemplateResultInstruction<1> { + public: + LCallWithDescriptor(const CallInterfaceDescriptor* descriptor, + const ZoneList<LOperand*>& operands, + Zone* zone) + : descriptor_(descriptor), + inputs_(descriptor->environment_length() + 1, zone) { + ASSERT(descriptor->environment_length() + 1 == operands.length()); + inputs_.AddAll(operands, zone); + } + + LOperand* target() const { return inputs_[0]; } + + const CallInterfaceDescriptor* descriptor() { return descriptor_; } + + private: + DECLARE_CONCRETE_INSTRUCTION(CallWithDescriptor, "call-with-descriptor") + DECLARE_HYDROGEN_ACCESSOR(CallWithDescriptor) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + int arity() const { return hydrogen()->argument_count() - 1; } + + const CallInterfaceDescriptor* descriptor_; + ZoneList<LOperand*> inputs_; + + // Iterator support. + virtual int InputCount() V8_FINAL V8_OVERRIDE { return inputs_.length(); } + virtual LOperand* InputAt(int i) V8_FINAL V8_OVERRIDE { return inputs_[i]; } + + virtual int TempCount() V8_FINAL V8_OVERRIDE { return 0; } + virtual LOperand* TempAt(int i) V8_FINAL V8_OVERRIDE { return NULL; } +}; + + +class LInvokeFunction V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LInvokeFunction(LOperand* context, LOperand* function) { + inputs_[0] = context; + inputs_[1] = function; + } + + LOperand* context() { return inputs_[0]; } + LOperand* function() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(InvokeFunction, "invoke-function") + DECLARE_HYDROGEN_ACCESSOR(InvokeFunction) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LIsConstructCallAndBranch V8_FINAL : public LControlInstruction<0, 2> { + public: + LIsConstructCallAndBranch(LOperand* temp1, LOperand* temp2) { + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(IsConstructCallAndBranch, + "is-construct-call-and-branch") +}; + + +class LIsObjectAndBranch V8_FINAL : public LControlInstruction<1, 2> { + public: + LIsObjectAndBranch(LOperand* value, LOperand* temp1, LOperand* temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(IsObjectAndBranch, "is-object-and-branch") + DECLARE_HYDROGEN_ACCESSOR(IsObjectAndBranch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LIsStringAndBranch V8_FINAL : public LControlInstruction<1, 1> { + public: + LIsStringAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(IsStringAndBranch, "is-string-and-branch") + DECLARE_HYDROGEN_ACCESSOR(IsStringAndBranch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LIsSmiAndBranch V8_FINAL : public LControlInstruction<1, 0> { + public: + explicit LIsSmiAndBranch(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(IsSmiAndBranch, "is-smi-and-branch") + DECLARE_HYDROGEN_ACCESSOR(IsSmiAndBranch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LIsUndetectableAndBranch V8_FINAL : public LControlInstruction<1, 1> { + public: + explicit LIsUndetectableAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(IsUndetectableAndBranch, + "is-undetectable-and-branch") + DECLARE_HYDROGEN_ACCESSOR(IsUndetectableAndBranch) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LLoadContextSlot V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LLoadContextSlot(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadContextSlot, "load-context-slot") + DECLARE_HYDROGEN_ACCESSOR(LoadContextSlot) + + int slot_index() const { return hydrogen()->slot_index(); } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LLoadNamedField V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LLoadNamedField(LOperand* object) { + inputs_[0] = object; + } + + LOperand* object() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedField, "load-named-field") + DECLARE_HYDROGEN_ACCESSOR(LoadNamedField) +}; + + +class LFunctionLiteral V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LFunctionLiteral(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(FunctionLiteral, "function-literal") + DECLARE_HYDROGEN_ACCESSOR(FunctionLiteral) +}; + + +class LLoadFunctionPrototype V8_FINAL : public LTemplateInstruction<1, 1, 1> { + public: + LLoadFunctionPrototype(LOperand* function, LOperand* temp) { + inputs_[0] = function; + temps_[0] = temp; + } + + LOperand* function() { return inputs_[0]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadFunctionPrototype, "load-function-prototype") + DECLARE_HYDROGEN_ACCESSOR(LoadFunctionPrototype) +}; + + +class LLoadGlobalCell V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(LoadGlobalCell, "load-global-cell") + DECLARE_HYDROGEN_ACCESSOR(LoadGlobalCell) +}; + + +class LLoadGlobalGeneric V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LLoadGlobalGeneric(LOperand* context, LOperand* global_object) { + inputs_[0] = context; + inputs_[1] = global_object; + } + + LOperand* context() { return inputs_[0]; } + LOperand* global_object() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadGlobalGeneric, "load-global-generic") + DECLARE_HYDROGEN_ACCESSOR(LoadGlobalGeneric) + + Handle<Object> name() const { return hydrogen()->name(); } + bool for_typeof() const { return hydrogen()->for_typeof(); } +}; + + +template<int T> +class LLoadKeyed : public LTemplateInstruction<1, 2, T> { + public: + LLoadKeyed(LOperand* elements, LOperand* key) { + this->inputs_[0] = elements; + this->inputs_[1] = key; + } + + LOperand* elements() { return this->inputs_[0]; } + LOperand* key() { return this->inputs_[1]; } + ElementsKind elements_kind() const { + return this->hydrogen()->elements_kind(); + } + bool is_external() const { + return this->hydrogen()->is_external(); + } + bool is_fixed_typed_array() const { + return hydrogen()->is_fixed_typed_array(); + } + bool is_typed_elements() const { + return is_external() || is_fixed_typed_array(); + } + uint32_t base_offset() const { + return this->hydrogen()->base_offset(); + } + void PrintDataTo(StringStream* stream) V8_OVERRIDE { + this->elements()->PrintTo(stream); + stream->Add("["); + this->key()->PrintTo(stream); + if (this->base_offset() != 0) { + stream->Add(" + %d]", this->base_offset()); + } else { + stream->Add("]"); + } + } + + DECLARE_HYDROGEN_ACCESSOR(LoadKeyed) +}; + + +class LLoadKeyedExternal: public LLoadKeyed<1> { + public: + LLoadKeyedExternal(LOperand* elements, LOperand* key, LOperand* temp) : + LLoadKeyed<1>(elements, key) { + temps_[0] = temp; + } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedExternal, "load-keyed-external"); +}; + + +class LLoadKeyedFixed: public LLoadKeyed<1> { + public: + LLoadKeyedFixed(LOperand* elements, LOperand* key, LOperand* temp) : + LLoadKeyed<1>(elements, key) { + temps_[0] = temp; + } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedFixed, "load-keyed-fixed"); +}; + + +class LLoadKeyedFixedDouble: public LLoadKeyed<1> { + public: + LLoadKeyedFixedDouble(LOperand* elements, LOperand* key, LOperand* temp) : + LLoadKeyed<1>(elements, key) { + temps_[0] = temp; + } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedFixedDouble, "load-keyed-fixed-double"); +}; + + +class LLoadKeyedGeneric V8_FINAL : public LTemplateInstruction<1, 3, 0> { + public: + LLoadKeyedGeneric(LOperand* context, LOperand* object, LOperand* key) { + inputs_[0] = context; + inputs_[1] = object; + inputs_[2] = key; + } + + LOperand* context() { return inputs_[0]; } + LOperand* object() { return inputs_[1]; } + LOperand* key() { return inputs_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedGeneric, "load-keyed-generic") +}; + + +class LLoadNamedGeneric V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LLoadNamedGeneric(LOperand* context, LOperand* object) { + inputs_[0] = context; + inputs_[1] = object; + } + + LOperand* context() { return inputs_[0]; } + LOperand* object() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedGeneric, "load-named-generic") + DECLARE_HYDROGEN_ACCESSOR(LoadNamedGeneric) + + Handle<Object> name() const { return hydrogen()->name(); } +}; + + +class LLoadRoot V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(LoadRoot, "load-root") + DECLARE_HYDROGEN_ACCESSOR(LoadRoot) + + Heap::RootListIndex index() const { return hydrogen()->index(); } +}; + + +class LMapEnumLength V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LMapEnumLength(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(MapEnumLength, "map-enum-length") +}; + + +template<int T> +class LUnaryMathOperation : public LTemplateInstruction<1, 1, T> { + public: + explicit LUnaryMathOperation(LOperand* value) { + this->inputs_[0] = value; + } + + LOperand* value() { return this->inputs_[0]; } + BuiltinFunctionId op() const { return this->hydrogen()->op(); } + + void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + DECLARE_HYDROGEN_ACCESSOR(UnaryMathOperation) +}; + + +class LMathAbs V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathAbs(LOperand* value) : LUnaryMathOperation<0>(value) {} + + DECLARE_CONCRETE_INSTRUCTION(MathAbs, "math-abs") +}; + + +class LMathAbsTagged: public LTemplateInstruction<1, 2, 3> { + public: + LMathAbsTagged(LOperand* context, LOperand* value, + LOperand* temp1, LOperand* temp2, LOperand* temp3) { + inputs_[0] = context; + inputs_[1] = value; + temps_[0] = temp1; + temps_[1] = temp2; + temps_[2] = temp3; + } + + LOperand* context() { return inputs_[0]; } + LOperand* value() { return inputs_[1]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + LOperand* temp3() { return temps_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(MathAbsTagged, "math-abs-tagged") + DECLARE_HYDROGEN_ACCESSOR(UnaryMathOperation) +}; + + +class LMathExp V8_FINAL : public LUnaryMathOperation<4> { + public: + LMathExp(LOperand* value, + LOperand* double_temp1, + LOperand* temp1, + LOperand* temp2, + LOperand* temp3) + : LUnaryMathOperation<4>(value) { + temps_[0] = double_temp1; + temps_[1] = temp1; + temps_[2] = temp2; + temps_[3] = temp3; + ExternalReference::InitializeMathExpData(); + } + + LOperand* double_temp1() { return temps_[0]; } + LOperand* temp1() { return temps_[1]; } + LOperand* temp2() { return temps_[2]; } + LOperand* temp3() { return temps_[3]; } + + DECLARE_CONCRETE_INSTRUCTION(MathExp, "math-exp") +}; + + +// Math.floor with a double result. +class LMathFloorD V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathFloorD(LOperand* value) : LUnaryMathOperation<0>(value) { } + DECLARE_CONCRETE_INSTRUCTION(MathFloorD, "math-floor-d") +}; + + +// Math.floor with an integer result. +class LMathFloorI V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathFloorI(LOperand* value) : LUnaryMathOperation<0>(value) { } + DECLARE_CONCRETE_INSTRUCTION(MathFloorI, "math-floor-i") +}; + + +class LFlooringDivByPowerOf2I V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + LFlooringDivByPowerOf2I(LOperand* dividend, int32_t divisor) { + inputs_[0] = dividend; + divisor_ = divisor; + } + + LOperand* dividend() { return inputs_[0]; } + int32_t divisor() const { return divisor_; } + + DECLARE_CONCRETE_INSTRUCTION(FlooringDivByPowerOf2I, + "flooring-div-by-power-of-2-i") + DECLARE_HYDROGEN_ACCESSOR(MathFloorOfDiv) + + private: + int32_t divisor_; +}; + + +class LFlooringDivByConstI V8_FINAL : public LTemplateInstruction<1, 1, 2> { + public: + LFlooringDivByConstI(LOperand* dividend, int32_t divisor, LOperand* temp) { + inputs_[0] = dividend; + divisor_ = divisor; + temps_[0] = temp; + } + + LOperand* dividend() { return inputs_[0]; } + int32_t divisor() const { return divisor_; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(FlooringDivByConstI, "flooring-div-by-const-i") + DECLARE_HYDROGEN_ACCESSOR(MathFloorOfDiv) + + private: + int32_t divisor_; +}; + + +class LFlooringDivI V8_FINAL : public LTemplateInstruction<1, 2, 1> { + public: + LFlooringDivI(LOperand* dividend, LOperand* divisor, LOperand* temp) { + inputs_[0] = dividend; + inputs_[1] = divisor; + temps_[0] = temp; + } + + LOperand* dividend() { return inputs_[0]; } + LOperand* divisor() { return inputs_[1]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(FlooringDivI, "flooring-div-i") + DECLARE_HYDROGEN_ACCESSOR(MathFloorOfDiv) +}; + + +class LMathLog V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathLog(LOperand* value) : LUnaryMathOperation<0>(value) { } + DECLARE_CONCRETE_INSTRUCTION(MathLog, "math-log") +}; + + +class LMathClz32 V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathClz32(LOperand* value) : LUnaryMathOperation<0>(value) { } + DECLARE_CONCRETE_INSTRUCTION(MathClz32, "math-clz32") +}; + + +class LMathMinMax V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LMathMinMax(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(MathMinMax, "math-min-max") + DECLARE_HYDROGEN_ACCESSOR(MathMinMax) +}; + + +class LMathPowHalf V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathPowHalf(LOperand* value) : LUnaryMathOperation<0>(value) { } + DECLARE_CONCRETE_INSTRUCTION(MathPowHalf, "math-pow-half") +}; + + +// Math.round with an integer result. +class LMathRoundD V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathRoundD(LOperand* value) + : LUnaryMathOperation<0>(value) { + } + + DECLARE_CONCRETE_INSTRUCTION(MathRoundD, "math-round-d") +}; + + +// Math.round with an integer result. +class LMathRoundI V8_FINAL : public LUnaryMathOperation<1> { + public: + LMathRoundI(LOperand* value, LOperand* temp1) + : LUnaryMathOperation<1>(value) { + temps_[0] = temp1; + } + + LOperand* temp1() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(MathRoundI, "math-round-i") +}; + + +class LMathSqrt V8_FINAL : public LUnaryMathOperation<0> { + public: + explicit LMathSqrt(LOperand* value) : LUnaryMathOperation<0>(value) { } + DECLARE_CONCRETE_INSTRUCTION(MathSqrt, "math-sqrt") +}; + + +class LModByPowerOf2I V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + LModByPowerOf2I(LOperand* dividend, int32_t divisor) { + inputs_[0] = dividend; + divisor_ = divisor; + } + + LOperand* dividend() { return inputs_[0]; } + int32_t divisor() const { return divisor_; } + + DECLARE_CONCRETE_INSTRUCTION(ModByPowerOf2I, "mod-by-power-of-2-i") + DECLARE_HYDROGEN_ACCESSOR(Mod) + + private: + int32_t divisor_; +}; + + +class LModByConstI V8_FINAL : public LTemplateInstruction<1, 1, 1> { + public: + LModByConstI(LOperand* dividend, int32_t divisor, LOperand* temp) { + inputs_[0] = dividend; + divisor_ = divisor; + temps_[0] = temp; + } + + LOperand* dividend() { return inputs_[0]; } + int32_t divisor() const { return divisor_; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(ModByConstI, "mod-by-const-i") + DECLARE_HYDROGEN_ACCESSOR(Mod) + + private: + int32_t divisor_; +}; + + +class LModI V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LModI(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(ModI, "mod-i") + DECLARE_HYDROGEN_ACCESSOR(Mod) +}; + + +class LMulConstIS V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LMulConstIS(LOperand* left, LConstantOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LConstantOperand* right() { return LConstantOperand::cast(inputs_[1]); } + + DECLARE_CONCRETE_INSTRUCTION(MulConstIS, "mul-const-i-s") + DECLARE_HYDROGEN_ACCESSOR(Mul) +}; + + +class LMulI V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LMulI(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(MulI, "mul-i") + DECLARE_HYDROGEN_ACCESSOR(Mul) +}; + + +class LMulS V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LMulS(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(MulI, "mul-s") + DECLARE_HYDROGEN_ACCESSOR(Mul) +}; + + +class LNumberTagD V8_FINAL : public LTemplateInstruction<1, 1, 2> { + public: + LNumberTagD(LOperand* value, LOperand* temp1, LOperand* temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(NumberTagD, "number-tag-d") + DECLARE_HYDROGEN_ACCESSOR(Change) +}; + + +class LNumberTagU V8_FINAL : public LTemplateInstruction<1, 1, 2> { + public: + explicit LNumberTagU(LOperand* value, + LOperand* temp1, + LOperand* temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(NumberTagU, "number-tag-u") +}; + + +class LNumberUntagD V8_FINAL : public LTemplateInstruction<1, 1, 1> { + public: + LNumberUntagD(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(NumberUntagD, "double-untag") + DECLARE_HYDROGEN_ACCESSOR(Change) +}; + + +class LParameter V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + virtual bool HasInterestingComment(LCodeGen* gen) const { return false; } + DECLARE_CONCRETE_INSTRUCTION(Parameter, "parameter") +}; + + +class LPower V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LPower(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(Power, "power") + DECLARE_HYDROGEN_ACCESSOR(Power) +}; + + +class LPreparePushArguments V8_FINAL : public LTemplateInstruction<0, 0, 0> { + public: + explicit LPreparePushArguments(int argc) : argc_(argc) {} + + inline int argc() const { return argc_; } + + DECLARE_CONCRETE_INSTRUCTION(PreparePushArguments, "prepare-push-arguments") + + protected: + int argc_; +}; + + +class LPushArguments V8_FINAL : public LTemplateResultInstruction<0> { + public: + explicit LPushArguments(Zone* zone, + int capacity = kRecommendedMaxPushedArgs) + : zone_(zone), inputs_(capacity, zone) {} + + LOperand* argument(int i) { return inputs_[i]; } + int ArgumentCount() const { return inputs_.length(); } + + void AddArgument(LOperand* arg) { inputs_.Add(arg, zone_); } + + DECLARE_CONCRETE_INSTRUCTION(PushArguments, "push-arguments") + + // It is better to limit the number of arguments pushed simultaneously to + // avoid pressure on the register allocator. + static const int kRecommendedMaxPushedArgs = 4; + bool ShouldSplitPush() const { + return inputs_.length() >= kRecommendedMaxPushedArgs; + } + + protected: + Zone* zone_; + ZoneList<LOperand*> inputs_; + + private: + // Iterator support. + virtual int InputCount() V8_FINAL V8_OVERRIDE { return inputs_.length(); } + virtual LOperand* InputAt(int i) V8_FINAL V8_OVERRIDE { return inputs_[i]; } + + virtual int TempCount() V8_FINAL V8_OVERRIDE { return 0; } + virtual LOperand* TempAt(int i) V8_FINAL V8_OVERRIDE { return NULL; } +}; + + +class LRegExpLiteral V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LRegExpLiteral(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(RegExpLiteral, "regexp-literal") + DECLARE_HYDROGEN_ACCESSOR(RegExpLiteral) +}; + + +class LReturn V8_FINAL : public LTemplateInstruction<0, 3, 0> { + public: + LReturn(LOperand* value, LOperand* context, LOperand* parameter_count) { + inputs_[0] = value; + inputs_[1] = context; + inputs_[2] = parameter_count; + } + + LOperand* value() { return inputs_[0]; } + LOperand* parameter_count() { return inputs_[2]; } + + bool has_constant_parameter_count() { + return parameter_count()->IsConstantOperand(); + } + LConstantOperand* constant_parameter_count() { + ASSERT(has_constant_parameter_count()); + return LConstantOperand::cast(parameter_count()); + } + + DECLARE_CONCRETE_INSTRUCTION(Return, "return") +}; + + +class LSeqStringGetChar V8_FINAL : public LTemplateInstruction<1, 2, 1> { + public: + LSeqStringGetChar(LOperand* string, + LOperand* index, + LOperand* temp) { + inputs_[0] = string; + inputs_[1] = index; + temps_[0] = temp; + } + + LOperand* string() { return inputs_[0]; } + LOperand* index() { return inputs_[1]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(SeqStringGetChar, "seq-string-get-char") + DECLARE_HYDROGEN_ACCESSOR(SeqStringGetChar) +}; + + +class LSeqStringSetChar V8_FINAL : public LTemplateInstruction<1, 4, 1> { + public: + LSeqStringSetChar(LOperand* context, + LOperand* string, + LOperand* index, + LOperand* value, + LOperand* temp) { + inputs_[0] = context; + inputs_[1] = string; + inputs_[2] = index; + inputs_[3] = value; + temps_[0] = temp; + } + + LOperand* context() { return inputs_[0]; } + LOperand* string() { return inputs_[1]; } + LOperand* index() { return inputs_[2]; } + LOperand* value() { return inputs_[3]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(SeqStringSetChar, "seq-string-set-char") + DECLARE_HYDROGEN_ACCESSOR(SeqStringSetChar) +}; + + +class LSmiTag V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LSmiTag(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(SmiTag, "smi-tag") + DECLARE_HYDROGEN_ACCESSOR(Change) +}; + + +class LSmiUntag V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + LSmiUntag(LOperand* value, bool needs_check) + : needs_check_(needs_check) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + bool needs_check() const { return needs_check_; } + + DECLARE_CONCRETE_INSTRUCTION(SmiUntag, "smi-untag") + + private: + bool needs_check_; +}; + + +class LStackCheck V8_FINAL : public LTemplateInstruction<0, 1, 0> { + public: + explicit LStackCheck(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(StackCheck, "stack-check") + DECLARE_HYDROGEN_ACCESSOR(StackCheck) + + Label* done_label() { return &done_label_; } + + private: + Label done_label_; +}; + + +template<int T> +class LStoreKeyed : public LTemplateInstruction<0, 3, T> { + public: + LStoreKeyed(LOperand* elements, LOperand* key, LOperand* value) { + this->inputs_[0] = elements; + this->inputs_[1] = key; + this->inputs_[2] = value; + } + + bool is_external() const { return this->hydrogen()->is_external(); } + bool is_fixed_typed_array() const { + return hydrogen()->is_fixed_typed_array(); + } + bool is_typed_elements() const { + return is_external() || is_fixed_typed_array(); + } + LOperand* elements() { return this->inputs_[0]; } + LOperand* key() { return this->inputs_[1]; } + LOperand* value() { return this->inputs_[2]; } + ElementsKind elements_kind() const { + return this->hydrogen()->elements_kind(); + } + + bool NeedsCanonicalization() { + if (hydrogen()->value()->IsAdd() || hydrogen()->value()->IsSub() || + hydrogen()->value()->IsMul() || hydrogen()->value()->IsDiv()) { + return false; + } + return this->hydrogen()->NeedsCanonicalization(); + } + uint32_t base_offset() const { return this->hydrogen()->base_offset(); } + + void PrintDataTo(StringStream* stream) V8_OVERRIDE { + this->elements()->PrintTo(stream); + stream->Add("["); + this->key()->PrintTo(stream); + if (this->base_offset() != 0) { + stream->Add(" + %d] <-", this->base_offset()); + } else { + stream->Add("] <- "); + } + + if (this->value() == NULL) { + ASSERT(hydrogen()->IsConstantHoleStore() && + hydrogen()->value()->representation().IsDouble()); + stream->Add("<the hole(nan)>"); + } else { + this->value()->PrintTo(stream); + } + } + + DECLARE_HYDROGEN_ACCESSOR(StoreKeyed) +}; + + +class LStoreKeyedExternal V8_FINAL : public LStoreKeyed<1> { + public: + LStoreKeyedExternal(LOperand* elements, LOperand* key, LOperand* value, + LOperand* temp) : + LStoreKeyed<1>(elements, key, value) { + temps_[0] = temp; + } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedExternal, "store-keyed-external") +}; + + +class LStoreKeyedFixed V8_FINAL : public LStoreKeyed<1> { + public: + LStoreKeyedFixed(LOperand* elements, LOperand* key, LOperand* value, + LOperand* temp) : + LStoreKeyed<1>(elements, key, value) { + temps_[0] = temp; + } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedFixed, "store-keyed-fixed") +}; + + +class LStoreKeyedFixedDouble V8_FINAL : public LStoreKeyed<1> { + public: + LStoreKeyedFixedDouble(LOperand* elements, LOperand* key, LOperand* value, + LOperand* temp) : + LStoreKeyed<1>(elements, key, value) { + temps_[0] = temp; + } + + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedFixedDouble, + "store-keyed-fixed-double") +}; + + +class LStoreKeyedGeneric V8_FINAL : public LTemplateInstruction<0, 4, 0> { + public: + LStoreKeyedGeneric(LOperand* context, + LOperand* obj, + LOperand* key, + LOperand* value) { + inputs_[0] = context; + inputs_[1] = obj; + inputs_[2] = key; + inputs_[3] = value; + } + + LOperand* context() { return inputs_[0]; } + LOperand* object() { return inputs_[1]; } + LOperand* key() { return inputs_[2]; } + LOperand* value() { return inputs_[3]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedGeneric, "store-keyed-generic") + DECLARE_HYDROGEN_ACCESSOR(StoreKeyedGeneric) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + StrictMode strict_mode() { return hydrogen()->strict_mode(); } +}; + + +class LStoreNamedField V8_FINAL : public LTemplateInstruction<0, 2, 2> { + public: + LStoreNamedField(LOperand* object, LOperand* value, + LOperand* temp0, LOperand* temp1) { + inputs_[0] = object; + inputs_[1] = value; + temps_[0] = temp0; + temps_[1] = temp1; + } + + LOperand* object() { return inputs_[0]; } + LOperand* value() { return inputs_[1]; } + LOperand* temp0() { return temps_[0]; } + LOperand* temp1() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedField, "store-named-field") + DECLARE_HYDROGEN_ACCESSOR(StoreNamedField) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + Representation representation() const { + return hydrogen()->field_representation(); + } +}; + + +class LStoreNamedGeneric V8_FINAL: public LTemplateInstruction<0, 3, 0> { + public: + LStoreNamedGeneric(LOperand* context, LOperand* object, LOperand* value) { + inputs_[0] = context; + inputs_[1] = object; + inputs_[2] = value; + } + + LOperand* context() { return inputs_[0]; } + LOperand* object() { return inputs_[1]; } + LOperand* value() { return inputs_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedGeneric, "store-named-generic") + DECLARE_HYDROGEN_ACCESSOR(StoreNamedGeneric) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + Handle<Object> name() const { return hydrogen()->name(); } + StrictMode strict_mode() { return hydrogen()->strict_mode(); } +}; + + +class LStringAdd V8_FINAL : public LTemplateInstruction<1, 3, 0> { + public: + LStringAdd(LOperand* context, LOperand* left, LOperand* right) { + inputs_[0] = context; + inputs_[1] = left; + inputs_[2] = right; + } + + LOperand* context() { return inputs_[0]; } + LOperand* left() { return inputs_[1]; } + LOperand* right() { return inputs_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(StringAdd, "string-add") + DECLARE_HYDROGEN_ACCESSOR(StringAdd) +}; + + + +class LStringCharCodeAt V8_FINAL : public LTemplateInstruction<1, 3, 0> { + public: + LStringCharCodeAt(LOperand* context, LOperand* string, LOperand* index) { + inputs_[0] = context; + inputs_[1] = string; + inputs_[2] = index; + } + + LOperand* context() { return inputs_[0]; } + LOperand* string() { return inputs_[1]; } + LOperand* index() { return inputs_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(StringCharCodeAt, "string-char-code-at") + DECLARE_HYDROGEN_ACCESSOR(StringCharCodeAt) +}; + + +class LStringCharFromCode V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LStringCharFromCode(LOperand* context, LOperand* char_code) { + inputs_[0] = context; + inputs_[1] = char_code; + } + + LOperand* context() { return inputs_[0]; } + LOperand* char_code() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(StringCharFromCode, "string-char-from-code") + DECLARE_HYDROGEN_ACCESSOR(StringCharFromCode) +}; + + +class LStringCompareAndBranch V8_FINAL : public LControlInstruction<3, 0> { + public: + LStringCompareAndBranch(LOperand* context, LOperand* left, LOperand* right) { + inputs_[0] = context; + inputs_[1] = left; + inputs_[2] = right; + } + + LOperand* context() { return inputs_[0]; } + LOperand* left() { return inputs_[1]; } + LOperand* right() { return inputs_[2]; } + + DECLARE_CONCRETE_INSTRUCTION(StringCompareAndBranch, + "string-compare-and-branch") + DECLARE_HYDROGEN_ACCESSOR(StringCompareAndBranch) + + Token::Value op() const { return hydrogen()->token(); } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +// Truncating conversion from a tagged value to an int32. +class LTaggedToI V8_FINAL : public LTemplateInstruction<1, 1, 2> { + public: + explicit LTaggedToI(LOperand* value, LOperand* temp1, LOperand* temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(TaggedToI, "tagged-to-i") + DECLARE_HYDROGEN_ACCESSOR(Change) + + bool truncating() { return hydrogen()->CanTruncateToInt32(); } +}; + + +class LShiftI V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LShiftI(Token::Value op, LOperand* left, LOperand* right, bool can_deopt) + : op_(op), can_deopt_(can_deopt) { + inputs_[0] = left; + inputs_[1] = right; + } + + Token::Value op() const { return op_; } + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + bool can_deopt() const { return can_deopt_; } + + DECLARE_CONCRETE_INSTRUCTION(ShiftI, "shift-i") + + private: + Token::Value op_; + bool can_deopt_; +}; + + +class LShiftS V8_FINAL : public LTemplateInstruction<1, 2, 1> { + public: + LShiftS(Token::Value op, LOperand* left, LOperand* right, LOperand* temp, + bool can_deopt) : op_(op), can_deopt_(can_deopt) { + inputs_[0] = left; + inputs_[1] = right; + temps_[0] = temp; + } + + Token::Value op() const { return op_; } + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + LOperand* temp() { return temps_[0]; } + bool can_deopt() const { return can_deopt_; } + + DECLARE_CONCRETE_INSTRUCTION(ShiftS, "shift-s") + + private: + Token::Value op_; + bool can_deopt_; +}; + + +class LStoreCodeEntry V8_FINAL: public LTemplateInstruction<0, 2, 1> { + public: + LStoreCodeEntry(LOperand* function, LOperand* code_object, + LOperand* temp) { + inputs_[0] = function; + inputs_[1] = code_object; + temps_[0] = temp; + } + + LOperand* function() { return inputs_[0]; } + LOperand* code_object() { return inputs_[1]; } + LOperand* temp() { return temps_[0]; } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + DECLARE_CONCRETE_INSTRUCTION(StoreCodeEntry, "store-code-entry") + DECLARE_HYDROGEN_ACCESSOR(StoreCodeEntry) +}; + + +class LStoreContextSlot V8_FINAL : public LTemplateInstruction<0, 2, 1> { + public: + LStoreContextSlot(LOperand* context, LOperand* value, LOperand* temp) { + inputs_[0] = context; + inputs_[1] = value; + temps_[0] = temp; + } + + LOperand* context() { return inputs_[0]; } + LOperand* value() { return inputs_[1]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreContextSlot, "store-context-slot") + DECLARE_HYDROGEN_ACCESSOR(StoreContextSlot) + + int slot_index() { return hydrogen()->slot_index(); } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LStoreGlobalCell V8_FINAL : public LTemplateInstruction<0, 1, 2> { + public: + LStoreGlobalCell(LOperand* value, LOperand* temp1, LOperand* temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreGlobalCell, "store-global-cell") + DECLARE_HYDROGEN_ACCESSOR(StoreGlobalCell) +}; + + +class LSubI V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LSubI(LOperand* left, LOperand* right) + : shift_(NO_SHIFT), shift_amount_(0) { + inputs_[0] = left; + inputs_[1] = right; + } + + LSubI(LOperand* left, LOperand* right, Shift shift, LOperand* shift_amount) + : shift_(shift), shift_amount_(shift_amount) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + Shift shift() const { return shift_; } + LOperand* shift_amount() const { return shift_amount_; } + + DECLARE_CONCRETE_INSTRUCTION(SubI, "sub-i") + DECLARE_HYDROGEN_ACCESSOR(Sub) + + protected: + Shift shift_; + LOperand* shift_amount_; +}; + + +class LSubS: public LTemplateInstruction<1, 2, 0> { + public: + LSubS(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + LOperand* left() { return inputs_[0]; } + LOperand* right() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(SubS, "sub-s") + DECLARE_HYDROGEN_ACCESSOR(Sub) +}; + + +class LThisFunction V8_FINAL : public LTemplateInstruction<1, 0, 0> { + public: + DECLARE_CONCRETE_INSTRUCTION(ThisFunction, "this-function") + DECLARE_HYDROGEN_ACCESSOR(ThisFunction) +}; + + +class LToFastProperties V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LToFastProperties(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(ToFastProperties, "to-fast-properties") + DECLARE_HYDROGEN_ACCESSOR(ToFastProperties) +}; + + +class LTransitionElementsKind V8_FINAL : public LTemplateInstruction<0, 2, 2> { + public: + LTransitionElementsKind(LOperand* object, + LOperand* context, + LOperand* temp1, + LOperand* temp2) { + inputs_[0] = object; + inputs_[1] = context; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* object() { return inputs_[0]; } + LOperand* context() { return inputs_[1]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(TransitionElementsKind, + "transition-elements-kind") + DECLARE_HYDROGEN_ACCESSOR(TransitionElementsKind) + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; + + Handle<Map> original_map() { return hydrogen()->original_map().handle(); } + Handle<Map> transitioned_map() { + return hydrogen()->transitioned_map().handle(); + } + ElementsKind from_kind() const { return hydrogen()->from_kind(); } + ElementsKind to_kind() const { return hydrogen()->to_kind(); } +}; + + +class LTrapAllocationMemento V8_FINAL : public LTemplateInstruction<0, 1, 2> { + public: + LTrapAllocationMemento(LOperand* object, LOperand* temp1, LOperand* temp2) { + inputs_[0] = object; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* object() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(TrapAllocationMemento, "trap-allocation-memento") +}; + + +class LTruncateDoubleToIntOrSmi V8_FINAL + : public LTemplateInstruction<1, 1, 0> { + public: + explicit LTruncateDoubleToIntOrSmi(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(TruncateDoubleToIntOrSmi, + "truncate-double-to-int-or-smi") + DECLARE_HYDROGEN_ACCESSOR(UnaryOperation) + + bool tag_result() { return hydrogen()->representation().IsSmi(); } +}; + + +class LTypeof V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LTypeof(LOperand* context, LOperand* value) { + inputs_[0] = context; + inputs_[1] = value; + } + + LOperand* context() { return inputs_[0]; } + LOperand* value() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(Typeof, "typeof") +}; + + +class LTypeofIsAndBranch V8_FINAL : public LControlInstruction<1, 2> { + public: + LTypeofIsAndBranch(LOperand* value, LOperand* temp1, LOperand* temp2) { + inputs_[0] = value; + temps_[0] = temp1; + temps_[1] = temp2; + } + + LOperand* value() { return inputs_[0]; } + LOperand* temp1() { return temps_[0]; } + LOperand* temp2() { return temps_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(TypeofIsAndBranch, "typeof-is-and-branch") + DECLARE_HYDROGEN_ACCESSOR(TypeofIsAndBranch) + + Handle<String> type_literal() const { return hydrogen()->type_literal(); } + + virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE; +}; + + +class LUint32ToDouble V8_FINAL : public LTemplateInstruction<1, 1, 0> { + public: + explicit LUint32ToDouble(LOperand* value) { + inputs_[0] = value; + } + + LOperand* value() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(Uint32ToDouble, "uint32-to-double") +}; + + +class LCheckMapValue V8_FINAL : public LTemplateInstruction<0, 2, 1> { + public: + LCheckMapValue(LOperand* value, LOperand* map, LOperand* temp) { + inputs_[0] = value; + inputs_[1] = map; + temps_[0] = temp; + } + + LOperand* value() { return inputs_[0]; } + LOperand* map() { return inputs_[1]; } + LOperand* temp() { return temps_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(CheckMapValue, "check-map-value") +}; + + +class LLoadFieldByIndex V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LLoadFieldByIndex(LOperand* object, LOperand* index) { + inputs_[0] = object; + inputs_[1] = index; + } + + LOperand* object() { return inputs_[0]; } + LOperand* index() { return inputs_[1]; } + + DECLARE_CONCRETE_INSTRUCTION(LoadFieldByIndex, "load-field-by-index") +}; + + +class LStoreFrameContext: public LTemplateInstruction<0, 1, 0> { + public: + explicit LStoreFrameContext(LOperand* context) { + inputs_[0] = context; + } + + LOperand* context() { return inputs_[0]; } + + DECLARE_CONCRETE_INSTRUCTION(StoreFrameContext, "store-frame-context") +}; + + +class LAllocateBlockContext: public LTemplateInstruction<1, 2, 0> { + public: + LAllocateBlockContext(LOperand* context, LOperand* function) { + inputs_[0] = context; + inputs_[1] = function; + } + + LOperand* context() { return inputs_[0]; } + LOperand* function() { return inputs_[1]; } + + Handle<ScopeInfo> scope_info() { return hydrogen()->scope_info(); } + + DECLARE_CONCRETE_INSTRUCTION(AllocateBlockContext, "allocate-block-context") + DECLARE_HYDROGEN_ACCESSOR(AllocateBlockContext) +}; + + +class LWrapReceiver V8_FINAL : public LTemplateInstruction<1, 2, 0> { + public: + LWrapReceiver(LOperand* receiver, LOperand* function) { + inputs_[0] = receiver; + inputs_[1] = function; + } + + DECLARE_CONCRETE_INSTRUCTION(WrapReceiver, "wrap-receiver") + DECLARE_HYDROGEN_ACCESSOR(WrapReceiver) + + LOperand* receiver() { return inputs_[0]; } + LOperand* function() { return inputs_[1]; } +}; + + +class LChunkBuilder; +class LPlatformChunk V8_FINAL : public LChunk { + public: + LPlatformChunk(CompilationInfo* info, HGraph* graph) + : LChunk(info, graph) { } + + int GetNextSpillIndex(); + LOperand* GetNextSpillSlot(RegisterKind kind); +}; + + +class LChunkBuilder V8_FINAL : public LChunkBuilderBase { + public: + LChunkBuilder(CompilationInfo* info, HGraph* graph, LAllocator* allocator) + : LChunkBuilderBase(graph->zone()), + chunk_(NULL), + info_(info), + graph_(graph), + status_(UNUSED), + current_instruction_(NULL), + current_block_(NULL), + allocator_(allocator) { } + + // Build the sequence for the graph. + LPlatformChunk* Build(); + + // Declare methods that deal with the individual node types. +#define DECLARE_DO(type) LInstruction* Do##type(H##type* node); + HYDROGEN_CONCRETE_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + LInstruction* DoDivByPowerOf2I(HDiv* instr); + LInstruction* DoDivByConstI(HDiv* instr); + LInstruction* DoDivI(HBinaryOperation* instr); + LInstruction* DoModByPowerOf2I(HMod* instr); + LInstruction* DoModByConstI(HMod* instr); + LInstruction* DoModI(HMod* instr); + LInstruction* DoFlooringDivByPowerOf2I(HMathFloorOfDiv* instr); + LInstruction* DoFlooringDivByConstI(HMathFloorOfDiv* instr); + LInstruction* DoFlooringDivI(HMathFloorOfDiv* instr); + + static bool HasMagicNumberForDivision(int32_t divisor); + + private: + enum Status { + UNUSED, + BUILDING, + DONE, + ABORTED + }; + + HGraph* graph() const { return graph_; } + Isolate* isolate() const { return info_->isolate(); } + + bool is_unused() const { return status_ == UNUSED; } + bool is_building() const { return status_ == BUILDING; } + bool is_done() const { return status_ == DONE; } + bool is_aborted() const { return status_ == ABORTED; } + + int argument_count() const { return argument_count_; } + CompilationInfo* info() const { return info_; } + Heap* heap() const { return isolate()->heap(); } + + void Abort(BailoutReason reason); + + // Methods for getting operands for Use / Define / Temp. + LUnallocated* ToUnallocated(Register reg); + LUnallocated* ToUnallocated(DoubleRegister reg); + + // Methods for setting up define-use relationships. + MUST_USE_RESULT LOperand* Use(HValue* value, LUnallocated* operand); + MUST_USE_RESULT LOperand* UseFixed(HValue* value, Register fixed_register); + MUST_USE_RESULT LOperand* UseFixedDouble(HValue* value, + DoubleRegister fixed_register); + + // A value that is guaranteed to be allocated to a register. + // The operand created by UseRegister is guaranteed to be live until the end + // of the instruction. This means that register allocator will not reuse its + // register for any other operand inside instruction. + MUST_USE_RESULT LOperand* UseRegister(HValue* value); + + // The operand created by UseRegisterAndClobber is guaranteed to be live until + // the end of the end of the instruction, and it may also be used as a scratch + // register by the instruction implementation. + // + // This behaves identically to ARM's UseTempRegister. However, it is renamed + // to discourage its use in ARM64, since in most cases it is better to + // allocate a temporary register for the Lithium instruction. + MUST_USE_RESULT LOperand* UseRegisterAndClobber(HValue* value); + + // The operand created by UseRegisterAtStart is guaranteed to be live only at + // instruction start. The register allocator is free to assign the same + // register to some other operand used inside instruction (i.e. temporary or + // output). + MUST_USE_RESULT LOperand* UseRegisterAtStart(HValue* value); + + // An input operand in a register or a constant operand. + MUST_USE_RESULT LOperand* UseRegisterOrConstant(HValue* value); + MUST_USE_RESULT LOperand* UseRegisterOrConstantAtStart(HValue* value); + + // A constant operand. + MUST_USE_RESULT LConstantOperand* UseConstant(HValue* value); + + // An input operand in register, stack slot or a constant operand. + // Will not be moved to a register even if one is freely available. + virtual MUST_USE_RESULT LOperand* UseAny(HValue* value); + + // Temporary operand that must be in a register. + MUST_USE_RESULT LUnallocated* TempRegister(); + + // Temporary operand that must be in a double register. + MUST_USE_RESULT LUnallocated* TempDoubleRegister(); + + // Temporary operand that must be in a fixed double register. + MUST_USE_RESULT LOperand* FixedTemp(DoubleRegister reg); + + // Methods for setting up define-use relationships. + // Return the same instruction that they are passed. + LInstruction* Define(LTemplateResultInstruction<1>* instr, + LUnallocated* result); + LInstruction* DefineAsRegister(LTemplateResultInstruction<1>* instr); + LInstruction* DefineAsSpilled(LTemplateResultInstruction<1>* instr, + int index); + + LInstruction* DefineSameAsFirst(LTemplateResultInstruction<1>* instr); + LInstruction* DefineFixed(LTemplateResultInstruction<1>* instr, + Register reg); + LInstruction* DefineFixedDouble(LTemplateResultInstruction<1>* instr, + DoubleRegister reg); + + enum CanDeoptimize { CAN_DEOPTIMIZE_EAGERLY, CANNOT_DEOPTIMIZE_EAGERLY }; + + // By default we assume that instruction sequences generated for calls + // cannot deoptimize eagerly and we do not attach environment to this + // instruction. + LInstruction* MarkAsCall( + LInstruction* instr, + HInstruction* hinstr, + CanDeoptimize can_deoptimize = CANNOT_DEOPTIMIZE_EAGERLY); + + LInstruction* AssignPointerMap(LInstruction* instr); + LInstruction* AssignEnvironment(LInstruction* instr); + + void VisitInstruction(HInstruction* current); + void AddInstruction(LInstruction* instr, HInstruction* current); + void DoBasicBlock(HBasicBlock* block); + + int JSShiftAmountFromHConstant(HValue* constant) { + return HConstant::cast(constant)->Integer32Value() & 0x1f; + } + bool LikelyFitsImmField(HInstruction* instr, int imm) { + if (instr->IsAdd() || instr->IsSub()) { + return Assembler::IsImmAddSub(imm) || Assembler::IsImmAddSub(-imm); + } else { + ASSERT(instr->IsBitwise()); + unsigned unused_n, unused_imm_s, unused_imm_r; + return Assembler::IsImmLogical(imm, kWRegSizeInBits, + &unused_n, &unused_imm_s, &unused_imm_r); + } + } + + // Indicates if a sequence of the form + // lsl x8, x9, #imm + // add x0, x1, x8 + // can be replaced with: + // add x0, x1, x9 LSL #imm + // If this is not possible, the function returns NULL. Otherwise it returns a + // pointer to the shift instruction that would be optimized away. + HBitwiseBinaryOperation* CanTransformToShiftedOp(HValue* val, + HValue** left = NULL); + // Checks if all uses of the shift operation can optimize it away. + bool ShiftCanBeOptimizedAway(HBitwiseBinaryOperation* shift); + // Attempts to merge the binary operation and an eventual previous shift + // operation into a single operation. Returns the merged instruction on + // success, and NULL otherwise. + LInstruction* TryDoOpWithShiftedRightOperand(HBinaryOperation* op); + LInstruction* DoShiftedBinaryOp(HBinaryOperation* instr, + HValue* left, + HBitwiseBinaryOperation* shift); + + LInstruction* DoShift(Token::Value op, HBitwiseBinaryOperation* instr); + LInstruction* DoArithmeticD(Token::Value op, + HArithmeticBinaryOperation* instr); + LInstruction* DoArithmeticT(Token::Value op, + HBinaryOperation* instr); + + LPlatformChunk* chunk_; + CompilationInfo* info_; + HGraph* const graph_; + Status status_; + HInstruction* current_instruction_; + HBasicBlock* current_block_; + LAllocator* allocator_; + + DISALLOW_COPY_AND_ASSIGN(LChunkBuilder); +}; + +#undef DECLARE_HYDROGEN_ACCESSOR +#undef DECLARE_CONCRETE_INSTRUCTION + +} } // namespace v8::internal + +#endif // V8_ARM64_LITHIUM_ARM64_H_ diff --git a/chromium/v8/src/arm64/lithium-codegen-arm64.cc b/chromium/v8/src/arm64/lithium-codegen-arm64.cc new file mode 100644 index 00000000000..29c13ac5833 --- /dev/null +++ b/chromium/v8/src/arm64/lithium-codegen-arm64.cc @@ -0,0 +1,6061 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#include "src/arm64/lithium-codegen-arm64.h" +#include "src/arm64/lithium-gap-resolver-arm64.h" +#include "src/code-stubs.h" +#include "src/stub-cache.h" +#include "src/hydrogen-osr.h" + +namespace v8 { +namespace internal { + + +class SafepointGenerator V8_FINAL : public CallWrapper { + public: + SafepointGenerator(LCodeGen* codegen, + LPointerMap* pointers, + Safepoint::DeoptMode mode) + : codegen_(codegen), + pointers_(pointers), + deopt_mode_(mode) { } + virtual ~SafepointGenerator() { } + + virtual void BeforeCall(int call_size) const { } + + virtual void AfterCall() const { + codegen_->RecordSafepoint(pointers_, deopt_mode_); + } + + private: + LCodeGen* codegen_; + LPointerMap* pointers_; + Safepoint::DeoptMode deopt_mode_; +}; + + +#define __ masm()-> + +// Emit code to branch if the given condition holds. +// The code generated here doesn't modify the flags and they must have +// been set by some prior instructions. +// +// The EmitInverted function simply inverts the condition. +class BranchOnCondition : public BranchGenerator { + public: + BranchOnCondition(LCodeGen* codegen, Condition cond) + : BranchGenerator(codegen), + cond_(cond) { } + + virtual void Emit(Label* label) const { + __ B(cond_, label); + } + + virtual void EmitInverted(Label* label) const { + if (cond_ != al) { + __ B(NegateCondition(cond_), label); + } + } + + private: + Condition cond_; +}; + + +// Emit code to compare lhs and rhs and branch if the condition holds. +// This uses MacroAssembler's CompareAndBranch function so it will handle +// converting the comparison to Cbz/Cbnz if the right-hand side is 0. +// +// EmitInverted still compares the two operands but inverts the condition. +class CompareAndBranch : public BranchGenerator { + public: + CompareAndBranch(LCodeGen* codegen, + Condition cond, + const Register& lhs, + const Operand& rhs) + : BranchGenerator(codegen), + cond_(cond), + lhs_(lhs), + rhs_(rhs) { } + + virtual void Emit(Label* label) const { + __ CompareAndBranch(lhs_, rhs_, cond_, label); + } + + virtual void EmitInverted(Label* label) const { + __ CompareAndBranch(lhs_, rhs_, NegateCondition(cond_), label); + } + + private: + Condition cond_; + const Register& lhs_; + const Operand& rhs_; +}; + + +// Test the input with the given mask and branch if the condition holds. +// If the condition is 'eq' or 'ne' this will use MacroAssembler's +// TestAndBranchIfAllClear and TestAndBranchIfAnySet so it will handle the +// conversion to Tbz/Tbnz when possible. +class TestAndBranch : public BranchGenerator { + public: + TestAndBranch(LCodeGen* codegen, + Condition cond, + const Register& value, + uint64_t mask) + : BranchGenerator(codegen), + cond_(cond), + value_(value), + mask_(mask) { } + + virtual void Emit(Label* label) const { + switch (cond_) { + case eq: + __ TestAndBranchIfAllClear(value_, mask_, label); + break; + case ne: + __ TestAndBranchIfAnySet(value_, mask_, label); + break; + default: + __ Tst(value_, mask_); + __ B(cond_, label); + } + } + + virtual void EmitInverted(Label* label) const { + // The inverse of "all clear" is "any set" and vice versa. + switch (cond_) { + case eq: + __ TestAndBranchIfAnySet(value_, mask_, label); + break; + case ne: + __ TestAndBranchIfAllClear(value_, mask_, label); + break; + default: + __ Tst(value_, mask_); + __ B(NegateCondition(cond_), label); + } + } + + private: + Condition cond_; + const Register& value_; + uint64_t mask_; +}; + + +// Test the input and branch if it is non-zero and not a NaN. +class BranchIfNonZeroNumber : public BranchGenerator { + public: + BranchIfNonZeroNumber(LCodeGen* codegen, const FPRegister& value, + const FPRegister& scratch) + : BranchGenerator(codegen), value_(value), scratch_(scratch) { } + + virtual void Emit(Label* label) const { + __ Fabs(scratch_, value_); + // Compare with 0.0. Because scratch_ is positive, the result can be one of + // nZCv (equal), nzCv (greater) or nzCV (unordered). + __ Fcmp(scratch_, 0.0); + __ B(gt, label); + } + + virtual void EmitInverted(Label* label) const { + __ Fabs(scratch_, value_); + __ Fcmp(scratch_, 0.0); + __ B(le, label); + } + + private: + const FPRegister& value_; + const FPRegister& scratch_; +}; + + +// Test the input and branch if it is a heap number. +class BranchIfHeapNumber : public BranchGenerator { + public: + BranchIfHeapNumber(LCodeGen* codegen, const Register& value) + : BranchGenerator(codegen), value_(value) { } + + virtual void Emit(Label* label) const { + __ JumpIfHeapNumber(value_, label); + } + + virtual void EmitInverted(Label* label) const { + __ JumpIfNotHeapNumber(value_, label); + } + + private: + const Register& value_; +}; + + +// Test the input and branch if it is the specified root value. +class BranchIfRoot : public BranchGenerator { + public: + BranchIfRoot(LCodeGen* codegen, const Register& value, + Heap::RootListIndex index) + : BranchGenerator(codegen), value_(value), index_(index) { } + + virtual void Emit(Label* label) const { + __ JumpIfRoot(value_, index_, label); + } + + virtual void EmitInverted(Label* label) const { + __ JumpIfNotRoot(value_, index_, label); + } + + private: + const Register& value_; + const Heap::RootListIndex index_; +}; + + +void LCodeGen::WriteTranslation(LEnvironment* environment, + Translation* translation) { + if (environment == NULL) return; + + // The translation includes one command per value in the environment. + int translation_size = environment->translation_size(); + // The output frame height does not include the parameters. + int height = translation_size - environment->parameter_count(); + + WriteTranslation(environment->outer(), translation); + bool has_closure_id = !info()->closure().is_null() && + !info()->closure().is_identical_to(environment->closure()); + int closure_id = has_closure_id + ? DefineDeoptimizationLiteral(environment->closure()) + : Translation::kSelfLiteralId; + + switch (environment->frame_type()) { + case JS_FUNCTION: + translation->BeginJSFrame(environment->ast_id(), closure_id, height); + break; + case JS_CONSTRUCT: + translation->BeginConstructStubFrame(closure_id, translation_size); + break; + case JS_GETTER: + ASSERT(translation_size == 1); + ASSERT(height == 0); + translation->BeginGetterStubFrame(closure_id); + break; + case JS_SETTER: + ASSERT(translation_size == 2); + ASSERT(height == 0); + translation->BeginSetterStubFrame(closure_id); + break; + case STUB: + translation->BeginCompiledStubFrame(); + break; + case ARGUMENTS_ADAPTOR: + translation->BeginArgumentsAdaptorFrame(closure_id, translation_size); + break; + default: + UNREACHABLE(); + } + + int object_index = 0; + int dematerialized_index = 0; + for (int i = 0; i < translation_size; ++i) { + LOperand* value = environment->values()->at(i); + + AddToTranslation(environment, + translation, + value, + environment->HasTaggedValueAt(i), + environment->HasUint32ValueAt(i), + &object_index, + &dematerialized_index); + } +} + + +void LCodeGen::AddToTranslation(LEnvironment* environment, + Translation* translation, + LOperand* op, + bool is_tagged, + bool is_uint32, + int* object_index_pointer, + int* dematerialized_index_pointer) { + if (op == LEnvironment::materialization_marker()) { + int object_index = (*object_index_pointer)++; + if (environment->ObjectIsDuplicateAt(object_index)) { + int dupe_of = environment->ObjectDuplicateOfAt(object_index); + translation->DuplicateObject(dupe_of); + return; + } + int object_length = environment->ObjectLengthAt(object_index); + if (environment->ObjectIsArgumentsAt(object_index)) { + translation->BeginArgumentsObject(object_length); + } else { + translation->BeginCapturedObject(object_length); + } + int dematerialized_index = *dematerialized_index_pointer; + int env_offset = environment->translation_size() + dematerialized_index; + *dematerialized_index_pointer += object_length; + for (int i = 0; i < object_length; ++i) { + LOperand* value = environment->values()->at(env_offset + i); + AddToTranslation(environment, + translation, + value, + environment->HasTaggedValueAt(env_offset + i), + environment->HasUint32ValueAt(env_offset + i), + object_index_pointer, + dematerialized_index_pointer); + } + return; + } + + if (op->IsStackSlot()) { + if (is_tagged) { + translation->StoreStackSlot(op->index()); + } else if (is_uint32) { + translation->StoreUint32StackSlot(op->index()); + } else { + translation->StoreInt32StackSlot(op->index()); + } + } else if (op->IsDoubleStackSlot()) { + translation->StoreDoubleStackSlot(op->index()); + } else if (op->IsRegister()) { + Register reg = ToRegister(op); + if (is_tagged) { + translation->StoreRegister(reg); + } else if (is_uint32) { + translation->StoreUint32Register(reg); + } else { + translation->StoreInt32Register(reg); + } + } else if (op->IsDoubleRegister()) { + DoubleRegister reg = ToDoubleRegister(op); + translation->StoreDoubleRegister(reg); + } else if (op->IsConstantOperand()) { + HConstant* constant = chunk()->LookupConstant(LConstantOperand::cast(op)); + int src_index = DefineDeoptimizationLiteral(constant->handle(isolate())); + translation->StoreLiteral(src_index); + } else { + UNREACHABLE(); + } +} + + +int LCodeGen::DefineDeoptimizationLiteral(Handle<Object> literal) { + int result = deoptimization_literals_.length(); + for (int i = 0; i < deoptimization_literals_.length(); ++i) { + if (deoptimization_literals_[i].is_identical_to(literal)) return i; + } + deoptimization_literals_.Add(literal, zone()); + return result; +} + + +void LCodeGen::RegisterEnvironmentForDeoptimization(LEnvironment* environment, + Safepoint::DeoptMode mode) { + environment->set_has_been_used(); + if (!environment->HasBeenRegistered()) { + int frame_count = 0; + int jsframe_count = 0; + for (LEnvironment* e = environment; e != NULL; e = e->outer()) { + ++frame_count; + if (e->frame_type() == JS_FUNCTION) { + ++jsframe_count; + } + } + Translation translation(&translations_, frame_count, jsframe_count, zone()); + WriteTranslation(environment, &translation); + int deoptimization_index = deoptimizations_.length(); + int pc_offset = masm()->pc_offset(); + environment->Register(deoptimization_index, + translation.index(), + (mode == Safepoint::kLazyDeopt) ? pc_offset : -1); + deoptimizations_.Add(environment, zone()); + } +} + + +void LCodeGen::CallCode(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr) { + CallCodeGeneric(code, mode, instr, RECORD_SIMPLE_SAFEPOINT); +} + + +void LCodeGen::CallCodeGeneric(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr, + SafepointMode safepoint_mode) { + ASSERT(instr != NULL); + + Assembler::BlockPoolsScope scope(masm_); + __ Call(code, mode); + RecordSafepointWithLazyDeopt(instr, safepoint_mode); + + if ((code->kind() == Code::BINARY_OP_IC) || + (code->kind() == Code::COMPARE_IC)) { + // Signal that we don't inline smi code before these stubs in the + // optimizing code generator. + InlineSmiCheckInfo::EmitNotInlined(masm()); + } +} + + +void LCodeGen::DoCallFunction(LCallFunction* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->function()).Is(x1)); + ASSERT(ToRegister(instr->result()).Is(x0)); + + int arity = instr->arity(); + CallFunctionStub stub(isolate(), arity, instr->hydrogen()->function_flags()); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + after_push_argument_ = false; +} + + +void LCodeGen::DoCallNew(LCallNew* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(instr->IsMarkedAsCall()); + ASSERT(ToRegister(instr->constructor()).is(x1)); + + __ Mov(x0, instr->arity()); + // No cell in x2 for construct type feedback in optimized code. + __ LoadRoot(x2, Heap::kUndefinedValueRootIndex); + + CallConstructStub stub(isolate(), NO_CALL_CONSTRUCTOR_FLAGS); + CallCode(stub.GetCode(), RelocInfo::CONSTRUCT_CALL, instr); + after_push_argument_ = false; + + ASSERT(ToRegister(instr->result()).is(x0)); +} + + +void LCodeGen::DoCallNewArray(LCallNewArray* instr) { + ASSERT(instr->IsMarkedAsCall()); + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->constructor()).is(x1)); + + __ Mov(x0, Operand(instr->arity())); + __ LoadRoot(x2, Heap::kUndefinedValueRootIndex); + + ElementsKind kind = instr->hydrogen()->elements_kind(); + AllocationSiteOverrideMode override_mode = + (AllocationSite::GetMode(kind) == TRACK_ALLOCATION_SITE) + ? DISABLE_ALLOCATION_SITES + : DONT_OVERRIDE; + + if (instr->arity() == 0) { + ArrayNoArgumentConstructorStub stub(isolate(), kind, override_mode); + CallCode(stub.GetCode(), RelocInfo::CONSTRUCT_CALL, instr); + } else if (instr->arity() == 1) { + Label done; + if (IsFastPackedElementsKind(kind)) { + Label packed_case; + + // We might need to create a holey array; look at the first argument. + __ Peek(x10, 0); + __ Cbz(x10, &packed_case); + + ElementsKind holey_kind = GetHoleyElementsKind(kind); + ArraySingleArgumentConstructorStub stub(isolate(), + holey_kind, + override_mode); + CallCode(stub.GetCode(), RelocInfo::CONSTRUCT_CALL, instr); + __ B(&done); + __ Bind(&packed_case); + } + + ArraySingleArgumentConstructorStub stub(isolate(), kind, override_mode); + CallCode(stub.GetCode(), RelocInfo::CONSTRUCT_CALL, instr); + __ Bind(&done); + } else { + ArrayNArgumentsConstructorStub stub(isolate(), kind, override_mode); + CallCode(stub.GetCode(), RelocInfo::CONSTRUCT_CALL, instr); + } + after_push_argument_ = false; + + ASSERT(ToRegister(instr->result()).is(x0)); +} + + +void LCodeGen::CallRuntime(const Runtime::Function* function, + int num_arguments, + LInstruction* instr, + SaveFPRegsMode save_doubles) { + ASSERT(instr != NULL); + + __ CallRuntime(function, num_arguments, save_doubles); + + RecordSafepointWithLazyDeopt(instr, RECORD_SIMPLE_SAFEPOINT); +} + + +void LCodeGen::LoadContextFromDeferred(LOperand* context) { + if (context->IsRegister()) { + __ Mov(cp, ToRegister(context)); + } else if (context->IsStackSlot()) { + __ Ldr(cp, ToMemOperand(context, kMustUseFramePointer)); + } else if (context->IsConstantOperand()) { + HConstant* constant = + chunk_->LookupConstant(LConstantOperand::cast(context)); + __ LoadHeapObject(cp, + Handle<HeapObject>::cast(constant->handle(isolate()))); + } else { + UNREACHABLE(); + } +} + + +void LCodeGen::CallRuntimeFromDeferred(Runtime::FunctionId id, + int argc, + LInstruction* instr, + LOperand* context) { + LoadContextFromDeferred(context); + __ CallRuntimeSaveDoubles(id); + RecordSafepointWithRegisters( + instr->pointer_map(), argc, Safepoint::kNoLazyDeopt); +} + + +void LCodeGen::RecordAndWritePosition(int position) { + if (position == RelocInfo::kNoPosition) return; + masm()->positions_recorder()->RecordPosition(position); + masm()->positions_recorder()->WriteRecordedPositions(); +} + + +void LCodeGen::RecordSafepointWithLazyDeopt(LInstruction* instr, + SafepointMode safepoint_mode) { + if (safepoint_mode == RECORD_SIMPLE_SAFEPOINT) { + RecordSafepoint(instr->pointer_map(), Safepoint::kLazyDeopt); + } else { + ASSERT(safepoint_mode == RECORD_SAFEPOINT_WITH_REGISTERS_AND_NO_ARGUMENTS); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kLazyDeopt); + } +} + + +void LCodeGen::RecordSafepoint(LPointerMap* pointers, + Safepoint::Kind kind, + int arguments, + Safepoint::DeoptMode deopt_mode) { + ASSERT(expected_safepoint_kind_ == kind); + + const ZoneList<LOperand*>* operands = pointers->GetNormalizedOperands(); + Safepoint safepoint = safepoints_.DefineSafepoint( + masm(), kind, arguments, deopt_mode); + + for (int i = 0; i < operands->length(); i++) { + LOperand* pointer = operands->at(i); + if (pointer->IsStackSlot()) { + safepoint.DefinePointerSlot(pointer->index(), zone()); + } else if (pointer->IsRegister() && (kind & Safepoint::kWithRegisters)) { + safepoint.DefinePointerRegister(ToRegister(pointer), zone()); + } + } + + if (kind & Safepoint::kWithRegisters) { + // Register cp always contains a pointer to the context. + safepoint.DefinePointerRegister(cp, zone()); + } +} + +void LCodeGen::RecordSafepoint(LPointerMap* pointers, + Safepoint::DeoptMode deopt_mode) { + RecordSafepoint(pointers, Safepoint::kSimple, 0, deopt_mode); +} + + +void LCodeGen::RecordSafepoint(Safepoint::DeoptMode deopt_mode) { + LPointerMap empty_pointers(zone()); + RecordSafepoint(&empty_pointers, deopt_mode); +} + + +void LCodeGen::RecordSafepointWithRegisters(LPointerMap* pointers, + int arguments, + Safepoint::DeoptMode deopt_mode) { + RecordSafepoint(pointers, Safepoint::kWithRegisters, arguments, deopt_mode); +} + + +void LCodeGen::RecordSafepointWithRegistersAndDoubles( + LPointerMap* pointers, int arguments, Safepoint::DeoptMode deopt_mode) { + RecordSafepoint( + pointers, Safepoint::kWithRegistersAndDoubles, arguments, deopt_mode); +} + + +bool LCodeGen::GenerateCode() { + LPhase phase("Z_Code generation", chunk()); + ASSERT(is_unused()); + status_ = GENERATING; + + // Open a frame scope to indicate that there is a frame on the stack. The + // NONE indicates that the scope shouldn't actually generate code to set up + // the frame (that is done in GeneratePrologue). + FrameScope frame_scope(masm_, StackFrame::NONE); + + return GeneratePrologue() && + GenerateBody() && + GenerateDeferredCode() && + GenerateDeoptJumpTable() && + GenerateSafepointTable(); +} + + +void LCodeGen::SaveCallerDoubles() { + ASSERT(info()->saves_caller_doubles()); + ASSERT(NeedsEagerFrame()); + Comment(";;; Save clobbered callee double registers"); + BitVector* doubles = chunk()->allocated_double_registers(); + BitVector::Iterator iterator(doubles); + int count = 0; + while (!iterator.Done()) { + // TODO(all): Is this supposed to save just the callee-saved doubles? It + // looks like it's saving all of them. + FPRegister value = FPRegister::FromAllocationIndex(iterator.Current()); + __ Poke(value, count * kDoubleSize); + iterator.Advance(); + count++; + } +} + + +void LCodeGen::RestoreCallerDoubles() { + ASSERT(info()->saves_caller_doubles()); + ASSERT(NeedsEagerFrame()); + Comment(";;; Restore clobbered callee double registers"); + BitVector* doubles = chunk()->allocated_double_registers(); + BitVector::Iterator iterator(doubles); + int count = 0; + while (!iterator.Done()) { + // TODO(all): Is this supposed to restore just the callee-saved doubles? It + // looks like it's restoring all of them. + FPRegister value = FPRegister::FromAllocationIndex(iterator.Current()); + __ Peek(value, count * kDoubleSize); + iterator.Advance(); + count++; + } +} + + +bool LCodeGen::GeneratePrologue() { + ASSERT(is_generating()); + + if (info()->IsOptimizing()) { + ProfileEntryHookStub::MaybeCallEntryHook(masm_); + + // TODO(all): Add support for stop_t FLAG in DEBUG mode. + + // Sloppy mode functions and builtins need to replace the receiver with the + // global proxy when called as functions (without an explicit receiver + // object). + if (info_->this_has_uses() && + info_->strict_mode() == SLOPPY && + !info_->is_native()) { + Label ok; + int receiver_offset = info_->scope()->num_parameters() * kXRegSize; + __ Peek(x10, receiver_offset); + __ JumpIfNotRoot(x10, Heap::kUndefinedValueRootIndex, &ok); + + __ Ldr(x10, GlobalObjectMemOperand()); + __ Ldr(x10, FieldMemOperand(x10, GlobalObject::kGlobalReceiverOffset)); + __ Poke(x10, receiver_offset); + + __ Bind(&ok); + } + } + + ASSERT(__ StackPointer().Is(jssp)); + info()->set_prologue_offset(masm_->pc_offset()); + if (NeedsEagerFrame()) { + if (info()->IsStub()) { + __ StubPrologue(); + } else { + __ Prologue(info()->IsCodePreAgingActive()); + } + frame_is_built_ = true; + info_->AddNoFrameRange(0, masm_->pc_offset()); + } + + // Reserve space for the stack slots needed by the code. + int slots = GetStackSlotCount(); + if (slots > 0) { + __ Claim(slots, kPointerSize); + } + + if (info()->saves_caller_doubles()) { + SaveCallerDoubles(); + } + + // Allocate a local context if needed. + int heap_slots = info()->num_heap_slots() - Context::MIN_CONTEXT_SLOTS; + if (heap_slots > 0) { + Comment(";;; Allocate local context"); + bool need_write_barrier = true; + // Argument to NewContext is the function, which is in x1. + if (heap_slots <= FastNewContextStub::kMaximumSlots) { + FastNewContextStub stub(isolate(), heap_slots); + __ CallStub(&stub); + // Result of FastNewContextStub is always in new space. + need_write_barrier = false; + } else { + __ Push(x1); + __ CallRuntime(Runtime::kHiddenNewFunctionContext, 1); + } + RecordSafepoint(Safepoint::kNoLazyDeopt); + // Context is returned in x0. It replaces the context passed to us. It's + // saved in the stack and kept live in cp. + __ Mov(cp, x0); + __ Str(x0, MemOperand(fp, StandardFrameConstants::kContextOffset)); + // Copy any necessary parameters into the context. + int num_parameters = scope()->num_parameters(); + for (int i = 0; i < num_parameters; i++) { + Variable* var = scope()->parameter(i); + if (var->IsContextSlot()) { + Register value = x0; + Register scratch = x3; + + int parameter_offset = StandardFrameConstants::kCallerSPOffset + + (num_parameters - 1 - i) * kPointerSize; + // Load parameter from stack. + __ Ldr(value, MemOperand(fp, parameter_offset)); + // Store it in the context. + MemOperand target = ContextMemOperand(cp, var->index()); + __ Str(value, target); + // Update the write barrier. This clobbers value and scratch. + if (need_write_barrier) { + __ RecordWriteContextSlot(cp, target.offset(), value, scratch, + GetLinkRegisterState(), kSaveFPRegs); + } else if (FLAG_debug_code) { + Label done; + __ JumpIfInNewSpace(cp, &done); + __ Abort(kExpectedNewSpaceObject); + __ bind(&done); + } + } + } + Comment(";;; End allocate local context"); + } + + // Trace the call. + if (FLAG_trace && info()->IsOptimizing()) { + // We have not executed any compiled code yet, so cp still holds the + // incoming context. + __ CallRuntime(Runtime::kTraceEnter, 0); + } + + return !is_aborted(); +} + + +void LCodeGen::GenerateOsrPrologue() { + // Generate the OSR entry prologue at the first unknown OSR value, or if there + // are none, at the OSR entrypoint instruction. + if (osr_pc_offset_ >= 0) return; + + osr_pc_offset_ = masm()->pc_offset(); + + // Adjust the frame size, subsuming the unoptimized frame into the + // optimized frame. + int slots = GetStackSlotCount() - graph()->osr()->UnoptimizedFrameSlots(); + ASSERT(slots >= 0); + __ Claim(slots); +} + + +void LCodeGen::GenerateBodyInstructionPre(LInstruction* instr) { + if (instr->IsCall()) { + EnsureSpaceForLazyDeopt(Deoptimizer::patch_size()); + } + if (!instr->IsLazyBailout() && !instr->IsGap()) { + safepoints_.BumpLastLazySafepointIndex(); + } +} + + +bool LCodeGen::GenerateDeferredCode() { + ASSERT(is_generating()); + if (deferred_.length() > 0) { + for (int i = 0; !is_aborted() && (i < deferred_.length()); i++) { + LDeferredCode* code = deferred_[i]; + + HValue* value = + instructions_->at(code->instruction_index())->hydrogen_value(); + RecordAndWritePosition( + chunk()->graph()->SourcePositionToScriptPosition(value->position())); + + Comment(";;; <@%d,#%d> " + "-------------------- Deferred %s --------------------", + code->instruction_index(), + code->instr()->hydrogen_value()->id(), + code->instr()->Mnemonic()); + + __ Bind(code->entry()); + + if (NeedsDeferredFrame()) { + Comment(";;; Build frame"); + ASSERT(!frame_is_built_); + ASSERT(info()->IsStub()); + frame_is_built_ = true; + __ Push(lr, fp, cp); + __ Mov(fp, Smi::FromInt(StackFrame::STUB)); + __ Push(fp); + __ Add(fp, __ StackPointer(), + StandardFrameConstants::kFixedFrameSizeFromFp); + Comment(";;; Deferred code"); + } + + code->Generate(); + + if (NeedsDeferredFrame()) { + Comment(";;; Destroy frame"); + ASSERT(frame_is_built_); + __ Pop(xzr, cp, fp, lr); + frame_is_built_ = false; + } + + __ B(code->exit()); + } + } + + // Force constant pool emission at the end of the deferred code to make + // sure that no constant pools are emitted after deferred code because + // deferred code generation is the last step which generates code. The two + // following steps will only output data used by crakshaft. + masm()->CheckConstPool(true, false); + + return !is_aborted(); +} + + +bool LCodeGen::GenerateDeoptJumpTable() { + Label needs_frame, restore_caller_doubles, call_deopt_entry; + + if (deopt_jump_table_.length() > 0) { + Comment(";;; -------------------- Jump table --------------------"); + Address base = deopt_jump_table_[0]->address; + + UseScratchRegisterScope temps(masm()); + Register entry_offset = temps.AcquireX(); + + int length = deopt_jump_table_.length(); + for (int i = 0; i < length; i++) { + __ Bind(&deopt_jump_table_[i]->label); + + Deoptimizer::BailoutType type = deopt_jump_table_[i]->bailout_type; + Address entry = deopt_jump_table_[i]->address; + int id = Deoptimizer::GetDeoptimizationId(isolate(), entry, type); + if (id == Deoptimizer::kNotDeoptimizationEntry) { + Comment(";;; jump table entry %d.", i); + } else { + Comment(";;; jump table entry %d: deoptimization bailout %d.", i, id); + } + + // Second-level deopt table entries are contiguous and small, so instead + // of loading the full, absolute address of each one, load the base + // address and add an immediate offset. + __ Mov(entry_offset, entry - base); + + // The last entry can fall through into `call_deopt_entry`, avoiding a + // branch. + bool last_entry = (i + 1) == length; + + if (deopt_jump_table_[i]->needs_frame) { + ASSERT(!info()->saves_caller_doubles()); + if (!needs_frame.is_bound()) { + // This variant of deopt can only be used with stubs. Since we don't + // have a function pointer to install in the stack frame that we're + // building, install a special marker there instead. + ASSERT(info()->IsStub()); + + UseScratchRegisterScope temps(masm()); + Register stub_marker = temps.AcquireX(); + __ Bind(&needs_frame); + __ Mov(stub_marker, Smi::FromInt(StackFrame::STUB)); + __ Push(lr, fp, cp, stub_marker); + __ Add(fp, __ StackPointer(), 2 * kPointerSize); + if (!last_entry) __ B(&call_deopt_entry); + } else { + // Reuse the existing needs_frame code. + __ B(&needs_frame); + } + } else if (info()->saves_caller_doubles()) { + ASSERT(info()->IsStub()); + if (!restore_caller_doubles.is_bound()) { + __ Bind(&restore_caller_doubles); + RestoreCallerDoubles(); + if (!last_entry) __ B(&call_deopt_entry); + } else { + // Reuse the existing restore_caller_doubles code. + __ B(&restore_caller_doubles); + } + } else { + // There is nothing special to do, so just continue to the second-level + // table. + if (!last_entry) __ B(&call_deopt_entry); + } + + masm()->CheckConstPool(false, last_entry); + } + + // Generate common code for calling the second-level deopt table. + Register deopt_entry = temps.AcquireX(); + __ Bind(&call_deopt_entry); + __ Mov(deopt_entry, Operand(reinterpret_cast<uint64_t>(base), + RelocInfo::RUNTIME_ENTRY)); + __ Add(deopt_entry, deopt_entry, entry_offset); + __ Call(deopt_entry); + } + + // Force constant pool emission at the end of the deopt jump table to make + // sure that no constant pools are emitted after. + masm()->CheckConstPool(true, false); + + // The deoptimization jump table is the last part of the instruction + // sequence. Mark the generated code as done unless we bailed out. + if (!is_aborted()) status_ = DONE; + return !is_aborted(); +} + + +bool LCodeGen::GenerateSafepointTable() { + ASSERT(is_done()); + // We do not know how much data will be emitted for the safepoint table, so + // force emission of the veneer pool. + masm()->CheckVeneerPool(true, true); + safepoints_.Emit(masm(), GetStackSlotCount()); + return !is_aborted(); +} + + +void LCodeGen::FinishCode(Handle<Code> code) { + ASSERT(is_done()); + code->set_stack_slots(GetStackSlotCount()); + code->set_safepoint_table_offset(safepoints_.GetCodeOffset()); + if (code->is_optimized_code()) RegisterWeakObjectsInOptimizedCode(code); + PopulateDeoptimizationData(code); +} + + +void LCodeGen::PopulateDeoptimizationData(Handle<Code> code) { + int length = deoptimizations_.length(); + if (length == 0) return; + + Handle<DeoptimizationInputData> data = + DeoptimizationInputData::New(isolate(), length, TENURED); + + Handle<ByteArray> translations = + translations_.CreateByteArray(isolate()->factory()); + data->SetTranslationByteArray(*translations); + data->SetInlinedFunctionCount(Smi::FromInt(inlined_function_count_)); + data->SetOptimizationId(Smi::FromInt(info_->optimization_id())); + if (info_->IsOptimizing()) { + // Reference to shared function info does not change between phases. + AllowDeferredHandleDereference allow_handle_dereference; + data->SetSharedFunctionInfo(*info_->shared_info()); + } else { + data->SetSharedFunctionInfo(Smi::FromInt(0)); + } + + Handle<FixedArray> literals = + factory()->NewFixedArray(deoptimization_literals_.length(), TENURED); + { AllowDeferredHandleDereference copy_handles; + for (int i = 0; i < deoptimization_literals_.length(); i++) { + literals->set(i, *deoptimization_literals_[i]); + } + data->SetLiteralArray(*literals); + } + + data->SetOsrAstId(Smi::FromInt(info_->osr_ast_id().ToInt())); + data->SetOsrPcOffset(Smi::FromInt(osr_pc_offset_)); + + // Populate the deoptimization entries. + for (int i = 0; i < length; i++) { + LEnvironment* env = deoptimizations_[i]; + data->SetAstId(i, env->ast_id()); + data->SetTranslationIndex(i, Smi::FromInt(env->translation_index())); + data->SetArgumentsStackHeight(i, + Smi::FromInt(env->arguments_stack_height())); + data->SetPc(i, Smi::FromInt(env->pc_offset())); + } + + code->set_deoptimization_data(*data); +} + + +void LCodeGen::PopulateDeoptimizationLiteralsWithInlinedFunctions() { + ASSERT(deoptimization_literals_.length() == 0); + + const ZoneList<Handle<JSFunction> >* inlined_closures = + chunk()->inlined_closures(); + + for (int i = 0, length = inlined_closures->length(); i < length; i++) { + DefineDeoptimizationLiteral(inlined_closures->at(i)); + } + + inlined_function_count_ = deoptimization_literals_.length(); +} + + +void LCodeGen::DeoptimizeBranch( + LEnvironment* environment, + BranchType branch_type, Register reg, int bit, + Deoptimizer::BailoutType* override_bailout_type) { + RegisterEnvironmentForDeoptimization(environment, Safepoint::kNoLazyDeopt); + Deoptimizer::BailoutType bailout_type = + info()->IsStub() ? Deoptimizer::LAZY : Deoptimizer::EAGER; + + if (override_bailout_type != NULL) { + bailout_type = *override_bailout_type; + } + + ASSERT(environment->HasBeenRegistered()); + ASSERT(info()->IsOptimizing() || info()->IsStub()); + int id = environment->deoptimization_index(); + Address entry = + Deoptimizer::GetDeoptimizationEntry(isolate(), id, bailout_type); + + if (entry == NULL) { + Abort(kBailoutWasNotPrepared); + } + + if (FLAG_deopt_every_n_times != 0 && !info()->IsStub()) { + Label not_zero; + ExternalReference count = ExternalReference::stress_deopt_count(isolate()); + + __ Push(x0, x1, x2); + __ Mrs(x2, NZCV); + __ Mov(x0, count); + __ Ldr(w1, MemOperand(x0)); + __ Subs(x1, x1, 1); + __ B(gt, ¬_zero); + __ Mov(w1, FLAG_deopt_every_n_times); + __ Str(w1, MemOperand(x0)); + __ Pop(x2, x1, x0); + ASSERT(frame_is_built_); + __ Call(entry, RelocInfo::RUNTIME_ENTRY); + __ Unreachable(); + + __ Bind(¬_zero); + __ Str(w1, MemOperand(x0)); + __ Msr(NZCV, x2); + __ Pop(x2, x1, x0); + } + + if (info()->ShouldTrapOnDeopt()) { + Label dont_trap; + __ B(&dont_trap, InvertBranchType(branch_type), reg, bit); + __ Debug("trap_on_deopt", __LINE__, BREAK); + __ Bind(&dont_trap); + } + + ASSERT(info()->IsStub() || frame_is_built_); + // Go through jump table if we need to build frame, or restore caller doubles. + if (branch_type == always && + frame_is_built_ && !info()->saves_caller_doubles()) { + __ Call(entry, RelocInfo::RUNTIME_ENTRY); + } else { + // We often have several deopts to the same entry, reuse the last + // jump entry if this is the case. + if (deopt_jump_table_.is_empty() || + (deopt_jump_table_.last()->address != entry) || + (deopt_jump_table_.last()->bailout_type != bailout_type) || + (deopt_jump_table_.last()->needs_frame != !frame_is_built_)) { + Deoptimizer::JumpTableEntry* table_entry = + new(zone()) Deoptimizer::JumpTableEntry(entry, + bailout_type, + !frame_is_built_); + deopt_jump_table_.Add(table_entry, zone()); + } + __ B(&deopt_jump_table_.last()->label, + branch_type, reg, bit); + } +} + + +void LCodeGen::Deoptimize(LEnvironment* environment, + Deoptimizer::BailoutType* override_bailout_type) { + DeoptimizeBranch(environment, always, NoReg, -1, override_bailout_type); +} + + +void LCodeGen::DeoptimizeIf(Condition cond, LEnvironment* environment) { + DeoptimizeBranch(environment, static_cast<BranchType>(cond)); +} + + +void LCodeGen::DeoptimizeIfZero(Register rt, LEnvironment* environment) { + DeoptimizeBranch(environment, reg_zero, rt); +} + + +void LCodeGen::DeoptimizeIfNotZero(Register rt, LEnvironment* environment) { + DeoptimizeBranch(environment, reg_not_zero, rt); +} + + +void LCodeGen::DeoptimizeIfNegative(Register rt, LEnvironment* environment) { + int sign_bit = rt.Is64Bits() ? kXSignBit : kWSignBit; + DeoptimizeIfBitSet(rt, sign_bit, environment); +} + + +void LCodeGen::DeoptimizeIfSmi(Register rt, + LEnvironment* environment) { + DeoptimizeIfBitClear(rt, MaskToBit(kSmiTagMask), environment); +} + + +void LCodeGen::DeoptimizeIfNotSmi(Register rt, LEnvironment* environment) { + DeoptimizeIfBitSet(rt, MaskToBit(kSmiTagMask), environment); +} + + +void LCodeGen::DeoptimizeIfRoot(Register rt, + Heap::RootListIndex index, + LEnvironment* environment) { + __ CompareRoot(rt, index); + DeoptimizeIf(eq, environment); +} + + +void LCodeGen::DeoptimizeIfNotRoot(Register rt, + Heap::RootListIndex index, + LEnvironment* environment) { + __ CompareRoot(rt, index); + DeoptimizeIf(ne, environment); +} + + +void LCodeGen::DeoptimizeIfMinusZero(DoubleRegister input, + LEnvironment* environment) { + __ TestForMinusZero(input); + DeoptimizeIf(vs, environment); +} + + +void LCodeGen::DeoptimizeIfBitSet(Register rt, + int bit, + LEnvironment* environment) { + DeoptimizeBranch(environment, reg_bit_set, rt, bit); +} + + +void LCodeGen::DeoptimizeIfBitClear(Register rt, + int bit, + LEnvironment* environment) { + DeoptimizeBranch(environment, reg_bit_clear, rt, bit); +} + + +void LCodeGen::EnsureSpaceForLazyDeopt(int space_needed) { + if (!info()->IsStub()) { + // Ensure that we have enough space after the previous lazy-bailout + // instruction for patching the code here. + intptr_t current_pc = masm()->pc_offset(); + + if (current_pc < (last_lazy_deopt_pc_ + space_needed)) { + ptrdiff_t padding_size = last_lazy_deopt_pc_ + space_needed - current_pc; + ASSERT((padding_size % kInstructionSize) == 0); + InstructionAccurateScope instruction_accurate( + masm(), padding_size / kInstructionSize); + + while (padding_size > 0) { + __ nop(); + padding_size -= kInstructionSize; + } + } + } + last_lazy_deopt_pc_ = masm()->pc_offset(); +} + + +Register LCodeGen::ToRegister(LOperand* op) const { + // TODO(all): support zero register results, as ToRegister32. + ASSERT((op != NULL) && op->IsRegister()); + return Register::FromAllocationIndex(op->index()); +} + + +Register LCodeGen::ToRegister32(LOperand* op) const { + ASSERT(op != NULL); + if (op->IsConstantOperand()) { + // If this is a constant operand, the result must be the zero register. + ASSERT(ToInteger32(LConstantOperand::cast(op)) == 0); + return wzr; + } else { + return ToRegister(op).W(); + } +} + + +Smi* LCodeGen::ToSmi(LConstantOperand* op) const { + HConstant* constant = chunk_->LookupConstant(op); + return Smi::FromInt(constant->Integer32Value()); +} + + +DoubleRegister LCodeGen::ToDoubleRegister(LOperand* op) const { + ASSERT((op != NULL) && op->IsDoubleRegister()); + return DoubleRegister::FromAllocationIndex(op->index()); +} + + +Operand LCodeGen::ToOperand(LOperand* op) { + ASSERT(op != NULL); + if (op->IsConstantOperand()) { + LConstantOperand* const_op = LConstantOperand::cast(op); + HConstant* constant = chunk()->LookupConstant(const_op); + Representation r = chunk_->LookupLiteralRepresentation(const_op); + if (r.IsSmi()) { + ASSERT(constant->HasSmiValue()); + return Operand(Smi::FromInt(constant->Integer32Value())); + } else if (r.IsInteger32()) { + ASSERT(constant->HasInteger32Value()); + return Operand(constant->Integer32Value()); + } else if (r.IsDouble()) { + Abort(kToOperandUnsupportedDoubleImmediate); + } + ASSERT(r.IsTagged()); + return Operand(constant->handle(isolate())); + } else if (op->IsRegister()) { + return Operand(ToRegister(op)); + } else if (op->IsDoubleRegister()) { + Abort(kToOperandIsDoubleRegisterUnimplemented); + return Operand(0); + } + // Stack slots not implemented, use ToMemOperand instead. + UNREACHABLE(); + return Operand(0); +} + + +Operand LCodeGen::ToOperand32I(LOperand* op) { + return ToOperand32(op, SIGNED_INT32); +} + + +Operand LCodeGen::ToOperand32U(LOperand* op) { + return ToOperand32(op, UNSIGNED_INT32); +} + + +Operand LCodeGen::ToOperand32(LOperand* op, IntegerSignedness signedness) { + ASSERT(op != NULL); + if (op->IsRegister()) { + return Operand(ToRegister32(op)); + } else if (op->IsConstantOperand()) { + LConstantOperand* const_op = LConstantOperand::cast(op); + HConstant* constant = chunk()->LookupConstant(const_op); + Representation r = chunk_->LookupLiteralRepresentation(const_op); + if (r.IsInteger32()) { + ASSERT(constant->HasInteger32Value()); + return (signedness == SIGNED_INT32) + ? Operand(constant->Integer32Value()) + : Operand(static_cast<uint32_t>(constant->Integer32Value())); + } else { + // Other constants not implemented. + Abort(kToOperand32UnsupportedImmediate); + } + } + // Other cases are not implemented. + UNREACHABLE(); + return Operand(0); +} + + +static ptrdiff_t ArgumentsOffsetWithoutFrame(ptrdiff_t index) { + ASSERT(index < 0); + return -(index + 1) * kPointerSize; +} + + +MemOperand LCodeGen::ToMemOperand(LOperand* op, StackMode stack_mode) const { + ASSERT(op != NULL); + ASSERT(!op->IsRegister()); + ASSERT(!op->IsDoubleRegister()); + ASSERT(op->IsStackSlot() || op->IsDoubleStackSlot()); + if (NeedsEagerFrame()) { + int fp_offset = StackSlotOffset(op->index()); + if (op->index() >= 0) { + // Loads and stores have a bigger reach in positive offset than negative. + // When the load or the store can't be done in one instruction via fp + // (too big negative offset), we try to access via jssp (positive offset). + // We can reference a stack slot from jssp only if jssp references the end + // of the stack slots. It's not the case when: + // - stack_mode != kCanUseStackPointer: this is the case when a deferred + // code saved the registers. + // - after_push_argument_: arguments has been pushed for a call. + // - inlined_arguments_: inlined arguments have been pushed once. All the + // remainder of the function cannot trust jssp any longer. + // - saves_caller_doubles: some double registers have been pushed, jssp + // references the end of the double registers and not the end of the + // stack slots. + // Also, if the offset from fp is small enough to make a load/store in + // one instruction, we use a fp access. + if ((stack_mode == kCanUseStackPointer) && !after_push_argument_ && + !inlined_arguments_ && !is_int9(fp_offset) && + !info()->saves_caller_doubles()) { + int jssp_offset = + (GetStackSlotCount() - op->index() - 1) * kPointerSize; + return MemOperand(masm()->StackPointer(), jssp_offset); + } + } + return MemOperand(fp, fp_offset); + } else { + // Retrieve parameter without eager stack-frame relative to the + // stack-pointer. + return MemOperand(masm()->StackPointer(), + ArgumentsOffsetWithoutFrame(op->index())); + } +} + + +Handle<Object> LCodeGen::ToHandle(LConstantOperand* op) const { + HConstant* constant = chunk_->LookupConstant(op); + ASSERT(chunk_->LookupLiteralRepresentation(op).IsSmiOrTagged()); + return constant->handle(isolate()); +} + + +template<class LI> +Operand LCodeGen::ToShiftedRightOperand32(LOperand* right, LI* shift_info, + IntegerSignedness signedness) { + if (shift_info->shift() == NO_SHIFT) { + return (signedness == SIGNED_INT32) ? ToOperand32I(right) + : ToOperand32U(right); + } else { + return Operand( + ToRegister32(right), + shift_info->shift(), + JSShiftAmountFromLConstant(shift_info->shift_amount())); + } +} + + +bool LCodeGen::IsSmi(LConstantOperand* op) const { + return chunk_->LookupLiteralRepresentation(op).IsSmi(); +} + + +bool LCodeGen::IsInteger32Constant(LConstantOperand* op) const { + return chunk_->LookupLiteralRepresentation(op).IsSmiOrInteger32(); +} + + +int32_t LCodeGen::ToInteger32(LConstantOperand* op) const { + HConstant* constant = chunk_->LookupConstant(op); + return constant->Integer32Value(); +} + + +double LCodeGen::ToDouble(LConstantOperand* op) const { + HConstant* constant = chunk_->LookupConstant(op); + ASSERT(constant->HasDoubleValue()); + return constant->DoubleValue(); +} + + +Condition LCodeGen::TokenToCondition(Token::Value op, bool is_unsigned) { + Condition cond = nv; + switch (op) { + case Token::EQ: + case Token::EQ_STRICT: + cond = eq; + break; + case Token::NE: + case Token::NE_STRICT: + cond = ne; + break; + case Token::LT: + cond = is_unsigned ? lo : lt; + break; + case Token::GT: + cond = is_unsigned ? hi : gt; + break; + case Token::LTE: + cond = is_unsigned ? ls : le; + break; + case Token::GTE: + cond = is_unsigned ? hs : ge; + break; + case Token::IN: + case Token::INSTANCEOF: + default: + UNREACHABLE(); + } + return cond; +} + + +template<class InstrType> +void LCodeGen::EmitBranchGeneric(InstrType instr, + const BranchGenerator& branch) { + int left_block = instr->TrueDestination(chunk_); + int right_block = instr->FalseDestination(chunk_); + + int next_block = GetNextEmittedBlock(); + + if (right_block == left_block) { + EmitGoto(left_block); + } else if (left_block == next_block) { + branch.EmitInverted(chunk_->GetAssemblyLabel(right_block)); + } else if (right_block == next_block) { + branch.Emit(chunk_->GetAssemblyLabel(left_block)); + } else { + branch.Emit(chunk_->GetAssemblyLabel(left_block)); + __ B(chunk_->GetAssemblyLabel(right_block)); + } +} + + +template<class InstrType> +void LCodeGen::EmitBranch(InstrType instr, Condition condition) { + ASSERT((condition != al) && (condition != nv)); + BranchOnCondition branch(this, condition); + EmitBranchGeneric(instr, branch); +} + + +template<class InstrType> +void LCodeGen::EmitCompareAndBranch(InstrType instr, + Condition condition, + const Register& lhs, + const Operand& rhs) { + ASSERT((condition != al) && (condition != nv)); + CompareAndBranch branch(this, condition, lhs, rhs); + EmitBranchGeneric(instr, branch); +} + + +template<class InstrType> +void LCodeGen::EmitTestAndBranch(InstrType instr, + Condition condition, + const Register& value, + uint64_t mask) { + ASSERT((condition != al) && (condition != nv)); + TestAndBranch branch(this, condition, value, mask); + EmitBranchGeneric(instr, branch); +} + + +template<class InstrType> +void LCodeGen::EmitBranchIfNonZeroNumber(InstrType instr, + const FPRegister& value, + const FPRegister& scratch) { + BranchIfNonZeroNumber branch(this, value, scratch); + EmitBranchGeneric(instr, branch); +} + + +template<class InstrType> +void LCodeGen::EmitBranchIfHeapNumber(InstrType instr, + const Register& value) { + BranchIfHeapNumber branch(this, value); + EmitBranchGeneric(instr, branch); +} + + +template<class InstrType> +void LCodeGen::EmitBranchIfRoot(InstrType instr, + const Register& value, + Heap::RootListIndex index) { + BranchIfRoot branch(this, value, index); + EmitBranchGeneric(instr, branch); +} + + +void LCodeGen::DoGap(LGap* gap) { + for (int i = LGap::FIRST_INNER_POSITION; + i <= LGap::LAST_INNER_POSITION; + i++) { + LGap::InnerPosition inner_pos = static_cast<LGap::InnerPosition>(i); + LParallelMove* move = gap->GetParallelMove(inner_pos); + if (move != NULL) { + resolver_.Resolve(move); + } + } +} + + +void LCodeGen::DoAccessArgumentsAt(LAccessArgumentsAt* instr) { + Register arguments = ToRegister(instr->arguments()); + Register result = ToRegister(instr->result()); + + // The pointer to the arguments array come from DoArgumentsElements. + // It does not point directly to the arguments and there is an offest of + // two words that we must take into account when accessing an argument. + // Subtracting the index from length accounts for one, so we add one more. + + if (instr->length()->IsConstantOperand() && + instr->index()->IsConstantOperand()) { + int index = ToInteger32(LConstantOperand::cast(instr->index())); + int length = ToInteger32(LConstantOperand::cast(instr->length())); + int offset = ((length - index) + 1) * kPointerSize; + __ Ldr(result, MemOperand(arguments, offset)); + } else if (instr->index()->IsConstantOperand()) { + Register length = ToRegister32(instr->length()); + int index = ToInteger32(LConstantOperand::cast(instr->index())); + int loc = index - 1; + if (loc != 0) { + __ Sub(result.W(), length, loc); + __ Ldr(result, MemOperand(arguments, result, UXTW, kPointerSizeLog2)); + } else { + __ Ldr(result, MemOperand(arguments, length, UXTW, kPointerSizeLog2)); + } + } else { + Register length = ToRegister32(instr->length()); + Operand index = ToOperand32I(instr->index()); + __ Sub(result.W(), length, index); + __ Add(result.W(), result.W(), 1); + __ Ldr(result, MemOperand(arguments, result, UXTW, kPointerSizeLog2)); + } +} + + +void LCodeGen::DoAddE(LAddE* instr) { + Register result = ToRegister(instr->result()); + Register left = ToRegister(instr->left()); + Operand right = (instr->right()->IsConstantOperand()) + ? ToInteger32(LConstantOperand::cast(instr->right())) + : Operand(ToRegister32(instr->right()), SXTW); + + ASSERT(!instr->hydrogen()->CheckFlag(HValue::kCanOverflow)); + __ Add(result, left, right); +} + + +void LCodeGen::DoAddI(LAddI* instr) { + bool can_overflow = instr->hydrogen()->CheckFlag(HValue::kCanOverflow); + Register result = ToRegister32(instr->result()); + Register left = ToRegister32(instr->left()); + Operand right = ToShiftedRightOperand32I(instr->right(), instr); + + if (can_overflow) { + __ Adds(result, left, right); + DeoptimizeIf(vs, instr->environment()); + } else { + __ Add(result, left, right); + } +} + + +void LCodeGen::DoAddS(LAddS* instr) { + bool can_overflow = instr->hydrogen()->CheckFlag(HValue::kCanOverflow); + Register result = ToRegister(instr->result()); + Register left = ToRegister(instr->left()); + Operand right = ToOperand(instr->right()); + if (can_overflow) { + __ Adds(result, left, right); + DeoptimizeIf(vs, instr->environment()); + } else { + __ Add(result, left, right); + } +} + + +void LCodeGen::DoAllocate(LAllocate* instr) { + class DeferredAllocate: public LDeferredCode { + public: + DeferredAllocate(LCodeGen* codegen, LAllocate* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredAllocate(instr_); } + virtual LInstruction* instr() { return instr_; } + private: + LAllocate* instr_; + }; + + DeferredAllocate* deferred = new(zone()) DeferredAllocate(this, instr); + + Register result = ToRegister(instr->result()); + Register temp1 = ToRegister(instr->temp1()); + Register temp2 = ToRegister(instr->temp2()); + + // Allocate memory for the object. + AllocationFlags flags = TAG_OBJECT; + if (instr->hydrogen()->MustAllocateDoubleAligned()) { + flags = static_cast<AllocationFlags>(flags | DOUBLE_ALIGNMENT); + } + + if (instr->hydrogen()->IsOldPointerSpaceAllocation()) { + ASSERT(!instr->hydrogen()->IsOldDataSpaceAllocation()); + ASSERT(!instr->hydrogen()->IsNewSpaceAllocation()); + flags = static_cast<AllocationFlags>(flags | PRETENURE_OLD_POINTER_SPACE); + } else if (instr->hydrogen()->IsOldDataSpaceAllocation()) { + ASSERT(!instr->hydrogen()->IsNewSpaceAllocation()); + flags = static_cast<AllocationFlags>(flags | PRETENURE_OLD_DATA_SPACE); + } + + if (instr->size()->IsConstantOperand()) { + int32_t size = ToInteger32(LConstantOperand::cast(instr->size())); + if (size <= Page::kMaxRegularHeapObjectSize) { + __ Allocate(size, result, temp1, temp2, deferred->entry(), flags); + } else { + __ B(deferred->entry()); + } + } else { + Register size = ToRegister32(instr->size()); + __ Sxtw(size.X(), size); + __ Allocate(size.X(), result, temp1, temp2, deferred->entry(), flags); + } + + __ Bind(deferred->exit()); + + if (instr->hydrogen()->MustPrefillWithFiller()) { + Register filler_count = temp1; + Register filler = temp2; + Register untagged_result = ToRegister(instr->temp3()); + + if (instr->size()->IsConstantOperand()) { + int32_t size = ToInteger32(LConstantOperand::cast(instr->size())); + __ Mov(filler_count, size / kPointerSize); + } else { + __ Lsr(filler_count.W(), ToRegister32(instr->size()), kPointerSizeLog2); + } + + __ Sub(untagged_result, result, kHeapObjectTag); + __ Mov(filler, Operand(isolate()->factory()->one_pointer_filler_map())); + __ FillFields(untagged_result, filler_count, filler); + } else { + ASSERT(instr->temp3() == NULL); + } +} + + +void LCodeGen::DoDeferredAllocate(LAllocate* instr) { + // TODO(3095996): Get rid of this. For now, we need to make the + // result register contain a valid pointer because it is already + // contained in the register pointer map. + __ Mov(ToRegister(instr->result()), Smi::FromInt(0)); + + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + // We're in a SafepointRegistersScope so we can use any scratch registers. + Register size = x0; + if (instr->size()->IsConstantOperand()) { + __ Mov(size, ToSmi(LConstantOperand::cast(instr->size()))); + } else { + __ SmiTag(size, ToRegister32(instr->size()).X()); + } + int flags = AllocateDoubleAlignFlag::encode( + instr->hydrogen()->MustAllocateDoubleAligned()); + if (instr->hydrogen()->IsOldPointerSpaceAllocation()) { + ASSERT(!instr->hydrogen()->IsOldDataSpaceAllocation()); + ASSERT(!instr->hydrogen()->IsNewSpaceAllocation()); + flags = AllocateTargetSpace::update(flags, OLD_POINTER_SPACE); + } else if (instr->hydrogen()->IsOldDataSpaceAllocation()) { + ASSERT(!instr->hydrogen()->IsNewSpaceAllocation()); + flags = AllocateTargetSpace::update(flags, OLD_DATA_SPACE); + } else { + flags = AllocateTargetSpace::update(flags, NEW_SPACE); + } + __ Mov(x10, Smi::FromInt(flags)); + __ Push(size, x10); + + CallRuntimeFromDeferred( + Runtime::kHiddenAllocateInTargetSpace, 2, instr, instr->context()); + __ StoreToSafepointRegisterSlot(x0, ToRegister(instr->result())); +} + + +void LCodeGen::DoApplyArguments(LApplyArguments* instr) { + Register receiver = ToRegister(instr->receiver()); + Register function = ToRegister(instr->function()); + Register length = ToRegister32(instr->length()); + + Register elements = ToRegister(instr->elements()); + Register scratch = x5; + ASSERT(receiver.Is(x0)); // Used for parameter count. + ASSERT(function.Is(x1)); // Required by InvokeFunction. + ASSERT(ToRegister(instr->result()).Is(x0)); + ASSERT(instr->IsMarkedAsCall()); + + // Copy the arguments to this function possibly from the + // adaptor frame below it. + const uint32_t kArgumentsLimit = 1 * KB; + __ Cmp(length, kArgumentsLimit); + DeoptimizeIf(hi, instr->environment()); + + // Push the receiver and use the register to keep the original + // number of arguments. + __ Push(receiver); + Register argc = receiver; + receiver = NoReg; + __ Sxtw(argc, length); + // The arguments are at a one pointer size offset from elements. + __ Add(elements, elements, 1 * kPointerSize); + + // Loop through the arguments pushing them onto the execution + // stack. + Label invoke, loop; + // length is a small non-negative integer, due to the test above. + __ Cbz(length, &invoke); + __ Bind(&loop); + __ Ldr(scratch, MemOperand(elements, length, SXTW, kPointerSizeLog2)); + __ Push(scratch); + __ Subs(length, length, 1); + __ B(ne, &loop); + + __ Bind(&invoke); + ASSERT(instr->HasPointerMap()); + LPointerMap* pointers = instr->pointer_map(); + SafepointGenerator safepoint_generator(this, pointers, Safepoint::kLazyDeopt); + // The number of arguments is stored in argc (receiver) which is x0, as + // expected by InvokeFunction. + ParameterCount actual(argc); + __ InvokeFunction(function, actual, CALL_FUNCTION, safepoint_generator); +} + + +void LCodeGen::DoArgumentsElements(LArgumentsElements* instr) { + // We push some arguments and they will be pop in an other block. We can't + // trust that jssp references the end of the stack slots until the end of + // the function. + inlined_arguments_ = true; + Register result = ToRegister(instr->result()); + + if (instr->hydrogen()->from_inlined()) { + // When we are inside an inlined function, the arguments are the last things + // that have been pushed on the stack. Therefore the arguments array can be + // accessed directly from jssp. + // However in the normal case, it is accessed via fp but there are two words + // on the stack between fp and the arguments (the saved lr and fp) and the + // LAccessArgumentsAt implementation take that into account. + // In the inlined case we need to subtract the size of 2 words to jssp to + // get a pointer which will work well with LAccessArgumentsAt. + ASSERT(masm()->StackPointer().Is(jssp)); + __ Sub(result, jssp, 2 * kPointerSize); + } else { + ASSERT(instr->temp() != NULL); + Register previous_fp = ToRegister(instr->temp()); + + __ Ldr(previous_fp, + MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + __ Ldr(result, + MemOperand(previous_fp, StandardFrameConstants::kContextOffset)); + __ Cmp(result, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ Csel(result, fp, previous_fp, ne); + } +} + + +void LCodeGen::DoArgumentsLength(LArgumentsLength* instr) { + Register elements = ToRegister(instr->elements()); + Register result = ToRegister32(instr->result()); + Label done; + + // If no arguments adaptor frame the number of arguments is fixed. + __ Cmp(fp, elements); + __ Mov(result, scope()->num_parameters()); + __ B(eq, &done); + + // Arguments adaptor frame present. Get argument length from there. + __ Ldr(result.X(), MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + __ Ldr(result, + UntagSmiMemOperand(result.X(), + ArgumentsAdaptorFrameConstants::kLengthOffset)); + + // Argument length is in result register. + __ Bind(&done); +} + + +void LCodeGen::DoArithmeticD(LArithmeticD* instr) { + DoubleRegister left = ToDoubleRegister(instr->left()); + DoubleRegister right = ToDoubleRegister(instr->right()); + DoubleRegister result = ToDoubleRegister(instr->result()); + + switch (instr->op()) { + case Token::ADD: __ Fadd(result, left, right); break; + case Token::SUB: __ Fsub(result, left, right); break; + case Token::MUL: __ Fmul(result, left, right); break; + case Token::DIV: __ Fdiv(result, left, right); break; + case Token::MOD: { + // The ECMA-262 remainder operator is the remainder from a truncating + // (round-towards-zero) division. Note that this differs from IEEE-754. + // + // TODO(jbramley): See if it's possible to do this inline, rather than by + // calling a helper function. With frintz (to produce the intermediate + // quotient) and fmsub (to calculate the remainder without loss of + // precision), it should be possible. However, we would need support for + // fdiv in round-towards-zero mode, and the ARM64 simulator doesn't + // support that yet. + ASSERT(left.Is(d0)); + ASSERT(right.Is(d1)); + __ CallCFunction( + ExternalReference::mod_two_doubles_operation(isolate()), + 0, 2); + ASSERT(result.Is(d0)); + break; + } + default: + UNREACHABLE(); + break; + } +} + + +void LCodeGen::DoArithmeticT(LArithmeticT* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->left()).is(x1)); + ASSERT(ToRegister(instr->right()).is(x0)); + ASSERT(ToRegister(instr->result()).is(x0)); + + BinaryOpICStub stub(isolate(), instr->op(), NO_OVERWRITE); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoBitI(LBitI* instr) { + Register result = ToRegister32(instr->result()); + Register left = ToRegister32(instr->left()); + Operand right = ToShiftedRightOperand32U(instr->right(), instr); + + switch (instr->op()) { + case Token::BIT_AND: __ And(result, left, right); break; + case Token::BIT_OR: __ Orr(result, left, right); break; + case Token::BIT_XOR: __ Eor(result, left, right); break; + default: + UNREACHABLE(); + break; + } +} + + +void LCodeGen::DoBitS(LBitS* instr) { + Register result = ToRegister(instr->result()); + Register left = ToRegister(instr->left()); + Operand right = ToOperand(instr->right()); + + switch (instr->op()) { + case Token::BIT_AND: __ And(result, left, right); break; + case Token::BIT_OR: __ Orr(result, left, right); break; + case Token::BIT_XOR: __ Eor(result, left, right); break; + default: + UNREACHABLE(); + break; + } +} + + +void LCodeGen::DoBoundsCheck(LBoundsCheck *instr) { + Condition cond = instr->hydrogen()->allow_equality() ? hi : hs; + ASSERT(instr->hydrogen()->index()->representation().IsInteger32()); + ASSERT(instr->hydrogen()->length()->representation().IsInteger32()); + if (instr->index()->IsConstantOperand()) { + Operand index = ToOperand32I(instr->index()); + Register length = ToRegister32(instr->length()); + __ Cmp(length, index); + cond = CommuteCondition(cond); + } else { + Register index = ToRegister32(instr->index()); + Operand length = ToOperand32I(instr->length()); + __ Cmp(index, length); + } + if (FLAG_debug_code && instr->hydrogen()->skip_check()) { + __ Assert(NegateCondition(cond), kEliminatedBoundsCheckFailed); + } else { + DeoptimizeIf(cond, instr->environment()); + } +} + + +void LCodeGen::DoBranch(LBranch* instr) { + Representation r = instr->hydrogen()->value()->representation(); + Label* true_label = instr->TrueLabel(chunk_); + Label* false_label = instr->FalseLabel(chunk_); + + if (r.IsInteger32()) { + ASSERT(!info()->IsStub()); + EmitCompareAndBranch(instr, ne, ToRegister32(instr->value()), 0); + } else if (r.IsSmi()) { + ASSERT(!info()->IsStub()); + STATIC_ASSERT(kSmiTag == 0); + EmitCompareAndBranch(instr, ne, ToRegister(instr->value()), 0); + } else if (r.IsDouble()) { + DoubleRegister value = ToDoubleRegister(instr->value()); + // Test the double value. Zero and NaN are false. + EmitBranchIfNonZeroNumber(instr, value, double_scratch()); + } else { + ASSERT(r.IsTagged()); + Register value = ToRegister(instr->value()); + HType type = instr->hydrogen()->value()->type(); + + if (type.IsBoolean()) { + ASSERT(!info()->IsStub()); + __ CompareRoot(value, Heap::kTrueValueRootIndex); + EmitBranch(instr, eq); + } else if (type.IsSmi()) { + ASSERT(!info()->IsStub()); + EmitCompareAndBranch(instr, ne, value, Smi::FromInt(0)); + } else if (type.IsJSArray()) { + ASSERT(!info()->IsStub()); + EmitGoto(instr->TrueDestination(chunk())); + } else if (type.IsHeapNumber()) { + ASSERT(!info()->IsStub()); + __ Ldr(double_scratch(), FieldMemOperand(value, + HeapNumber::kValueOffset)); + // Test the double value. Zero and NaN are false. + EmitBranchIfNonZeroNumber(instr, double_scratch(), double_scratch()); + } else if (type.IsString()) { + ASSERT(!info()->IsStub()); + Register temp = ToRegister(instr->temp1()); + __ Ldr(temp, FieldMemOperand(value, String::kLengthOffset)); + EmitCompareAndBranch(instr, ne, temp, 0); + } else { + ToBooleanStub::Types expected = instr->hydrogen()->expected_input_types(); + // Avoid deopts in the case where we've never executed this path before. + if (expected.IsEmpty()) expected = ToBooleanStub::Types::Generic(); + + if (expected.Contains(ToBooleanStub::UNDEFINED)) { + // undefined -> false. + __ JumpIfRoot( + value, Heap::kUndefinedValueRootIndex, false_label); + } + + if (expected.Contains(ToBooleanStub::BOOLEAN)) { + // Boolean -> its value. + __ JumpIfRoot( + value, Heap::kTrueValueRootIndex, true_label); + __ JumpIfRoot( + value, Heap::kFalseValueRootIndex, false_label); + } + + if (expected.Contains(ToBooleanStub::NULL_TYPE)) { + // 'null' -> false. + __ JumpIfRoot( + value, Heap::kNullValueRootIndex, false_label); + } + + if (expected.Contains(ToBooleanStub::SMI)) { + // Smis: 0 -> false, all other -> true. + ASSERT(Smi::FromInt(0) == 0); + __ Cbz(value, false_label); + __ JumpIfSmi(value, true_label); + } else if (expected.NeedsMap()) { + // If we need a map later and have a smi, deopt. + DeoptimizeIfSmi(value, instr->environment()); + } + + Register map = NoReg; + Register scratch = NoReg; + + if (expected.NeedsMap()) { + ASSERT((instr->temp1() != NULL) && (instr->temp2() != NULL)); + map = ToRegister(instr->temp1()); + scratch = ToRegister(instr->temp2()); + + __ Ldr(map, FieldMemOperand(value, HeapObject::kMapOffset)); + + if (expected.CanBeUndetectable()) { + // Undetectable -> false. + __ Ldrb(scratch, FieldMemOperand(map, Map::kBitFieldOffset)); + __ TestAndBranchIfAnySet( + scratch, 1 << Map::kIsUndetectable, false_label); + } + } + + if (expected.Contains(ToBooleanStub::SPEC_OBJECT)) { + // spec object -> true. + __ CompareInstanceType(map, scratch, FIRST_SPEC_OBJECT_TYPE); + __ B(ge, true_label); + } + + if (expected.Contains(ToBooleanStub::STRING)) { + // String value -> false iff empty. + Label not_string; + __ CompareInstanceType(map, scratch, FIRST_NONSTRING_TYPE); + __ B(ge, ¬_string); + __ Ldr(scratch, FieldMemOperand(value, String::kLengthOffset)); + __ Cbz(scratch, false_label); + __ B(true_label); + __ Bind(¬_string); + } + + if (expected.Contains(ToBooleanStub::SYMBOL)) { + // Symbol value -> true. + __ CompareInstanceType(map, scratch, SYMBOL_TYPE); + __ B(eq, true_label); + } + + if (expected.Contains(ToBooleanStub::HEAP_NUMBER)) { + Label not_heap_number; + __ JumpIfNotRoot(map, Heap::kHeapNumberMapRootIndex, ¬_heap_number); + + __ Ldr(double_scratch(), + FieldMemOperand(value, HeapNumber::kValueOffset)); + __ Fcmp(double_scratch(), 0.0); + // If we got a NaN (overflow bit is set), jump to the false branch. + __ B(vs, false_label); + __ B(eq, false_label); + __ B(true_label); + __ Bind(¬_heap_number); + } + + if (!expected.IsGeneric()) { + // We've seen something for the first time -> deopt. + // This can only happen if we are not generic already. + Deoptimize(instr->environment()); + } + } + } +} + + +void LCodeGen::CallKnownFunction(Handle<JSFunction> function, + int formal_parameter_count, + int arity, + LInstruction* instr, + Register function_reg) { + bool dont_adapt_arguments = + formal_parameter_count == SharedFunctionInfo::kDontAdaptArgumentsSentinel; + bool can_invoke_directly = + dont_adapt_arguments || formal_parameter_count == arity; + + // The function interface relies on the following register assignments. + ASSERT(function_reg.Is(x1) || function_reg.IsNone()); + Register arity_reg = x0; + + LPointerMap* pointers = instr->pointer_map(); + + // If necessary, load the function object. + if (function_reg.IsNone()) { + function_reg = x1; + __ LoadObject(function_reg, function); + } + + if (FLAG_debug_code) { + Label is_not_smi; + // Try to confirm that function_reg (x1) is a tagged pointer. + __ JumpIfNotSmi(function_reg, &is_not_smi); + __ Abort(kExpectedFunctionObject); + __ Bind(&is_not_smi); + } + + if (can_invoke_directly) { + // Change context. + __ Ldr(cp, FieldMemOperand(function_reg, JSFunction::kContextOffset)); + + // Set the arguments count if adaption is not needed. Assumes that x0 is + // available to write to at this point. + if (dont_adapt_arguments) { + __ Mov(arity_reg, arity); + } + + // Invoke function. + __ Ldr(x10, FieldMemOperand(function_reg, JSFunction::kCodeEntryOffset)); + __ Call(x10); + + // Set up deoptimization. + RecordSafepointWithLazyDeopt(instr, RECORD_SIMPLE_SAFEPOINT); + } else { + SafepointGenerator generator(this, pointers, Safepoint::kLazyDeopt); + ParameterCount count(arity); + ParameterCount expected(formal_parameter_count); + __ InvokeFunction(function_reg, expected, count, CALL_FUNCTION, generator); + } +} + + +void LCodeGen::DoCallWithDescriptor(LCallWithDescriptor* instr) { + ASSERT(instr->IsMarkedAsCall()); + ASSERT(ToRegister(instr->result()).Is(x0)); + + LPointerMap* pointers = instr->pointer_map(); + SafepointGenerator generator(this, pointers, Safepoint::kLazyDeopt); + + if (instr->target()->IsConstantOperand()) { + LConstantOperand* target = LConstantOperand::cast(instr->target()); + Handle<Code> code = Handle<Code>::cast(ToHandle(target)); + generator.BeforeCall(__ CallSize(code, RelocInfo::CODE_TARGET)); + // TODO(all): on ARM we use a call descriptor to specify a storage mode + // but on ARM64 we only have one storage mode so it isn't necessary. Check + // this understanding is correct. + __ Call(code, RelocInfo::CODE_TARGET, TypeFeedbackId::None()); + } else { + ASSERT(instr->target()->IsRegister()); + Register target = ToRegister(instr->target()); + generator.BeforeCall(__ CallSize(target)); + __ Add(target, target, Code::kHeaderSize - kHeapObjectTag); + __ Call(target); + } + generator.AfterCall(); + after_push_argument_ = false; +} + + +void LCodeGen::DoCallJSFunction(LCallJSFunction* instr) { + ASSERT(instr->IsMarkedAsCall()); + ASSERT(ToRegister(instr->function()).is(x1)); + + if (instr->hydrogen()->pass_argument_count()) { + __ Mov(x0, Operand(instr->arity())); + } + + // Change context. + __ Ldr(cp, FieldMemOperand(x1, JSFunction::kContextOffset)); + + // Load the code entry address + __ Ldr(x10, FieldMemOperand(x1, JSFunction::kCodeEntryOffset)); + __ Call(x10); + + RecordSafepointWithLazyDeopt(instr, RECORD_SIMPLE_SAFEPOINT); + after_push_argument_ = false; +} + + +void LCodeGen::DoCallRuntime(LCallRuntime* instr) { + CallRuntime(instr->function(), instr->arity(), instr); + after_push_argument_ = false; +} + + +void LCodeGen::DoCallStub(LCallStub* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->result()).is(x0)); + switch (instr->hydrogen()->major_key()) { + case CodeStub::RegExpExec: { + RegExpExecStub stub(isolate()); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::SubString: { + SubStringStub stub(isolate()); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::StringCompare: { + StringCompareStub stub(isolate()); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + default: + UNREACHABLE(); + } + after_push_argument_ = false; +} + + +void LCodeGen::DoUnknownOSRValue(LUnknownOSRValue* instr) { + GenerateOsrPrologue(); +} + + +void LCodeGen::DoDeferredInstanceMigration(LCheckMaps* instr, Register object) { + Register temp = ToRegister(instr->temp()); + { + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + __ Push(object); + __ Mov(cp, 0); + __ CallRuntimeSaveDoubles(Runtime::kTryMigrateInstance); + RecordSafepointWithRegisters( + instr->pointer_map(), 1, Safepoint::kNoLazyDeopt); + __ StoreToSafepointRegisterSlot(x0, temp); + } + DeoptimizeIfSmi(temp, instr->environment()); +} + + +void LCodeGen::DoCheckMaps(LCheckMaps* instr) { + class DeferredCheckMaps: public LDeferredCode { + public: + DeferredCheckMaps(LCodeGen* codegen, LCheckMaps* instr, Register object) + : LDeferredCode(codegen), instr_(instr), object_(object) { + SetExit(check_maps()); + } + virtual void Generate() { + codegen()->DoDeferredInstanceMigration(instr_, object_); + } + Label* check_maps() { return &check_maps_; } + virtual LInstruction* instr() { return instr_; } + private: + LCheckMaps* instr_; + Label check_maps_; + Register object_; + }; + + if (instr->hydrogen()->IsStabilityCheck()) { + const UniqueSet<Map>* maps = instr->hydrogen()->maps(); + for (int i = 0; i < maps->size(); ++i) { + AddStabilityDependency(maps->at(i).handle()); + } + return; + } + + Register object = ToRegister(instr->value()); + Register map_reg = ToRegister(instr->temp()); + + __ Ldr(map_reg, FieldMemOperand(object, HeapObject::kMapOffset)); + + DeferredCheckMaps* deferred = NULL; + if (instr->hydrogen()->HasMigrationTarget()) { + deferred = new(zone()) DeferredCheckMaps(this, instr, object); + __ Bind(deferred->check_maps()); + } + + const UniqueSet<Map>* maps = instr->hydrogen()->maps(); + Label success; + for (int i = 0; i < maps->size() - 1; i++) { + Handle<Map> map = maps->at(i).handle(); + __ CompareMap(map_reg, map); + __ B(eq, &success); + } + Handle<Map> map = maps->at(maps->size() - 1).handle(); + __ CompareMap(map_reg, map); + + // We didn't match a map. + if (instr->hydrogen()->HasMigrationTarget()) { + __ B(ne, deferred->entry()); + } else { + DeoptimizeIf(ne, instr->environment()); + } + + __ Bind(&success); +} + + +void LCodeGen::DoCheckNonSmi(LCheckNonSmi* instr) { + if (!instr->hydrogen()->value()->type().IsHeapObject()) { + DeoptimizeIfSmi(ToRegister(instr->value()), instr->environment()); + } +} + + +void LCodeGen::DoCheckSmi(LCheckSmi* instr) { + Register value = ToRegister(instr->value()); + ASSERT(!instr->result() || ToRegister(instr->result()).Is(value)); + DeoptimizeIfNotSmi(value, instr->environment()); +} + + +void LCodeGen::DoCheckInstanceType(LCheckInstanceType* instr) { + Register input = ToRegister(instr->value()); + Register scratch = ToRegister(instr->temp()); + + __ Ldr(scratch, FieldMemOperand(input, HeapObject::kMapOffset)); + __ Ldrb(scratch, FieldMemOperand(scratch, Map::kInstanceTypeOffset)); + + if (instr->hydrogen()->is_interval_check()) { + InstanceType first, last; + instr->hydrogen()->GetCheckInterval(&first, &last); + + __ Cmp(scratch, first); + if (first == last) { + // If there is only one type in the interval check for equality. + DeoptimizeIf(ne, instr->environment()); + } else if (last == LAST_TYPE) { + // We don't need to compare with the higher bound of the interval. + DeoptimizeIf(lo, instr->environment()); + } else { + // If we are below the lower bound, set the C flag and clear the Z flag + // to force a deopt. + __ Ccmp(scratch, last, CFlag, hs); + DeoptimizeIf(hi, instr->environment()); + } + } else { + uint8_t mask; + uint8_t tag; + instr->hydrogen()->GetCheckMaskAndTag(&mask, &tag); + + if (IsPowerOf2(mask)) { + ASSERT((tag == 0) || (tag == mask)); + if (tag == 0) { + DeoptimizeIfBitSet(scratch, MaskToBit(mask), instr->environment()); + } else { + DeoptimizeIfBitClear(scratch, MaskToBit(mask), instr->environment()); + } + } else { + if (tag == 0) { + __ Tst(scratch, mask); + } else { + __ And(scratch, scratch, mask); + __ Cmp(scratch, tag); + } + DeoptimizeIf(ne, instr->environment()); + } + } +} + + +void LCodeGen::DoClampDToUint8(LClampDToUint8* instr) { + DoubleRegister input = ToDoubleRegister(instr->unclamped()); + Register result = ToRegister32(instr->result()); + __ ClampDoubleToUint8(result, input, double_scratch()); +} + + +void LCodeGen::DoClampIToUint8(LClampIToUint8* instr) { + Register input = ToRegister32(instr->unclamped()); + Register result = ToRegister32(instr->result()); + __ ClampInt32ToUint8(result, input); +} + + +void LCodeGen::DoClampTToUint8(LClampTToUint8* instr) { + Register input = ToRegister(instr->unclamped()); + Register result = ToRegister32(instr->result()); + Register scratch = ToRegister(instr->temp1()); + Label done; + + // Both smi and heap number cases are handled. + Label is_not_smi; + __ JumpIfNotSmi(input, &is_not_smi); + __ SmiUntag(result.X(), input); + __ ClampInt32ToUint8(result); + __ B(&done); + + __ Bind(&is_not_smi); + + // Check for heap number. + Label is_heap_number; + __ Ldr(scratch, FieldMemOperand(input, HeapObject::kMapOffset)); + __ JumpIfRoot(scratch, Heap::kHeapNumberMapRootIndex, &is_heap_number); + + // Check for undefined. Undefined is coverted to zero for clamping conversion. + DeoptimizeIfNotRoot(input, Heap::kUndefinedValueRootIndex, + instr->environment()); + __ Mov(result, 0); + __ B(&done); + + // Heap number case. + __ Bind(&is_heap_number); + DoubleRegister dbl_scratch = double_scratch(); + DoubleRegister dbl_scratch2 = ToDoubleRegister(instr->temp2()); + __ Ldr(dbl_scratch, FieldMemOperand(input, HeapNumber::kValueOffset)); + __ ClampDoubleToUint8(result, dbl_scratch, dbl_scratch2); + + __ Bind(&done); +} + + +void LCodeGen::DoDoubleBits(LDoubleBits* instr) { + DoubleRegister value_reg = ToDoubleRegister(instr->value()); + Register result_reg = ToRegister(instr->result()); + if (instr->hydrogen()->bits() == HDoubleBits::HIGH) { + __ Fmov(result_reg, value_reg); + __ Lsr(result_reg, result_reg, 32); + } else { + __ Fmov(result_reg.W(), value_reg.S()); + } +} + + +void LCodeGen::DoConstructDouble(LConstructDouble* instr) { + Register hi_reg = ToRegister(instr->hi()); + Register lo_reg = ToRegister(instr->lo()); + DoubleRegister result_reg = ToDoubleRegister(instr->result()); + + // Insert the least significant 32 bits of hi_reg into the most significant + // 32 bits of lo_reg, and move to a floating point register. + __ Bfi(lo_reg, hi_reg, 32, 32); + __ Fmov(result_reg, lo_reg); +} + + +void LCodeGen::DoClassOfTestAndBranch(LClassOfTestAndBranch* instr) { + Handle<String> class_name = instr->hydrogen()->class_name(); + Label* true_label = instr->TrueLabel(chunk_); + Label* false_label = instr->FalseLabel(chunk_); + Register input = ToRegister(instr->value()); + Register scratch1 = ToRegister(instr->temp1()); + Register scratch2 = ToRegister(instr->temp2()); + + __ JumpIfSmi(input, false_label); + + Register map = scratch2; + if (class_name->IsUtf8EqualTo(CStrVector("Function"))) { + // Assuming the following assertions, we can use the same compares to test + // for both being a function type and being in the object type range. + STATIC_ASSERT(NUM_OF_CALLABLE_SPEC_OBJECT_TYPES == 2); + STATIC_ASSERT(FIRST_NONCALLABLE_SPEC_OBJECT_TYPE == + FIRST_SPEC_OBJECT_TYPE + 1); + STATIC_ASSERT(LAST_NONCALLABLE_SPEC_OBJECT_TYPE == + LAST_SPEC_OBJECT_TYPE - 1); + STATIC_ASSERT(LAST_SPEC_OBJECT_TYPE == LAST_TYPE); + + // We expect CompareObjectType to load the object instance type in scratch1. + __ CompareObjectType(input, map, scratch1, FIRST_SPEC_OBJECT_TYPE); + __ B(lt, false_label); + __ B(eq, true_label); + __ Cmp(scratch1, LAST_SPEC_OBJECT_TYPE); + __ B(eq, true_label); + } else { + __ IsObjectJSObjectType(input, map, scratch1, false_label); + } + + // Now we are in the FIRST-LAST_NONCALLABLE_SPEC_OBJECT_TYPE range. + // Check if the constructor in the map is a function. + __ Ldr(scratch1, FieldMemOperand(map, Map::kConstructorOffset)); + + // Objects with a non-function constructor have class 'Object'. + if (class_name->IsUtf8EqualTo(CStrVector("Object"))) { + __ JumpIfNotObjectType( + scratch1, scratch2, scratch2, JS_FUNCTION_TYPE, true_label); + } else { + __ JumpIfNotObjectType( + scratch1, scratch2, scratch2, JS_FUNCTION_TYPE, false_label); + } + + // The constructor function is in scratch1. Get its instance class name. + __ Ldr(scratch1, + FieldMemOperand(scratch1, JSFunction::kSharedFunctionInfoOffset)); + __ Ldr(scratch1, + FieldMemOperand(scratch1, + SharedFunctionInfo::kInstanceClassNameOffset)); + + // The class name we are testing against is internalized since it's a literal. + // The name in the constructor is internalized because of the way the context + // is booted. This routine isn't expected to work for random API-created + // classes and it doesn't have to because you can't access it with natives + // syntax. Since both sides are internalized it is sufficient to use an + // identity comparison. + EmitCompareAndBranch(instr, eq, scratch1, Operand(class_name)); +} + + +void LCodeGen::DoCmpHoleAndBranchD(LCmpHoleAndBranchD* instr) { + ASSERT(instr->hydrogen()->representation().IsDouble()); + FPRegister object = ToDoubleRegister(instr->object()); + Register temp = ToRegister(instr->temp()); + + // If we don't have a NaN, we don't have the hole, so branch now to avoid the + // (relatively expensive) hole-NaN check. + __ Fcmp(object, object); + __ B(vc, instr->FalseLabel(chunk_)); + + // We have a NaN, but is it the hole? + __ Fmov(temp, object); + EmitCompareAndBranch(instr, eq, temp, kHoleNanInt64); +} + + +void LCodeGen::DoCmpHoleAndBranchT(LCmpHoleAndBranchT* instr) { + ASSERT(instr->hydrogen()->representation().IsTagged()); + Register object = ToRegister(instr->object()); + + EmitBranchIfRoot(instr, object, Heap::kTheHoleValueRootIndex); +} + + +void LCodeGen::DoCmpMapAndBranch(LCmpMapAndBranch* instr) { + Register value = ToRegister(instr->value()); + Register map = ToRegister(instr->temp()); + + __ Ldr(map, FieldMemOperand(value, HeapObject::kMapOffset)); + EmitCompareAndBranch(instr, eq, map, Operand(instr->map())); +} + + +void LCodeGen::DoCompareMinusZeroAndBranch(LCompareMinusZeroAndBranch* instr) { + Representation rep = instr->hydrogen()->value()->representation(); + ASSERT(!rep.IsInteger32()); + Register scratch = ToRegister(instr->temp()); + + if (rep.IsDouble()) { + __ JumpIfMinusZero(ToDoubleRegister(instr->value()), + instr->TrueLabel(chunk())); + } else { + Register value = ToRegister(instr->value()); + __ CheckMap(value, scratch, Heap::kHeapNumberMapRootIndex, + instr->FalseLabel(chunk()), DO_SMI_CHECK); + __ Ldr(scratch, FieldMemOperand(value, HeapNumber::kValueOffset)); + __ JumpIfMinusZero(scratch, instr->TrueLabel(chunk())); + } + EmitGoto(instr->FalseDestination(chunk())); +} + + +void LCodeGen::DoCompareNumericAndBranch(LCompareNumericAndBranch* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + bool is_unsigned = + instr->hydrogen()->left()->CheckFlag(HInstruction::kUint32) || + instr->hydrogen()->right()->CheckFlag(HInstruction::kUint32); + Condition cond = TokenToCondition(instr->op(), is_unsigned); + + if (left->IsConstantOperand() && right->IsConstantOperand()) { + // We can statically evaluate the comparison. + double left_val = ToDouble(LConstantOperand::cast(left)); + double right_val = ToDouble(LConstantOperand::cast(right)); + int next_block = EvalComparison(instr->op(), left_val, right_val) ? + instr->TrueDestination(chunk_) : instr->FalseDestination(chunk_); + EmitGoto(next_block); + } else { + if (instr->is_double()) { + if (right->IsConstantOperand()) { + __ Fcmp(ToDoubleRegister(left), + ToDouble(LConstantOperand::cast(right))); + } else if (left->IsConstantOperand()) { + // Commute the operands and the condition. + __ Fcmp(ToDoubleRegister(right), + ToDouble(LConstantOperand::cast(left))); + cond = CommuteCondition(cond); + } else { + __ Fcmp(ToDoubleRegister(left), ToDoubleRegister(right)); + } + + // If a NaN is involved, i.e. the result is unordered (V set), + // jump to false block label. + __ B(vs, instr->FalseLabel(chunk_)); + EmitBranch(instr, cond); + } else { + if (instr->hydrogen_value()->representation().IsInteger32()) { + if (right->IsConstantOperand()) { + EmitCompareAndBranch(instr, + cond, + ToRegister32(left), + ToOperand32I(right)); + } else { + // Commute the operands and the condition. + EmitCompareAndBranch(instr, + CommuteCondition(cond), + ToRegister32(right), + ToOperand32I(left)); + } + } else { + ASSERT(instr->hydrogen_value()->representation().IsSmi()); + if (right->IsConstantOperand()) { + int32_t value = ToInteger32(LConstantOperand::cast(right)); + EmitCompareAndBranch(instr, + cond, + ToRegister(left), + Operand(Smi::FromInt(value))); + } else if (left->IsConstantOperand()) { + // Commute the operands and the condition. + int32_t value = ToInteger32(LConstantOperand::cast(left)); + EmitCompareAndBranch(instr, + CommuteCondition(cond), + ToRegister(right), + Operand(Smi::FromInt(value))); + } else { + EmitCompareAndBranch(instr, + cond, + ToRegister(left), + ToRegister(right)); + } + } + } + } +} + + +void LCodeGen::DoCmpObjectEqAndBranch(LCmpObjectEqAndBranch* instr) { + Register left = ToRegister(instr->left()); + Register right = ToRegister(instr->right()); + EmitCompareAndBranch(instr, eq, left, right); +} + + +void LCodeGen::DoCmpT(LCmpT* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + Token::Value op = instr->op(); + Condition cond = TokenToCondition(op, false); + + ASSERT(ToRegister(instr->left()).Is(x1)); + ASSERT(ToRegister(instr->right()).Is(x0)); + Handle<Code> ic = CompareIC::GetUninitialized(isolate(), op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + // Signal that we don't inline smi code before this stub. + InlineSmiCheckInfo::EmitNotInlined(masm()); + + // Return true or false depending on CompareIC result. + // This instruction is marked as call. We can clobber any register. + ASSERT(instr->IsMarkedAsCall()); + __ LoadTrueFalseRoots(x1, x2); + __ Cmp(x0, 0); + __ Csel(ToRegister(instr->result()), x1, x2, cond); +} + + +void LCodeGen::DoConstantD(LConstantD* instr) { + ASSERT(instr->result()->IsDoubleRegister()); + DoubleRegister result = ToDoubleRegister(instr->result()); + if (instr->value() == 0) { + if (copysign(1.0, instr->value()) == 1.0) { + __ Fmov(result, fp_zero); + } else { + __ Fneg(result, fp_zero); + } + } else { + __ Fmov(result, instr->value()); + } +} + + +void LCodeGen::DoConstantE(LConstantE* instr) { + __ Mov(ToRegister(instr->result()), Operand(instr->value())); +} + + +void LCodeGen::DoConstantI(LConstantI* instr) { + ASSERT(is_int32(instr->value())); + // Cast the value here to ensure that the value isn't sign extended by the + // implicit Operand constructor. + __ Mov(ToRegister32(instr->result()), static_cast<uint32_t>(instr->value())); +} + + +void LCodeGen::DoConstantS(LConstantS* instr) { + __ Mov(ToRegister(instr->result()), Operand(instr->value())); +} + + +void LCodeGen::DoConstantT(LConstantT* instr) { + Handle<Object> object = instr->value(isolate()); + AllowDeferredHandleDereference smi_check; + __ LoadObject(ToRegister(instr->result()), object); +} + + +void LCodeGen::DoContext(LContext* instr) { + // If there is a non-return use, the context must be moved to a register. + Register result = ToRegister(instr->result()); + if (info()->IsOptimizing()) { + __ Ldr(result, MemOperand(fp, StandardFrameConstants::kContextOffset)); + } else { + // If there is no frame, the context must be in cp. + ASSERT(result.is(cp)); + } +} + + +void LCodeGen::DoCheckValue(LCheckValue* instr) { + Register reg = ToRegister(instr->value()); + Handle<HeapObject> object = instr->hydrogen()->object().handle(); + AllowDeferredHandleDereference smi_check; + if (isolate()->heap()->InNewSpace(*object)) { + UseScratchRegisterScope temps(masm()); + Register temp = temps.AcquireX(); + Handle<Cell> cell = isolate()->factory()->NewCell(object); + __ Mov(temp, Operand(Handle<Object>(cell))); + __ Ldr(temp, FieldMemOperand(temp, Cell::kValueOffset)); + __ Cmp(reg, temp); + } else { + __ Cmp(reg, Operand(object)); + } + DeoptimizeIf(ne, instr->environment()); +} + + +void LCodeGen::DoLazyBailout(LLazyBailout* instr) { + last_lazy_deopt_pc_ = masm()->pc_offset(); + ASSERT(instr->HasEnvironment()); + LEnvironment* env = instr->environment(); + RegisterEnvironmentForDeoptimization(env, Safepoint::kLazyDeopt); + safepoints_.RecordLazyDeoptimizationIndex(env->deoptimization_index()); +} + + +void LCodeGen::DoDateField(LDateField* instr) { + Register object = ToRegister(instr->date()); + Register result = ToRegister(instr->result()); + Register temp1 = x10; + Register temp2 = x11; + Smi* index = instr->index(); + Label runtime, done; + + ASSERT(object.is(result) && object.Is(x0)); + ASSERT(instr->IsMarkedAsCall()); + + DeoptimizeIfSmi(object, instr->environment()); + __ CompareObjectType(object, temp1, temp1, JS_DATE_TYPE); + DeoptimizeIf(ne, instr->environment()); + + if (index->value() == 0) { + __ Ldr(result, FieldMemOperand(object, JSDate::kValueOffset)); + } else { + if (index->value() < JSDate::kFirstUncachedField) { + ExternalReference stamp = ExternalReference::date_cache_stamp(isolate()); + __ Mov(temp1, Operand(stamp)); + __ Ldr(temp1, MemOperand(temp1)); + __ Ldr(temp2, FieldMemOperand(object, JSDate::kCacheStampOffset)); + __ Cmp(temp1, temp2); + __ B(ne, &runtime); + __ Ldr(result, FieldMemOperand(object, JSDate::kValueOffset + + kPointerSize * index->value())); + __ B(&done); + } + + __ Bind(&runtime); + __ Mov(x1, Operand(index)); + __ CallCFunction(ExternalReference::get_date_field_function(isolate()), 2); + } + + __ Bind(&done); +} + + +void LCodeGen::DoDeoptimize(LDeoptimize* instr) { + Deoptimizer::BailoutType type = instr->hydrogen()->type(); + // TODO(danno): Stubs expect all deopts to be lazy for historical reasons (the + // needed return address), even though the implementation of LAZY and EAGER is + // now identical. When LAZY is eventually completely folded into EAGER, remove + // the special case below. + if (info()->IsStub() && (type == Deoptimizer::EAGER)) { + type = Deoptimizer::LAZY; + } + + Comment(";;; deoptimize: %s", instr->hydrogen()->reason()); + Deoptimize(instr->environment(), &type); +} + + +void LCodeGen::DoDivByPowerOf2I(LDivByPowerOf2I* instr) { + Register dividend = ToRegister32(instr->dividend()); + int32_t divisor = instr->divisor(); + Register result = ToRegister32(instr->result()); + ASSERT(divisor == kMinInt || IsPowerOf2(Abs(divisor))); + ASSERT(!result.is(dividend)); + + // Check for (0 / -x) that will produce negative zero. + HDiv* hdiv = instr->hydrogen(); + if (hdiv->CheckFlag(HValue::kBailoutOnMinusZero) && divisor < 0) { + DeoptimizeIfZero(dividend, instr->environment()); + } + // Check for (kMinInt / -1). + if (hdiv->CheckFlag(HValue::kCanOverflow) && divisor == -1) { + // Test dividend for kMinInt by subtracting one (cmp) and checking for + // overflow. + __ Cmp(dividend, 1); + DeoptimizeIf(vs, instr->environment()); + } + // Deoptimize if remainder will not be 0. + if (!hdiv->CheckFlag(HInstruction::kAllUsesTruncatingToInt32) && + divisor != 1 && divisor != -1) { + int32_t mask = divisor < 0 ? -(divisor + 1) : (divisor - 1); + __ Tst(dividend, mask); + DeoptimizeIf(ne, instr->environment()); + } + + if (divisor == -1) { // Nice shortcut, not needed for correctness. + __ Neg(result, dividend); + return; + } + int32_t shift = WhichPowerOf2Abs(divisor); + if (shift == 0) { + __ Mov(result, dividend); + } else if (shift == 1) { + __ Add(result, dividend, Operand(dividend, LSR, 31)); + } else { + __ Mov(result, Operand(dividend, ASR, 31)); + __ Add(result, dividend, Operand(result, LSR, 32 - shift)); + } + if (shift > 0) __ Mov(result, Operand(result, ASR, shift)); + if (divisor < 0) __ Neg(result, result); +} + + +void LCodeGen::DoDivByConstI(LDivByConstI* instr) { + Register dividend = ToRegister32(instr->dividend()); + int32_t divisor = instr->divisor(); + Register result = ToRegister32(instr->result()); + ASSERT(!AreAliased(dividend, result)); + + if (divisor == 0) { + Deoptimize(instr->environment()); + return; + } + + // Check for (0 / -x) that will produce negative zero. + HDiv* hdiv = instr->hydrogen(); + if (hdiv->CheckFlag(HValue::kBailoutOnMinusZero) && divisor < 0) { + DeoptimizeIfZero(dividend, instr->environment()); + } + + __ TruncatingDiv(result, dividend, Abs(divisor)); + if (divisor < 0) __ Neg(result, result); + + if (!hdiv->CheckFlag(HInstruction::kAllUsesTruncatingToInt32)) { + Register temp = ToRegister32(instr->temp()); + ASSERT(!AreAliased(dividend, result, temp)); + __ Sxtw(dividend.X(), dividend); + __ Mov(temp, divisor); + __ Smsubl(temp.X(), result, temp, dividend.X()); + DeoptimizeIfNotZero(temp, instr->environment()); + } +} + + +// TODO(svenpanne) Refactor this to avoid code duplication with DoFlooringDivI. +void LCodeGen::DoDivI(LDivI* instr) { + HBinaryOperation* hdiv = instr->hydrogen(); + Register dividend = ToRegister32(instr->dividend()); + Register divisor = ToRegister32(instr->divisor()); + Register result = ToRegister32(instr->result()); + + // Issue the division first, and then check for any deopt cases whilst the + // result is computed. + __ Sdiv(result, dividend, divisor); + + if (hdiv->CheckFlag(HValue::kAllUsesTruncatingToInt32)) { + ASSERT_EQ(NULL, instr->temp()); + return; + } + + // Check for x / 0. + if (hdiv->CheckFlag(HValue::kCanBeDivByZero)) { + DeoptimizeIfZero(divisor, instr->environment()); + } + + // Check for (0 / -x) as that will produce negative zero. + if (hdiv->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ Cmp(divisor, 0); + + // If the divisor < 0 (mi), compare the dividend, and deopt if it is + // zero, ie. zero dividend with negative divisor deopts. + // If the divisor >= 0 (pl, the opposite of mi) set the flags to + // condition ne, so we don't deopt, ie. positive divisor doesn't deopt. + __ Ccmp(dividend, 0, NoFlag, mi); + DeoptimizeIf(eq, instr->environment()); + } + + // Check for (kMinInt / -1). + if (hdiv->CheckFlag(HValue::kCanOverflow)) { + // Test dividend for kMinInt by subtracting one (cmp) and checking for + // overflow. + __ Cmp(dividend, 1); + // If overflow is set, ie. dividend = kMinInt, compare the divisor with + // -1. If overflow is clear, set the flags for condition ne, as the + // dividend isn't -1, and thus we shouldn't deopt. + __ Ccmp(divisor, -1, NoFlag, vs); + DeoptimizeIf(eq, instr->environment()); + } + + // Compute remainder and deopt if it's not zero. + Register remainder = ToRegister32(instr->temp()); + __ Msub(remainder, result, divisor, dividend); + DeoptimizeIfNotZero(remainder, instr->environment()); +} + + +void LCodeGen::DoDoubleToIntOrSmi(LDoubleToIntOrSmi* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + Register result = ToRegister32(instr->result()); + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + DeoptimizeIfMinusZero(input, instr->environment()); + } + + __ TryRepresentDoubleAsInt32(result, input, double_scratch()); + DeoptimizeIf(ne, instr->environment()); + + if (instr->tag_result()) { + __ SmiTag(result.X()); + } +} + + +void LCodeGen::DoDrop(LDrop* instr) { + __ Drop(instr->count()); +} + + +void LCodeGen::DoDummy(LDummy* instr) { + // Nothing to see here, move on! +} + + +void LCodeGen::DoDummyUse(LDummyUse* instr) { + // Nothing to see here, move on! +} + + +void LCodeGen::DoFunctionLiteral(LFunctionLiteral* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + // FunctionLiteral instruction is marked as call, we can trash any register. + ASSERT(instr->IsMarkedAsCall()); + + // Use the fast case closure allocation code that allocates in new + // space for nested functions that don't need literals cloning. + bool pretenure = instr->hydrogen()->pretenure(); + if (!pretenure && instr->hydrogen()->has_no_literals()) { + FastNewClosureStub stub(isolate(), + instr->hydrogen()->strict_mode(), + instr->hydrogen()->is_generator()); + __ Mov(x2, Operand(instr->hydrogen()->shared_info())); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + } else { + __ Mov(x2, Operand(instr->hydrogen()->shared_info())); + __ Mov(x1, Operand(pretenure ? factory()->true_value() + : factory()->false_value())); + __ Push(cp, x2, x1); + CallRuntime(Runtime::kHiddenNewClosure, 3, instr); + } +} + + +void LCodeGen::DoForInCacheArray(LForInCacheArray* instr) { + Register map = ToRegister(instr->map()); + Register result = ToRegister(instr->result()); + Label load_cache, done; + + __ EnumLengthUntagged(result, map); + __ Cbnz(result, &load_cache); + + __ Mov(result, Operand(isolate()->factory()->empty_fixed_array())); + __ B(&done); + + __ Bind(&load_cache); + __ LoadInstanceDescriptors(map, result); + __ Ldr(result, FieldMemOperand(result, DescriptorArray::kEnumCacheOffset)); + __ Ldr(result, FieldMemOperand(result, FixedArray::SizeFor(instr->idx()))); + DeoptimizeIfZero(result, instr->environment()); + + __ Bind(&done); +} + + +void LCodeGen::DoForInPrepareMap(LForInPrepareMap* instr) { + Register object = ToRegister(instr->object()); + Register null_value = x5; + + ASSERT(instr->IsMarkedAsCall()); + ASSERT(object.Is(x0)); + + DeoptimizeIfRoot(object, Heap::kUndefinedValueRootIndex, + instr->environment()); + + __ LoadRoot(null_value, Heap::kNullValueRootIndex); + __ Cmp(object, null_value); + DeoptimizeIf(eq, instr->environment()); + + DeoptimizeIfSmi(object, instr->environment()); + + STATIC_ASSERT(FIRST_JS_PROXY_TYPE == FIRST_SPEC_OBJECT_TYPE); + __ CompareObjectType(object, x1, x1, LAST_JS_PROXY_TYPE); + DeoptimizeIf(le, instr->environment()); + + Label use_cache, call_runtime; + __ CheckEnumCache(object, null_value, x1, x2, x3, x4, &call_runtime); + + __ Ldr(object, FieldMemOperand(object, HeapObject::kMapOffset)); + __ B(&use_cache); + + // Get the set of properties to enumerate. + __ Bind(&call_runtime); + __ Push(object); + CallRuntime(Runtime::kGetPropertyNamesFast, 1, instr); + + __ Ldr(x1, FieldMemOperand(object, HeapObject::kMapOffset)); + DeoptimizeIfNotRoot(x1, Heap::kMetaMapRootIndex, instr->environment()); + + __ Bind(&use_cache); +} + + +void LCodeGen::DoGetCachedArrayIndex(LGetCachedArrayIndex* instr) { + Register input = ToRegister(instr->value()); + Register result = ToRegister(instr->result()); + + __ AssertString(input); + + // Assert that we can use a W register load to get the hash. + ASSERT((String::kHashShift + String::kArrayIndexValueBits) < kWRegSizeInBits); + __ Ldr(result.W(), FieldMemOperand(input, String::kHashFieldOffset)); + __ IndexFromHash(result, result); +} + + +void LCodeGen::EmitGoto(int block) { + // Do not emit jump if we are emitting a goto to the next block. + if (!IsNextEmittedBlock(block)) { + __ B(chunk_->GetAssemblyLabel(LookupDestination(block))); + } +} + + +void LCodeGen::DoGoto(LGoto* instr) { + EmitGoto(instr->block_id()); +} + + +void LCodeGen::DoHasCachedArrayIndexAndBranch( + LHasCachedArrayIndexAndBranch* instr) { + Register input = ToRegister(instr->value()); + Register temp = ToRegister32(instr->temp()); + + // Assert that the cache status bits fit in a W register. + ASSERT(is_uint32(String::kContainsCachedArrayIndexMask)); + __ Ldr(temp, FieldMemOperand(input, String::kHashFieldOffset)); + __ Tst(temp, String::kContainsCachedArrayIndexMask); + EmitBranch(instr, eq); +} + + +// HHasInstanceTypeAndBranch instruction is built with an interval of type +// to test but is only used in very restricted ways. The only possible kinds +// of intervals are: +// - [ FIRST_TYPE, instr->to() ] +// - [ instr->form(), LAST_TYPE ] +// - instr->from() == instr->to() +// +// These kinds of intervals can be check with only one compare instruction +// providing the correct value and test condition are used. +// +// TestType() will return the value to use in the compare instruction and +// BranchCondition() will return the condition to use depending on the kind +// of interval actually specified in the instruction. +static InstanceType TestType(HHasInstanceTypeAndBranch* instr) { + InstanceType from = instr->from(); + InstanceType to = instr->to(); + if (from == FIRST_TYPE) return to; + ASSERT((from == to) || (to == LAST_TYPE)); + return from; +} + + +// See comment above TestType function for what this function does. +static Condition BranchCondition(HHasInstanceTypeAndBranch* instr) { + InstanceType from = instr->from(); + InstanceType to = instr->to(); + if (from == to) return eq; + if (to == LAST_TYPE) return hs; + if (from == FIRST_TYPE) return ls; + UNREACHABLE(); + return eq; +} + + +void LCodeGen::DoHasInstanceTypeAndBranch(LHasInstanceTypeAndBranch* instr) { + Register input = ToRegister(instr->value()); + Register scratch = ToRegister(instr->temp()); + + if (!instr->hydrogen()->value()->type().IsHeapObject()) { + __ JumpIfSmi(input, instr->FalseLabel(chunk_)); + } + __ CompareObjectType(input, scratch, scratch, TestType(instr->hydrogen())); + EmitBranch(instr, BranchCondition(instr->hydrogen())); +} + + +void LCodeGen::DoInnerAllocatedObject(LInnerAllocatedObject* instr) { + Register result = ToRegister(instr->result()); + Register base = ToRegister(instr->base_object()); + if (instr->offset()->IsConstantOperand()) { + __ Add(result, base, ToOperand32I(instr->offset())); + } else { + __ Add(result, base, Operand(ToRegister32(instr->offset()), SXTW)); + } +} + + +void LCodeGen::DoInstanceOf(LInstanceOf* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + // Assert that the arguments are in the registers expected by InstanceofStub. + ASSERT(ToRegister(instr->left()).Is(InstanceofStub::left())); + ASSERT(ToRegister(instr->right()).Is(InstanceofStub::right())); + + InstanceofStub stub(isolate(), InstanceofStub::kArgsInRegisters); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + + // InstanceofStub returns a result in x0: + // 0 => not an instance + // smi 1 => instance. + __ Cmp(x0, 0); + __ LoadTrueFalseRoots(x0, x1); + __ Csel(x0, x0, x1, eq); +} + + +void LCodeGen::DoInstanceOfKnownGlobal(LInstanceOfKnownGlobal* instr) { + class DeferredInstanceOfKnownGlobal: public LDeferredCode { + public: + DeferredInstanceOfKnownGlobal(LCodeGen* codegen, + LInstanceOfKnownGlobal* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { + codegen()->DoDeferredInstanceOfKnownGlobal(instr_); + } + virtual LInstruction* instr() { return instr_; } + private: + LInstanceOfKnownGlobal* instr_; + }; + + DeferredInstanceOfKnownGlobal* deferred = + new(zone()) DeferredInstanceOfKnownGlobal(this, instr); + + Label map_check, return_false, cache_miss, done; + Register object = ToRegister(instr->value()); + Register result = ToRegister(instr->result()); + // x4 is expected in the associated deferred code and stub. + Register map_check_site = x4; + Register map = x5; + + // This instruction is marked as call. We can clobber any register. + ASSERT(instr->IsMarkedAsCall()); + + // We must take into account that object is in x11. + ASSERT(object.Is(x11)); + Register scratch = x10; + + // A Smi is not instance of anything. + __ JumpIfSmi(object, &return_false); + + // This is the inlined call site instanceof cache. The two occurences of the + // hole value will be patched to the last map/result pair generated by the + // instanceof stub. + __ Ldr(map, FieldMemOperand(object, HeapObject::kMapOffset)); + { + // Below we use Factory::the_hole_value() on purpose instead of loading from + // the root array to force relocation and later be able to patch with a + // custom value. + InstructionAccurateScope scope(masm(), 5); + __ bind(&map_check); + // Will be patched with the cached map. + Handle<Cell> cell = factory()->NewCell(factory()->the_hole_value()); + __ ldr(scratch, Immediate(Handle<Object>(cell))); + __ ldr(scratch, FieldMemOperand(scratch, PropertyCell::kValueOffset)); + __ cmp(map, scratch); + __ b(&cache_miss, ne); + // The address of this instruction is computed relative to the map check + // above, so check the size of the code generated. + ASSERT(masm()->InstructionsGeneratedSince(&map_check) == 4); + // Will be patched with the cached result. + __ ldr(result, Immediate(factory()->the_hole_value())); + } + __ B(&done); + + // The inlined call site cache did not match. + // Check null and string before calling the deferred code. + __ Bind(&cache_miss); + // Compute the address of the map check. It must not be clobbered until the + // InstanceOfStub has used it. + __ Adr(map_check_site, &map_check); + // Null is not instance of anything. + __ JumpIfRoot(object, Heap::kNullValueRootIndex, &return_false); + + // String values are not instances of anything. + // Return false if the object is a string. Otherwise, jump to the deferred + // code. + // Note that we can't jump directly to deferred code from + // IsObjectJSStringType, because it uses tbz for the jump and the deferred + // code can be out of range. + __ IsObjectJSStringType(object, scratch, NULL, &return_false); + __ B(deferred->entry()); + + __ Bind(&return_false); + __ LoadRoot(result, Heap::kFalseValueRootIndex); + + // Here result is either true or false. + __ Bind(deferred->exit()); + __ Bind(&done); +} + + +void LCodeGen::DoDeferredInstanceOfKnownGlobal(LInstanceOfKnownGlobal* instr) { + Register result = ToRegister(instr->result()); + ASSERT(result.Is(x0)); // InstanceofStub returns its result in x0. + InstanceofStub::Flags flags = InstanceofStub::kNoFlags; + flags = static_cast<InstanceofStub::Flags>( + flags | InstanceofStub::kArgsInRegisters); + flags = static_cast<InstanceofStub::Flags>( + flags | InstanceofStub::kReturnTrueFalseObject); + flags = static_cast<InstanceofStub::Flags>( + flags | InstanceofStub::kCallSiteInlineCheck); + + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + LoadContextFromDeferred(instr->context()); + + // Prepare InstanceofStub arguments. + ASSERT(ToRegister(instr->value()).Is(InstanceofStub::left())); + __ LoadObject(InstanceofStub::right(), instr->function()); + + InstanceofStub stub(isolate(), flags); + CallCodeGeneric(stub.GetCode(), + RelocInfo::CODE_TARGET, + instr, + RECORD_SAFEPOINT_WITH_REGISTERS_AND_NO_ARGUMENTS); + LEnvironment* env = instr->GetDeferredLazyDeoptimizationEnvironment(); + safepoints_.RecordLazyDeoptimizationIndex(env->deoptimization_index()); + + // Put the result value into the result register slot. + __ StoreToSafepointRegisterSlot(result, result); +} + + +void LCodeGen::DoInstructionGap(LInstructionGap* instr) { + DoGap(instr); +} + + +void LCodeGen::DoInteger32ToDouble(LInteger32ToDouble* instr) { + Register value = ToRegister32(instr->value()); + DoubleRegister result = ToDoubleRegister(instr->result()); + __ Scvtf(result, value); +} + + +void LCodeGen::DoInvokeFunction(LInvokeFunction* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + // The function is required to be in x1. + ASSERT(ToRegister(instr->function()).is(x1)); + ASSERT(instr->HasPointerMap()); + + Handle<JSFunction> known_function = instr->hydrogen()->known_function(); + if (known_function.is_null()) { + LPointerMap* pointers = instr->pointer_map(); + SafepointGenerator generator(this, pointers, Safepoint::kLazyDeopt); + ParameterCount count(instr->arity()); + __ InvokeFunction(x1, count, CALL_FUNCTION, generator); + } else { + CallKnownFunction(known_function, + instr->hydrogen()->formal_parameter_count(), + instr->arity(), + instr, + x1); + } + after_push_argument_ = false; +} + + +void LCodeGen::DoIsConstructCallAndBranch(LIsConstructCallAndBranch* instr) { + Register temp1 = ToRegister(instr->temp1()); + Register temp2 = ToRegister(instr->temp2()); + + // Get the frame pointer for the calling frame. + __ Ldr(temp1, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); + + // Skip the arguments adaptor frame if it exists. + Label check_frame_marker; + __ Ldr(temp2, MemOperand(temp1, StandardFrameConstants::kContextOffset)); + __ Cmp(temp2, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + __ B(ne, &check_frame_marker); + __ Ldr(temp1, MemOperand(temp1, StandardFrameConstants::kCallerFPOffset)); + + // Check the marker in the calling frame. + __ Bind(&check_frame_marker); + __ Ldr(temp1, MemOperand(temp1, StandardFrameConstants::kMarkerOffset)); + + EmitCompareAndBranch( + instr, eq, temp1, Operand(Smi::FromInt(StackFrame::CONSTRUCT))); +} + + +void LCodeGen::DoIsObjectAndBranch(LIsObjectAndBranch* instr) { + Label* is_object = instr->TrueLabel(chunk_); + Label* is_not_object = instr->FalseLabel(chunk_); + Register value = ToRegister(instr->value()); + Register map = ToRegister(instr->temp1()); + Register scratch = ToRegister(instr->temp2()); + + __ JumpIfSmi(value, is_not_object); + __ JumpIfRoot(value, Heap::kNullValueRootIndex, is_object); + + __ Ldr(map, FieldMemOperand(value, HeapObject::kMapOffset)); + + // Check for undetectable objects. + __ Ldrb(scratch, FieldMemOperand(map, Map::kBitFieldOffset)); + __ TestAndBranchIfAnySet(scratch, 1 << Map::kIsUndetectable, is_not_object); + + // Check that instance type is in object type range. + __ IsInstanceJSObjectType(map, scratch, NULL); + // Flags have been updated by IsInstanceJSObjectType. We can now test the + // flags for "le" condition to check if the object's type is a valid + // JS object type. + EmitBranch(instr, le); +} + + +Condition LCodeGen::EmitIsString(Register input, + Register temp1, + Label* is_not_string, + SmiCheck check_needed = INLINE_SMI_CHECK) { + if (check_needed == INLINE_SMI_CHECK) { + __ JumpIfSmi(input, is_not_string); + } + __ CompareObjectType(input, temp1, temp1, FIRST_NONSTRING_TYPE); + + return lt; +} + + +void LCodeGen::DoIsStringAndBranch(LIsStringAndBranch* instr) { + Register val = ToRegister(instr->value()); + Register scratch = ToRegister(instr->temp()); + + SmiCheck check_needed = + instr->hydrogen()->value()->type().IsHeapObject() + ? OMIT_SMI_CHECK : INLINE_SMI_CHECK; + Condition true_cond = + EmitIsString(val, scratch, instr->FalseLabel(chunk_), check_needed); + + EmitBranch(instr, true_cond); +} + + +void LCodeGen::DoIsSmiAndBranch(LIsSmiAndBranch* instr) { + Register value = ToRegister(instr->value()); + STATIC_ASSERT(kSmiTag == 0); + EmitTestAndBranch(instr, eq, value, kSmiTagMask); +} + + +void LCodeGen::DoIsUndetectableAndBranch(LIsUndetectableAndBranch* instr) { + Register input = ToRegister(instr->value()); + Register temp = ToRegister(instr->temp()); + + if (!instr->hydrogen()->value()->type().IsHeapObject()) { + __ JumpIfSmi(input, instr->FalseLabel(chunk_)); + } + __ Ldr(temp, FieldMemOperand(input, HeapObject::kMapOffset)); + __ Ldrb(temp, FieldMemOperand(temp, Map::kBitFieldOffset)); + + EmitTestAndBranch(instr, ne, temp, 1 << Map::kIsUndetectable); +} + + +static const char* LabelType(LLabel* label) { + if (label->is_loop_header()) return " (loop header)"; + if (label->is_osr_entry()) return " (OSR entry)"; + return ""; +} + + +void LCodeGen::DoLabel(LLabel* label) { + Comment(";;; <@%d,#%d> -------------------- B%d%s --------------------", + current_instruction_, + label->hydrogen_value()->id(), + label->block_id(), + LabelType(label)); + + __ Bind(label->label()); + current_block_ = label->block_id(); + DoGap(label); +} + + +void LCodeGen::DoLoadContextSlot(LLoadContextSlot* instr) { + Register context = ToRegister(instr->context()); + Register result = ToRegister(instr->result()); + __ Ldr(result, ContextMemOperand(context, instr->slot_index())); + if (instr->hydrogen()->RequiresHoleCheck()) { + if (instr->hydrogen()->DeoptimizesOnHole()) { + DeoptimizeIfRoot(result, Heap::kTheHoleValueRootIndex, + instr->environment()); + } else { + Label not_the_hole; + __ JumpIfNotRoot(result, Heap::kTheHoleValueRootIndex, ¬_the_hole); + __ LoadRoot(result, Heap::kUndefinedValueRootIndex); + __ Bind(¬_the_hole); + } + } +} + + +void LCodeGen::DoLoadFunctionPrototype(LLoadFunctionPrototype* instr) { + Register function = ToRegister(instr->function()); + Register result = ToRegister(instr->result()); + Register temp = ToRegister(instr->temp()); + + // Check that the function really is a function. Leaves map in the result + // register. + __ CompareObjectType(function, result, temp, JS_FUNCTION_TYPE); + DeoptimizeIf(ne, instr->environment()); + + // Make sure that the function has an instance prototype. + Label non_instance; + __ Ldrb(temp, FieldMemOperand(result, Map::kBitFieldOffset)); + __ Tbnz(temp, Map::kHasNonInstancePrototype, &non_instance); + + // Get the prototype or initial map from the function. + __ Ldr(result, FieldMemOperand(function, + JSFunction::kPrototypeOrInitialMapOffset)); + + // Check that the function has a prototype or an initial map. + DeoptimizeIfRoot(result, Heap::kTheHoleValueRootIndex, + instr->environment()); + + // If the function does not have an initial map, we're done. + Label done; + __ CompareObjectType(result, temp, temp, MAP_TYPE); + __ B(ne, &done); + + // Get the prototype from the initial map. + __ Ldr(result, FieldMemOperand(result, Map::kPrototypeOffset)); + __ B(&done); + + // Non-instance prototype: fetch prototype from constructor field in initial + // map. + __ Bind(&non_instance); + __ Ldr(result, FieldMemOperand(result, Map::kConstructorOffset)); + + // All done. + __ Bind(&done); +} + + +void LCodeGen::DoLoadGlobalCell(LLoadGlobalCell* instr) { + Register result = ToRegister(instr->result()); + __ Mov(result, Operand(Handle<Object>(instr->hydrogen()->cell().handle()))); + __ Ldr(result, FieldMemOperand(result, Cell::kValueOffset)); + if (instr->hydrogen()->RequiresHoleCheck()) { + DeoptimizeIfRoot( + result, Heap::kTheHoleValueRootIndex, instr->environment()); + } +} + + +void LCodeGen::DoLoadGlobalGeneric(LLoadGlobalGeneric* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->global_object()).Is(x0)); + ASSERT(ToRegister(instr->result()).Is(x0)); + __ Mov(x2, Operand(instr->name())); + ContextualMode mode = instr->for_typeof() ? NOT_CONTEXTUAL : CONTEXTUAL; + Handle<Code> ic = LoadIC::initialize_stub(isolate(), mode); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +MemOperand LCodeGen::PrepareKeyedExternalArrayOperand( + Register key, + Register base, + Register scratch, + bool key_is_smi, + bool key_is_constant, + int constant_key, + ElementsKind elements_kind, + int base_offset) { + int element_size_shift = ElementsKindToShiftSize(elements_kind); + + if (key_is_constant) { + int key_offset = constant_key << element_size_shift; + return MemOperand(base, key_offset + base_offset); + } + + if (key_is_smi) { + __ Add(scratch, base, Operand::UntagSmiAndScale(key, element_size_shift)); + return MemOperand(scratch, base_offset); + } + + if (base_offset == 0) { + return MemOperand(base, key, SXTW, element_size_shift); + } + + ASSERT(!AreAliased(scratch, key)); + __ Add(scratch, base, base_offset); + return MemOperand(scratch, key, SXTW, element_size_shift); +} + + +void LCodeGen::DoLoadKeyedExternal(LLoadKeyedExternal* instr) { + Register ext_ptr = ToRegister(instr->elements()); + Register scratch; + ElementsKind elements_kind = instr->elements_kind(); + + bool key_is_smi = instr->hydrogen()->key()->representation().IsSmi(); + bool key_is_constant = instr->key()->IsConstantOperand(); + Register key = no_reg; + int constant_key = 0; + if (key_is_constant) { + ASSERT(instr->temp() == NULL); + constant_key = ToInteger32(LConstantOperand::cast(instr->key())); + if (constant_key & 0xf0000000) { + Abort(kArrayIndexConstantValueTooBig); + } + } else { + scratch = ToRegister(instr->temp()); + key = ToRegister(instr->key()); + } + + MemOperand mem_op = + PrepareKeyedExternalArrayOperand(key, ext_ptr, scratch, key_is_smi, + key_is_constant, constant_key, + elements_kind, + instr->base_offset()); + + if ((elements_kind == EXTERNAL_FLOAT32_ELEMENTS) || + (elements_kind == FLOAT32_ELEMENTS)) { + DoubleRegister result = ToDoubleRegister(instr->result()); + __ Ldr(result.S(), mem_op); + __ Fcvt(result, result.S()); + } else if ((elements_kind == EXTERNAL_FLOAT64_ELEMENTS) || + (elements_kind == FLOAT64_ELEMENTS)) { + DoubleRegister result = ToDoubleRegister(instr->result()); + __ Ldr(result, mem_op); + } else { + Register result = ToRegister(instr->result()); + + switch (elements_kind) { + case EXTERNAL_INT8_ELEMENTS: + case INT8_ELEMENTS: + __ Ldrsb(result, mem_op); + break; + case EXTERNAL_UINT8_CLAMPED_ELEMENTS: + case EXTERNAL_UINT8_ELEMENTS: + case UINT8_ELEMENTS: + case UINT8_CLAMPED_ELEMENTS: + __ Ldrb(result, mem_op); + break; + case EXTERNAL_INT16_ELEMENTS: + case INT16_ELEMENTS: + __ Ldrsh(result, mem_op); + break; + case EXTERNAL_UINT16_ELEMENTS: + case UINT16_ELEMENTS: + __ Ldrh(result, mem_op); + break; + case EXTERNAL_INT32_ELEMENTS: + case INT32_ELEMENTS: + __ Ldrsw(result, mem_op); + break; + case EXTERNAL_UINT32_ELEMENTS: + case UINT32_ELEMENTS: + __ Ldr(result.W(), mem_op); + if (!instr->hydrogen()->CheckFlag(HInstruction::kUint32)) { + // Deopt if value > 0x80000000. + __ Tst(result, 0xFFFFFFFF80000000); + DeoptimizeIf(ne, instr->environment()); + } + break; + case FLOAT32_ELEMENTS: + case FLOAT64_ELEMENTS: + case EXTERNAL_FLOAT32_ELEMENTS: + case EXTERNAL_FLOAT64_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: + case FAST_HOLEY_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_DOUBLE_ELEMENTS: + case FAST_ELEMENTS: + case FAST_SMI_ELEMENTS: + case DICTIONARY_ELEMENTS: + case SLOPPY_ARGUMENTS_ELEMENTS: + UNREACHABLE(); + break; + } + } +} + + +MemOperand LCodeGen::PrepareKeyedArrayOperand(Register base, + Register elements, + Register key, + bool key_is_tagged, + ElementsKind elements_kind, + Representation representation, + int base_offset) { + STATIC_ASSERT((kSmiValueSize == 32) && (kSmiShift == 32) && (kSmiTag == 0)); + int element_size_shift = ElementsKindToShiftSize(elements_kind); + + // Even though the HLoad/StoreKeyed instructions force the input + // representation for the key to be an integer, the input gets replaced during + // bounds check elimination with the index argument to the bounds check, which + // can be tagged, so that case must be handled here, too. + if (key_is_tagged) { + __ Add(base, elements, Operand::UntagSmiAndScale(key, element_size_shift)); + if (representation.IsInteger32()) { + ASSERT(elements_kind == FAST_SMI_ELEMENTS); + // Read or write only the most-significant 32 bits in the case of fast smi + // arrays. + return UntagSmiMemOperand(base, base_offset); + } else { + return MemOperand(base, base_offset); + } + } else { + // Sign extend key because it could be a 32-bit negative value or contain + // garbage in the top 32-bits. The address computation happens in 64-bit. + ASSERT((element_size_shift >= 0) && (element_size_shift <= 4)); + if (representation.IsInteger32()) { + ASSERT(elements_kind == FAST_SMI_ELEMENTS); + // Read or write only the most-significant 32 bits in the case of fast smi + // arrays. + __ Add(base, elements, Operand(key, SXTW, element_size_shift)); + return UntagSmiMemOperand(base, base_offset); + } else { + __ Add(base, elements, base_offset); + return MemOperand(base, key, SXTW, element_size_shift); + } + } +} + + +void LCodeGen::DoLoadKeyedFixedDouble(LLoadKeyedFixedDouble* instr) { + Register elements = ToRegister(instr->elements()); + DoubleRegister result = ToDoubleRegister(instr->result()); + MemOperand mem_op; + + if (instr->key()->IsConstantOperand()) { + ASSERT(instr->hydrogen()->RequiresHoleCheck() || + (instr->temp() == NULL)); + + int constant_key = ToInteger32(LConstantOperand::cast(instr->key())); + if (constant_key & 0xf0000000) { + Abort(kArrayIndexConstantValueTooBig); + } + int offset = instr->base_offset() + constant_key * kDoubleSize; + mem_op = MemOperand(elements, offset); + } else { + Register load_base = ToRegister(instr->temp()); + Register key = ToRegister(instr->key()); + bool key_is_tagged = instr->hydrogen()->key()->representation().IsSmi(); + mem_op = PrepareKeyedArrayOperand(load_base, elements, key, key_is_tagged, + instr->hydrogen()->elements_kind(), + instr->hydrogen()->representation(), + instr->base_offset()); + } + + __ Ldr(result, mem_op); + + if (instr->hydrogen()->RequiresHoleCheck()) { + Register scratch = ToRegister(instr->temp()); + // Detect the hole NaN by adding one to the integer representation of the + // result, and checking for overflow. + STATIC_ASSERT(kHoleNanInt64 == 0x7fffffffffffffff); + __ Ldr(scratch, mem_op); + __ Cmn(scratch, 1); + DeoptimizeIf(vs, instr->environment()); + } +} + + +void LCodeGen::DoLoadKeyedFixed(LLoadKeyedFixed* instr) { + Register elements = ToRegister(instr->elements()); + Register result = ToRegister(instr->result()); + MemOperand mem_op; + + Representation representation = instr->hydrogen()->representation(); + if (instr->key()->IsConstantOperand()) { + ASSERT(instr->temp() == NULL); + LConstantOperand* const_operand = LConstantOperand::cast(instr->key()); + int offset = instr->base_offset() + + ToInteger32(const_operand) * kPointerSize; + if (representation.IsInteger32()) { + ASSERT(instr->hydrogen()->elements_kind() == FAST_SMI_ELEMENTS); + STATIC_ASSERT((kSmiValueSize == 32) && (kSmiShift == 32) && + (kSmiTag == 0)); + mem_op = UntagSmiMemOperand(elements, offset); + } else { + mem_op = MemOperand(elements, offset); + } + } else { + Register load_base = ToRegister(instr->temp()); + Register key = ToRegister(instr->key()); + bool key_is_tagged = instr->hydrogen()->key()->representation().IsSmi(); + + mem_op = PrepareKeyedArrayOperand(load_base, elements, key, key_is_tagged, + instr->hydrogen()->elements_kind(), + representation, instr->base_offset()); + } + + __ Load(result, mem_op, representation); + + if (instr->hydrogen()->RequiresHoleCheck()) { + if (IsFastSmiElementsKind(instr->hydrogen()->elements_kind())) { + DeoptimizeIfNotSmi(result, instr->environment()); + } else { + DeoptimizeIfRoot(result, Heap::kTheHoleValueRootIndex, + instr->environment()); + } + } +} + + +void LCodeGen::DoLoadKeyedGeneric(LLoadKeyedGeneric* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->object()).Is(x1)); + ASSERT(ToRegister(instr->key()).Is(x0)); + + Handle<Code> ic = isolate()->builtins()->KeyedLoadIC_Initialize(); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + + ASSERT(ToRegister(instr->result()).Is(x0)); +} + + +void LCodeGen::DoLoadNamedField(LLoadNamedField* instr) { + HObjectAccess access = instr->hydrogen()->access(); + int offset = access.offset(); + Register object = ToRegister(instr->object()); + + if (access.IsExternalMemory()) { + Register result = ToRegister(instr->result()); + __ Load(result, MemOperand(object, offset), access.representation()); + return; + } + + if (instr->hydrogen()->representation().IsDouble()) { + FPRegister result = ToDoubleRegister(instr->result()); + __ Ldr(result, FieldMemOperand(object, offset)); + return; + } + + Register result = ToRegister(instr->result()); + Register source; + if (access.IsInobject()) { + source = object; + } else { + // Load the properties array, using result as a scratch register. + __ Ldr(result, FieldMemOperand(object, JSObject::kPropertiesOffset)); + source = result; + } + + if (access.representation().IsSmi() && + instr->hydrogen()->representation().IsInteger32()) { + // Read int value directly from upper half of the smi. + STATIC_ASSERT(kSmiValueSize == 32 && kSmiShift == 32 && kSmiTag == 0); + __ Load(result, UntagSmiFieldMemOperand(source, offset), + Representation::Integer32()); + } else { + __ Load(result, FieldMemOperand(source, offset), access.representation()); + } +} + + +void LCodeGen::DoLoadNamedGeneric(LLoadNamedGeneric* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + // LoadIC expects x2 to hold the name, and x0 to hold the receiver. + ASSERT(ToRegister(instr->object()).is(x0)); + __ Mov(x2, Operand(instr->name())); + + Handle<Code> ic = LoadIC::initialize_stub(isolate(), NOT_CONTEXTUAL); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + + ASSERT(ToRegister(instr->result()).is(x0)); +} + + +void LCodeGen::DoLoadRoot(LLoadRoot* instr) { + Register result = ToRegister(instr->result()); + __ LoadRoot(result, instr->index()); +} + + +void LCodeGen::DoMapEnumLength(LMapEnumLength* instr) { + Register result = ToRegister(instr->result()); + Register map = ToRegister(instr->value()); + __ EnumLengthSmi(result, map); +} + + +void LCodeGen::DoMathAbs(LMathAbs* instr) { + Representation r = instr->hydrogen()->value()->representation(); + if (r.IsDouble()) { + DoubleRegister input = ToDoubleRegister(instr->value()); + DoubleRegister result = ToDoubleRegister(instr->result()); + __ Fabs(result, input); + } else if (r.IsSmi() || r.IsInteger32()) { + Register input = r.IsSmi() ? ToRegister(instr->value()) + : ToRegister32(instr->value()); + Register result = r.IsSmi() ? ToRegister(instr->result()) + : ToRegister32(instr->result()); + __ Abs(result, input); + DeoptimizeIf(vs, instr->environment()); + } +} + + +void LCodeGen::DoDeferredMathAbsTagged(LMathAbsTagged* instr, + Label* exit, + Label* allocation_entry) { + // Handle the tricky cases of MathAbsTagged: + // - HeapNumber inputs. + // - Negative inputs produce a positive result, so a new HeapNumber is + // allocated to hold it. + // - Positive inputs are returned as-is, since there is no need to allocate + // a new HeapNumber for the result. + // - The (smi) input -0x80000000, produces +0x80000000, which does not fit + // a smi. In this case, the inline code sets the result and jumps directly + // to the allocation_entry label. + ASSERT(instr->context() != NULL); + ASSERT(ToRegister(instr->context()).is(cp)); + Register input = ToRegister(instr->value()); + Register temp1 = ToRegister(instr->temp1()); + Register temp2 = ToRegister(instr->temp2()); + Register result_bits = ToRegister(instr->temp3()); + Register result = ToRegister(instr->result()); + + Label runtime_allocation; + + // Deoptimize if the input is not a HeapNumber. + __ Ldr(temp1, FieldMemOperand(input, HeapObject::kMapOffset)); + DeoptimizeIfNotRoot(temp1, Heap::kHeapNumberMapRootIndex, + instr->environment()); + + // If the argument is positive, we can return it as-is, without any need to + // allocate a new HeapNumber for the result. We have to do this in integer + // registers (rather than with fabs) because we need to be able to distinguish + // the two zeroes. + __ Ldr(result_bits, FieldMemOperand(input, HeapNumber::kValueOffset)); + __ Mov(result, input); + __ Tbz(result_bits, kXSignBit, exit); + + // Calculate abs(input) by clearing the sign bit. + __ Bic(result_bits, result_bits, kXSignMask); + + // Allocate a new HeapNumber to hold the result. + // result_bits The bit representation of the (double) result. + __ Bind(allocation_entry); + __ AllocateHeapNumber(result, &runtime_allocation, temp1, temp2); + // The inline (non-deferred) code will store result_bits into result. + __ B(exit); + + __ Bind(&runtime_allocation); + if (FLAG_debug_code) { + // Because result is in the pointer map, we need to make sure it has a valid + // tagged value before we call the runtime. We speculatively set it to the + // input (for abs(+x)) or to a smi (for abs(-SMI_MIN)), so it should already + // be valid. + Label result_ok; + Register input = ToRegister(instr->value()); + __ JumpIfSmi(result, &result_ok); + __ Cmp(input, result); + __ Assert(eq, kUnexpectedValue); + __ Bind(&result_ok); + } + + { PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + CallRuntimeFromDeferred(Runtime::kHiddenAllocateHeapNumber, 0, instr, + instr->context()); + __ StoreToSafepointRegisterSlot(x0, result); + } + // The inline (non-deferred) code will store result_bits into result. +} + + +void LCodeGen::DoMathAbsTagged(LMathAbsTagged* instr) { + // Class for deferred case. + class DeferredMathAbsTagged: public LDeferredCode { + public: + DeferredMathAbsTagged(LCodeGen* codegen, LMathAbsTagged* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { + codegen()->DoDeferredMathAbsTagged(instr_, exit(), + allocation_entry()); + } + virtual LInstruction* instr() { return instr_; } + Label* allocation_entry() { return &allocation; } + private: + LMathAbsTagged* instr_; + Label allocation; + }; + + // TODO(jbramley): The early-exit mechanism would skip the new frame handling + // in GenerateDeferredCode. Tidy this up. + ASSERT(!NeedsDeferredFrame()); + + DeferredMathAbsTagged* deferred = + new(zone()) DeferredMathAbsTagged(this, instr); + + ASSERT(instr->hydrogen()->value()->representation().IsTagged() || + instr->hydrogen()->value()->representation().IsSmi()); + Register input = ToRegister(instr->value()); + Register result_bits = ToRegister(instr->temp3()); + Register result = ToRegister(instr->result()); + Label done; + + // Handle smis inline. + // We can treat smis as 64-bit integers, since the (low-order) tag bits will + // never get set by the negation. This is therefore the same as the Integer32 + // case in DoMathAbs, except that it operates on 64-bit values. + STATIC_ASSERT((kSmiValueSize == 32) && (kSmiShift == 32) && (kSmiTag == 0)); + + __ JumpIfNotSmi(input, deferred->entry()); + + __ Abs(result, input, NULL, &done); + + // The result is the magnitude (abs) of the smallest value a smi can + // represent, encoded as a double. + __ Mov(result_bits, double_to_rawbits(0x80000000)); + __ B(deferred->allocation_entry()); + + __ Bind(deferred->exit()); + __ Str(result_bits, FieldMemOperand(result, HeapNumber::kValueOffset)); + + __ Bind(&done); +} + + +void LCodeGen::DoMathExp(LMathExp* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + DoubleRegister result = ToDoubleRegister(instr->result()); + DoubleRegister double_temp1 = ToDoubleRegister(instr->double_temp1()); + DoubleRegister double_temp2 = double_scratch(); + Register temp1 = ToRegister(instr->temp1()); + Register temp2 = ToRegister(instr->temp2()); + Register temp3 = ToRegister(instr->temp3()); + + MathExpGenerator::EmitMathExp(masm(), input, result, + double_temp1, double_temp2, + temp1, temp2, temp3); +} + + +void LCodeGen::DoMathFloorD(LMathFloorD* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + DoubleRegister result = ToDoubleRegister(instr->result()); + + __ Frintm(result, input); +} + + +void LCodeGen::DoMathFloorI(LMathFloorI* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + Register result = ToRegister(instr->result()); + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + DeoptimizeIfMinusZero(input, instr->environment()); + } + + __ Fcvtms(result, input); + + // Check that the result fits into a 32-bit integer. + // - The result did not overflow. + __ Cmp(result, Operand(result, SXTW)); + // - The input was not NaN. + __ Fccmp(input, input, NoFlag, eq); + DeoptimizeIf(ne, instr->environment()); +} + + +void LCodeGen::DoFlooringDivByPowerOf2I(LFlooringDivByPowerOf2I* instr) { + Register dividend = ToRegister32(instr->dividend()); + Register result = ToRegister32(instr->result()); + int32_t divisor = instr->divisor(); + + // If the divisor is 1, return the dividend. + if (divisor == 1) { + __ Mov(result, dividend, kDiscardForSameWReg); + return; + } + + // If the divisor is positive, things are easy: There can be no deopts and we + // can simply do an arithmetic right shift. + int32_t shift = WhichPowerOf2Abs(divisor); + if (divisor > 1) { + __ Mov(result, Operand(dividend, ASR, shift)); + return; + } + + // If the divisor is negative, we have to negate and handle edge cases. + __ Negs(result, dividend); + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + DeoptimizeIf(eq, instr->environment()); + } + + // Dividing by -1 is basically negation, unless we overflow. + if (divisor == -1) { + if (instr->hydrogen()->CheckFlag(HValue::kLeftCanBeMinInt)) { + DeoptimizeIf(vs, instr->environment()); + } + return; + } + + // If the negation could not overflow, simply shifting is OK. + if (!instr->hydrogen()->CheckFlag(HValue::kLeftCanBeMinInt)) { + __ Mov(result, Operand(dividend, ASR, shift)); + return; + } + + __ Asr(result, result, shift); + __ Csel(result, result, kMinInt / divisor, vc); +} + + +void LCodeGen::DoFlooringDivByConstI(LFlooringDivByConstI* instr) { + Register dividend = ToRegister32(instr->dividend()); + int32_t divisor = instr->divisor(); + Register result = ToRegister32(instr->result()); + ASSERT(!AreAliased(dividend, result)); + + if (divisor == 0) { + Deoptimize(instr->environment()); + return; + } + + // Check for (0 / -x) that will produce negative zero. + HMathFloorOfDiv* hdiv = instr->hydrogen(); + if (hdiv->CheckFlag(HValue::kBailoutOnMinusZero) && divisor < 0) { + DeoptimizeIfZero(dividend, instr->environment()); + } + + // Easy case: We need no dynamic check for the dividend and the flooring + // division is the same as the truncating division. + if ((divisor > 0 && !hdiv->CheckFlag(HValue::kLeftCanBeNegative)) || + (divisor < 0 && !hdiv->CheckFlag(HValue::kLeftCanBePositive))) { + __ TruncatingDiv(result, dividend, Abs(divisor)); + if (divisor < 0) __ Neg(result, result); + return; + } + + // In the general case we may need to adjust before and after the truncating + // division to get a flooring division. + Register temp = ToRegister32(instr->temp()); + ASSERT(!AreAliased(temp, dividend, result)); + Label needs_adjustment, done; + __ Cmp(dividend, 0); + __ B(divisor > 0 ? lt : gt, &needs_adjustment); + __ TruncatingDiv(result, dividend, Abs(divisor)); + if (divisor < 0) __ Neg(result, result); + __ B(&done); + __ Bind(&needs_adjustment); + __ Add(temp, dividend, Operand(divisor > 0 ? 1 : -1)); + __ TruncatingDiv(result, temp, Abs(divisor)); + if (divisor < 0) __ Neg(result, result); + __ Sub(result, result, Operand(1)); + __ Bind(&done); +} + + +// TODO(svenpanne) Refactor this to avoid code duplication with DoDivI. +void LCodeGen::DoFlooringDivI(LFlooringDivI* instr) { + Register dividend = ToRegister32(instr->dividend()); + Register divisor = ToRegister32(instr->divisor()); + Register remainder = ToRegister32(instr->temp()); + Register result = ToRegister32(instr->result()); + + // This can't cause an exception on ARM, so we can speculatively + // execute it already now. + __ Sdiv(result, dividend, divisor); + + // Check for x / 0. + DeoptimizeIfZero(divisor, instr->environment()); + + // Check for (kMinInt / -1). + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + // The V flag will be set iff dividend == kMinInt. + __ Cmp(dividend, 1); + __ Ccmp(divisor, -1, NoFlag, vs); + DeoptimizeIf(eq, instr->environment()); + } + + // Check for (0 / -x) that will produce negative zero. + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ Cmp(divisor, 0); + __ Ccmp(dividend, 0, ZFlag, mi); + // "divisor" can't be null because the code would have already been + // deoptimized. The Z flag is set only if (divisor < 0) and (dividend == 0). + // In this case we need to deoptimize to produce a -0. + DeoptimizeIf(eq, instr->environment()); + } + + Label done; + // If both operands have the same sign then we are done. + __ Eor(remainder, dividend, divisor); + __ Tbz(remainder, kWSignBit, &done); + + // Check if the result needs to be corrected. + __ Msub(remainder, result, divisor, dividend); + __ Cbz(remainder, &done); + __ Sub(result, result, 1); + + __ Bind(&done); +} + + +void LCodeGen::DoMathLog(LMathLog* instr) { + ASSERT(instr->IsMarkedAsCall()); + ASSERT(ToDoubleRegister(instr->value()).is(d0)); + __ CallCFunction(ExternalReference::math_log_double_function(isolate()), + 0, 1); + ASSERT(ToDoubleRegister(instr->result()).Is(d0)); +} + + +void LCodeGen::DoMathClz32(LMathClz32* instr) { + Register input = ToRegister32(instr->value()); + Register result = ToRegister32(instr->result()); + __ Clz(result, input); +} + + +void LCodeGen::DoMathPowHalf(LMathPowHalf* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + DoubleRegister result = ToDoubleRegister(instr->result()); + Label done; + + // Math.pow(x, 0.5) differs from fsqrt(x) in the following cases: + // Math.pow(-Infinity, 0.5) == +Infinity + // Math.pow(-0.0, 0.5) == +0.0 + + // Catch -infinity inputs first. + // TODO(jbramley): A constant infinity register would be helpful here. + __ Fmov(double_scratch(), kFP64NegativeInfinity); + __ Fcmp(double_scratch(), input); + __ Fabs(result, input); + __ B(&done, eq); + + // Add +0.0 to convert -0.0 to +0.0. + __ Fadd(double_scratch(), input, fp_zero); + __ Fsqrt(result, double_scratch()); + + __ Bind(&done); +} + + +void LCodeGen::DoPower(LPower* instr) { + Representation exponent_type = instr->hydrogen()->right()->representation(); + // Having marked this as a call, we can use any registers. + // Just make sure that the input/output registers are the expected ones. + ASSERT(!instr->right()->IsDoubleRegister() || + ToDoubleRegister(instr->right()).is(d1)); + ASSERT(exponent_type.IsInteger32() || !instr->right()->IsRegister() || + ToRegister(instr->right()).is(x11)); + ASSERT(!exponent_type.IsInteger32() || ToRegister(instr->right()).is(x12)); + ASSERT(ToDoubleRegister(instr->left()).is(d0)); + ASSERT(ToDoubleRegister(instr->result()).is(d0)); + + if (exponent_type.IsSmi()) { + MathPowStub stub(isolate(), MathPowStub::TAGGED); + __ CallStub(&stub); + } else if (exponent_type.IsTagged()) { + Label no_deopt; + __ JumpIfSmi(x11, &no_deopt); + __ Ldr(x0, FieldMemOperand(x11, HeapObject::kMapOffset)); + DeoptimizeIfNotRoot(x0, Heap::kHeapNumberMapRootIndex, + instr->environment()); + __ Bind(&no_deopt); + MathPowStub stub(isolate(), MathPowStub::TAGGED); + __ CallStub(&stub); + } else if (exponent_type.IsInteger32()) { + // Ensure integer exponent has no garbage in top 32-bits, as MathPowStub + // supports large integer exponents. + Register exponent = ToRegister(instr->right()); + __ Sxtw(exponent, exponent); + MathPowStub stub(isolate(), MathPowStub::INTEGER); + __ CallStub(&stub); + } else { + ASSERT(exponent_type.IsDouble()); + MathPowStub stub(isolate(), MathPowStub::DOUBLE); + __ CallStub(&stub); + } +} + + +void LCodeGen::DoMathRoundD(LMathRoundD* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + DoubleRegister result = ToDoubleRegister(instr->result()); + DoubleRegister scratch_d = double_scratch(); + + ASSERT(!AreAliased(input, result, scratch_d)); + + Label done; + + __ Frinta(result, input); + __ Fcmp(input, 0.0); + __ Fccmp(result, input, ZFlag, lt); + // The result is correct if the input was in [-0, +infinity], or was a + // negative integral value. + __ B(eq, &done); + + // Here the input is negative, non integral, with an exponent lower than 52. + // We do not have to worry about the 0.49999999999999994 (0x3fdfffffffffffff) + // case. So we can safely add 0.5. + __ Fmov(scratch_d, 0.5); + __ Fadd(result, input, scratch_d); + __ Frintm(result, result); + // The range [-0.5, -0.0[ yielded +0.0. Force the sign to negative. + __ Fabs(result, result); + __ Fneg(result, result); + + __ Bind(&done); +} + + +void LCodeGen::DoMathRoundI(LMathRoundI* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + DoubleRegister temp = ToDoubleRegister(instr->temp1()); + DoubleRegister dot_five = double_scratch(); + Register result = ToRegister(instr->result()); + Label done; + + // Math.round() rounds to the nearest integer, with ties going towards + // +infinity. This does not match any IEEE-754 rounding mode. + // - Infinities and NaNs are propagated unchanged, but cause deopts because + // they can't be represented as integers. + // - The sign of the result is the same as the sign of the input. This means + // that -0.0 rounds to itself, and values -0.5 <= input < 0 also produce a + // result of -0.0. + + // Add 0.5 and round towards -infinity. + __ Fmov(dot_five, 0.5); + __ Fadd(temp, input, dot_five); + __ Fcvtms(result, temp); + + // The result is correct if: + // result is not 0, as the input could be NaN or [-0.5, -0.0]. + // result is not 1, as 0.499...94 will wrongly map to 1. + // result fits in 32 bits. + __ Cmp(result, Operand(result.W(), SXTW)); + __ Ccmp(result, 1, ZFlag, eq); + __ B(hi, &done); + + // At this point, we have to handle possible inputs of NaN or numbers in the + // range [-0.5, 1.5[, or numbers larger than 32 bits. + + // Deoptimize if the result > 1, as it must be larger than 32 bits. + __ Cmp(result, 1); + DeoptimizeIf(hi, instr->environment()); + + // Deoptimize for negative inputs, which at this point are only numbers in + // the range [-0.5, -0.0] + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ Fmov(result, input); + DeoptimizeIfNegative(result, instr->environment()); + } + + // Deoptimize if the input was NaN. + __ Fcmp(input, dot_five); + DeoptimizeIf(vs, instr->environment()); + + // Now, the only unhandled inputs are in the range [0.0, 1.5[ (or [-0.5, 1.5[ + // if we didn't generate a -0.0 bailout). If input >= 0.5 then return 1, + // else 0; we avoid dealing with 0.499...94 directly. + __ Cset(result, ge); + __ Bind(&done); +} + + +void LCodeGen::DoMathSqrt(LMathSqrt* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + DoubleRegister result = ToDoubleRegister(instr->result()); + __ Fsqrt(result, input); +} + + +void LCodeGen::DoMathMinMax(LMathMinMax* instr) { + HMathMinMax::Operation op = instr->hydrogen()->operation(); + if (instr->hydrogen()->representation().IsInteger32()) { + Register result = ToRegister32(instr->result()); + Register left = ToRegister32(instr->left()); + Operand right = ToOperand32I(instr->right()); + + __ Cmp(left, right); + __ Csel(result, left, right, (op == HMathMinMax::kMathMax) ? ge : le); + } else if (instr->hydrogen()->representation().IsSmi()) { + Register result = ToRegister(instr->result()); + Register left = ToRegister(instr->left()); + Operand right = ToOperand(instr->right()); + + __ Cmp(left, right); + __ Csel(result, left, right, (op == HMathMinMax::kMathMax) ? ge : le); + } else { + ASSERT(instr->hydrogen()->representation().IsDouble()); + DoubleRegister result = ToDoubleRegister(instr->result()); + DoubleRegister left = ToDoubleRegister(instr->left()); + DoubleRegister right = ToDoubleRegister(instr->right()); + + if (op == HMathMinMax::kMathMax) { + __ Fmax(result, left, right); + } else { + ASSERT(op == HMathMinMax::kMathMin); + __ Fmin(result, left, right); + } + } +} + + +void LCodeGen::DoModByPowerOf2I(LModByPowerOf2I* instr) { + Register dividend = ToRegister32(instr->dividend()); + int32_t divisor = instr->divisor(); + ASSERT(dividend.is(ToRegister32(instr->result()))); + + // Theoretically, a variation of the branch-free code for integer division by + // a power of 2 (calculating the remainder via an additional multiplication + // (which gets simplified to an 'and') and subtraction) should be faster, and + // this is exactly what GCC and clang emit. Nevertheless, benchmarks seem to + // indicate that positive dividends are heavily favored, so the branching + // version performs better. + HMod* hmod = instr->hydrogen(); + int32_t mask = divisor < 0 ? -(divisor + 1) : (divisor - 1); + Label dividend_is_not_negative, done; + if (hmod->CheckFlag(HValue::kLeftCanBeNegative)) { + __ Tbz(dividend, kWSignBit, ÷nd_is_not_negative); + // Note that this is correct even for kMinInt operands. + __ Neg(dividend, dividend); + __ And(dividend, dividend, mask); + __ Negs(dividend, dividend); + if (hmod->CheckFlag(HValue::kBailoutOnMinusZero)) { + DeoptimizeIf(eq, instr->environment()); + } + __ B(&done); + } + + __ bind(÷nd_is_not_negative); + __ And(dividend, dividend, mask); + __ bind(&done); +} + + +void LCodeGen::DoModByConstI(LModByConstI* instr) { + Register dividend = ToRegister32(instr->dividend()); + int32_t divisor = instr->divisor(); + Register result = ToRegister32(instr->result()); + Register temp = ToRegister32(instr->temp()); + ASSERT(!AreAliased(dividend, result, temp)); + + if (divisor == 0) { + Deoptimize(instr->environment()); + return; + } + + __ TruncatingDiv(result, dividend, Abs(divisor)); + __ Sxtw(dividend.X(), dividend); + __ Mov(temp, Abs(divisor)); + __ Smsubl(result.X(), result, temp, dividend.X()); + + // Check for negative zero. + HMod* hmod = instr->hydrogen(); + if (hmod->CheckFlag(HValue::kBailoutOnMinusZero)) { + Label remainder_not_zero; + __ Cbnz(result, &remainder_not_zero); + DeoptimizeIfNegative(dividend, instr->environment()); + __ bind(&remainder_not_zero); + } +} + + +void LCodeGen::DoModI(LModI* instr) { + Register dividend = ToRegister32(instr->left()); + Register divisor = ToRegister32(instr->right()); + Register result = ToRegister32(instr->result()); + + Label done; + // modulo = dividend - quotient * divisor + __ Sdiv(result, dividend, divisor); + if (instr->hydrogen()->CheckFlag(HValue::kCanBeDivByZero)) { + DeoptimizeIfZero(divisor, instr->environment()); + } + __ Msub(result, result, divisor, dividend); + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ Cbnz(result, &done); + DeoptimizeIfNegative(dividend, instr->environment()); + } + __ Bind(&done); +} + + +void LCodeGen::DoMulConstIS(LMulConstIS* instr) { + ASSERT(instr->hydrogen()->representation().IsSmiOrInteger32()); + bool is_smi = instr->hydrogen()->representation().IsSmi(); + Register result = + is_smi ? ToRegister(instr->result()) : ToRegister32(instr->result()); + Register left = + is_smi ? ToRegister(instr->left()) : ToRegister32(instr->left()) ; + int32_t right = ToInteger32(instr->right()); + ASSERT((right > -kMaxInt) || (right < kMaxInt)); + + bool can_overflow = instr->hydrogen()->CheckFlag(HValue::kCanOverflow); + bool bailout_on_minus_zero = + instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero); + + if (bailout_on_minus_zero) { + if (right < 0) { + // The result is -0 if right is negative and left is zero. + DeoptimizeIfZero(left, instr->environment()); + } else if (right == 0) { + // The result is -0 if the right is zero and the left is negative. + DeoptimizeIfNegative(left, instr->environment()); + } + } + + switch (right) { + // Cases which can detect overflow. + case -1: + if (can_overflow) { + // Only 0x80000000 can overflow here. + __ Negs(result, left); + DeoptimizeIf(vs, instr->environment()); + } else { + __ Neg(result, left); + } + break; + case 0: + // This case can never overflow. + __ Mov(result, 0); + break; + case 1: + // This case can never overflow. + __ Mov(result, left, kDiscardForSameWReg); + break; + case 2: + if (can_overflow) { + __ Adds(result, left, left); + DeoptimizeIf(vs, instr->environment()); + } else { + __ Add(result, left, left); + } + break; + + default: + // Multiplication by constant powers of two (and some related values) + // can be done efficiently with shifted operands. + int32_t right_abs = Abs(right); + + if (IsPowerOf2(right_abs)) { + int right_log2 = WhichPowerOf2(right_abs); + + if (can_overflow) { + Register scratch = result; + ASSERT(!AreAliased(scratch, left)); + __ Cls(scratch, left); + __ Cmp(scratch, right_log2); + DeoptimizeIf(lt, instr->environment()); + } + + if (right >= 0) { + // result = left << log2(right) + __ Lsl(result, left, right_log2); + } else { + // result = -left << log2(-right) + if (can_overflow) { + __ Negs(result, Operand(left, LSL, right_log2)); + DeoptimizeIf(vs, instr->environment()); + } else { + __ Neg(result, Operand(left, LSL, right_log2)); + } + } + return; + } + + + // For the following cases, we could perform a conservative overflow check + // with CLS as above. However the few cycles saved are likely not worth + // the risk of deoptimizing more often than required. + ASSERT(!can_overflow); + + if (right >= 0) { + if (IsPowerOf2(right - 1)) { + // result = left + left << log2(right - 1) + __ Add(result, left, Operand(left, LSL, WhichPowerOf2(right - 1))); + } else if (IsPowerOf2(right + 1)) { + // result = -left + left << log2(right + 1) + __ Sub(result, left, Operand(left, LSL, WhichPowerOf2(right + 1))); + __ Neg(result, result); + } else { + UNREACHABLE(); + } + } else { + if (IsPowerOf2(-right + 1)) { + // result = left - left << log2(-right + 1) + __ Sub(result, left, Operand(left, LSL, WhichPowerOf2(-right + 1))); + } else if (IsPowerOf2(-right - 1)) { + // result = -left - left << log2(-right - 1) + __ Add(result, left, Operand(left, LSL, WhichPowerOf2(-right - 1))); + __ Neg(result, result); + } else { + UNREACHABLE(); + } + } + } +} + + +void LCodeGen::DoMulI(LMulI* instr) { + Register result = ToRegister32(instr->result()); + Register left = ToRegister32(instr->left()); + Register right = ToRegister32(instr->right()); + + bool can_overflow = instr->hydrogen()->CheckFlag(HValue::kCanOverflow); + bool bailout_on_minus_zero = + instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero); + + if (bailout_on_minus_zero && !left.Is(right)) { + // If one operand is zero and the other is negative, the result is -0. + // - Set Z (eq) if either left or right, or both, are 0. + __ Cmp(left, 0); + __ Ccmp(right, 0, ZFlag, ne); + // - If so (eq), set N (mi) if left + right is negative. + // - Otherwise, clear N. + __ Ccmn(left, right, NoFlag, eq); + DeoptimizeIf(mi, instr->environment()); + } + + if (can_overflow) { + __ Smull(result.X(), left, right); + __ Cmp(result.X(), Operand(result, SXTW)); + DeoptimizeIf(ne, instr->environment()); + } else { + __ Mul(result, left, right); + } +} + + +void LCodeGen::DoMulS(LMulS* instr) { + Register result = ToRegister(instr->result()); + Register left = ToRegister(instr->left()); + Register right = ToRegister(instr->right()); + + bool can_overflow = instr->hydrogen()->CheckFlag(HValue::kCanOverflow); + bool bailout_on_minus_zero = + instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero); + + if (bailout_on_minus_zero && !left.Is(right)) { + // If one operand is zero and the other is negative, the result is -0. + // - Set Z (eq) if either left or right, or both, are 0. + __ Cmp(left, 0); + __ Ccmp(right, 0, ZFlag, ne); + // - If so (eq), set N (mi) if left + right is negative. + // - Otherwise, clear N. + __ Ccmn(left, right, NoFlag, eq); + DeoptimizeIf(mi, instr->environment()); + } + + STATIC_ASSERT((kSmiShift == 32) && (kSmiTag == 0)); + if (can_overflow) { + __ Smulh(result, left, right); + __ Cmp(result, Operand(result.W(), SXTW)); + __ SmiTag(result); + DeoptimizeIf(ne, instr->environment()); + } else { + if (AreAliased(result, left, right)) { + // All three registers are the same: half untag the input and then + // multiply, giving a tagged result. + STATIC_ASSERT((kSmiShift % 2) == 0); + __ Asr(result, left, kSmiShift / 2); + __ Mul(result, result, result); + } else if (result.Is(left) && !left.Is(right)) { + // Registers result and left alias, right is distinct: untag left into + // result, and then multiply by right, giving a tagged result. + __ SmiUntag(result, left); + __ Mul(result, result, right); + } else { + ASSERT(!left.Is(result)); + // Registers result and right alias, left is distinct, or all registers + // are distinct: untag right into result, and then multiply by left, + // giving a tagged result. + __ SmiUntag(result, right); + __ Mul(result, left, result); + } + } +} + + +void LCodeGen::DoDeferredNumberTagD(LNumberTagD* instr) { + // TODO(3095996): Get rid of this. For now, we need to make the + // result register contain a valid pointer because it is already + // contained in the register pointer map. + Register result = ToRegister(instr->result()); + __ Mov(result, 0); + + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + // NumberTagU and NumberTagD use the context from the frame, rather than + // the environment's HContext or HInlinedContext value. + // They only call Runtime::kHiddenAllocateHeapNumber. + // The corresponding HChange instructions are added in a phase that does + // not have easy access to the local context. + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ CallRuntimeSaveDoubles(Runtime::kHiddenAllocateHeapNumber); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoLazyDeopt); + __ StoreToSafepointRegisterSlot(x0, result); +} + + +void LCodeGen::DoNumberTagD(LNumberTagD* instr) { + class DeferredNumberTagD: public LDeferredCode { + public: + DeferredNumberTagD(LCodeGen* codegen, LNumberTagD* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredNumberTagD(instr_); } + virtual LInstruction* instr() { return instr_; } + private: + LNumberTagD* instr_; + }; + + DoubleRegister input = ToDoubleRegister(instr->value()); + Register result = ToRegister(instr->result()); + Register temp1 = ToRegister(instr->temp1()); + Register temp2 = ToRegister(instr->temp2()); + + DeferredNumberTagD* deferred = new(zone()) DeferredNumberTagD(this, instr); + if (FLAG_inline_new) { + __ AllocateHeapNumber(result, deferred->entry(), temp1, temp2); + } else { + __ B(deferred->entry()); + } + + __ Bind(deferred->exit()); + __ Str(input, FieldMemOperand(result, HeapNumber::kValueOffset)); +} + + +void LCodeGen::DoDeferredNumberTagU(LInstruction* instr, + LOperand* value, + LOperand* temp1, + LOperand* temp2) { + Label slow, convert_and_store; + Register src = ToRegister32(value); + Register dst = ToRegister(instr->result()); + Register scratch1 = ToRegister(temp1); + + if (FLAG_inline_new) { + Register scratch2 = ToRegister(temp2); + __ AllocateHeapNumber(dst, &slow, scratch1, scratch2); + __ B(&convert_and_store); + } + + // Slow case: call the runtime system to do the number allocation. + __ Bind(&slow); + // TODO(3095996): Put a valid pointer value in the stack slot where the result + // register is stored, as this register is in the pointer map, but contains an + // integer value. + __ Mov(dst, 0); + { + // Preserve the value of all registers. + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + + // NumberTagU and NumberTagD use the context from the frame, rather than + // the environment's HContext or HInlinedContext value. + // They only call Runtime::kHiddenAllocateHeapNumber. + // The corresponding HChange instructions are added in a phase that does + // not have easy access to the local context. + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ CallRuntimeSaveDoubles(Runtime::kHiddenAllocateHeapNumber); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoLazyDeopt); + __ StoreToSafepointRegisterSlot(x0, dst); + } + + // Convert number to floating point and store in the newly allocated heap + // number. + __ Bind(&convert_and_store); + DoubleRegister dbl_scratch = double_scratch(); + __ Ucvtf(dbl_scratch, src); + __ Str(dbl_scratch, FieldMemOperand(dst, HeapNumber::kValueOffset)); +} + + +void LCodeGen::DoNumberTagU(LNumberTagU* instr) { + class DeferredNumberTagU: public LDeferredCode { + public: + DeferredNumberTagU(LCodeGen* codegen, LNumberTagU* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { + codegen()->DoDeferredNumberTagU(instr_, + instr_->value(), + instr_->temp1(), + instr_->temp2()); + } + virtual LInstruction* instr() { return instr_; } + private: + LNumberTagU* instr_; + }; + + Register value = ToRegister32(instr->value()); + Register result = ToRegister(instr->result()); + + DeferredNumberTagU* deferred = new(zone()) DeferredNumberTagU(this, instr); + __ Cmp(value, Smi::kMaxValue); + __ B(hi, deferred->entry()); + __ SmiTag(result, value.X()); + __ Bind(deferred->exit()); +} + + +void LCodeGen::DoNumberUntagD(LNumberUntagD* instr) { + Register input = ToRegister(instr->value()); + Register scratch = ToRegister(instr->temp()); + DoubleRegister result = ToDoubleRegister(instr->result()); + bool can_convert_undefined_to_nan = + instr->hydrogen()->can_convert_undefined_to_nan(); + + Label done, load_smi; + + // Work out what untag mode we're working with. + HValue* value = instr->hydrogen()->value(); + NumberUntagDMode mode = value->representation().IsSmi() + ? NUMBER_CANDIDATE_IS_SMI : NUMBER_CANDIDATE_IS_ANY_TAGGED; + + if (mode == NUMBER_CANDIDATE_IS_ANY_TAGGED) { + __ JumpIfSmi(input, &load_smi); + + Label convert_undefined; + + // Heap number map check. + __ Ldr(scratch, FieldMemOperand(input, HeapObject::kMapOffset)); + if (can_convert_undefined_to_nan) { + __ JumpIfNotRoot(scratch, Heap::kHeapNumberMapRootIndex, + &convert_undefined); + } else { + DeoptimizeIfNotRoot(scratch, Heap::kHeapNumberMapRootIndex, + instr->environment()); + } + + // Load heap number. + __ Ldr(result, FieldMemOperand(input, HeapNumber::kValueOffset)); + if (instr->hydrogen()->deoptimize_on_minus_zero()) { + DeoptimizeIfMinusZero(result, instr->environment()); + } + __ B(&done); + + if (can_convert_undefined_to_nan) { + __ Bind(&convert_undefined); + DeoptimizeIfNotRoot(input, Heap::kUndefinedValueRootIndex, + instr->environment()); + + __ LoadRoot(scratch, Heap::kNanValueRootIndex); + __ Ldr(result, FieldMemOperand(scratch, HeapNumber::kValueOffset)); + __ B(&done); + } + + } else { + ASSERT(mode == NUMBER_CANDIDATE_IS_SMI); + // Fall through to load_smi. + } + + // Smi to double register conversion. + __ Bind(&load_smi); + __ SmiUntagToDouble(result, input); + + __ Bind(&done); +} + + +void LCodeGen::DoOsrEntry(LOsrEntry* instr) { + // This is a pseudo-instruction that ensures that the environment here is + // properly registered for deoptimization and records the assembler's PC + // offset. + LEnvironment* environment = instr->environment(); + + // If the environment were already registered, we would have no way of + // backpatching it with the spill slot operands. + ASSERT(!environment->HasBeenRegistered()); + RegisterEnvironmentForDeoptimization(environment, Safepoint::kNoLazyDeopt); + + GenerateOsrPrologue(); +} + + +void LCodeGen::DoParameter(LParameter* instr) { + // Nothing to do. +} + + +void LCodeGen::DoPreparePushArguments(LPreparePushArguments* instr) { + __ PushPreamble(instr->argc(), kPointerSize); +} + + +void LCodeGen::DoPushArguments(LPushArguments* instr) { + MacroAssembler::PushPopQueue args(masm()); + + for (int i = 0; i < instr->ArgumentCount(); ++i) { + LOperand* arg = instr->argument(i); + if (arg->IsDoubleRegister() || arg->IsDoubleStackSlot()) { + Abort(kDoPushArgumentNotImplementedForDoubleType); + return; + } + args.Queue(ToRegister(arg)); + } + + // The preamble was done by LPreparePushArguments. + args.PushQueued(MacroAssembler::PushPopQueue::SKIP_PREAMBLE); + + after_push_argument_ = true; +} + + +void LCodeGen::DoReturn(LReturn* instr) { + if (FLAG_trace && info()->IsOptimizing()) { + // Push the return value on the stack as the parameter. + // Runtime::TraceExit returns its parameter in x0. We're leaving the code + // managed by the register allocator and tearing down the frame, it's + // safe to write to the context register. + __ Push(x0); + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + __ CallRuntime(Runtime::kTraceExit, 1); + } + + if (info()->saves_caller_doubles()) { + RestoreCallerDoubles(); + } + + int no_frame_start = -1; + if (NeedsEagerFrame()) { + Register stack_pointer = masm()->StackPointer(); + __ Mov(stack_pointer, fp); + no_frame_start = masm_->pc_offset(); + __ Pop(fp, lr); + } + + if (instr->has_constant_parameter_count()) { + int parameter_count = ToInteger32(instr->constant_parameter_count()); + __ Drop(parameter_count + 1); + } else { + Register parameter_count = ToRegister(instr->parameter_count()); + __ DropBySMI(parameter_count); + } + __ Ret(); + + if (no_frame_start != -1) { + info_->AddNoFrameRange(no_frame_start, masm_->pc_offset()); + } +} + + +MemOperand LCodeGen::BuildSeqStringOperand(Register string, + Register temp, + LOperand* index, + String::Encoding encoding) { + if (index->IsConstantOperand()) { + int offset = ToInteger32(LConstantOperand::cast(index)); + if (encoding == String::TWO_BYTE_ENCODING) { + offset *= kUC16Size; + } + STATIC_ASSERT(kCharSize == 1); + return FieldMemOperand(string, SeqString::kHeaderSize + offset); + } + + __ Add(temp, string, SeqString::kHeaderSize - kHeapObjectTag); + if (encoding == String::ONE_BYTE_ENCODING) { + return MemOperand(temp, ToRegister32(index), SXTW); + } else { + STATIC_ASSERT(kUC16Size == 2); + return MemOperand(temp, ToRegister32(index), SXTW, 1); + } +} + + +void LCodeGen::DoSeqStringGetChar(LSeqStringGetChar* instr) { + String::Encoding encoding = instr->hydrogen()->encoding(); + Register string = ToRegister(instr->string()); + Register result = ToRegister(instr->result()); + Register temp = ToRegister(instr->temp()); + + if (FLAG_debug_code) { + // Even though this lithium instruction comes with a temp register, we + // can't use it here because we want to use "AtStart" constraints on the + // inputs and the debug code here needs a scratch register. + UseScratchRegisterScope temps(masm()); + Register dbg_temp = temps.AcquireX(); + + __ Ldr(dbg_temp, FieldMemOperand(string, HeapObject::kMapOffset)); + __ Ldrb(dbg_temp, FieldMemOperand(dbg_temp, Map::kInstanceTypeOffset)); + + __ And(dbg_temp, dbg_temp, + Operand(kStringRepresentationMask | kStringEncodingMask)); + static const uint32_t one_byte_seq_type = kSeqStringTag | kOneByteStringTag; + static const uint32_t two_byte_seq_type = kSeqStringTag | kTwoByteStringTag; + __ Cmp(dbg_temp, Operand(encoding == String::ONE_BYTE_ENCODING + ? one_byte_seq_type : two_byte_seq_type)); + __ Check(eq, kUnexpectedStringType); + } + + MemOperand operand = + BuildSeqStringOperand(string, temp, instr->index(), encoding); + if (encoding == String::ONE_BYTE_ENCODING) { + __ Ldrb(result, operand); + } else { + __ Ldrh(result, operand); + } +} + + +void LCodeGen::DoSeqStringSetChar(LSeqStringSetChar* instr) { + String::Encoding encoding = instr->hydrogen()->encoding(); + Register string = ToRegister(instr->string()); + Register value = ToRegister(instr->value()); + Register temp = ToRegister(instr->temp()); + + if (FLAG_debug_code) { + ASSERT(ToRegister(instr->context()).is(cp)); + Register index = ToRegister(instr->index()); + static const uint32_t one_byte_seq_type = kSeqStringTag | kOneByteStringTag; + static const uint32_t two_byte_seq_type = kSeqStringTag | kTwoByteStringTag; + int encoding_mask = + instr->hydrogen()->encoding() == String::ONE_BYTE_ENCODING + ? one_byte_seq_type : two_byte_seq_type; + __ EmitSeqStringSetCharCheck(string, index, kIndexIsInteger32, temp, + encoding_mask); + } + MemOperand operand = + BuildSeqStringOperand(string, temp, instr->index(), encoding); + if (encoding == String::ONE_BYTE_ENCODING) { + __ Strb(value, operand); + } else { + __ Strh(value, operand); + } +} + + +void LCodeGen::DoSmiTag(LSmiTag* instr) { + HChange* hchange = instr->hydrogen(); + Register input = ToRegister(instr->value()); + Register output = ToRegister(instr->result()); + if (hchange->CheckFlag(HValue::kCanOverflow) && + hchange->value()->CheckFlag(HValue::kUint32)) { + DeoptimizeIfNegative(input.W(), instr->environment()); + } + __ SmiTag(output, input); +} + + +void LCodeGen::DoSmiUntag(LSmiUntag* instr) { + Register input = ToRegister(instr->value()); + Register result = ToRegister(instr->result()); + Label done, untag; + + if (instr->needs_check()) { + DeoptimizeIfNotSmi(input, instr->environment()); + } + + __ Bind(&untag); + __ SmiUntag(result, input); + __ Bind(&done); +} + + +void LCodeGen::DoShiftI(LShiftI* instr) { + LOperand* right_op = instr->right(); + Register left = ToRegister32(instr->left()); + Register result = ToRegister32(instr->result()); + + if (right_op->IsRegister()) { + Register right = ToRegister32(instr->right()); + switch (instr->op()) { + case Token::ROR: __ Ror(result, left, right); break; + case Token::SAR: __ Asr(result, left, right); break; + case Token::SHL: __ Lsl(result, left, right); break; + case Token::SHR: + if (instr->can_deopt()) { + Label right_not_zero; + __ Cbnz(right, &right_not_zero); + DeoptimizeIfNegative(left, instr->environment()); + __ Bind(&right_not_zero); + } + __ Lsr(result, left, right); + break; + default: UNREACHABLE(); + } + } else { + ASSERT(right_op->IsConstantOperand()); + int shift_count = JSShiftAmountFromLConstant(right_op); + if (shift_count == 0) { + if ((instr->op() == Token::SHR) && instr->can_deopt()) { + DeoptimizeIfNegative(left, instr->environment()); + } + __ Mov(result, left, kDiscardForSameWReg); + } else { + switch (instr->op()) { + case Token::ROR: __ Ror(result, left, shift_count); break; + case Token::SAR: __ Asr(result, left, shift_count); break; + case Token::SHL: __ Lsl(result, left, shift_count); break; + case Token::SHR: __ Lsr(result, left, shift_count); break; + default: UNREACHABLE(); + } + } + } +} + + +void LCodeGen::DoShiftS(LShiftS* instr) { + LOperand* right_op = instr->right(); + Register left = ToRegister(instr->left()); + Register result = ToRegister(instr->result()); + + // Only ROR by register needs a temp. + ASSERT(((instr->op() == Token::ROR) && right_op->IsRegister()) || + (instr->temp() == NULL)); + + if (right_op->IsRegister()) { + Register right = ToRegister(instr->right()); + switch (instr->op()) { + case Token::ROR: { + Register temp = ToRegister(instr->temp()); + __ Ubfx(temp, right, kSmiShift, 5); + __ SmiUntag(result, left); + __ Ror(result.W(), result.W(), temp.W()); + __ SmiTag(result); + break; + } + case Token::SAR: + __ Ubfx(result, right, kSmiShift, 5); + __ Asr(result, left, result); + __ Bic(result, result, kSmiShiftMask); + break; + case Token::SHL: + __ Ubfx(result, right, kSmiShift, 5); + __ Lsl(result, left, result); + break; + case Token::SHR: + if (instr->can_deopt()) { + Label right_not_zero; + __ Cbnz(right, &right_not_zero); + DeoptimizeIfNegative(left, instr->environment()); + __ Bind(&right_not_zero); + } + __ Ubfx(result, right, kSmiShift, 5); + __ Lsr(result, left, result); + __ Bic(result, result, kSmiShiftMask); + break; + default: UNREACHABLE(); + } + } else { + ASSERT(right_op->IsConstantOperand()); + int shift_count = JSShiftAmountFromLConstant(right_op); + if (shift_count == 0) { + if ((instr->op() == Token::SHR) && instr->can_deopt()) { + DeoptimizeIfNegative(left, instr->environment()); + } + __ Mov(result, left); + } else { + switch (instr->op()) { + case Token::ROR: + __ SmiUntag(result, left); + __ Ror(result.W(), result.W(), shift_count); + __ SmiTag(result); + break; + case Token::SAR: + __ Asr(result, left, shift_count); + __ Bic(result, result, kSmiShiftMask); + break; + case Token::SHL: + __ Lsl(result, left, shift_count); + break; + case Token::SHR: + __ Lsr(result, left, shift_count); + __ Bic(result, result, kSmiShiftMask); + break; + default: UNREACHABLE(); + } + } + } +} + + +void LCodeGen::DoDebugBreak(LDebugBreak* instr) { + __ Debug("LDebugBreak", 0, BREAK); +} + + +void LCodeGen::DoDeclareGlobals(LDeclareGlobals* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + Register scratch1 = x5; + Register scratch2 = x6; + ASSERT(instr->IsMarkedAsCall()); + + ASM_UNIMPLEMENTED_BREAK("DoDeclareGlobals"); + // TODO(all): if Mov could handle object in new space then it could be used + // here. + __ LoadHeapObject(scratch1, instr->hydrogen()->pairs()); + __ Mov(scratch2, Smi::FromInt(instr->hydrogen()->flags())); + __ Push(cp, scratch1, scratch2); // The context is the first argument. + CallRuntime(Runtime::kHiddenDeclareGlobals, 3, instr); +} + + +void LCodeGen::DoDeferredStackCheck(LStackCheck* instr) { + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + LoadContextFromDeferred(instr->context()); + __ CallRuntimeSaveDoubles(Runtime::kHiddenStackGuard); + RecordSafepointWithLazyDeopt( + instr, RECORD_SAFEPOINT_WITH_REGISTERS_AND_NO_ARGUMENTS); + ASSERT(instr->HasEnvironment()); + LEnvironment* env = instr->environment(); + safepoints_.RecordLazyDeoptimizationIndex(env->deoptimization_index()); +} + + +void LCodeGen::DoStackCheck(LStackCheck* instr) { + class DeferredStackCheck: public LDeferredCode { + public: + DeferredStackCheck(LCodeGen* codegen, LStackCheck* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredStackCheck(instr_); } + virtual LInstruction* instr() { return instr_; } + private: + LStackCheck* instr_; + }; + + ASSERT(instr->HasEnvironment()); + LEnvironment* env = instr->environment(); + // There is no LLazyBailout instruction for stack-checks. We have to + // prepare for lazy deoptimization explicitly here. + if (instr->hydrogen()->is_function_entry()) { + // Perform stack overflow check. + Label done; + __ CompareRoot(masm()->StackPointer(), Heap::kStackLimitRootIndex); + __ B(hs, &done); + + PredictableCodeSizeScope predictable(masm_, + Assembler::kCallSizeWithRelocation); + ASSERT(instr->context()->IsRegister()); + ASSERT(ToRegister(instr->context()).is(cp)); + CallCode(isolate()->builtins()->StackCheck(), + RelocInfo::CODE_TARGET, + instr); + __ Bind(&done); + } else { + ASSERT(instr->hydrogen()->is_backwards_branch()); + // Perform stack overflow check if this goto needs it before jumping. + DeferredStackCheck* deferred_stack_check = + new(zone()) DeferredStackCheck(this, instr); + __ CompareRoot(masm()->StackPointer(), Heap::kStackLimitRootIndex); + __ B(lo, deferred_stack_check->entry()); + + EnsureSpaceForLazyDeopt(Deoptimizer::patch_size()); + __ Bind(instr->done_label()); + deferred_stack_check->SetExit(instr->done_label()); + RegisterEnvironmentForDeoptimization(env, Safepoint::kLazyDeopt); + // Don't record a deoptimization index for the safepoint here. + // This will be done explicitly when emitting call and the safepoint in + // the deferred code. + } +} + + +void LCodeGen::DoStoreCodeEntry(LStoreCodeEntry* instr) { + Register function = ToRegister(instr->function()); + Register code_object = ToRegister(instr->code_object()); + Register temp = ToRegister(instr->temp()); + __ Add(temp, code_object, Code::kHeaderSize - kHeapObjectTag); + __ Str(temp, FieldMemOperand(function, JSFunction::kCodeEntryOffset)); +} + + +void LCodeGen::DoStoreContextSlot(LStoreContextSlot* instr) { + Register context = ToRegister(instr->context()); + Register value = ToRegister(instr->value()); + Register scratch = ToRegister(instr->temp()); + MemOperand target = ContextMemOperand(context, instr->slot_index()); + + Label skip_assignment; + + if (instr->hydrogen()->RequiresHoleCheck()) { + __ Ldr(scratch, target); + if (instr->hydrogen()->DeoptimizesOnHole()) { + DeoptimizeIfRoot(scratch, Heap::kTheHoleValueRootIndex, + instr->environment()); + } else { + __ JumpIfNotRoot(scratch, Heap::kTheHoleValueRootIndex, &skip_assignment); + } + } + + __ Str(value, target); + if (instr->hydrogen()->NeedsWriteBarrier()) { + SmiCheck check_needed = + instr->hydrogen()->value()->type().IsHeapObject() + ? OMIT_SMI_CHECK : INLINE_SMI_CHECK; + __ RecordWriteContextSlot(context, + target.offset(), + value, + scratch, + GetLinkRegisterState(), + kSaveFPRegs, + EMIT_REMEMBERED_SET, + check_needed); + } + __ Bind(&skip_assignment); +} + + +void LCodeGen::DoStoreGlobalCell(LStoreGlobalCell* instr) { + Register value = ToRegister(instr->value()); + Register cell = ToRegister(instr->temp1()); + + // Load the cell. + __ Mov(cell, Operand(instr->hydrogen()->cell().handle())); + + // If the cell we are storing to contains the hole it could have + // been deleted from the property dictionary. In that case, we need + // to update the property details in the property dictionary to mark + // it as no longer deleted. We deoptimize in that case. + if (instr->hydrogen()->RequiresHoleCheck()) { + Register payload = ToRegister(instr->temp2()); + __ Ldr(payload, FieldMemOperand(cell, Cell::kValueOffset)); + DeoptimizeIfRoot( + payload, Heap::kTheHoleValueRootIndex, instr->environment()); + } + + // Store the value. + __ Str(value, FieldMemOperand(cell, Cell::kValueOffset)); + // Cells are always rescanned, so no write barrier here. +} + + +void LCodeGen::DoStoreKeyedExternal(LStoreKeyedExternal* instr) { + Register ext_ptr = ToRegister(instr->elements()); + Register key = no_reg; + Register scratch; + ElementsKind elements_kind = instr->elements_kind(); + + bool key_is_smi = instr->hydrogen()->key()->representation().IsSmi(); + bool key_is_constant = instr->key()->IsConstantOperand(); + int constant_key = 0; + if (key_is_constant) { + ASSERT(instr->temp() == NULL); + constant_key = ToInteger32(LConstantOperand::cast(instr->key())); + if (constant_key & 0xf0000000) { + Abort(kArrayIndexConstantValueTooBig); + } + } else { + key = ToRegister(instr->key()); + scratch = ToRegister(instr->temp()); + } + + MemOperand dst = + PrepareKeyedExternalArrayOperand(key, ext_ptr, scratch, key_is_smi, + key_is_constant, constant_key, + elements_kind, + instr->base_offset()); + + if ((elements_kind == EXTERNAL_FLOAT32_ELEMENTS) || + (elements_kind == FLOAT32_ELEMENTS)) { + DoubleRegister value = ToDoubleRegister(instr->value()); + DoubleRegister dbl_scratch = double_scratch(); + __ Fcvt(dbl_scratch.S(), value); + __ Str(dbl_scratch.S(), dst); + } else if ((elements_kind == EXTERNAL_FLOAT64_ELEMENTS) || + (elements_kind == FLOAT64_ELEMENTS)) { + DoubleRegister value = ToDoubleRegister(instr->value()); + __ Str(value, dst); + } else { + Register value = ToRegister(instr->value()); + + switch (elements_kind) { + case EXTERNAL_UINT8_CLAMPED_ELEMENTS: + case EXTERNAL_INT8_ELEMENTS: + case EXTERNAL_UINT8_ELEMENTS: + case UINT8_ELEMENTS: + case UINT8_CLAMPED_ELEMENTS: + case INT8_ELEMENTS: + __ Strb(value, dst); + break; + case EXTERNAL_INT16_ELEMENTS: + case EXTERNAL_UINT16_ELEMENTS: + case INT16_ELEMENTS: + case UINT16_ELEMENTS: + __ Strh(value, dst); + break; + case EXTERNAL_INT32_ELEMENTS: + case EXTERNAL_UINT32_ELEMENTS: + case INT32_ELEMENTS: + case UINT32_ELEMENTS: + __ Str(value.W(), dst); + break; + case FLOAT32_ELEMENTS: + case FLOAT64_ELEMENTS: + case EXTERNAL_FLOAT32_ELEMENTS: + case EXTERNAL_FLOAT64_ELEMENTS: + case FAST_DOUBLE_ELEMENTS: + case FAST_ELEMENTS: + case FAST_SMI_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: + case FAST_HOLEY_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case DICTIONARY_ELEMENTS: + case SLOPPY_ARGUMENTS_ELEMENTS: + UNREACHABLE(); + break; + } + } +} + + +void LCodeGen::DoStoreKeyedFixedDouble(LStoreKeyedFixedDouble* instr) { + Register elements = ToRegister(instr->elements()); + DoubleRegister value = ToDoubleRegister(instr->value()); + MemOperand mem_op; + + if (instr->key()->IsConstantOperand()) { + int constant_key = ToInteger32(LConstantOperand::cast(instr->key())); + if (constant_key & 0xf0000000) { + Abort(kArrayIndexConstantValueTooBig); + } + int offset = instr->base_offset() + constant_key * kDoubleSize; + mem_op = MemOperand(elements, offset); + } else { + Register store_base = ToRegister(instr->temp()); + Register key = ToRegister(instr->key()); + bool key_is_tagged = instr->hydrogen()->key()->representation().IsSmi(); + mem_op = PrepareKeyedArrayOperand(store_base, elements, key, key_is_tagged, + instr->hydrogen()->elements_kind(), + instr->hydrogen()->representation(), + instr->base_offset()); + } + + if (instr->NeedsCanonicalization()) { + __ CanonicalizeNaN(double_scratch(), value); + __ Str(double_scratch(), mem_op); + } else { + __ Str(value, mem_op); + } +} + + +void LCodeGen::DoStoreKeyedFixed(LStoreKeyedFixed* instr) { + Register value = ToRegister(instr->value()); + Register elements = ToRegister(instr->elements()); + Register scratch = no_reg; + Register store_base = no_reg; + Register key = no_reg; + MemOperand mem_op; + + if (!instr->key()->IsConstantOperand() || + instr->hydrogen()->NeedsWriteBarrier()) { + scratch = ToRegister(instr->temp()); + } + + Representation representation = instr->hydrogen()->value()->representation(); + if (instr->key()->IsConstantOperand()) { + LConstantOperand* const_operand = LConstantOperand::cast(instr->key()); + int offset = instr->base_offset() + + ToInteger32(const_operand) * kPointerSize; + store_base = elements; + if (representation.IsInteger32()) { + ASSERT(instr->hydrogen()->store_mode() == STORE_TO_INITIALIZED_ENTRY); + ASSERT(instr->hydrogen()->elements_kind() == FAST_SMI_ELEMENTS); + STATIC_ASSERT((kSmiValueSize == 32) && (kSmiShift == 32) && + (kSmiTag == 0)); + mem_op = UntagSmiMemOperand(store_base, offset); + } else { + mem_op = MemOperand(store_base, offset); + } + } else { + store_base = scratch; + key = ToRegister(instr->key()); + bool key_is_tagged = instr->hydrogen()->key()->representation().IsSmi(); + + mem_op = PrepareKeyedArrayOperand(store_base, elements, key, key_is_tagged, + instr->hydrogen()->elements_kind(), + representation, instr->base_offset()); + } + + __ Store(value, mem_op, representation); + + if (instr->hydrogen()->NeedsWriteBarrier()) { + ASSERT(representation.IsTagged()); + // This assignment may cause element_addr to alias store_base. + Register element_addr = scratch; + SmiCheck check_needed = + instr->hydrogen()->value()->type().IsHeapObject() + ? OMIT_SMI_CHECK : INLINE_SMI_CHECK; + // Compute address of modified element and store it into key register. + __ Add(element_addr, mem_op.base(), mem_op.OffsetAsOperand()); + __ RecordWrite(elements, element_addr, value, GetLinkRegisterState(), + kSaveFPRegs, EMIT_REMEMBERED_SET, check_needed, + instr->hydrogen()->PointersToHereCheckForValue()); + } +} + + +void LCodeGen::DoStoreKeyedGeneric(LStoreKeyedGeneric* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->object()).Is(x2)); + ASSERT(ToRegister(instr->key()).Is(x1)); + ASSERT(ToRegister(instr->value()).Is(x0)); + + Handle<Code> ic = instr->strict_mode() == STRICT + ? isolate()->builtins()->KeyedStoreIC_Initialize_Strict() + : isolate()->builtins()->KeyedStoreIC_Initialize(); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoStoreNamedField(LStoreNamedField* instr) { + Representation representation = instr->representation(); + + Register object = ToRegister(instr->object()); + HObjectAccess access = instr->hydrogen()->access(); + int offset = access.offset(); + + if (access.IsExternalMemory()) { + ASSERT(!instr->hydrogen()->has_transition()); + ASSERT(!instr->hydrogen()->NeedsWriteBarrier()); + Register value = ToRegister(instr->value()); + __ Store(value, MemOperand(object, offset), representation); + return; + } + + __ AssertNotSmi(object); + + if (representation.IsDouble()) { + ASSERT(access.IsInobject()); + ASSERT(!instr->hydrogen()->has_transition()); + ASSERT(!instr->hydrogen()->NeedsWriteBarrier()); + FPRegister value = ToDoubleRegister(instr->value()); + __ Str(value, FieldMemOperand(object, offset)); + return; + } + + Register value = ToRegister(instr->value()); + + ASSERT(!representation.IsSmi() || + !instr->value()->IsConstantOperand() || + IsInteger32Constant(LConstantOperand::cast(instr->value()))); + + if (instr->hydrogen()->has_transition()) { + Handle<Map> transition = instr->hydrogen()->transition_map(); + AddDeprecationDependency(transition); + // Store the new map value. + Register new_map_value = ToRegister(instr->temp0()); + __ Mov(new_map_value, Operand(transition)); + __ Str(new_map_value, FieldMemOperand(object, HeapObject::kMapOffset)); + if (instr->hydrogen()->NeedsWriteBarrierForMap()) { + // Update the write barrier for the map field. + __ RecordWriteForMap(object, + new_map_value, + ToRegister(instr->temp1()), + GetLinkRegisterState(), + kSaveFPRegs); + } + } + + // Do the store. + Register destination; + if (access.IsInobject()) { + destination = object; + } else { + Register temp0 = ToRegister(instr->temp0()); + __ Ldr(temp0, FieldMemOperand(object, JSObject::kPropertiesOffset)); + destination = temp0; + } + + if (representation.IsSmi() && + instr->hydrogen()->value()->representation().IsInteger32()) { + ASSERT(instr->hydrogen()->store_mode() == STORE_TO_INITIALIZED_ENTRY); +#ifdef DEBUG + Register temp0 = ToRegister(instr->temp0()); + __ Ldr(temp0, FieldMemOperand(destination, offset)); + __ AssertSmi(temp0); + // If destination aliased temp0, restore it to the address calculated + // earlier. + if (destination.Is(temp0)) { + ASSERT(!access.IsInobject()); + __ Ldr(destination, FieldMemOperand(object, JSObject::kPropertiesOffset)); + } +#endif + STATIC_ASSERT(kSmiValueSize == 32 && kSmiShift == 32 && kSmiTag == 0); + __ Store(value, UntagSmiFieldMemOperand(destination, offset), + Representation::Integer32()); + } else { + __ Store(value, FieldMemOperand(destination, offset), representation); + } + if (instr->hydrogen()->NeedsWriteBarrier()) { + __ RecordWriteField(destination, + offset, + value, // Clobbered. + ToRegister(instr->temp1()), // Clobbered. + GetLinkRegisterState(), + kSaveFPRegs, + EMIT_REMEMBERED_SET, + instr->hydrogen()->SmiCheckForWriteBarrier(), + instr->hydrogen()->PointersToHereCheckForValue()); + } +} + + +void LCodeGen::DoStoreNamedGeneric(LStoreNamedGeneric* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->value()).is(x0)); + ASSERT(ToRegister(instr->object()).is(x1)); + + // Name must be in x2. + __ Mov(x2, Operand(instr->name())); + Handle<Code> ic = StoreIC::initialize_stub(isolate(), instr->strict_mode()); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoStringAdd(LStringAdd* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + ASSERT(ToRegister(instr->left()).Is(x1)); + ASSERT(ToRegister(instr->right()).Is(x0)); + StringAddStub stub(isolate(), + instr->hydrogen()->flags(), + instr->hydrogen()->pretenure_flag()); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoStringCharCodeAt(LStringCharCodeAt* instr) { + class DeferredStringCharCodeAt: public LDeferredCode { + public: + DeferredStringCharCodeAt(LCodeGen* codegen, LStringCharCodeAt* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredStringCharCodeAt(instr_); } + virtual LInstruction* instr() { return instr_; } + private: + LStringCharCodeAt* instr_; + }; + + DeferredStringCharCodeAt* deferred = + new(zone()) DeferredStringCharCodeAt(this, instr); + + StringCharLoadGenerator::Generate(masm(), + ToRegister(instr->string()), + ToRegister32(instr->index()), + ToRegister(instr->result()), + deferred->entry()); + __ Bind(deferred->exit()); +} + + +void LCodeGen::DoDeferredStringCharCodeAt(LStringCharCodeAt* instr) { + Register string = ToRegister(instr->string()); + Register result = ToRegister(instr->result()); + + // TODO(3095996): Get rid of this. For now, we need to make the + // result register contain a valid pointer because it is already + // contained in the register pointer map. + __ Mov(result, 0); + + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + __ Push(string); + // Push the index as a smi. This is safe because of the checks in + // DoStringCharCodeAt above. + Register index = ToRegister(instr->index()); + __ SmiTagAndPush(index); + + CallRuntimeFromDeferred(Runtime::kHiddenStringCharCodeAt, 2, instr, + instr->context()); + __ AssertSmi(x0); + __ SmiUntag(x0); + __ StoreToSafepointRegisterSlot(x0, result); +} + + +void LCodeGen::DoStringCharFromCode(LStringCharFromCode* instr) { + class DeferredStringCharFromCode: public LDeferredCode { + public: + DeferredStringCharFromCode(LCodeGen* codegen, LStringCharFromCode* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredStringCharFromCode(instr_); } + virtual LInstruction* instr() { return instr_; } + private: + LStringCharFromCode* instr_; + }; + + DeferredStringCharFromCode* deferred = + new(zone()) DeferredStringCharFromCode(this, instr); + + ASSERT(instr->hydrogen()->value()->representation().IsInteger32()); + Register char_code = ToRegister32(instr->char_code()); + Register result = ToRegister(instr->result()); + + __ Cmp(char_code, String::kMaxOneByteCharCode); + __ B(hi, deferred->entry()); + __ LoadRoot(result, Heap::kSingleCharacterStringCacheRootIndex); + __ Add(result, result, FixedArray::kHeaderSize - kHeapObjectTag); + __ Ldr(result, MemOperand(result, char_code, SXTW, kPointerSizeLog2)); + __ CompareRoot(result, Heap::kUndefinedValueRootIndex); + __ B(eq, deferred->entry()); + __ Bind(deferred->exit()); +} + + +void LCodeGen::DoDeferredStringCharFromCode(LStringCharFromCode* instr) { + Register char_code = ToRegister(instr->char_code()); + Register result = ToRegister(instr->result()); + + // TODO(3095996): Get rid of this. For now, we need to make the + // result register contain a valid pointer because it is already + // contained in the register pointer map. + __ Mov(result, 0); + + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + __ SmiTagAndPush(char_code); + CallRuntimeFromDeferred(Runtime::kCharFromCode, 1, instr, instr->context()); + __ StoreToSafepointRegisterSlot(x0, result); +} + + +void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + Token::Value op = instr->op(); + + Handle<Code> ic = CompareIC::GetUninitialized(isolate(), op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + InlineSmiCheckInfo::EmitNotInlined(masm()); + + Condition condition = TokenToCondition(op, false); + + EmitCompareAndBranch(instr, condition, x0, 0); +} + + +void LCodeGen::DoSubI(LSubI* instr) { + bool can_overflow = instr->hydrogen()->CheckFlag(HValue::kCanOverflow); + Register result = ToRegister32(instr->result()); + Register left = ToRegister32(instr->left()); + Operand right = ToShiftedRightOperand32I(instr->right(), instr); + + if (can_overflow) { + __ Subs(result, left, right); + DeoptimizeIf(vs, instr->environment()); + } else { + __ Sub(result, left, right); + } +} + + +void LCodeGen::DoSubS(LSubS* instr) { + bool can_overflow = instr->hydrogen()->CheckFlag(HValue::kCanOverflow); + Register result = ToRegister(instr->result()); + Register left = ToRegister(instr->left()); + Operand right = ToOperand(instr->right()); + if (can_overflow) { + __ Subs(result, left, right); + DeoptimizeIf(vs, instr->environment()); + } else { + __ Sub(result, left, right); + } +} + + +void LCodeGen::DoDeferredTaggedToI(LTaggedToI* instr, + LOperand* value, + LOperand* temp1, + LOperand* temp2) { + Register input = ToRegister(value); + Register scratch1 = ToRegister(temp1); + DoubleRegister dbl_scratch1 = double_scratch(); + + Label done; + + // Load heap object map. + __ Ldr(scratch1, FieldMemOperand(input, HeapObject::kMapOffset)); + + if (instr->truncating()) { + Register output = ToRegister(instr->result()); + Label check_bools; + + // If it's not a heap number, jump to undefined check. + __ JumpIfNotRoot(scratch1, Heap::kHeapNumberMapRootIndex, &check_bools); + + // A heap number: load value and convert to int32 using truncating function. + __ TruncateHeapNumberToI(output, input); + __ B(&done); + + __ Bind(&check_bools); + + Register true_root = output; + Register false_root = scratch1; + __ LoadTrueFalseRoots(true_root, false_root); + __ Cmp(input, true_root); + __ Cset(output, eq); + __ Ccmp(input, false_root, ZFlag, ne); + __ B(eq, &done); + + // Output contains zero, undefined is converted to zero for truncating + // conversions. + DeoptimizeIfNotRoot(input, Heap::kUndefinedValueRootIndex, + instr->environment()); + } else { + Register output = ToRegister32(instr->result()); + + DoubleRegister dbl_scratch2 = ToDoubleRegister(temp2); + + // Deoptimized if it's not a heap number. + DeoptimizeIfNotRoot(scratch1, Heap::kHeapNumberMapRootIndex, + instr->environment()); + + // A heap number: load value and convert to int32 using non-truncating + // function. If the result is out of range, branch to deoptimize. + __ Ldr(dbl_scratch1, FieldMemOperand(input, HeapNumber::kValueOffset)); + __ TryRepresentDoubleAsInt32(output, dbl_scratch1, dbl_scratch2); + DeoptimizeIf(ne, instr->environment()); + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ Cmp(output, 0); + __ B(ne, &done); + __ Fmov(scratch1, dbl_scratch1); + DeoptimizeIfNegative(scratch1, instr->environment()); + } + } + __ Bind(&done); +} + + +void LCodeGen::DoTaggedToI(LTaggedToI* instr) { + class DeferredTaggedToI: public LDeferredCode { + public: + DeferredTaggedToI(LCodeGen* codegen, LTaggedToI* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { + codegen()->DoDeferredTaggedToI(instr_, instr_->value(), instr_->temp1(), + instr_->temp2()); + } + + virtual LInstruction* instr() { return instr_; } + private: + LTaggedToI* instr_; + }; + + Register input = ToRegister(instr->value()); + Register output = ToRegister(instr->result()); + + if (instr->hydrogen()->value()->representation().IsSmi()) { + __ SmiUntag(output, input); + } else { + DeferredTaggedToI* deferred = new(zone()) DeferredTaggedToI(this, instr); + + __ JumpIfNotSmi(input, deferred->entry()); + __ SmiUntag(output, input); + __ Bind(deferred->exit()); + } +} + + +void LCodeGen::DoThisFunction(LThisFunction* instr) { + Register result = ToRegister(instr->result()); + __ Ldr(result, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); +} + + +void LCodeGen::DoToFastProperties(LToFastProperties* instr) { + ASSERT(ToRegister(instr->value()).Is(x0)); + ASSERT(ToRegister(instr->result()).Is(x0)); + __ Push(x0); + CallRuntime(Runtime::kToFastProperties, 1, instr); +} + + +void LCodeGen::DoRegExpLiteral(LRegExpLiteral* instr) { + ASSERT(ToRegister(instr->context()).is(cp)); + Label materialized; + // Registers will be used as follows: + // x7 = literals array. + // x1 = regexp literal. + // x0 = regexp literal clone. + // x10-x12 are used as temporaries. + int literal_offset = + FixedArray::OffsetOfElementAt(instr->hydrogen()->literal_index()); + __ LoadObject(x7, instr->hydrogen()->literals()); + __ Ldr(x1, FieldMemOperand(x7, literal_offset)); + __ JumpIfNotRoot(x1, Heap::kUndefinedValueRootIndex, &materialized); + + // Create regexp literal using runtime function + // Result will be in x0. + __ Mov(x12, Operand(Smi::FromInt(instr->hydrogen()->literal_index()))); + __ Mov(x11, Operand(instr->hydrogen()->pattern())); + __ Mov(x10, Operand(instr->hydrogen()->flags())); + __ Push(x7, x12, x11, x10); + CallRuntime(Runtime::kHiddenMaterializeRegExpLiteral, 4, instr); + __ Mov(x1, x0); + + __ Bind(&materialized); + int size = JSRegExp::kSize + JSRegExp::kInObjectFieldCount * kPointerSize; + Label allocated, runtime_allocate; + + __ Allocate(size, x0, x10, x11, &runtime_allocate, TAG_OBJECT); + __ B(&allocated); + + __ Bind(&runtime_allocate); + __ Mov(x0, Smi::FromInt(size)); + __ Push(x1, x0); + CallRuntime(Runtime::kHiddenAllocateInNewSpace, 1, instr); + __ Pop(x1); + + __ Bind(&allocated); + // Copy the content into the newly allocated memory. + __ CopyFields(x0, x1, CPURegList(x10, x11, x12), size / kPointerSize); +} + + +void LCodeGen::DoTransitionElementsKind(LTransitionElementsKind* instr) { + Register object = ToRegister(instr->object()); + + Handle<Map> from_map = instr->original_map(); + Handle<Map> to_map = instr->transitioned_map(); + ElementsKind from_kind = instr->from_kind(); + ElementsKind to_kind = instr->to_kind(); + + Label not_applicable; + + if (IsSimpleMapChangeTransition(from_kind, to_kind)) { + Register temp1 = ToRegister(instr->temp1()); + Register new_map = ToRegister(instr->temp2()); + __ CheckMap(object, temp1, from_map, ¬_applicable, DONT_DO_SMI_CHECK); + __ Mov(new_map, Operand(to_map)); + __ Str(new_map, FieldMemOperand(object, HeapObject::kMapOffset)); + // Write barrier. + __ RecordWriteForMap(object, new_map, temp1, GetLinkRegisterState(), + kDontSaveFPRegs); + } else { + { + UseScratchRegisterScope temps(masm()); + // Use the temp register only in a restricted scope - the codegen checks + // that we do not use any register across a call. + __ CheckMap(object, temps.AcquireX(), from_map, ¬_applicable, + DONT_DO_SMI_CHECK); + } + ASSERT(object.is(x0)); + ASSERT(ToRegister(instr->context()).is(cp)); + PushSafepointRegistersScope scope( + this, Safepoint::kWithRegistersAndDoubles); + __ Mov(x1, Operand(to_map)); + bool is_js_array = from_map->instance_type() == JS_ARRAY_TYPE; + TransitionElementsKindStub stub(isolate(), from_kind, to_kind, is_js_array); + __ CallStub(&stub); + RecordSafepointWithRegistersAndDoubles( + instr->pointer_map(), 0, Safepoint::kLazyDeopt); + } + __ Bind(¬_applicable); +} + + +void LCodeGen::DoTrapAllocationMemento(LTrapAllocationMemento* instr) { + Register object = ToRegister(instr->object()); + Register temp1 = ToRegister(instr->temp1()); + Register temp2 = ToRegister(instr->temp2()); + + Label no_memento_found; + __ TestJSArrayForAllocationMemento(object, temp1, temp2, &no_memento_found); + DeoptimizeIf(eq, instr->environment()); + __ Bind(&no_memento_found); +} + + +void LCodeGen::DoTruncateDoubleToIntOrSmi(LTruncateDoubleToIntOrSmi* instr) { + DoubleRegister input = ToDoubleRegister(instr->value()); + Register result = ToRegister(instr->result()); + __ TruncateDoubleToI(result, input); + if (instr->tag_result()) { + __ SmiTag(result, result); + } +} + + +void LCodeGen::DoTypeof(LTypeof* instr) { + Register input = ToRegister(instr->value()); + __ Push(input); + CallRuntime(Runtime::kTypeof, 1, instr); +} + + +void LCodeGen::DoTypeofIsAndBranch(LTypeofIsAndBranch* instr) { + Handle<String> type_name = instr->type_literal(); + Label* true_label = instr->TrueLabel(chunk_); + Label* false_label = instr->FalseLabel(chunk_); + Register value = ToRegister(instr->value()); + + Factory* factory = isolate()->factory(); + if (String::Equals(type_name, factory->number_string())) { + ASSERT(instr->temp1() != NULL); + Register map = ToRegister(instr->temp1()); + + __ JumpIfSmi(value, true_label); + __ Ldr(map, FieldMemOperand(value, HeapObject::kMapOffset)); + __ CompareRoot(map, Heap::kHeapNumberMapRootIndex); + EmitBranch(instr, eq); + + } else if (String::Equals(type_name, factory->string_string())) { + ASSERT((instr->temp1() != NULL) && (instr->temp2() != NULL)); + Register map = ToRegister(instr->temp1()); + Register scratch = ToRegister(instr->temp2()); + + __ JumpIfSmi(value, false_label); + __ JumpIfObjectType( + value, map, scratch, FIRST_NONSTRING_TYPE, false_label, ge); + __ Ldrb(scratch, FieldMemOperand(map, Map::kBitFieldOffset)); + EmitTestAndBranch(instr, eq, scratch, 1 << Map::kIsUndetectable); + + } else if (String::Equals(type_name, factory->symbol_string())) { + ASSERT((instr->temp1() != NULL) && (instr->temp2() != NULL)); + Register map = ToRegister(instr->temp1()); + Register scratch = ToRegister(instr->temp2()); + + __ JumpIfSmi(value, false_label); + __ CompareObjectType(value, map, scratch, SYMBOL_TYPE); + EmitBranch(instr, eq); + + } else if (String::Equals(type_name, factory->boolean_string())) { + __ JumpIfRoot(value, Heap::kTrueValueRootIndex, true_label); + __ CompareRoot(value, Heap::kFalseValueRootIndex); + EmitBranch(instr, eq); + + } else if (FLAG_harmony_typeof && + String::Equals(type_name, factory->null_string())) { + __ CompareRoot(value, Heap::kNullValueRootIndex); + EmitBranch(instr, eq); + + } else if (String::Equals(type_name, factory->undefined_string())) { + ASSERT(instr->temp1() != NULL); + Register scratch = ToRegister(instr->temp1()); + + __ JumpIfRoot(value, Heap::kUndefinedValueRootIndex, true_label); + __ JumpIfSmi(value, false_label); + // Check for undetectable objects and jump to the true branch in this case. + __ Ldr(scratch, FieldMemOperand(value, HeapObject::kMapOffset)); + __ Ldrb(scratch, FieldMemOperand(scratch, Map::kBitFieldOffset)); + EmitTestAndBranch(instr, ne, scratch, 1 << Map::kIsUndetectable); + + } else if (String::Equals(type_name, factory->function_string())) { + STATIC_ASSERT(NUM_OF_CALLABLE_SPEC_OBJECT_TYPES == 2); + ASSERT(instr->temp1() != NULL); + Register type = ToRegister(instr->temp1()); + + __ JumpIfSmi(value, false_label); + __ JumpIfObjectType(value, type, type, JS_FUNCTION_TYPE, true_label); + // HeapObject's type has been loaded into type register by JumpIfObjectType. + EmitCompareAndBranch(instr, eq, type, JS_FUNCTION_PROXY_TYPE); + + } else if (String::Equals(type_name, factory->object_string())) { + ASSERT((instr->temp1() != NULL) && (instr->temp2() != NULL)); + Register map = ToRegister(instr->temp1()); + Register scratch = ToRegister(instr->temp2()); + + __ JumpIfSmi(value, false_label); + if (!FLAG_harmony_typeof) { + __ JumpIfRoot(value, Heap::kNullValueRootIndex, true_label); + } + __ JumpIfObjectType(value, map, scratch, + FIRST_NONCALLABLE_SPEC_OBJECT_TYPE, false_label, lt); + __ CompareInstanceType(map, scratch, LAST_NONCALLABLE_SPEC_OBJECT_TYPE); + __ B(gt, false_label); + // Check for undetectable objects => false. + __ Ldrb(scratch, FieldMemOperand(map, Map::kBitFieldOffset)); + EmitTestAndBranch(instr, eq, scratch, 1 << Map::kIsUndetectable); + + } else { + __ B(false_label); + } +} + + +void LCodeGen::DoUint32ToDouble(LUint32ToDouble* instr) { + __ Ucvtf(ToDoubleRegister(instr->result()), ToRegister32(instr->value())); +} + + +void LCodeGen::DoCheckMapValue(LCheckMapValue* instr) { + Register object = ToRegister(instr->value()); + Register map = ToRegister(instr->map()); + Register temp = ToRegister(instr->temp()); + __ Ldr(temp, FieldMemOperand(object, HeapObject::kMapOffset)); + __ Cmp(map, temp); + DeoptimizeIf(ne, instr->environment()); +} + + +void LCodeGen::DoWrapReceiver(LWrapReceiver* instr) { + Register receiver = ToRegister(instr->receiver()); + Register function = ToRegister(instr->function()); + Register result = ToRegister(instr->result()); + + // If the receiver is null or undefined, we have to pass the global object as + // a receiver to normal functions. Values have to be passed unchanged to + // builtins and strict-mode functions. + Label global_object, done, copy_receiver; + + if (!instr->hydrogen()->known_function()) { + __ Ldr(result, FieldMemOperand(function, + JSFunction::kSharedFunctionInfoOffset)); + + // CompilerHints is an int32 field. See objects.h. + __ Ldr(result.W(), + FieldMemOperand(result, SharedFunctionInfo::kCompilerHintsOffset)); + + // Do not transform the receiver to object for strict mode functions. + __ Tbnz(result, SharedFunctionInfo::kStrictModeFunction, ©_receiver); + + // Do not transform the receiver to object for builtins. + __ Tbnz(result, SharedFunctionInfo::kNative, ©_receiver); + } + + // Normal function. Replace undefined or null with global receiver. + __ JumpIfRoot(receiver, Heap::kNullValueRootIndex, &global_object); + __ JumpIfRoot(receiver, Heap::kUndefinedValueRootIndex, &global_object); + + // Deoptimize if the receiver is not a JS object. + DeoptimizeIfSmi(receiver, instr->environment()); + __ CompareObjectType(receiver, result, result, FIRST_SPEC_OBJECT_TYPE); + __ B(ge, ©_receiver); + Deoptimize(instr->environment()); + + __ Bind(&global_object); + __ Ldr(result, FieldMemOperand(function, JSFunction::kContextOffset)); + __ Ldr(result, ContextMemOperand(result, Context::GLOBAL_OBJECT_INDEX)); + __ Ldr(result, FieldMemOperand(result, GlobalObject::kGlobalReceiverOffset)); + __ B(&done); + + __ Bind(©_receiver); + __ Mov(result, receiver); + __ Bind(&done); +} + + +void LCodeGen::DoDeferredLoadMutableDouble(LLoadFieldByIndex* instr, + Register result, + Register object, + Register index) { + PushSafepointRegistersScope scope(this, Safepoint::kWithRegisters); + __ Push(object); + __ Push(index); + __ Mov(cp, 0); + __ CallRuntimeSaveDoubles(Runtime::kLoadMutableDouble); + RecordSafepointWithRegisters( + instr->pointer_map(), 2, Safepoint::kNoLazyDeopt); + __ StoreToSafepointRegisterSlot(x0, result); +} + + +void LCodeGen::DoLoadFieldByIndex(LLoadFieldByIndex* instr) { + class DeferredLoadMutableDouble V8_FINAL : public LDeferredCode { + public: + DeferredLoadMutableDouble(LCodeGen* codegen, + LLoadFieldByIndex* instr, + Register result, + Register object, + Register index) + : LDeferredCode(codegen), + instr_(instr), + result_(result), + object_(object), + index_(index) { + } + virtual void Generate() V8_OVERRIDE { + codegen()->DoDeferredLoadMutableDouble(instr_, result_, object_, index_); + } + virtual LInstruction* instr() V8_OVERRIDE { return instr_; } + private: + LLoadFieldByIndex* instr_; + Register result_; + Register object_; + Register index_; + }; + Register object = ToRegister(instr->object()); + Register index = ToRegister(instr->index()); + Register result = ToRegister(instr->result()); + + __ AssertSmi(index); + + DeferredLoadMutableDouble* deferred; + deferred = new(zone()) DeferredLoadMutableDouble( + this, instr, result, object, index); + + Label out_of_object, done; + + __ TestAndBranchIfAnySet( + index, reinterpret_cast<uint64_t>(Smi::FromInt(1)), deferred->entry()); + __ Mov(index, Operand(index, ASR, 1)); + + __ Cmp(index, Smi::FromInt(0)); + __ B(lt, &out_of_object); + + STATIC_ASSERT(kPointerSizeLog2 > kSmiTagSize); + __ Add(result, object, Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ Ldr(result, FieldMemOperand(result, JSObject::kHeaderSize)); + + __ B(&done); + + __ Bind(&out_of_object); + __ Ldr(result, FieldMemOperand(object, JSObject::kPropertiesOffset)); + // Index is equal to negated out of object property index plus 1. + __ Sub(result, result, Operand::UntagSmiAndScale(index, kPointerSizeLog2)); + __ Ldr(result, FieldMemOperand(result, + FixedArray::kHeaderSize - kPointerSize)); + __ Bind(deferred->exit()); + __ Bind(&done); +} + + +void LCodeGen::DoStoreFrameContext(LStoreFrameContext* instr) { + Register context = ToRegister(instr->context()); + __ Str(context, MemOperand(fp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoAllocateBlockContext(LAllocateBlockContext* instr) { + Handle<ScopeInfo> scope_info = instr->scope_info(); + __ Push(scope_info); + __ Push(ToRegister(instr->function())); + CallRuntime(Runtime::kHiddenPushBlockContext, 2, instr); + RecordSafepoint(Safepoint::kNoLazyDeopt); +} + + + +} } // namespace v8::internal diff --git a/chromium/v8/src/arm64/lithium-codegen-arm64.h b/chromium/v8/src/arm64/lithium-codegen-arm64.h new file mode 100644 index 00000000000..43cf13f9fe9 --- /dev/null +++ b/chromium/v8/src/arm64/lithium-codegen-arm64.h @@ -0,0 +1,506 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_LITHIUM_CODEGEN_ARM64_H_ +#define V8_ARM64_LITHIUM_CODEGEN_ARM64_H_ + +#include "src/arm64/lithium-arm64.h" + +#include "src/arm64/lithium-gap-resolver-arm64.h" +#include "src/deoptimizer.h" +#include "src/lithium-codegen.h" +#include "src/safepoint-table.h" +#include "src/scopes.h" +#include "src/utils.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LDeferredCode; +class SafepointGenerator; +class BranchGenerator; + +class LCodeGen: public LCodeGenBase { + public: + LCodeGen(LChunk* chunk, MacroAssembler* assembler, CompilationInfo* info) + : LCodeGenBase(chunk, assembler, info), + deoptimizations_(4, info->zone()), + deopt_jump_table_(4, info->zone()), + deoptimization_literals_(8, info->zone()), + inlined_function_count_(0), + scope_(info->scope()), + translations_(info->zone()), + deferred_(8, info->zone()), + osr_pc_offset_(-1), + frame_is_built_(false), + safepoints_(info->zone()), + resolver_(this), + expected_safepoint_kind_(Safepoint::kSimple), + after_push_argument_(false), + inlined_arguments_(false) { + PopulateDeoptimizationLiteralsWithInlinedFunctions(); + } + + ~LCodeGen() { + ASSERT(!after_push_argument_ || inlined_arguments_); + } + + // Simple accessors. + Scope* scope() const { return scope_; } + + int LookupDestination(int block_id) const { + return chunk()->LookupDestination(block_id); + } + + bool IsNextEmittedBlock(int block_id) const { + return LookupDestination(block_id) == GetNextEmittedBlock(); + } + + bool NeedsEagerFrame() const { + return GetStackSlotCount() > 0 || + info()->is_non_deferred_calling() || + !info()->IsStub() || + info()->requires_frame(); + } + bool NeedsDeferredFrame() const { + return !NeedsEagerFrame() && info()->is_deferred_calling(); + } + + LinkRegisterStatus GetLinkRegisterState() const { + return frame_is_built_ ? kLRHasBeenSaved : kLRHasNotBeenSaved; + } + + // Try to generate code for the entire chunk, but it may fail if the + // chunk contains constructs we cannot handle. Returns true if the + // code generation attempt succeeded. + bool GenerateCode(); + + // Finish the code by setting stack height, safepoint, and bailout + // information on it. + void FinishCode(Handle<Code> code); + + enum IntegerSignedness { SIGNED_INT32, UNSIGNED_INT32 }; + // Support for converting LOperands to assembler types. + // LOperand must be a register. + Register ToRegister(LOperand* op) const; + Register ToRegister32(LOperand* op) const; + Operand ToOperand(LOperand* op); + Operand ToOperand32I(LOperand* op); + Operand ToOperand32U(LOperand* op); + enum StackMode { kMustUseFramePointer, kCanUseStackPointer }; + MemOperand ToMemOperand(LOperand* op, + StackMode stack_mode = kCanUseStackPointer) const; + Handle<Object> ToHandle(LConstantOperand* op) const; + + template<class LI> + Operand ToShiftedRightOperand32I(LOperand* right, + LI* shift_info) { + return ToShiftedRightOperand32(right, shift_info, SIGNED_INT32); + } + template<class LI> + Operand ToShiftedRightOperand32U(LOperand* right, + LI* shift_info) { + return ToShiftedRightOperand32(right, shift_info, UNSIGNED_INT32); + } + template<class LI> + Operand ToShiftedRightOperand32(LOperand* right, + LI* shift_info, + IntegerSignedness signedness); + + int JSShiftAmountFromLConstant(LOperand* constant) { + return ToInteger32(LConstantOperand::cast(constant)) & 0x1f; + } + + // TODO(jbramley): Examine these helpers and check that they make sense. + // IsInteger32Constant returns true for smi constants, for example. + bool IsInteger32Constant(LConstantOperand* op) const; + bool IsSmi(LConstantOperand* op) const; + + int32_t ToInteger32(LConstantOperand* op) const; + Smi* ToSmi(LConstantOperand* op) const; + double ToDouble(LConstantOperand* op) const; + DoubleRegister ToDoubleRegister(LOperand* op) const; + + // Declare methods that deal with the individual node types. +#define DECLARE_DO(type) void Do##type(L##type* node); + LITHIUM_CONCRETE_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + private: + // Return a double scratch register which can be used locally + // when generating code for a lithium instruction. + DoubleRegister double_scratch() { return crankshaft_fp_scratch; } + + // Deferred code support. + void DoDeferredNumberTagD(LNumberTagD* instr); + void DoDeferredStackCheck(LStackCheck* instr); + void DoDeferredStringCharCodeAt(LStringCharCodeAt* instr); + void DoDeferredStringCharFromCode(LStringCharFromCode* instr); + void DoDeferredMathAbsTagged(LMathAbsTagged* instr, + Label* exit, + Label* allocation_entry); + + void DoDeferredNumberTagU(LInstruction* instr, + LOperand* value, + LOperand* temp1, + LOperand* temp2); + void DoDeferredTaggedToI(LTaggedToI* instr, + LOperand* value, + LOperand* temp1, + LOperand* temp2); + void DoDeferredAllocate(LAllocate* instr); + void DoDeferredInstanceOfKnownGlobal(LInstanceOfKnownGlobal* instr); + void DoDeferredInstanceMigration(LCheckMaps* instr, Register object); + void DoDeferredLoadMutableDouble(LLoadFieldByIndex* instr, + Register result, + Register object, + Register index); + + Operand ToOperand32(LOperand* op, IntegerSignedness signedness); + + static Condition TokenToCondition(Token::Value op, bool is_unsigned); + void EmitGoto(int block); + void DoGap(LGap* instr); + + // Generic version of EmitBranch. It contains some code to avoid emitting a + // branch on the next emitted basic block where we could just fall-through. + // You shouldn't use that directly but rather consider one of the helper like + // LCodeGen::EmitBranch, LCodeGen::EmitCompareAndBranch... + template<class InstrType> + void EmitBranchGeneric(InstrType instr, + const BranchGenerator& branch); + + template<class InstrType> + void EmitBranch(InstrType instr, Condition condition); + + template<class InstrType> + void EmitCompareAndBranch(InstrType instr, + Condition condition, + const Register& lhs, + const Operand& rhs); + + template<class InstrType> + void EmitTestAndBranch(InstrType instr, + Condition condition, + const Register& value, + uint64_t mask); + + template<class InstrType> + void EmitBranchIfNonZeroNumber(InstrType instr, + const FPRegister& value, + const FPRegister& scratch); + + template<class InstrType> + void EmitBranchIfHeapNumber(InstrType instr, + const Register& value); + + template<class InstrType> + void EmitBranchIfRoot(InstrType instr, + const Register& value, + Heap::RootListIndex index); + + // Emits optimized code to deep-copy the contents of statically known object + // graphs (e.g. object literal boilerplate). Expects a pointer to the + // allocated destination object in the result register, and a pointer to the + // source object in the source register. + void EmitDeepCopy(Handle<JSObject> object, + Register result, + Register source, + Register scratch, + int* offset, + AllocationSiteMode mode); + + // Emits optimized code for %_IsString(x). Preserves input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitIsString(Register input, Register temp1, Label* is_not_string, + SmiCheck check_needed); + + int DefineDeoptimizationLiteral(Handle<Object> literal); + void PopulateDeoptimizationData(Handle<Code> code); + void PopulateDeoptimizationLiteralsWithInlinedFunctions(); + + MemOperand BuildSeqStringOperand(Register string, + Register temp, + LOperand* index, + String::Encoding encoding); + void DeoptimizeBranch( + LEnvironment* environment, + BranchType branch_type, Register reg = NoReg, int bit = -1, + Deoptimizer::BailoutType* override_bailout_type = NULL); + void Deoptimize(LEnvironment* environment, + Deoptimizer::BailoutType* override_bailout_type = NULL); + void DeoptimizeIf(Condition cond, LEnvironment* environment); + void DeoptimizeIfZero(Register rt, LEnvironment* environment); + void DeoptimizeIfNotZero(Register rt, LEnvironment* environment); + void DeoptimizeIfNegative(Register rt, LEnvironment* environment); + void DeoptimizeIfSmi(Register rt, LEnvironment* environment); + void DeoptimizeIfNotSmi(Register rt, LEnvironment* environment); + void DeoptimizeIfRoot(Register rt, + Heap::RootListIndex index, + LEnvironment* environment); + void DeoptimizeIfNotRoot(Register rt, + Heap::RootListIndex index, + LEnvironment* environment); + void DeoptimizeIfMinusZero(DoubleRegister input, LEnvironment* environment); + void DeoptimizeIfBitSet(Register rt, int bit, LEnvironment* environment); + void DeoptimizeIfBitClear(Register rt, int bit, LEnvironment* environment); + + MemOperand PrepareKeyedExternalArrayOperand(Register key, + Register base, + Register scratch, + bool key_is_smi, + bool key_is_constant, + int constant_key, + ElementsKind elements_kind, + int base_offset); + MemOperand PrepareKeyedArrayOperand(Register base, + Register elements, + Register key, + bool key_is_tagged, + ElementsKind elements_kind, + Representation representation, + int base_offset); + + void RegisterEnvironmentForDeoptimization(LEnvironment* environment, + Safepoint::DeoptMode mode); + + int GetStackSlotCount() const { return chunk()->spill_slot_count(); } + + void AddDeferredCode(LDeferredCode* code) { deferred_.Add(code, zone()); } + + // Emit frame translation commands for an environment. + void WriteTranslation(LEnvironment* environment, Translation* translation); + + void AddToTranslation(LEnvironment* environment, + Translation* translation, + LOperand* op, + bool is_tagged, + bool is_uint32, + int* object_index_pointer, + int* dematerialized_index_pointer); + + void SaveCallerDoubles(); + void RestoreCallerDoubles(); + + // Code generation steps. Returns true if code generation should continue. + void GenerateBodyInstructionPre(LInstruction* instr) V8_OVERRIDE; + bool GeneratePrologue(); + bool GenerateDeferredCode(); + bool GenerateDeoptJumpTable(); + bool GenerateSafepointTable(); + + // Generates the custom OSR entrypoint and sets the osr_pc_offset. + void GenerateOsrPrologue(); + + enum SafepointMode { + RECORD_SIMPLE_SAFEPOINT, + RECORD_SAFEPOINT_WITH_REGISTERS_AND_NO_ARGUMENTS + }; + + void CallCode(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr); + + void CallCodeGeneric(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr, + SafepointMode safepoint_mode); + + void CallRuntime(const Runtime::Function* function, + int num_arguments, + LInstruction* instr, + SaveFPRegsMode save_doubles = kDontSaveFPRegs); + + void CallRuntime(Runtime::FunctionId id, + int num_arguments, + LInstruction* instr) { + const Runtime::Function* function = Runtime::FunctionForId(id); + CallRuntime(function, num_arguments, instr); + } + + void LoadContextFromDeferred(LOperand* context); + void CallRuntimeFromDeferred(Runtime::FunctionId id, + int argc, + LInstruction* instr, + LOperand* context); + + // Generate a direct call to a known function. + // If the function is already loaded into x1 by the caller, function_reg may + // be set to x1. Otherwise, it must be NoReg, and CallKnownFunction will + // automatically load it. + void CallKnownFunction(Handle<JSFunction> function, + int formal_parameter_count, + int arity, + LInstruction* instr, + Register function_reg = NoReg); + + // Support for recording safepoint and position information. + void RecordAndWritePosition(int position) V8_OVERRIDE; + void RecordSafepoint(LPointerMap* pointers, + Safepoint::Kind kind, + int arguments, + Safepoint::DeoptMode mode); + void RecordSafepoint(LPointerMap* pointers, Safepoint::DeoptMode mode); + void RecordSafepoint(Safepoint::DeoptMode mode); + void RecordSafepointWithRegisters(LPointerMap* pointers, + int arguments, + Safepoint::DeoptMode mode); + void RecordSafepointWithRegistersAndDoubles(LPointerMap* pointers, + int arguments, + Safepoint::DeoptMode mode); + void RecordSafepointWithLazyDeopt(LInstruction* instr, + SafepointMode safepoint_mode); + + void EnsureSpaceForLazyDeopt(int space_needed) V8_OVERRIDE; + + ZoneList<LEnvironment*> deoptimizations_; + ZoneList<Deoptimizer::JumpTableEntry*> deopt_jump_table_; + ZoneList<Handle<Object> > deoptimization_literals_; + int inlined_function_count_; + Scope* const scope_; + TranslationBuffer translations_; + ZoneList<LDeferredCode*> deferred_; + int osr_pc_offset_; + bool frame_is_built_; + + // Builder that keeps track of safepoints in the code. The table itself is + // emitted at the end of the generated code. + SafepointTableBuilder safepoints_; + + // Compiler from a set of parallel moves to a sequential list of moves. + LGapResolver resolver_; + + Safepoint::Kind expected_safepoint_kind_; + + // This flag is true when we are after a push (but before a call). + // In this situation, jssp no longer references the end of the stack slots so, + // we can only reference a stack slot via fp. + bool after_push_argument_; + // If we have inlined arguments, we are no longer able to use jssp because + // jssp is modified and we never know if we are in a block after or before + // the pop of the arguments (which restores jssp). + bool inlined_arguments_; + + int old_position_; + + class PushSafepointRegistersScope BASE_EMBEDDED { + public: + PushSafepointRegistersScope(LCodeGen* codegen, + Safepoint::Kind kind) + : codegen_(codegen) { + ASSERT(codegen_->info()->is_calling()); + ASSERT(codegen_->expected_safepoint_kind_ == Safepoint::kSimple); + codegen_->expected_safepoint_kind_ = kind; + + UseScratchRegisterScope temps(codegen_->masm_); + // Preserve the value of lr which must be saved on the stack (the call to + // the stub will clobber it). + Register to_be_pushed_lr = + temps.UnsafeAcquire(StoreRegistersStateStub::to_be_pushed_lr()); + codegen_->masm_->Mov(to_be_pushed_lr, lr); + switch (codegen_->expected_safepoint_kind_) { + case Safepoint::kWithRegisters: { + StoreRegistersStateStub stub(codegen_->isolate(), kDontSaveFPRegs); + codegen_->masm_->CallStub(&stub); + break; + } + case Safepoint::kWithRegistersAndDoubles: { + StoreRegistersStateStub stub(codegen_->isolate(), kSaveFPRegs); + codegen_->masm_->CallStub(&stub); + break; + } + default: + UNREACHABLE(); + } + } + + ~PushSafepointRegistersScope() { + Safepoint::Kind kind = codegen_->expected_safepoint_kind_; + ASSERT((kind & Safepoint::kWithRegisters) != 0); + switch (kind) { + case Safepoint::kWithRegisters: { + RestoreRegistersStateStub stub(codegen_->isolate(), kDontSaveFPRegs); + codegen_->masm_->CallStub(&stub); + break; + } + case Safepoint::kWithRegistersAndDoubles: { + RestoreRegistersStateStub stub(codegen_->isolate(), kSaveFPRegs); + codegen_->masm_->CallStub(&stub); + break; + } + default: + UNREACHABLE(); + } + codegen_->expected_safepoint_kind_ = Safepoint::kSimple; + } + + private: + LCodeGen* codegen_; + }; + + friend class LDeferredCode; + friend class SafepointGenerator; + DISALLOW_COPY_AND_ASSIGN(LCodeGen); +}; + + +class LDeferredCode: public ZoneObject { + public: + explicit LDeferredCode(LCodeGen* codegen) + : codegen_(codegen), + external_exit_(NULL), + instruction_index_(codegen->current_instruction_) { + codegen->AddDeferredCode(this); + } + + virtual ~LDeferredCode() { } + virtual void Generate() = 0; + virtual LInstruction* instr() = 0; + + void SetExit(Label* exit) { external_exit_ = exit; } + Label* entry() { return &entry_; } + Label* exit() { return (external_exit_ != NULL) ? external_exit_ : &exit_; } + int instruction_index() const { return instruction_index_; } + + protected: + LCodeGen* codegen() const { return codegen_; } + MacroAssembler* masm() const { return codegen_->masm(); } + + private: + LCodeGen* codegen_; + Label entry_; + Label exit_; + Label* external_exit_; + int instruction_index_; +}; + + +// This is the abstract class used by EmitBranchGeneric. +// It is used to emit code for conditional branching. The Emit() function +// emits code to branch when the condition holds and EmitInverted() emits +// the branch when the inverted condition is verified. +// +// For actual examples of condition see the concrete implementation in +// lithium-codegen-arm64.cc (e.g. BranchOnCondition, CompareAndBranch). +class BranchGenerator BASE_EMBEDDED { + public: + explicit BranchGenerator(LCodeGen* codegen) + : codegen_(codegen) { } + + virtual ~BranchGenerator() { } + + virtual void Emit(Label* label) const = 0; + virtual void EmitInverted(Label* label) const = 0; + + protected: + MacroAssembler* masm() const { return codegen_->masm(); } + + LCodeGen* codegen_; +}; + +} } // namespace v8::internal + +#endif // V8_ARM64_LITHIUM_CODEGEN_ARM64_H_ diff --git a/chromium/v8/src/arm64/lithium-gap-resolver-arm64.cc b/chromium/v8/src/arm64/lithium-gap-resolver-arm64.cc new file mode 100644 index 00000000000..bd655eaae83 --- /dev/null +++ b/chromium/v8/src/arm64/lithium-gap-resolver-arm64.cc @@ -0,0 +1,311 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#include "src/arm64/lithium-gap-resolver-arm64.h" +#include "src/arm64/lithium-codegen-arm64.h" + +namespace v8 { +namespace internal { + +// We use the root register to spill a value while breaking a cycle in parallel +// moves. We don't need access to roots while resolving the move list and using +// the root register has two advantages: +// - It is not in crankshaft allocatable registers list, so it can't interfere +// with any of the moves we are resolving. +// - We don't need to push it on the stack, as we can reload it with its value +// once we have resolved a cycle. +#define kSavedValue root + +// We use the MacroAssembler floating-point scratch register to break a cycle +// involving double values as the MacroAssembler will not need it for the +// operations performed by the gap resolver. +#define kSavedDoubleValue fp_scratch + + +LGapResolver::LGapResolver(LCodeGen* owner) + : cgen_(owner), moves_(32, owner->zone()), root_index_(0), in_cycle_(false), + saved_destination_(NULL), need_to_restore_root_(false) { } + + +#define __ ACCESS_MASM(cgen_->masm()) + +void LGapResolver::Resolve(LParallelMove* parallel_move) { + ASSERT(moves_.is_empty()); + + // Build up a worklist of moves. + BuildInitialMoveList(parallel_move); + + for (int i = 0; i < moves_.length(); ++i) { + LMoveOperands move = moves_[i]; + + // Skip constants to perform them last. They don't block other moves + // and skipping such moves with register destinations keeps those + // registers free for the whole algorithm. + if (!move.IsEliminated() && !move.source()->IsConstantOperand()) { + root_index_ = i; // Any cycle is found when we reach this move again. + PerformMove(i); + if (in_cycle_) RestoreValue(); + } + } + + // Perform the moves with constant sources. + for (int i = 0; i < moves_.length(); ++i) { + LMoveOperands move = moves_[i]; + + if (!move.IsEliminated()) { + ASSERT(move.source()->IsConstantOperand()); + EmitMove(i); + } + } + + if (need_to_restore_root_) { + ASSERT(kSavedValue.Is(root)); + __ InitializeRootRegister(); + need_to_restore_root_ = false; + } + + moves_.Rewind(0); +} + + +void LGapResolver::BuildInitialMoveList(LParallelMove* parallel_move) { + // Perform a linear sweep of the moves to add them to the initial list of + // moves to perform, ignoring any move that is redundant (the source is + // the same as the destination, the destination is ignored and + // unallocated, or the move was already eliminated). + const ZoneList<LMoveOperands>* moves = parallel_move->move_operands(); + for (int i = 0; i < moves->length(); ++i) { + LMoveOperands move = moves->at(i); + if (!move.IsRedundant()) moves_.Add(move, cgen_->zone()); + } + Verify(); +} + + +void LGapResolver::PerformMove(int index) { + // Each call to this function performs a move and deletes it from the move + // graph. We first recursively perform any move blocking this one. We + // mark a move as "pending" on entry to PerformMove in order to detect + // cycles in the move graph. + LMoveOperands& current_move = moves_[index]; + + ASSERT(!current_move.IsPending()); + ASSERT(!current_move.IsRedundant()); + + // Clear this move's destination to indicate a pending move. The actual + // destination is saved in a stack allocated local. Multiple moves can + // be pending because this function is recursive. + ASSERT(current_move.source() != NULL); // Otherwise it will look eliminated. + LOperand* destination = current_move.destination(); + current_move.set_destination(NULL); + + // Perform a depth-first traversal of the move graph to resolve + // dependencies. Any unperformed, unpending move with a source the same + // as this one's destination blocks this one so recursively perform all + // such moves. + for (int i = 0; i < moves_.length(); ++i) { + LMoveOperands other_move = moves_[i]; + if (other_move.Blocks(destination) && !other_move.IsPending()) { + PerformMove(i); + // If there is a blocking, pending move it must be moves_[root_index_] + // and all other moves with the same source as moves_[root_index_] are + // sucessfully executed (because they are cycle-free) by this loop. + } + } + + // We are about to resolve this move and don't need it marked as + // pending, so restore its destination. + current_move.set_destination(destination); + + // The move may be blocked on a pending move, which must be the starting move. + // In this case, we have a cycle, and we save the source of this move to + // a scratch register to break it. + LMoveOperands other_move = moves_[root_index_]; + if (other_move.Blocks(destination)) { + ASSERT(other_move.IsPending()); + BreakCycle(index); + return; + } + + // This move is no longer blocked. + EmitMove(index); +} + + +void LGapResolver::Verify() { +#ifdef ENABLE_SLOW_ASSERTS + // No operand should be the destination for more than one move. + for (int i = 0; i < moves_.length(); ++i) { + LOperand* destination = moves_[i].destination(); + for (int j = i + 1; j < moves_.length(); ++j) { + SLOW_ASSERT(!destination->Equals(moves_[j].destination())); + } + } +#endif +} + + +void LGapResolver::BreakCycle(int index) { + ASSERT(moves_[index].destination()->Equals(moves_[root_index_].source())); + ASSERT(!in_cycle_); + + // We use registers which are not allocatable by crankshaft to break the cycle + // to be sure they don't interfere with the moves we are resolving. + ASSERT(!kSavedValue.IsAllocatable()); + ASSERT(!kSavedDoubleValue.IsAllocatable()); + + // We save in a register the source of that move and we remember its + // destination. Then we mark this move as resolved so the cycle is + // broken and we can perform the other moves. + in_cycle_ = true; + LOperand* source = moves_[index].source(); + saved_destination_ = moves_[index].destination(); + + if (source->IsRegister()) { + need_to_restore_root_ = true; + __ Mov(kSavedValue, cgen_->ToRegister(source)); + } else if (source->IsStackSlot()) { + need_to_restore_root_ = true; + __ Ldr(kSavedValue, cgen_->ToMemOperand(source)); + } else if (source->IsDoubleRegister()) { + ASSERT(cgen_->masm()->FPTmpList()->IncludesAliasOf(kSavedDoubleValue)); + cgen_->masm()->FPTmpList()->Remove(kSavedDoubleValue); + __ Fmov(kSavedDoubleValue, cgen_->ToDoubleRegister(source)); + } else if (source->IsDoubleStackSlot()) { + ASSERT(cgen_->masm()->FPTmpList()->IncludesAliasOf(kSavedDoubleValue)); + cgen_->masm()->FPTmpList()->Remove(kSavedDoubleValue); + __ Ldr(kSavedDoubleValue, cgen_->ToMemOperand(source)); + } else { + UNREACHABLE(); + } + + // Mark this move as resolved. + // This move will be actually performed by moving the saved value to this + // move's destination in LGapResolver::RestoreValue(). + moves_[index].Eliminate(); +} + + +void LGapResolver::RestoreValue() { + ASSERT(in_cycle_); + ASSERT(saved_destination_ != NULL); + + if (saved_destination_->IsRegister()) { + __ Mov(cgen_->ToRegister(saved_destination_), kSavedValue); + } else if (saved_destination_->IsStackSlot()) { + __ Str(kSavedValue, cgen_->ToMemOperand(saved_destination_)); + } else if (saved_destination_->IsDoubleRegister()) { + __ Fmov(cgen_->ToDoubleRegister(saved_destination_), kSavedDoubleValue); + cgen_->masm()->FPTmpList()->Combine(kSavedDoubleValue); + } else if (saved_destination_->IsDoubleStackSlot()) { + __ Str(kSavedDoubleValue, cgen_->ToMemOperand(saved_destination_)); + cgen_->masm()->FPTmpList()->Combine(kSavedDoubleValue); + } else { + UNREACHABLE(); + } + + in_cycle_ = false; + saved_destination_ = NULL; +} + + +void LGapResolver::EmitMove(int index) { + LOperand* source = moves_[index].source(); + LOperand* destination = moves_[index].destination(); + + // Dispatch on the source and destination operand kinds. Not all + // combinations are possible. + + if (source->IsRegister()) { + Register source_register = cgen_->ToRegister(source); + if (destination->IsRegister()) { + __ Mov(cgen_->ToRegister(destination), source_register); + } else { + ASSERT(destination->IsStackSlot()); + __ Str(source_register, cgen_->ToMemOperand(destination)); + } + + } else if (source->IsStackSlot()) { + MemOperand source_operand = cgen_->ToMemOperand(source); + if (destination->IsRegister()) { + __ Ldr(cgen_->ToRegister(destination), source_operand); + } else { + ASSERT(destination->IsStackSlot()); + EmitStackSlotMove(index); + } + + } else if (source->IsConstantOperand()) { + LConstantOperand* constant_source = LConstantOperand::cast(source); + if (destination->IsRegister()) { + Register dst = cgen_->ToRegister(destination); + if (cgen_->IsSmi(constant_source)) { + __ Mov(dst, cgen_->ToSmi(constant_source)); + } else if (cgen_->IsInteger32Constant(constant_source)) { + __ Mov(dst, cgen_->ToInteger32(constant_source)); + } else { + __ LoadObject(dst, cgen_->ToHandle(constant_source)); + } + } else if (destination->IsDoubleRegister()) { + DoubleRegister result = cgen_->ToDoubleRegister(destination); + __ Fmov(result, cgen_->ToDouble(constant_source)); + } else { + ASSERT(destination->IsStackSlot()); + ASSERT(!in_cycle_); // Constant moves happen after all cycles are gone. + need_to_restore_root_ = true; + if (cgen_->IsSmi(constant_source)) { + __ Mov(kSavedValue, cgen_->ToSmi(constant_source)); + } else if (cgen_->IsInteger32Constant(constant_source)) { + __ Mov(kSavedValue, cgen_->ToInteger32(constant_source)); + } else { + __ LoadObject(kSavedValue, cgen_->ToHandle(constant_source)); + } + __ Str(kSavedValue, cgen_->ToMemOperand(destination)); + } + + } else if (source->IsDoubleRegister()) { + DoubleRegister src = cgen_->ToDoubleRegister(source); + if (destination->IsDoubleRegister()) { + __ Fmov(cgen_->ToDoubleRegister(destination), src); + } else { + ASSERT(destination->IsDoubleStackSlot()); + __ Str(src, cgen_->ToMemOperand(destination)); + } + + } else if (source->IsDoubleStackSlot()) { + MemOperand src = cgen_->ToMemOperand(source); + if (destination->IsDoubleRegister()) { + __ Ldr(cgen_->ToDoubleRegister(destination), src); + } else { + ASSERT(destination->IsDoubleStackSlot()); + EmitStackSlotMove(index); + } + + } else { + UNREACHABLE(); + } + + // The move has been emitted, we can eliminate it. + moves_[index].Eliminate(); +} + + +void LGapResolver::EmitStackSlotMove(int index) { + // We need a temp register to perform a stack slot to stack slot move, and + // the register must not be involved in breaking cycles. + + // Use the Crankshaft double scratch register as the temporary. + DoubleRegister temp = crankshaft_fp_scratch; + + LOperand* src = moves_[index].source(); + LOperand* dst = moves_[index].destination(); + + ASSERT(src->IsStackSlot()); + ASSERT(dst->IsStackSlot()); + __ Ldr(temp, cgen_->ToMemOperand(src)); + __ Str(temp, cgen_->ToMemOperand(dst)); +} + +} } // namespace v8::internal diff --git a/chromium/v8/src/arm64/lithium-gap-resolver-arm64.h b/chromium/v8/src/arm64/lithium-gap-resolver-arm64.h new file mode 100644 index 00000000000..55d4ecbf9d2 --- /dev/null +++ b/chromium/v8/src/arm64/lithium-gap-resolver-arm64.h @@ -0,0 +1,67 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_LITHIUM_GAP_RESOLVER_ARM64_H_ +#define V8_ARM64_LITHIUM_GAP_RESOLVER_ARM64_H_ + +#include "src/v8.h" + +#include "src/lithium.h" + +namespace v8 { +namespace internal { + +class LCodeGen; +class LGapResolver; + +class LGapResolver BASE_EMBEDDED { + public: + explicit LGapResolver(LCodeGen* owner); + + // Resolve a set of parallel moves, emitting assembler instructions. + void Resolve(LParallelMove* parallel_move); + + private: + // Build the initial list of moves. + void BuildInitialMoveList(LParallelMove* parallel_move); + + // Perform the move at the moves_ index in question (possibly requiring + // other moves to satisfy dependencies). + void PerformMove(int index); + + // If a cycle is found in the series of moves, save the blocking value to + // a scratch register. The cycle must be found by hitting the root of the + // depth-first search. + void BreakCycle(int index); + + // After a cycle has been resolved, restore the value from the scratch + // register to its proper destination. + void RestoreValue(); + + // Emit a move and remove it from the move graph. + void EmitMove(int index); + + // Emit a move from one stack slot to another. + void EmitStackSlotMove(int index); + + // Verify the move list before performing moves. + void Verify(); + + LCodeGen* cgen_; + + // List of moves not yet resolved. + ZoneList<LMoveOperands> moves_; + + int root_index_; + bool in_cycle_; + LOperand* saved_destination_; + + // We use the root register as a scratch in a few places. When that happens, + // this flag is set to indicate that it needs to be restored. + bool need_to_restore_root_; +}; + +} } // namespace v8::internal + +#endif // V8_ARM64_LITHIUM_GAP_RESOLVER_ARM64_H_ diff --git a/chromium/v8/src/arm64/macro-assembler-arm64-inl.h b/chromium/v8/src/arm64/macro-assembler-arm64-inl.h new file mode 100644 index 00000000000..0c6aadf6b9e --- /dev/null +++ b/chromium/v8/src/arm64/macro-assembler-arm64-inl.h @@ -0,0 +1,1706 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_MACRO_ASSEMBLER_ARM64_INL_H_ +#define V8_ARM64_MACRO_ASSEMBLER_ARM64_INL_H_ + +#include <ctype.h> + +#include "src/globals.h" + +#include "src/arm64/assembler-arm64.h" +#include "src/arm64/assembler-arm64-inl.h" +#include "src/arm64/macro-assembler-arm64.h" +#include "src/arm64/instrument-arm64.h" + + +namespace v8 { +namespace internal { + + +MemOperand FieldMemOperand(Register object, int offset) { + return MemOperand(object, offset - kHeapObjectTag); +} + + +MemOperand UntagSmiFieldMemOperand(Register object, int offset) { + return UntagSmiMemOperand(object, offset - kHeapObjectTag); +} + + +MemOperand UntagSmiMemOperand(Register object, int offset) { + // Assumes that Smis are shifted by 32 bits and little endianness. + STATIC_ASSERT(kSmiShift == 32); + return MemOperand(object, offset + (kSmiShift / kBitsPerByte)); +} + + +Handle<Object> MacroAssembler::CodeObject() { + ASSERT(!code_object_.is_null()); + return code_object_; +} + + +void MacroAssembler::And(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, AND); +} + + +void MacroAssembler::Ands(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, ANDS); +} + + +void MacroAssembler::Tst(const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + LogicalMacro(AppropriateZeroRegFor(rn), rn, operand, ANDS); +} + + +void MacroAssembler::Bic(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, BIC); +} + + +void MacroAssembler::Bics(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, BICS); +} + + +void MacroAssembler::Orr(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, ORR); +} + + +void MacroAssembler::Orn(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, ORN); +} + + +void MacroAssembler::Eor(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, EOR); +} + + +void MacroAssembler::Eon(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + LogicalMacro(rd, rn, operand, EON); +} + + +void MacroAssembler::Ccmp(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond) { + ASSERT(allow_macro_instructions_); + if (operand.IsImmediate() && (operand.ImmediateValue() < 0)) { + ConditionalCompareMacro(rn, -operand.ImmediateValue(), nzcv, cond, CCMN); + } else { + ConditionalCompareMacro(rn, operand, nzcv, cond, CCMP); + } +} + + +void MacroAssembler::Ccmn(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond) { + ASSERT(allow_macro_instructions_); + if (operand.IsImmediate() && (operand.ImmediateValue() < 0)) { + ConditionalCompareMacro(rn, -operand.ImmediateValue(), nzcv, cond, CCMP); + } else { + ConditionalCompareMacro(rn, operand, nzcv, cond, CCMN); + } +} + + +void MacroAssembler::Add(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + if (operand.IsImmediate() && (operand.ImmediateValue() < 0)) { + AddSubMacro(rd, rn, -operand.ImmediateValue(), LeaveFlags, SUB); + } else { + AddSubMacro(rd, rn, operand, LeaveFlags, ADD); + } +} + +void MacroAssembler::Adds(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + if (operand.IsImmediate() && (operand.ImmediateValue() < 0)) { + AddSubMacro(rd, rn, -operand.ImmediateValue(), SetFlags, SUB); + } else { + AddSubMacro(rd, rn, operand, SetFlags, ADD); + } +} + + +void MacroAssembler::Sub(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + if (operand.IsImmediate() && (operand.ImmediateValue() < 0)) { + AddSubMacro(rd, rn, -operand.ImmediateValue(), LeaveFlags, ADD); + } else { + AddSubMacro(rd, rn, operand, LeaveFlags, SUB); + } +} + + +void MacroAssembler::Subs(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + if (operand.IsImmediate() && (operand.ImmediateValue() < 0)) { + AddSubMacro(rd, rn, -operand.ImmediateValue(), SetFlags, ADD); + } else { + AddSubMacro(rd, rn, operand, SetFlags, SUB); + } +} + + +void MacroAssembler::Cmn(const Register& rn, const Operand& operand) { + ASSERT(allow_macro_instructions_); + Adds(AppropriateZeroRegFor(rn), rn, operand); +} + + +void MacroAssembler::Cmp(const Register& rn, const Operand& operand) { + ASSERT(allow_macro_instructions_); + Subs(AppropriateZeroRegFor(rn), rn, operand); +} + + +void MacroAssembler::Neg(const Register& rd, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + if (operand.IsImmediate()) { + Mov(rd, -operand.ImmediateValue()); + } else { + Sub(rd, AppropriateZeroRegFor(rd), operand); + } +} + + +void MacroAssembler::Negs(const Register& rd, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + Subs(rd, AppropriateZeroRegFor(rd), operand); +} + + +void MacroAssembler::Adc(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + AddSubWithCarryMacro(rd, rn, operand, LeaveFlags, ADC); +} + + +void MacroAssembler::Adcs(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + AddSubWithCarryMacro(rd, rn, operand, SetFlags, ADC); +} + + +void MacroAssembler::Sbc(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + AddSubWithCarryMacro(rd, rn, operand, LeaveFlags, SBC); +} + + +void MacroAssembler::Sbcs(const Register& rd, + const Register& rn, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + AddSubWithCarryMacro(rd, rn, operand, SetFlags, SBC); +} + + +void MacroAssembler::Ngc(const Register& rd, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + Register zr = AppropriateZeroRegFor(rd); + Sbc(rd, zr, operand); +} + + +void MacroAssembler::Ngcs(const Register& rd, + const Operand& operand) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + Register zr = AppropriateZeroRegFor(rd); + Sbcs(rd, zr, operand); +} + + +void MacroAssembler::Mvn(const Register& rd, uint64_t imm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + Mov(rd, ~imm); +} + + +#define DEFINE_FUNCTION(FN, REGTYPE, REG, OP) \ +void MacroAssembler::FN(const REGTYPE REG, const MemOperand& addr) { \ + ASSERT(allow_macro_instructions_); \ + LoadStoreMacro(REG, addr, OP); \ +} +LS_MACRO_LIST(DEFINE_FUNCTION) +#undef DEFINE_FUNCTION + + +void MacroAssembler::Asr(const Register& rd, + const Register& rn, + unsigned shift) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + asr(rd, rn, shift); +} + + +void MacroAssembler::Asr(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + asrv(rd, rn, rm); +} + + +void MacroAssembler::B(Label* label) { + b(label); + CheckVeneerPool(false, false); +} + + +void MacroAssembler::B(Condition cond, Label* label) { + ASSERT(allow_macro_instructions_); + B(label, cond); +} + + +void MacroAssembler::Bfi(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + bfi(rd, rn, lsb, width); +} + + +void MacroAssembler::Bfxil(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + bfxil(rd, rn, lsb, width); +} + + +void MacroAssembler::Bind(Label* label) { + ASSERT(allow_macro_instructions_); + bind(label); +} + + +void MacroAssembler::Bl(Label* label) { + ASSERT(allow_macro_instructions_); + bl(label); +} + + +void MacroAssembler::Blr(const Register& xn) { + ASSERT(allow_macro_instructions_); + ASSERT(!xn.IsZero()); + blr(xn); +} + + +void MacroAssembler::Br(const Register& xn) { + ASSERT(allow_macro_instructions_); + ASSERT(!xn.IsZero()); + br(xn); +} + + +void MacroAssembler::Brk(int code) { + ASSERT(allow_macro_instructions_); + brk(code); +} + + +void MacroAssembler::Cinc(const Register& rd, + const Register& rn, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + cinc(rd, rn, cond); +} + + +void MacroAssembler::Cinv(const Register& rd, + const Register& rn, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + cinv(rd, rn, cond); +} + + +void MacroAssembler::Cls(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + cls(rd, rn); +} + + +void MacroAssembler::Clz(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + clz(rd, rn); +} + + +void MacroAssembler::Cneg(const Register& rd, + const Register& rn, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + cneg(rd, rn, cond); +} + + +// Conditionally zero the destination register. Only X registers are supported +// due to the truncation side-effect when used on W registers. +void MacroAssembler::CzeroX(const Register& rd, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsSP() && rd.Is64Bits()); + ASSERT((cond != al) && (cond != nv)); + csel(rd, xzr, rd, cond); +} + + +// Conditionally move a value into the destination register. Only X registers +// are supported due to the truncation side-effect when used on W registers. +void MacroAssembler::CmovX(const Register& rd, + const Register& rn, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsSP()); + ASSERT(rd.Is64Bits() && rn.Is64Bits()); + ASSERT((cond != al) && (cond != nv)); + if (!rd.is(rn)) { + csel(rd, rn, rd, cond); + } +} + + +void MacroAssembler::Cset(const Register& rd, Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + cset(rd, cond); +} + + +void MacroAssembler::Csetm(const Register& rd, Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + csetm(rd, cond); +} + + +void MacroAssembler::Csinc(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + csinc(rd, rn, rm, cond); +} + + +void MacroAssembler::Csinv(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + csinv(rd, rn, rm, cond); +} + + +void MacroAssembler::Csneg(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + csneg(rd, rn, rm, cond); +} + + +void MacroAssembler::Dmb(BarrierDomain domain, BarrierType type) { + ASSERT(allow_macro_instructions_); + dmb(domain, type); +} + + +void MacroAssembler::Dsb(BarrierDomain domain, BarrierType type) { + ASSERT(allow_macro_instructions_); + dsb(domain, type); +} + + +void MacroAssembler::Debug(const char* message, uint32_t code, Instr params) { + ASSERT(allow_macro_instructions_); + debug(message, code, params); +} + + +void MacroAssembler::Extr(const Register& rd, + const Register& rn, + const Register& rm, + unsigned lsb) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + extr(rd, rn, rm, lsb); +} + + +void MacroAssembler::Fabs(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + fabs(fd, fn); +} + + +void MacroAssembler::Fadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fadd(fd, fn, fm); +} + + +void MacroAssembler::Fccmp(const FPRegister& fn, + const FPRegister& fm, + StatusFlags nzcv, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT((cond != al) && (cond != nv)); + fccmp(fn, fm, nzcv, cond); +} + + +void MacroAssembler::Fcmp(const FPRegister& fn, const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fcmp(fn, fm); +} + + +void MacroAssembler::Fcmp(const FPRegister& fn, double value) { + ASSERT(allow_macro_instructions_); + if (value != 0.0) { + UseScratchRegisterScope temps(this); + FPRegister tmp = temps.AcquireSameSizeAs(fn); + Fmov(tmp, value); + fcmp(fn, tmp); + } else { + fcmp(fn, value); + } +} + + +void MacroAssembler::Fcsel(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT((cond != al) && (cond != nv)); + fcsel(fd, fn, fm, cond); +} + + +void MacroAssembler::Fcvt(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + fcvt(fd, fn); +} + + +void MacroAssembler::Fcvtas(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtas(rd, fn); +} + + +void MacroAssembler::Fcvtau(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtau(rd, fn); +} + + +void MacroAssembler::Fcvtms(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtms(rd, fn); +} + + +void MacroAssembler::Fcvtmu(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtmu(rd, fn); +} + + +void MacroAssembler::Fcvtns(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtns(rd, fn); +} + + +void MacroAssembler::Fcvtnu(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtnu(rd, fn); +} + + +void MacroAssembler::Fcvtzs(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtzs(rd, fn); +} +void MacroAssembler::Fcvtzu(const Register& rd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fcvtzu(rd, fn); +} + + +void MacroAssembler::Fdiv(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fdiv(fd, fn, fm); +} + + +void MacroAssembler::Fmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + ASSERT(allow_macro_instructions_); + fmadd(fd, fn, fm, fa); +} + + +void MacroAssembler::Fmax(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fmax(fd, fn, fm); +} + + +void MacroAssembler::Fmaxnm(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fmaxnm(fd, fn, fm); +} + + +void MacroAssembler::Fmin(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fmin(fd, fn, fm); +} + + +void MacroAssembler::Fminnm(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fminnm(fd, fn, fm); +} + + +void MacroAssembler::Fmov(FPRegister fd, FPRegister fn) { + ASSERT(allow_macro_instructions_); + // Only emit an instruction if fd and fn are different, and they are both D + // registers. fmov(s0, s0) is not a no-op because it clears the top word of + // d0. Technically, fmov(d0, d0) is not a no-op either because it clears the + // top of q0, but FPRegister does not currently support Q registers. + if (!fd.Is(fn) || !fd.Is64Bits()) { + fmov(fd, fn); + } +} + + +void MacroAssembler::Fmov(FPRegister fd, Register rn) { + ASSERT(allow_macro_instructions_); + fmov(fd, rn); +} + + +void MacroAssembler::Fmov(FPRegister fd, double imm) { + ASSERT(allow_macro_instructions_); + if (fd.Is32Bits()) { + Fmov(fd, static_cast<float>(imm)); + return; + } + + ASSERT(fd.Is64Bits()); + if (IsImmFP64(imm)) { + fmov(fd, imm); + } else if ((imm == 0.0) && (copysign(1.0, imm) == 1.0)) { + fmov(fd, xzr); + } else { + Ldr(fd, imm); + } +} + + +void MacroAssembler::Fmov(FPRegister fd, float imm) { + ASSERT(allow_macro_instructions_); + if (fd.Is64Bits()) { + Fmov(fd, static_cast<double>(imm)); + return; + } + + ASSERT(fd.Is32Bits()); + if (IsImmFP32(imm)) { + fmov(fd, imm); + } else if ((imm == 0.0) && (copysign(1.0, imm) == 1.0)) { + fmov(fd, wzr); + } else { + UseScratchRegisterScope temps(this); + Register tmp = temps.AcquireW(); + // TODO(all): Use Assembler::ldr(const FPRegister& ft, float imm). + Mov(tmp, float_to_rawbits(imm)); + Fmov(fd, tmp); + } +} + + +void MacroAssembler::Fmov(Register rd, FPRegister fn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + fmov(rd, fn); +} + + +void MacroAssembler::Fmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + ASSERT(allow_macro_instructions_); + fmsub(fd, fn, fm, fa); +} + + +void MacroAssembler::Fmul(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fmul(fd, fn, fm); +} + + +void MacroAssembler::Fneg(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + fneg(fd, fn); +} + + +void MacroAssembler::Fnmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + ASSERT(allow_macro_instructions_); + fnmadd(fd, fn, fm, fa); +} + + +void MacroAssembler::Fnmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa) { + ASSERT(allow_macro_instructions_); + fnmsub(fd, fn, fm, fa); +} + + +void MacroAssembler::Frinta(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + frinta(fd, fn); +} + + +void MacroAssembler::Frintm(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + frintm(fd, fn); +} + + +void MacroAssembler::Frintn(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + frintn(fd, fn); +} + + +void MacroAssembler::Frintz(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + frintz(fd, fn); +} + + +void MacroAssembler::Fsqrt(const FPRegister& fd, const FPRegister& fn) { + ASSERT(allow_macro_instructions_); + fsqrt(fd, fn); +} + + +void MacroAssembler::Fsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm) { + ASSERT(allow_macro_instructions_); + fsub(fd, fn, fm); +} + + +void MacroAssembler::Hint(SystemHint code) { + ASSERT(allow_macro_instructions_); + hint(code); +} + + +void MacroAssembler::Hlt(int code) { + ASSERT(allow_macro_instructions_); + hlt(code); +} + + +void MacroAssembler::Isb() { + ASSERT(allow_macro_instructions_); + isb(); +} + + +void MacroAssembler::Ldnp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& src) { + ASSERT(allow_macro_instructions_); + ASSERT(!AreAliased(rt, rt2)); + ldnp(rt, rt2, src); +} + + +void MacroAssembler::Ldp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& src) { + ASSERT(allow_macro_instructions_); + ASSERT(!AreAliased(rt, rt2)); + ldp(rt, rt2, src); +} + + +void MacroAssembler::Ldpsw(const Register& rt, + const Register& rt2, + const MemOperand& src) { + ASSERT(allow_macro_instructions_); + ASSERT(!rt.IsZero()); + ASSERT(!rt2.IsZero()); + ldpsw(rt, rt2, src); +} + + +void MacroAssembler::Ldr(const CPURegister& rt, const Immediate& imm) { + ASSERT(allow_macro_instructions_); + ldr(rt, imm); +} + + +void MacroAssembler::Ldr(const CPURegister& rt, double imm) { + ASSERT(allow_macro_instructions_); + ASSERT(rt.Is64Bits()); + ldr(rt, Immediate(double_to_rawbits(imm))); +} + + +void MacroAssembler::Lsl(const Register& rd, + const Register& rn, + unsigned shift) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + lsl(rd, rn, shift); +} + + +void MacroAssembler::Lsl(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + lslv(rd, rn, rm); +} + + +void MacroAssembler::Lsr(const Register& rd, + const Register& rn, + unsigned shift) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + lsr(rd, rn, shift); +} + + +void MacroAssembler::Lsr(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + lsrv(rd, rn, rm); +} + + +void MacroAssembler::Madd(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + madd(rd, rn, rm, ra); +} + + +void MacroAssembler::Mneg(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + mneg(rd, rn, rm); +} + + +void MacroAssembler::Mov(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + // Emit a register move only if the registers are distinct, or if they are + // not X registers. Note that mov(w0, w0) is not a no-op because it clears + // the top word of x0. + if (!rd.Is(rn) || !rd.Is64Bits()) { + Assembler::mov(rd, rn); + } +} + + +void MacroAssembler::Movk(const Register& rd, uint64_t imm, int shift) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + movk(rd, imm, shift); +} + + +void MacroAssembler::Mrs(const Register& rt, SystemRegister sysreg) { + ASSERT(allow_macro_instructions_); + ASSERT(!rt.IsZero()); + mrs(rt, sysreg); +} + + +void MacroAssembler::Msr(SystemRegister sysreg, const Register& rt) { + ASSERT(allow_macro_instructions_); + msr(sysreg, rt); +} + + +void MacroAssembler::Msub(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + msub(rd, rn, rm, ra); +} + + +void MacroAssembler::Mul(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + mul(rd, rn, rm); +} + + +void MacroAssembler::Rbit(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + rbit(rd, rn); +} + + +void MacroAssembler::Ret(const Register& xn) { + ASSERT(allow_macro_instructions_); + ASSERT(!xn.IsZero()); + ret(xn); + CheckVeneerPool(false, false); +} + + +void MacroAssembler::Rev(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + rev(rd, rn); +} + + +void MacroAssembler::Rev16(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + rev16(rd, rn); +} + + +void MacroAssembler::Rev32(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + rev32(rd, rn); +} + + +void MacroAssembler::Ror(const Register& rd, + const Register& rs, + unsigned shift) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ror(rd, rs, shift); +} + + +void MacroAssembler::Ror(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + rorv(rd, rn, rm); +} + + +void MacroAssembler::Sbfiz(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + sbfiz(rd, rn, lsb, width); +} + + +void MacroAssembler::Sbfx(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + sbfx(rd, rn, lsb, width); +} + + +void MacroAssembler::Scvtf(const FPRegister& fd, + const Register& rn, + unsigned fbits) { + ASSERT(allow_macro_instructions_); + scvtf(fd, rn, fbits); +} + + +void MacroAssembler::Sdiv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + sdiv(rd, rn, rm); +} + + +void MacroAssembler::Smaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + smaddl(rd, rn, rm, ra); +} + + +void MacroAssembler::Smsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + smsubl(rd, rn, rm, ra); +} + + +void MacroAssembler::Smull(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + smull(rd, rn, rm); +} + + +void MacroAssembler::Smulh(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + smulh(rd, rn, rm); +} + + +void MacroAssembler::Stnp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& dst) { + ASSERT(allow_macro_instructions_); + stnp(rt, rt2, dst); +} + + +void MacroAssembler::Stp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& dst) { + ASSERT(allow_macro_instructions_); + stp(rt, rt2, dst); +} + + +void MacroAssembler::Sxtb(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + sxtb(rd, rn); +} + + +void MacroAssembler::Sxth(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + sxth(rd, rn); +} + + +void MacroAssembler::Sxtw(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + sxtw(rd, rn); +} + + +void MacroAssembler::Ubfiz(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ubfiz(rd, rn, lsb, width); +} + + +void MacroAssembler::Ubfx(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ubfx(rd, rn, lsb, width); +} + + +void MacroAssembler::Ucvtf(const FPRegister& fd, + const Register& rn, + unsigned fbits) { + ASSERT(allow_macro_instructions_); + ucvtf(fd, rn, fbits); +} + + +void MacroAssembler::Udiv(const Register& rd, + const Register& rn, + const Register& rm) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + udiv(rd, rn, rm); +} + + +void MacroAssembler::Umaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + umaddl(rd, rn, rm, ra); +} + + +void MacroAssembler::Umsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + umsubl(rd, rn, rm, ra); +} + + +void MacroAssembler::Uxtb(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + uxtb(rd, rn); +} + + +void MacroAssembler::Uxth(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + uxth(rd, rn); +} + + +void MacroAssembler::Uxtw(const Register& rd, const Register& rn) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + uxtw(rd, rn); +} + + +void MacroAssembler::BumpSystemStackPointer(const Operand& space) { + ASSERT(!csp.Is(sp_)); + if (!TmpList()->IsEmpty()) { + if (CpuFeatures::IsSupported(ALWAYS_ALIGN_CSP)) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Sub(temp, StackPointer(), space); + Bic(csp, temp, 0xf); + } else { + Sub(csp, StackPointer(), space); + } + } else { + // TODO(jbramley): Several callers rely on this not using scratch + // registers, so we use the assembler directly here. However, this means + // that large immediate values of 'space' cannot be handled cleanly. (Only + // 24-bits immediates or values of 'space' that can be encoded in one + // instruction are accepted.) Once we implement our flexible scratch + // register idea, we could greatly simplify this function. + InstructionAccurateScope scope(this); + ASSERT(space.IsImmediate()); + // Align to 16 bytes. + uint64_t imm = RoundUp(space.ImmediateValue(), 0x10); + ASSERT(is_uint24(imm)); + + Register source = StackPointer(); + if (CpuFeatures::IsSupported(ALWAYS_ALIGN_CSP)) { + bic(csp, source, 0xf); + source = csp; + } + if (!is_uint12(imm)) { + int64_t imm_top_12_bits = imm >> 12; + sub(csp, source, imm_top_12_bits << 12); + source = csp; + imm -= imm_top_12_bits << 12; + } + if (imm > 0) { + sub(csp, source, imm); + } + } + AssertStackConsistency(); +} + + +void MacroAssembler::SyncSystemStackPointer() { + ASSERT(emit_debug_code()); + ASSERT(!csp.Is(sp_)); + { InstructionAccurateScope scope(this); + if (CpuFeatures::IsSupported(ALWAYS_ALIGN_CSP)) { + bic(csp, StackPointer(), 0xf); + } else { + mov(csp, StackPointer()); + } + } + AssertStackConsistency(); +} + + +void MacroAssembler::InitializeRootRegister() { + ExternalReference roots_array_start = + ExternalReference::roots_array_start(isolate()); + Mov(root, Operand(roots_array_start)); +} + + +void MacroAssembler::SmiTag(Register dst, Register src) { + ASSERT(dst.Is64Bits() && src.Is64Bits()); + Lsl(dst, src, kSmiShift); +} + + +void MacroAssembler::SmiTag(Register smi) { SmiTag(smi, smi); } + + +void MacroAssembler::SmiUntag(Register dst, Register src) { + ASSERT(dst.Is64Bits() && src.Is64Bits()); + if (FLAG_enable_slow_asserts) { + AssertSmi(src); + } + Asr(dst, src, kSmiShift); +} + + +void MacroAssembler::SmiUntag(Register smi) { SmiUntag(smi, smi); } + + +void MacroAssembler::SmiUntagToDouble(FPRegister dst, + Register src, + UntagMode mode) { + ASSERT(dst.Is64Bits() && src.Is64Bits()); + if (FLAG_enable_slow_asserts && (mode == kNotSpeculativeUntag)) { + AssertSmi(src); + } + Scvtf(dst, src, kSmiShift); +} + + +void MacroAssembler::SmiUntagToFloat(FPRegister dst, + Register src, + UntagMode mode) { + ASSERT(dst.Is32Bits() && src.Is64Bits()); + if (FLAG_enable_slow_asserts && (mode == kNotSpeculativeUntag)) { + AssertSmi(src); + } + Scvtf(dst, src, kSmiShift); +} + + +void MacroAssembler::SmiTagAndPush(Register src) { + STATIC_ASSERT((kSmiShift == 32) && (kSmiTag == 0)); + Push(src.W(), wzr); +} + + +void MacroAssembler::SmiTagAndPush(Register src1, Register src2) { + STATIC_ASSERT((kSmiShift == 32) && (kSmiTag == 0)); + Push(src1.W(), wzr, src2.W(), wzr); +} + + +void MacroAssembler::JumpIfSmi(Register value, + Label* smi_label, + Label* not_smi_label) { + STATIC_ASSERT((kSmiTagSize == 1) && (kSmiTag == 0)); + // Check if the tag bit is set. + if (smi_label) { + Tbz(value, 0, smi_label); + if (not_smi_label) { + B(not_smi_label); + } + } else { + ASSERT(not_smi_label); + Tbnz(value, 0, not_smi_label); + } +} + + +void MacroAssembler::JumpIfNotSmi(Register value, Label* not_smi_label) { + JumpIfSmi(value, NULL, not_smi_label); +} + + +void MacroAssembler::JumpIfBothSmi(Register value1, + Register value2, + Label* both_smi_label, + Label* not_smi_label) { + STATIC_ASSERT((kSmiTagSize == 1) && (kSmiTag == 0)); + UseScratchRegisterScope temps(this); + Register tmp = temps.AcquireX(); + // Check if both tag bits are clear. + Orr(tmp, value1, value2); + JumpIfSmi(tmp, both_smi_label, not_smi_label); +} + + +void MacroAssembler::JumpIfEitherSmi(Register value1, + Register value2, + Label* either_smi_label, + Label* not_smi_label) { + STATIC_ASSERT((kSmiTagSize == 1) && (kSmiTag == 0)); + UseScratchRegisterScope temps(this); + Register tmp = temps.AcquireX(); + // Check if either tag bit is clear. + And(tmp, value1, value2); + JumpIfSmi(tmp, either_smi_label, not_smi_label); +} + + +void MacroAssembler::JumpIfEitherNotSmi(Register value1, + Register value2, + Label* not_smi_label) { + JumpIfBothSmi(value1, value2, NULL, not_smi_label); +} + + +void MacroAssembler::JumpIfBothNotSmi(Register value1, + Register value2, + Label* not_smi_label) { + JumpIfEitherSmi(value1, value2, NULL, not_smi_label); +} + + +void MacroAssembler::ObjectTag(Register tagged_obj, Register obj) { + STATIC_ASSERT(kHeapObjectTag == 1); + if (emit_debug_code()) { + Label ok; + Tbz(obj, 0, &ok); + Abort(kObjectTagged); + Bind(&ok); + } + Orr(tagged_obj, obj, kHeapObjectTag); +} + + +void MacroAssembler::ObjectUntag(Register untagged_obj, Register obj) { + STATIC_ASSERT(kHeapObjectTag == 1); + if (emit_debug_code()) { + Label ok; + Tbnz(obj, 0, &ok); + Abort(kObjectNotTagged); + Bind(&ok); + } + Bic(untagged_obj, obj, kHeapObjectTag); +} + + +void MacroAssembler::IsObjectNameType(Register object, + Register type, + Label* fail) { + CompareObjectType(object, type, type, LAST_NAME_TYPE); + B(hi, fail); +} + + +void MacroAssembler::IsObjectJSObjectType(Register heap_object, + Register map, + Register scratch, + Label* fail) { + Ldr(map, FieldMemOperand(heap_object, HeapObject::kMapOffset)); + IsInstanceJSObjectType(map, scratch, fail); +} + + +void MacroAssembler::IsInstanceJSObjectType(Register map, + Register scratch, + Label* fail) { + Ldrb(scratch, FieldMemOperand(map, Map::kInstanceTypeOffset)); + // If cmp result is lt, the following ccmp will clear all flags. + // Z == 0, N == V implies gt condition. + Cmp(scratch, FIRST_NONCALLABLE_SPEC_OBJECT_TYPE); + Ccmp(scratch, LAST_NONCALLABLE_SPEC_OBJECT_TYPE, NoFlag, ge); + + // If we didn't get a valid label object just fall through and leave the + // flags updated. + if (fail != NULL) { + B(gt, fail); + } +} + + +void MacroAssembler::IsObjectJSStringType(Register object, + Register type, + Label* not_string, + Label* string) { + Ldr(type, FieldMemOperand(object, HeapObject::kMapOffset)); + Ldrb(type.W(), FieldMemOperand(type, Map::kInstanceTypeOffset)); + + STATIC_ASSERT(kStringTag == 0); + ASSERT((string != NULL) || (not_string != NULL)); + if (string == NULL) { + TestAndBranchIfAnySet(type.W(), kIsNotStringMask, not_string); + } else if (not_string == NULL) { + TestAndBranchIfAllClear(type.W(), kIsNotStringMask, string); + } else { + TestAndBranchIfAnySet(type.W(), kIsNotStringMask, not_string); + B(string); + } +} + + +void MacroAssembler::Push(Handle<Object> handle) { + UseScratchRegisterScope temps(this); + Register tmp = temps.AcquireX(); + Mov(tmp, Operand(handle)); + Push(tmp); +} + + +void MacroAssembler::Claim(uint64_t count, uint64_t unit_size) { + uint64_t size = count * unit_size; + + if (size == 0) { + return; + } + + if (csp.Is(StackPointer())) { + ASSERT(size % 16 == 0); + } else { + BumpSystemStackPointer(size); + } + + Sub(StackPointer(), StackPointer(), size); +} + + +void MacroAssembler::Claim(const Register& count, uint64_t unit_size) { + if (unit_size == 0) return; + ASSERT(IsPowerOf2(unit_size)); + + const int shift = CountTrailingZeros(unit_size, kXRegSizeInBits); + const Operand size(count, LSL, shift); + + if (size.IsZero()) { + return; + } + + if (!csp.Is(StackPointer())) { + BumpSystemStackPointer(size); + } + + Sub(StackPointer(), StackPointer(), size); +} + + +void MacroAssembler::ClaimBySMI(const Register& count_smi, uint64_t unit_size) { + ASSERT(unit_size == 0 || IsPowerOf2(unit_size)); + const int shift = CountTrailingZeros(unit_size, kXRegSizeInBits) - kSmiShift; + const Operand size(count_smi, + (shift >= 0) ? (LSL) : (LSR), + (shift >= 0) ? (shift) : (-shift)); + + if (size.IsZero()) { + return; + } + + if (!csp.Is(StackPointer())) { + BumpSystemStackPointer(size); + } + + Sub(StackPointer(), StackPointer(), size); +} + + +void MacroAssembler::Drop(uint64_t count, uint64_t unit_size) { + uint64_t size = count * unit_size; + + if (size == 0) { + return; + } + + Add(StackPointer(), StackPointer(), size); + + if (csp.Is(StackPointer())) { + ASSERT(size % 16 == 0); + } else if (emit_debug_code()) { + // It is safe to leave csp where it is when unwinding the JavaScript stack, + // but if we keep it matching StackPointer, the simulator can detect memory + // accesses in the now-free part of the stack. + SyncSystemStackPointer(); + } +} + + +void MacroAssembler::Drop(const Register& count, uint64_t unit_size) { + if (unit_size == 0) return; + ASSERT(IsPowerOf2(unit_size)); + + const int shift = CountTrailingZeros(unit_size, kXRegSizeInBits); + const Operand size(count, LSL, shift); + + if (size.IsZero()) { + return; + } + + Add(StackPointer(), StackPointer(), size); + + if (!csp.Is(StackPointer()) && emit_debug_code()) { + // It is safe to leave csp where it is when unwinding the JavaScript stack, + // but if we keep it matching StackPointer, the simulator can detect memory + // accesses in the now-free part of the stack. + SyncSystemStackPointer(); + } +} + + +void MacroAssembler::DropBySMI(const Register& count_smi, uint64_t unit_size) { + ASSERT(unit_size == 0 || IsPowerOf2(unit_size)); + const int shift = CountTrailingZeros(unit_size, kXRegSizeInBits) - kSmiShift; + const Operand size(count_smi, + (shift >= 0) ? (LSL) : (LSR), + (shift >= 0) ? (shift) : (-shift)); + + if (size.IsZero()) { + return; + } + + Add(StackPointer(), StackPointer(), size); + + if (!csp.Is(StackPointer()) && emit_debug_code()) { + // It is safe to leave csp where it is when unwinding the JavaScript stack, + // but if we keep it matching StackPointer, the simulator can detect memory + // accesses in the now-free part of the stack. + SyncSystemStackPointer(); + } +} + + +void MacroAssembler::CompareAndBranch(const Register& lhs, + const Operand& rhs, + Condition cond, + Label* label) { + if (rhs.IsImmediate() && (rhs.ImmediateValue() == 0) && + ((cond == eq) || (cond == ne))) { + if (cond == eq) { + Cbz(lhs, label); + } else { + Cbnz(lhs, label); + } + } else { + Cmp(lhs, rhs); + B(cond, label); + } +} + + +void MacroAssembler::TestAndBranchIfAnySet(const Register& reg, + const uint64_t bit_pattern, + Label* label) { + int bits = reg.SizeInBits(); + ASSERT(CountSetBits(bit_pattern, bits) > 0); + if (CountSetBits(bit_pattern, bits) == 1) { + Tbnz(reg, MaskToBit(bit_pattern), label); + } else { + Tst(reg, bit_pattern); + B(ne, label); + } +} + + +void MacroAssembler::TestAndBranchIfAllClear(const Register& reg, + const uint64_t bit_pattern, + Label* label) { + int bits = reg.SizeInBits(); + ASSERT(CountSetBits(bit_pattern, bits) > 0); + if (CountSetBits(bit_pattern, bits) == 1) { + Tbz(reg, MaskToBit(bit_pattern), label); + } else { + Tst(reg, bit_pattern); + B(eq, label); + } +} + + +void MacroAssembler::InlineData(uint64_t data) { + ASSERT(is_uint16(data)); + InstructionAccurateScope scope(this, 1); + movz(xzr, data); +} + + +void MacroAssembler::EnableInstrumentation() { + InstructionAccurateScope scope(this, 1); + movn(xzr, InstrumentStateEnable); +} + + +void MacroAssembler::DisableInstrumentation() { + InstructionAccurateScope scope(this, 1); + movn(xzr, InstrumentStateDisable); +} + + +void MacroAssembler::AnnotateInstrumentation(const char* marker_name) { + ASSERT(strlen(marker_name) == 2); + + // We allow only printable characters in the marker names. Unprintable + // characters are reserved for controlling features of the instrumentation. + ASSERT(isprint(marker_name[0]) && isprint(marker_name[1])); + + InstructionAccurateScope scope(this, 1); + movn(xzr, (marker_name[1] << 8) | marker_name[0]); +} + +} } // namespace v8::internal + +#endif // V8_ARM64_MACRO_ASSEMBLER_ARM64_INL_H_ diff --git a/chromium/v8/src/arm64/macro-assembler-arm64.cc b/chromium/v8/src/arm64/macro-assembler-arm64.cc new file mode 100644 index 00000000000..f2e49b4639c --- /dev/null +++ b/chromium/v8/src/arm64/macro-assembler-arm64.cc @@ -0,0 +1,5303 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/bootstrapper.h" +#include "src/codegen.h" +#include "src/cpu-profiler.h" +#include "src/debug.h" +#include "src/isolate-inl.h" +#include "src/runtime.h" + +namespace v8 { +namespace internal { + +// Define a fake double underscore to use with the ASM_UNIMPLEMENTED macros. +#define __ + + +MacroAssembler::MacroAssembler(Isolate* arg_isolate, + byte * buffer, + unsigned buffer_size) + : Assembler(arg_isolate, buffer, buffer_size), + generating_stub_(false), +#if DEBUG + allow_macro_instructions_(true), +#endif + has_frame_(false), + use_real_aborts_(true), + sp_(jssp), + tmp_list_(DefaultTmpList()), + fptmp_list_(DefaultFPTmpList()) { + if (isolate() != NULL) { + code_object_ = Handle<Object>(isolate()->heap()->undefined_value(), + isolate()); + } +} + + +CPURegList MacroAssembler::DefaultTmpList() { + return CPURegList(ip0, ip1); +} + + +CPURegList MacroAssembler::DefaultFPTmpList() { + return CPURegList(fp_scratch1, fp_scratch2); +} + + +void MacroAssembler::LogicalMacro(const Register& rd, + const Register& rn, + const Operand& operand, + LogicalOp op) { + UseScratchRegisterScope temps(this); + + if (operand.NeedsRelocation(this)) { + Register temp = temps.AcquireX(); + Ldr(temp, operand.immediate()); + Logical(rd, rn, temp, op); + + } else if (operand.IsImmediate()) { + int64_t immediate = operand.ImmediateValue(); + unsigned reg_size = rd.SizeInBits(); + ASSERT(rd.Is64Bits() || is_uint32(immediate)); + + // If the operation is NOT, invert the operation and immediate. + if ((op & NOT) == NOT) { + op = static_cast<LogicalOp>(op & ~NOT); + immediate = ~immediate; + if (rd.Is32Bits()) { + immediate &= kWRegMask; + } + } + + // Special cases for all set or all clear immediates. + if (immediate == 0) { + switch (op) { + case AND: + Mov(rd, 0); + return; + case ORR: // Fall through. + case EOR: + Mov(rd, rn); + return; + case ANDS: // Fall through. + case BICS: + break; + default: + UNREACHABLE(); + } + } else if ((rd.Is64Bits() && (immediate == -1L)) || + (rd.Is32Bits() && (immediate == 0xffffffffL))) { + switch (op) { + case AND: + Mov(rd, rn); + return; + case ORR: + Mov(rd, immediate); + return; + case EOR: + Mvn(rd, rn); + return; + case ANDS: // Fall through. + case BICS: + break; + default: + UNREACHABLE(); + } + } + + unsigned n, imm_s, imm_r; + if (IsImmLogical(immediate, reg_size, &n, &imm_s, &imm_r)) { + // Immediate can be encoded in the instruction. + LogicalImmediate(rd, rn, n, imm_s, imm_r, op); + } else { + // Immediate can't be encoded: synthesize using move immediate. + Register temp = temps.AcquireSameSizeAs(rn); + Mov(temp, immediate); + if (rd.Is(csp)) { + // If rd is the stack pointer we cannot use it as the destination + // register so we use the temp register as an intermediate again. + Logical(temp, rn, temp, op); + Mov(csp, temp); + AssertStackConsistency(); + } else { + Logical(rd, rn, temp, op); + } + } + + } else if (operand.IsExtendedRegister()) { + ASSERT(operand.reg().SizeInBits() <= rd.SizeInBits()); + // Add/sub extended supports shift <= 4. We want to support exactly the + // same modes here. + ASSERT(operand.shift_amount() <= 4); + ASSERT(operand.reg().Is64Bits() || + ((operand.extend() != UXTX) && (operand.extend() != SXTX))); + Register temp = temps.AcquireSameSizeAs(rn); + EmitExtendShift(temp, operand.reg(), operand.extend(), + operand.shift_amount()); + Logical(rd, rn, temp, op); + + } else { + // The operand can be encoded in the instruction. + ASSERT(operand.IsShiftedRegister()); + Logical(rd, rn, operand, op); + } +} + + +void MacroAssembler::Mov(const Register& rd, uint64_t imm) { + ASSERT(allow_macro_instructions_); + ASSERT(is_uint32(imm) || is_int32(imm) || rd.Is64Bits()); + ASSERT(!rd.IsZero()); + + // TODO(all) extend to support more immediates. + // + // Immediates on Aarch64 can be produced using an initial value, and zero to + // three move keep operations. + // + // Initial values can be generated with: + // 1. 64-bit move zero (movz). + // 2. 32-bit move inverted (movn). + // 3. 64-bit move inverted. + // 4. 32-bit orr immediate. + // 5. 64-bit orr immediate. + // Move-keep may then be used to modify each of the 16-bit half-words. + // + // The code below supports all five initial value generators, and + // applying move-keep operations to move-zero and move-inverted initial + // values. + + unsigned reg_size = rd.SizeInBits(); + unsigned n, imm_s, imm_r; + if (IsImmMovz(imm, reg_size) && !rd.IsSP()) { + // Immediate can be represented in a move zero instruction. Movz can't + // write to the stack pointer. + movz(rd, imm); + } else if (IsImmMovn(imm, reg_size) && !rd.IsSP()) { + // Immediate can be represented in a move inverted instruction. Movn can't + // write to the stack pointer. + movn(rd, rd.Is64Bits() ? ~imm : (~imm & kWRegMask)); + } else if (IsImmLogical(imm, reg_size, &n, &imm_s, &imm_r)) { + // Immediate can be represented in a logical orr instruction. + LogicalImmediate(rd, AppropriateZeroRegFor(rd), n, imm_s, imm_r, ORR); + } else { + // Generic immediate case. Imm will be represented by + // [imm3, imm2, imm1, imm0], where each imm is 16 bits. + // A move-zero or move-inverted is generated for the first non-zero or + // non-0xffff immX, and a move-keep for subsequent non-zero immX. + + uint64_t ignored_halfword = 0; + bool invert_move = false; + // If the number of 0xffff halfwords is greater than the number of 0x0000 + // halfwords, it's more efficient to use move-inverted. + if (CountClearHalfWords(~imm, reg_size) > + CountClearHalfWords(imm, reg_size)) { + ignored_halfword = 0xffffL; + invert_move = true; + } + + // Mov instructions can't move immediate values into the stack pointer, so + // set up a temporary register, if needed. + UseScratchRegisterScope temps(this); + Register temp = rd.IsSP() ? temps.AcquireSameSizeAs(rd) : rd; + + // Iterate through the halfwords. Use movn/movz for the first non-ignored + // halfword, and movk for subsequent halfwords. + ASSERT((reg_size % 16) == 0); + bool first_mov_done = false; + for (unsigned i = 0; i < (rd.SizeInBits() / 16); i++) { + uint64_t imm16 = (imm >> (16 * i)) & 0xffffL; + if (imm16 != ignored_halfword) { + if (!first_mov_done) { + if (invert_move) { + movn(temp, (~imm16) & 0xffffL, 16 * i); + } else { + movz(temp, imm16, 16 * i); + } + first_mov_done = true; + } else { + // Construct a wider constant. + movk(temp, imm16, 16 * i); + } + } + } + ASSERT(first_mov_done); + + // Move the temporary if the original destination register was the stack + // pointer. + if (rd.IsSP()) { + mov(rd, temp); + AssertStackConsistency(); + } + } +} + + +void MacroAssembler::Mov(const Register& rd, + const Operand& operand, + DiscardMoveMode discard_mode) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + + // Provide a swap register for instructions that need to write into the + // system stack pointer (and can't do this inherently). + UseScratchRegisterScope temps(this); + Register dst = (rd.IsSP()) ? temps.AcquireSameSizeAs(rd) : rd; + + if (operand.NeedsRelocation(this)) { + Ldr(dst, operand.immediate()); + + } else if (operand.IsImmediate()) { + // Call the macro assembler for generic immediates. + Mov(dst, operand.ImmediateValue()); + + } else if (operand.IsShiftedRegister() && (operand.shift_amount() != 0)) { + // Emit a shift instruction if moving a shifted register. This operation + // could also be achieved using an orr instruction (like orn used by Mvn), + // but using a shift instruction makes the disassembly clearer. + EmitShift(dst, operand.reg(), operand.shift(), operand.shift_amount()); + + } else if (operand.IsExtendedRegister()) { + // Emit an extend instruction if moving an extended register. This handles + // extend with post-shift operations, too. + EmitExtendShift(dst, operand.reg(), operand.extend(), + operand.shift_amount()); + + } else { + // Otherwise, emit a register move only if the registers are distinct, or + // if they are not X registers. + // + // Note that mov(w0, w0) is not a no-op because it clears the top word of + // x0. A flag is provided (kDiscardForSameWReg) if a move between the same W + // registers is not required to clear the top word of the X register. In + // this case, the instruction is discarded. + // + // If csp is an operand, add #0 is emitted, otherwise, orr #0. + if (!rd.Is(operand.reg()) || (rd.Is32Bits() && + (discard_mode == kDontDiscardForSameWReg))) { + Assembler::mov(rd, operand.reg()); + } + // This case can handle writes into the system stack pointer directly. + dst = rd; + } + + // Copy the result to the system stack pointer. + if (!dst.Is(rd)) { + ASSERT(rd.IsSP()); + Assembler::mov(rd, dst); + } +} + + +void MacroAssembler::Mvn(const Register& rd, const Operand& operand) { + ASSERT(allow_macro_instructions_); + + if (operand.NeedsRelocation(this)) { + Ldr(rd, operand.immediate()); + mvn(rd, rd); + + } else if (operand.IsImmediate()) { + // Call the macro assembler for generic immediates. + Mov(rd, ~operand.ImmediateValue()); + + } else if (operand.IsExtendedRegister()) { + // Emit two instructions for the extend case. This differs from Mov, as + // the extend and invert can't be achieved in one instruction. + EmitExtendShift(rd, operand.reg(), operand.extend(), + operand.shift_amount()); + mvn(rd, rd); + + } else { + mvn(rd, operand); + } +} + + +unsigned MacroAssembler::CountClearHalfWords(uint64_t imm, unsigned reg_size) { + ASSERT((reg_size % 8) == 0); + int count = 0; + for (unsigned i = 0; i < (reg_size / 16); i++) { + if ((imm & 0xffff) == 0) { + count++; + } + imm >>= 16; + } + return count; +} + + +// The movz instruction can generate immediates containing an arbitrary 16-bit +// half-word, with remaining bits clear, eg. 0x00001234, 0x0000123400000000. +bool MacroAssembler::IsImmMovz(uint64_t imm, unsigned reg_size) { + ASSERT((reg_size == kXRegSizeInBits) || (reg_size == kWRegSizeInBits)); + return CountClearHalfWords(imm, reg_size) >= ((reg_size / 16) - 1); +} + + +// The movn instruction can generate immediates containing an arbitrary 16-bit +// half-word, with remaining bits set, eg. 0xffff1234, 0xffff1234ffffffff. +bool MacroAssembler::IsImmMovn(uint64_t imm, unsigned reg_size) { + return IsImmMovz(~imm, reg_size); +} + + +void MacroAssembler::ConditionalCompareMacro(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond, + ConditionalCompareOp op) { + ASSERT((cond != al) && (cond != nv)); + if (operand.NeedsRelocation(this)) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Ldr(temp, operand.immediate()); + ConditionalCompareMacro(rn, temp, nzcv, cond, op); + + } else if ((operand.IsShiftedRegister() && (operand.shift_amount() == 0)) || + (operand.IsImmediate() && + IsImmConditionalCompare(operand.ImmediateValue()))) { + // The immediate can be encoded in the instruction, or the operand is an + // unshifted register: call the assembler. + ConditionalCompare(rn, operand, nzcv, cond, op); + + } else { + // The operand isn't directly supported by the instruction: perform the + // operation on a temporary register. + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireSameSizeAs(rn); + Mov(temp, operand); + ConditionalCompare(rn, temp, nzcv, cond, op); + } +} + + +void MacroAssembler::Csel(const Register& rd, + const Register& rn, + const Operand& operand, + Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + ASSERT((cond != al) && (cond != nv)); + if (operand.IsImmediate()) { + // Immediate argument. Handle special cases of 0, 1 and -1 using zero + // register. + int64_t imm = operand.ImmediateValue(); + Register zr = AppropriateZeroRegFor(rn); + if (imm == 0) { + csel(rd, rn, zr, cond); + } else if (imm == 1) { + csinc(rd, rn, zr, cond); + } else if (imm == -1) { + csinv(rd, rn, zr, cond); + } else { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireSameSizeAs(rn); + Mov(temp, imm); + csel(rd, rn, temp, cond); + } + } else if (operand.IsShiftedRegister() && (operand.shift_amount() == 0)) { + // Unshifted register argument. + csel(rd, rn, operand.reg(), cond); + } else { + // All other arguments. + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireSameSizeAs(rn); + Mov(temp, operand); + csel(rd, rn, temp, cond); + } +} + + +void MacroAssembler::AddSubMacro(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubOp op) { + if (operand.IsZero() && rd.Is(rn) && rd.Is64Bits() && rn.Is64Bits() && + !operand.NeedsRelocation(this) && (S == LeaveFlags)) { + // The instruction would be a nop. Avoid generating useless code. + return; + } + + if (operand.NeedsRelocation(this)) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Ldr(temp, operand.immediate()); + AddSubMacro(rd, rn, temp, S, op); + } else if ((operand.IsImmediate() && + !IsImmAddSub(operand.ImmediateValue())) || + (rn.IsZero() && !operand.IsShiftedRegister()) || + (operand.IsShiftedRegister() && (operand.shift() == ROR))) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireSameSizeAs(rn); + Mov(temp, operand); + AddSub(rd, rn, temp, S, op); + } else { + AddSub(rd, rn, operand, S, op); + } +} + + +void MacroAssembler::AddSubWithCarryMacro(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubWithCarryOp op) { + ASSERT(rd.SizeInBits() == rn.SizeInBits()); + UseScratchRegisterScope temps(this); + + if (operand.NeedsRelocation(this)) { + Register temp = temps.AcquireX(); + Ldr(temp, operand.immediate()); + AddSubWithCarryMacro(rd, rn, temp, S, op); + + } else if (operand.IsImmediate() || + (operand.IsShiftedRegister() && (operand.shift() == ROR))) { + // Add/sub with carry (immediate or ROR shifted register.) + Register temp = temps.AcquireSameSizeAs(rn); + Mov(temp, operand); + AddSubWithCarry(rd, rn, temp, S, op); + + } else if (operand.IsShiftedRegister() && (operand.shift_amount() != 0)) { + // Add/sub with carry (shifted register). + ASSERT(operand.reg().SizeInBits() == rd.SizeInBits()); + ASSERT(operand.shift() != ROR); + ASSERT(is_uintn(operand.shift_amount(), + rd.SizeInBits() == kXRegSizeInBits ? kXRegSizeInBitsLog2 + : kWRegSizeInBitsLog2)); + Register temp = temps.AcquireSameSizeAs(rn); + EmitShift(temp, operand.reg(), operand.shift(), operand.shift_amount()); + AddSubWithCarry(rd, rn, temp, S, op); + + } else if (operand.IsExtendedRegister()) { + // Add/sub with carry (extended register). + ASSERT(operand.reg().SizeInBits() <= rd.SizeInBits()); + // Add/sub extended supports a shift <= 4. We want to support exactly the + // same modes. + ASSERT(operand.shift_amount() <= 4); + ASSERT(operand.reg().Is64Bits() || + ((operand.extend() != UXTX) && (operand.extend() != SXTX))); + Register temp = temps.AcquireSameSizeAs(rn); + EmitExtendShift(temp, operand.reg(), operand.extend(), + operand.shift_amount()); + AddSubWithCarry(rd, rn, temp, S, op); + + } else { + // The addressing mode is directly supported by the instruction. + AddSubWithCarry(rd, rn, operand, S, op); + } +} + + +void MacroAssembler::LoadStoreMacro(const CPURegister& rt, + const MemOperand& addr, + LoadStoreOp op) { + int64_t offset = addr.offset(); + LSDataSize size = CalcLSDataSize(op); + + // Check if an immediate offset fits in the immediate field of the + // appropriate instruction. If not, emit two instructions to perform + // the operation. + if (addr.IsImmediateOffset() && !IsImmLSScaled(offset, size) && + !IsImmLSUnscaled(offset)) { + // Immediate offset that can't be encoded using unsigned or unscaled + // addressing modes. + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireSameSizeAs(addr.base()); + Mov(temp, addr.offset()); + LoadStore(rt, MemOperand(addr.base(), temp), op); + } else if (addr.IsPostIndex() && !IsImmLSUnscaled(offset)) { + // Post-index beyond unscaled addressing range. + LoadStore(rt, MemOperand(addr.base()), op); + add(addr.base(), addr.base(), offset); + } else if (addr.IsPreIndex() && !IsImmLSUnscaled(offset)) { + // Pre-index beyond unscaled addressing range. + add(addr.base(), addr.base(), offset); + LoadStore(rt, MemOperand(addr.base()), op); + } else { + // Encodable in one load/store instruction. + LoadStore(rt, addr, op); + } +} + + +void MacroAssembler::Load(const Register& rt, + const MemOperand& addr, + Representation r) { + ASSERT(!r.IsDouble()); + + if (r.IsInteger8()) { + Ldrsb(rt, addr); + } else if (r.IsUInteger8()) { + Ldrb(rt, addr); + } else if (r.IsInteger16()) { + Ldrsh(rt, addr); + } else if (r.IsUInteger16()) { + Ldrh(rt, addr); + } else if (r.IsInteger32()) { + Ldr(rt.W(), addr); + } else { + ASSERT(rt.Is64Bits()); + Ldr(rt, addr); + } +} + + +void MacroAssembler::Store(const Register& rt, + const MemOperand& addr, + Representation r) { + ASSERT(!r.IsDouble()); + + if (r.IsInteger8() || r.IsUInteger8()) { + Strb(rt, addr); + } else if (r.IsInteger16() || r.IsUInteger16()) { + Strh(rt, addr); + } else if (r.IsInteger32()) { + Str(rt.W(), addr); + } else { + ASSERT(rt.Is64Bits()); + if (r.IsHeapObject()) { + AssertNotSmi(rt); + } else if (r.IsSmi()) { + AssertSmi(rt); + } + Str(rt, addr); + } +} + + +bool MacroAssembler::NeedExtraInstructionsOrRegisterBranch( + Label *label, ImmBranchType b_type) { + bool need_longer_range = false; + // There are two situations in which we care about the offset being out of + // range: + // - The label is bound but too far away. + // - The label is not bound but linked, and the previous branch + // instruction in the chain is too far away. + if (label->is_bound() || label->is_linked()) { + need_longer_range = + !Instruction::IsValidImmPCOffset(b_type, label->pos() - pc_offset()); + } + if (!need_longer_range && !label->is_bound()) { + int max_reachable_pc = pc_offset() + Instruction::ImmBranchRange(b_type); + unresolved_branches_.insert( + std::pair<int, FarBranchInfo>(max_reachable_pc, + FarBranchInfo(pc_offset(), label))); + // Also maintain the next pool check. + next_veneer_pool_check_ = + Min(next_veneer_pool_check_, + max_reachable_pc - kVeneerDistanceCheckMargin); + } + return need_longer_range; +} + + +void MacroAssembler::Adr(const Register& rd, Label* label, AdrHint hint) { + ASSERT(allow_macro_instructions_); + ASSERT(!rd.IsZero()); + + if (hint == kAdrNear) { + adr(rd, label); + return; + } + + ASSERT(hint == kAdrFar); + UseScratchRegisterScope temps(this); + Register scratch = temps.AcquireX(); + ASSERT(!AreAliased(rd, scratch)); + + if (label->is_bound()) { + int label_offset = label->pos() - pc_offset(); + if (Instruction::IsValidPCRelOffset(label_offset)) { + adr(rd, label); + } else { + ASSERT(label_offset <= 0); + int min_adr_offset = -(1 << (Instruction::ImmPCRelRangeBitwidth - 1)); + adr(rd, min_adr_offset); + Add(rd, rd, label_offset - min_adr_offset); + } + } else { + InstructionAccurateScope scope( + this, PatchingAssembler::kAdrFarPatchableNInstrs); + adr(rd, label); + for (int i = 0; i < PatchingAssembler::kAdrFarPatchableNNops; ++i) { + nop(ADR_FAR_NOP); + } + movz(scratch, 0); + add(rd, rd, scratch); + } +} + + +void MacroAssembler::B(Label* label, BranchType type, Register reg, int bit) { + ASSERT((reg.Is(NoReg) || type >= kBranchTypeFirstUsingReg) && + (bit == -1 || type >= kBranchTypeFirstUsingBit)); + if (kBranchTypeFirstCondition <= type && type <= kBranchTypeLastCondition) { + B(static_cast<Condition>(type), label); + } else { + switch (type) { + case always: B(label); break; + case never: break; + case reg_zero: Cbz(reg, label); break; + case reg_not_zero: Cbnz(reg, label); break; + case reg_bit_clear: Tbz(reg, bit, label); break; + case reg_bit_set: Tbnz(reg, bit, label); break; + default: + UNREACHABLE(); + } + } +} + + +void MacroAssembler::B(Label* label, Condition cond) { + ASSERT(allow_macro_instructions_); + ASSERT((cond != al) && (cond != nv)); + + Label done; + bool need_extra_instructions = + NeedExtraInstructionsOrRegisterBranch(label, CondBranchType); + + if (need_extra_instructions) { + b(&done, NegateCondition(cond)); + B(label); + } else { + b(label, cond); + } + bind(&done); +} + + +void MacroAssembler::Tbnz(const Register& rt, unsigned bit_pos, Label* label) { + ASSERT(allow_macro_instructions_); + + Label done; + bool need_extra_instructions = + NeedExtraInstructionsOrRegisterBranch(label, TestBranchType); + + if (need_extra_instructions) { + tbz(rt, bit_pos, &done); + B(label); + } else { + tbnz(rt, bit_pos, label); + } + bind(&done); +} + + +void MacroAssembler::Tbz(const Register& rt, unsigned bit_pos, Label* label) { + ASSERT(allow_macro_instructions_); + + Label done; + bool need_extra_instructions = + NeedExtraInstructionsOrRegisterBranch(label, TestBranchType); + + if (need_extra_instructions) { + tbnz(rt, bit_pos, &done); + B(label); + } else { + tbz(rt, bit_pos, label); + } + bind(&done); +} + + +void MacroAssembler::Cbnz(const Register& rt, Label* label) { + ASSERT(allow_macro_instructions_); + + Label done; + bool need_extra_instructions = + NeedExtraInstructionsOrRegisterBranch(label, CompareBranchType); + + if (need_extra_instructions) { + cbz(rt, &done); + B(label); + } else { + cbnz(rt, label); + } + bind(&done); +} + + +void MacroAssembler::Cbz(const Register& rt, Label* label) { + ASSERT(allow_macro_instructions_); + + Label done; + bool need_extra_instructions = + NeedExtraInstructionsOrRegisterBranch(label, CompareBranchType); + + if (need_extra_instructions) { + cbnz(rt, &done); + B(label); + } else { + cbz(rt, label); + } + bind(&done); +} + + +// Pseudo-instructions. + + +void MacroAssembler::Abs(const Register& rd, const Register& rm, + Label* is_not_representable, + Label* is_representable) { + ASSERT(allow_macro_instructions_); + ASSERT(AreSameSizeAndType(rd, rm)); + + Cmp(rm, 1); + Cneg(rd, rm, lt); + + // If the comparison sets the v flag, the input was the smallest value + // representable by rm, and the mathematical result of abs(rm) is not + // representable using two's complement. + if ((is_not_representable != NULL) && (is_representable != NULL)) { + B(is_not_representable, vs); + B(is_representable); + } else if (is_not_representable != NULL) { + B(is_not_representable, vs); + } else if (is_representable != NULL) { + B(is_representable, vc); + } +} + + +// Abstracted stack operations. + + +void MacroAssembler::Push(const CPURegister& src0, const CPURegister& src1, + const CPURegister& src2, const CPURegister& src3) { + ASSERT(AreSameSizeAndType(src0, src1, src2, src3)); + + int count = 1 + src1.IsValid() + src2.IsValid() + src3.IsValid(); + int size = src0.SizeInBytes(); + + PushPreamble(count, size); + PushHelper(count, size, src0, src1, src2, src3); +} + + +void MacroAssembler::Push(const CPURegister& src0, const CPURegister& src1, + const CPURegister& src2, const CPURegister& src3, + const CPURegister& src4, const CPURegister& src5, + const CPURegister& src6, const CPURegister& src7) { + ASSERT(AreSameSizeAndType(src0, src1, src2, src3, src4, src5, src6, src7)); + + int count = 5 + src5.IsValid() + src6.IsValid() + src6.IsValid(); + int size = src0.SizeInBytes(); + + PushPreamble(count, size); + PushHelper(4, size, src0, src1, src2, src3); + PushHelper(count - 4, size, src4, src5, src6, src7); +} + + +void MacroAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1, + const CPURegister& dst2, const CPURegister& dst3) { + // It is not valid to pop into the same register more than once in one + // instruction, not even into the zero register. + ASSERT(!AreAliased(dst0, dst1, dst2, dst3)); + ASSERT(AreSameSizeAndType(dst0, dst1, dst2, dst3)); + ASSERT(dst0.IsValid()); + + int count = 1 + dst1.IsValid() + dst2.IsValid() + dst3.IsValid(); + int size = dst0.SizeInBytes(); + + PopHelper(count, size, dst0, dst1, dst2, dst3); + PopPostamble(count, size); +} + + +void MacroAssembler::PushPopQueue::PushQueued( + PreambleDirective preamble_directive) { + if (queued_.empty()) return; + + if (preamble_directive == WITH_PREAMBLE) { + masm_->PushPreamble(size_); + } + + int count = queued_.size(); + int index = 0; + while (index < count) { + // PushHelper can only handle registers with the same size and type, and it + // can handle only four at a time. Batch them up accordingly. + CPURegister batch[4] = {NoReg, NoReg, NoReg, NoReg}; + int batch_index = 0; + do { + batch[batch_index++] = queued_[index++]; + } while ((batch_index < 4) && (index < count) && + batch[0].IsSameSizeAndType(queued_[index])); + + masm_->PushHelper(batch_index, batch[0].SizeInBytes(), + batch[0], batch[1], batch[2], batch[3]); + } + + queued_.clear(); +} + + +void MacroAssembler::PushPopQueue::PopQueued() { + if (queued_.empty()) return; + + int count = queued_.size(); + int index = 0; + while (index < count) { + // PopHelper can only handle registers with the same size and type, and it + // can handle only four at a time. Batch them up accordingly. + CPURegister batch[4] = {NoReg, NoReg, NoReg, NoReg}; + int batch_index = 0; + do { + batch[batch_index++] = queued_[index++]; + } while ((batch_index < 4) && (index < count) && + batch[0].IsSameSizeAndType(queued_[index])); + + masm_->PopHelper(batch_index, batch[0].SizeInBytes(), + batch[0], batch[1], batch[2], batch[3]); + } + + masm_->PopPostamble(size_); + queued_.clear(); +} + + +void MacroAssembler::PushCPURegList(CPURegList registers) { + int size = registers.RegisterSizeInBytes(); + + PushPreamble(registers.Count(), size); + // Push up to four registers at a time because if the current stack pointer is + // csp and reg_size is 32, registers must be pushed in blocks of four in order + // to maintain the 16-byte alignment for csp. + while (!registers.IsEmpty()) { + int count_before = registers.Count(); + const CPURegister& src0 = registers.PopHighestIndex(); + const CPURegister& src1 = registers.PopHighestIndex(); + const CPURegister& src2 = registers.PopHighestIndex(); + const CPURegister& src3 = registers.PopHighestIndex(); + int count = count_before - registers.Count(); + PushHelper(count, size, src0, src1, src2, src3); + } +} + + +void MacroAssembler::PopCPURegList(CPURegList registers) { + int size = registers.RegisterSizeInBytes(); + + // Pop up to four registers at a time because if the current stack pointer is + // csp and reg_size is 32, registers must be pushed in blocks of four in + // order to maintain the 16-byte alignment for csp. + while (!registers.IsEmpty()) { + int count_before = registers.Count(); + const CPURegister& dst0 = registers.PopLowestIndex(); + const CPURegister& dst1 = registers.PopLowestIndex(); + const CPURegister& dst2 = registers.PopLowestIndex(); + const CPURegister& dst3 = registers.PopLowestIndex(); + int count = count_before - registers.Count(); + PopHelper(count, size, dst0, dst1, dst2, dst3); + } + PopPostamble(registers.Count(), size); +} + + +void MacroAssembler::PushMultipleTimes(CPURegister src, int count) { + int size = src.SizeInBytes(); + + PushPreamble(count, size); + + if (FLAG_optimize_for_size && count > 8) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + + Label loop; + __ Mov(temp, count / 2); + __ Bind(&loop); + PushHelper(2, size, src, src, NoReg, NoReg); + __ Subs(temp, temp, 1); + __ B(ne, &loop); + + count %= 2; + } + + // Push up to four registers at a time if possible because if the current + // stack pointer is csp and the register size is 32, registers must be pushed + // in blocks of four in order to maintain the 16-byte alignment for csp. + while (count >= 4) { + PushHelper(4, size, src, src, src, src); + count -= 4; + } + if (count >= 2) { + PushHelper(2, size, src, src, NoReg, NoReg); + count -= 2; + } + if (count == 1) { + PushHelper(1, size, src, NoReg, NoReg, NoReg); + count -= 1; + } + ASSERT(count == 0); +} + + +void MacroAssembler::PushMultipleTimes(CPURegister src, Register count) { + PushPreamble(Operand(count, UXTW, WhichPowerOf2(src.SizeInBytes()))); + + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireSameSizeAs(count); + + if (FLAG_optimize_for_size) { + Label loop, done; + + Subs(temp, count, 1); + B(mi, &done); + + // Push all registers individually, to save code size. + Bind(&loop); + Subs(temp, temp, 1); + PushHelper(1, src.SizeInBytes(), src, NoReg, NoReg, NoReg); + B(pl, &loop); + + Bind(&done); + } else { + Label loop, leftover2, leftover1, done; + + Subs(temp, count, 4); + B(mi, &leftover2); + + // Push groups of four first. + Bind(&loop); + Subs(temp, temp, 4); + PushHelper(4, src.SizeInBytes(), src, src, src, src); + B(pl, &loop); + + // Push groups of two. + Bind(&leftover2); + Tbz(count, 1, &leftover1); + PushHelper(2, src.SizeInBytes(), src, src, NoReg, NoReg); + + // Push the last one (if required). + Bind(&leftover1); + Tbz(count, 0, &done); + PushHelper(1, src.SizeInBytes(), src, NoReg, NoReg, NoReg); + + Bind(&done); + } +} + + +void MacroAssembler::PushHelper(int count, int size, + const CPURegister& src0, + const CPURegister& src1, + const CPURegister& src2, + const CPURegister& src3) { + // Ensure that we don't unintentially modify scratch or debug registers. + InstructionAccurateScope scope(this); + + ASSERT(AreSameSizeAndType(src0, src1, src2, src3)); + ASSERT(size == src0.SizeInBytes()); + + // When pushing multiple registers, the store order is chosen such that + // Push(a, b) is equivalent to Push(a) followed by Push(b). + switch (count) { + case 1: + ASSERT(src1.IsNone() && src2.IsNone() && src3.IsNone()); + str(src0, MemOperand(StackPointer(), -1 * size, PreIndex)); + break; + case 2: + ASSERT(src2.IsNone() && src3.IsNone()); + stp(src1, src0, MemOperand(StackPointer(), -2 * size, PreIndex)); + break; + case 3: + ASSERT(src3.IsNone()); + stp(src2, src1, MemOperand(StackPointer(), -3 * size, PreIndex)); + str(src0, MemOperand(StackPointer(), 2 * size)); + break; + case 4: + // Skip over 4 * size, then fill in the gap. This allows four W registers + // to be pushed using csp, whilst maintaining 16-byte alignment for csp + // at all times. + stp(src3, src2, MemOperand(StackPointer(), -4 * size, PreIndex)); + stp(src1, src0, MemOperand(StackPointer(), 2 * size)); + break; + default: + UNREACHABLE(); + } +} + + +void MacroAssembler::PopHelper(int count, int size, + const CPURegister& dst0, + const CPURegister& dst1, + const CPURegister& dst2, + const CPURegister& dst3) { + // Ensure that we don't unintentially modify scratch or debug registers. + InstructionAccurateScope scope(this); + + ASSERT(AreSameSizeAndType(dst0, dst1, dst2, dst3)); + ASSERT(size == dst0.SizeInBytes()); + + // When popping multiple registers, the load order is chosen such that + // Pop(a, b) is equivalent to Pop(a) followed by Pop(b). + switch (count) { + case 1: + ASSERT(dst1.IsNone() && dst2.IsNone() && dst3.IsNone()); + ldr(dst0, MemOperand(StackPointer(), 1 * size, PostIndex)); + break; + case 2: + ASSERT(dst2.IsNone() && dst3.IsNone()); + ldp(dst0, dst1, MemOperand(StackPointer(), 2 * size, PostIndex)); + break; + case 3: + ASSERT(dst3.IsNone()); + ldr(dst2, MemOperand(StackPointer(), 2 * size)); + ldp(dst0, dst1, MemOperand(StackPointer(), 3 * size, PostIndex)); + break; + case 4: + // Load the higher addresses first, then load the lower addresses and + // skip the whole block in the second instruction. This allows four W + // registers to be popped using csp, whilst maintaining 16-byte alignment + // for csp at all times. + ldp(dst2, dst3, MemOperand(StackPointer(), 2 * size)); + ldp(dst0, dst1, MemOperand(StackPointer(), 4 * size, PostIndex)); + break; + default: + UNREACHABLE(); + } +} + + +void MacroAssembler::PushPreamble(Operand total_size) { + if (csp.Is(StackPointer())) { + // If the current stack pointer is csp, then it must be aligned to 16 bytes + // on entry and the total size of the specified registers must also be a + // multiple of 16 bytes. + if (total_size.IsImmediate()) { + ASSERT((total_size.ImmediateValue() % 16) == 0); + } + + // Don't check access size for non-immediate sizes. It's difficult to do + // well, and it will be caught by hardware (or the simulator) anyway. + } else { + // Even if the current stack pointer is not the system stack pointer (csp), + // the system stack pointer will still be modified in order to comply with + // ABI rules about accessing memory below the system stack pointer. + BumpSystemStackPointer(total_size); + } +} + + +void MacroAssembler::PopPostamble(Operand total_size) { + if (csp.Is(StackPointer())) { + // If the current stack pointer is csp, then it must be aligned to 16 bytes + // on entry and the total size of the specified registers must also be a + // multiple of 16 bytes. + if (total_size.IsImmediate()) { + ASSERT((total_size.ImmediateValue() % 16) == 0); + } + + // Don't check access size for non-immediate sizes. It's difficult to do + // well, and it will be caught by hardware (or the simulator) anyway. + } else if (emit_debug_code()) { + // It is safe to leave csp where it is when unwinding the JavaScript stack, + // but if we keep it matching StackPointer, the simulator can detect memory + // accesses in the now-free part of the stack. + SyncSystemStackPointer(); + } +} + + +void MacroAssembler::Poke(const CPURegister& src, const Operand& offset) { + if (offset.IsImmediate()) { + ASSERT(offset.ImmediateValue() >= 0); + } else if (emit_debug_code()) { + Cmp(xzr, offset); + Check(le, kStackAccessBelowStackPointer); + } + + Str(src, MemOperand(StackPointer(), offset)); +} + + +void MacroAssembler::Peek(const CPURegister& dst, const Operand& offset) { + if (offset.IsImmediate()) { + ASSERT(offset.ImmediateValue() >= 0); + } else if (emit_debug_code()) { + Cmp(xzr, offset); + Check(le, kStackAccessBelowStackPointer); + } + + Ldr(dst, MemOperand(StackPointer(), offset)); +} + + +void MacroAssembler::PokePair(const CPURegister& src1, + const CPURegister& src2, + int offset) { + ASSERT(AreSameSizeAndType(src1, src2)); + ASSERT((offset >= 0) && ((offset % src1.SizeInBytes()) == 0)); + Stp(src1, src2, MemOperand(StackPointer(), offset)); +} + + +void MacroAssembler::PeekPair(const CPURegister& dst1, + const CPURegister& dst2, + int offset) { + ASSERT(AreSameSizeAndType(dst1, dst2)); + ASSERT((offset >= 0) && ((offset % dst1.SizeInBytes()) == 0)); + Ldp(dst1, dst2, MemOperand(StackPointer(), offset)); +} + + +void MacroAssembler::PushCalleeSavedRegisters() { + // Ensure that the macro-assembler doesn't use any scratch registers. + InstructionAccurateScope scope(this); + + // This method must not be called unless the current stack pointer is the + // system stack pointer (csp). + ASSERT(csp.Is(StackPointer())); + + MemOperand tos(csp, -2 * kXRegSize, PreIndex); + + stp(d14, d15, tos); + stp(d12, d13, tos); + stp(d10, d11, tos); + stp(d8, d9, tos); + + stp(x29, x30, tos); + stp(x27, x28, tos); // x28 = jssp + stp(x25, x26, tos); + stp(x23, x24, tos); + stp(x21, x22, tos); + stp(x19, x20, tos); +} + + +void MacroAssembler::PopCalleeSavedRegisters() { + // Ensure that the macro-assembler doesn't use any scratch registers. + InstructionAccurateScope scope(this); + + // This method must not be called unless the current stack pointer is the + // system stack pointer (csp). + ASSERT(csp.Is(StackPointer())); + + MemOperand tos(csp, 2 * kXRegSize, PostIndex); + + ldp(x19, x20, tos); + ldp(x21, x22, tos); + ldp(x23, x24, tos); + ldp(x25, x26, tos); + ldp(x27, x28, tos); // x28 = jssp + ldp(x29, x30, tos); + + ldp(d8, d9, tos); + ldp(d10, d11, tos); + ldp(d12, d13, tos); + ldp(d14, d15, tos); +} + + +void MacroAssembler::AssertStackConsistency() { + // Avoid emitting code when !use_real_abort() since non-real aborts cause too + // much code to be generated. + if (emit_debug_code() && use_real_aborts()) { + if (csp.Is(StackPointer()) || CpuFeatures::IsSupported(ALWAYS_ALIGN_CSP)) { + // Always check the alignment of csp if ALWAYS_ALIGN_CSP is true. We + // can't check the alignment of csp without using a scratch register (or + // clobbering the flags), but the processor (or simulator) will abort if + // it is not properly aligned during a load. + ldr(xzr, MemOperand(csp, 0)); + } + if (FLAG_enable_slow_asserts && !csp.Is(StackPointer())) { + Label ok; + // Check that csp <= StackPointer(), preserving all registers and NZCV. + sub(StackPointer(), csp, StackPointer()); + cbz(StackPointer(), &ok); // Ok if csp == StackPointer(). + tbnz(StackPointer(), kXSignBit, &ok); // Ok if csp < StackPointer(). + + // Avoid generating AssertStackConsistency checks for the Push in Abort. + { DontEmitDebugCodeScope dont_emit_debug_code_scope(this); + Abort(kTheCurrentStackPointerIsBelowCsp); + } + + bind(&ok); + // Restore StackPointer(). + sub(StackPointer(), csp, StackPointer()); + } + } +} + + +void MacroAssembler::AssertFPCRState(Register fpcr) { + if (emit_debug_code()) { + Label unexpected_mode, done; + UseScratchRegisterScope temps(this); + if (fpcr.IsNone()) { + fpcr = temps.AcquireX(); + Mrs(fpcr, FPCR); + } + + // Settings overridden by ConfiugreFPCR(): + // - Assert that default-NaN mode is set. + Tbz(fpcr, DN_offset, &unexpected_mode); + + // Settings left to their default values: + // - Assert that flush-to-zero is not set. + Tbnz(fpcr, FZ_offset, &unexpected_mode); + // - Assert that the rounding mode is nearest-with-ties-to-even. + STATIC_ASSERT(FPTieEven == 0); + Tst(fpcr, RMode_mask); + B(eq, &done); + + Bind(&unexpected_mode); + Abort(kUnexpectedFPCRMode); + + Bind(&done); + } +} + + +void MacroAssembler::ConfigureFPCR() { + UseScratchRegisterScope temps(this); + Register fpcr = temps.AcquireX(); + Mrs(fpcr, FPCR); + + // If necessary, enable default-NaN mode. The default values of the other FPCR + // options should be suitable, and AssertFPCRState will verify that. + Label no_write_required; + Tbnz(fpcr, DN_offset, &no_write_required); + + Orr(fpcr, fpcr, DN_mask); + Msr(FPCR, fpcr); + + Bind(&no_write_required); + AssertFPCRState(fpcr); +} + + +void MacroAssembler::CanonicalizeNaN(const FPRegister& dst, + const FPRegister& src) { + AssertFPCRState(); + + // With DN=1 and RMode=FPTieEven, subtracting 0.0 preserves all inputs except + // for NaNs, which become the default NaN. We use fsub rather than fadd + // because sub preserves -0.0 inputs: -0.0 + 0.0 = 0.0, but -0.0 - 0.0 = -0.0. + Fsub(dst, src, fp_zero); +} + + +void MacroAssembler::LoadRoot(CPURegister destination, + Heap::RootListIndex index) { + // TODO(jbramley): Most root values are constants, and can be synthesized + // without a load. Refer to the ARM back end for details. + Ldr(destination, MemOperand(root, index << kPointerSizeLog2)); +} + + +void MacroAssembler::StoreRoot(Register source, + Heap::RootListIndex index) { + Str(source, MemOperand(root, index << kPointerSizeLog2)); +} + + +void MacroAssembler::LoadTrueFalseRoots(Register true_root, + Register false_root) { + STATIC_ASSERT((Heap::kTrueValueRootIndex + 1) == Heap::kFalseValueRootIndex); + Ldp(true_root, false_root, + MemOperand(root, Heap::kTrueValueRootIndex << kPointerSizeLog2)); +} + + +void MacroAssembler::LoadHeapObject(Register result, + Handle<HeapObject> object) { + AllowDeferredHandleDereference using_raw_address; + if (isolate()->heap()->InNewSpace(*object)) { + Handle<Cell> cell = isolate()->factory()->NewCell(object); + Mov(result, Operand(cell)); + Ldr(result, FieldMemOperand(result, Cell::kValueOffset)); + } else { + Mov(result, Operand(object)); + } +} + + +void MacroAssembler::LoadInstanceDescriptors(Register map, + Register descriptors) { + Ldr(descriptors, FieldMemOperand(map, Map::kDescriptorsOffset)); +} + + +void MacroAssembler::NumberOfOwnDescriptors(Register dst, Register map) { + Ldr(dst, FieldMemOperand(map, Map::kBitField3Offset)); + DecodeField<Map::NumberOfOwnDescriptorsBits>(dst); +} + + +void MacroAssembler::EnumLengthUntagged(Register dst, Register map) { + STATIC_ASSERT(Map::EnumLengthBits::kShift == 0); + Ldrsw(dst, FieldMemOperand(map, Map::kBitField3Offset)); + And(dst, dst, Map::EnumLengthBits::kMask); +} + + +void MacroAssembler::EnumLengthSmi(Register dst, Register map) { + EnumLengthUntagged(dst, map); + SmiTag(dst, dst); +} + + +void MacroAssembler::CheckEnumCache(Register object, + Register null_value, + Register scratch0, + Register scratch1, + Register scratch2, + Register scratch3, + Label* call_runtime) { + ASSERT(!AreAliased(object, null_value, scratch0, scratch1, scratch2, + scratch3)); + + Register empty_fixed_array_value = scratch0; + Register current_object = scratch1; + + LoadRoot(empty_fixed_array_value, Heap::kEmptyFixedArrayRootIndex); + Label next, start; + + Mov(current_object, object); + + // Check if the enum length field is properly initialized, indicating that + // there is an enum cache. + Register map = scratch2; + Register enum_length = scratch3; + Ldr(map, FieldMemOperand(current_object, HeapObject::kMapOffset)); + + EnumLengthUntagged(enum_length, map); + Cmp(enum_length, kInvalidEnumCacheSentinel); + B(eq, call_runtime); + + B(&start); + + Bind(&next); + Ldr(map, FieldMemOperand(current_object, HeapObject::kMapOffset)); + + // For all objects but the receiver, check that the cache is empty. + EnumLengthUntagged(enum_length, map); + Cbnz(enum_length, call_runtime); + + Bind(&start); + + // Check that there are no elements. Register current_object contains the + // current JS object we've reached through the prototype chain. + Label no_elements; + Ldr(current_object, FieldMemOperand(current_object, + JSObject::kElementsOffset)); + Cmp(current_object, empty_fixed_array_value); + B(eq, &no_elements); + + // Second chance, the object may be using the empty slow element dictionary. + CompareRoot(current_object, Heap::kEmptySlowElementDictionaryRootIndex); + B(ne, call_runtime); + + Bind(&no_elements); + Ldr(current_object, FieldMemOperand(map, Map::kPrototypeOffset)); + Cmp(current_object, null_value); + B(ne, &next); +} + + +void MacroAssembler::TestJSArrayForAllocationMemento(Register receiver, + Register scratch1, + Register scratch2, + Label* no_memento_found) { + ExternalReference new_space_start = + ExternalReference::new_space_start(isolate()); + ExternalReference new_space_allocation_top = + ExternalReference::new_space_allocation_top_address(isolate()); + + Add(scratch1, receiver, + JSArray::kSize + AllocationMemento::kSize - kHeapObjectTag); + Cmp(scratch1, new_space_start); + B(lt, no_memento_found); + + Mov(scratch2, new_space_allocation_top); + Ldr(scratch2, MemOperand(scratch2)); + Cmp(scratch1, scratch2); + B(gt, no_memento_found); + + Ldr(scratch1, MemOperand(scratch1, -AllocationMemento::kSize)); + Cmp(scratch1, + Operand(isolate()->factory()->allocation_memento_map())); +} + + +void MacroAssembler::JumpToHandlerEntry(Register exception, + Register object, + Register state, + Register scratch1, + Register scratch2) { + // Handler expects argument in x0. + ASSERT(exception.Is(x0)); + + // Compute the handler entry address and jump to it. The handler table is + // a fixed array of (smi-tagged) code offsets. + Ldr(scratch1, FieldMemOperand(object, Code::kHandlerTableOffset)); + Add(scratch1, scratch1, FixedArray::kHeaderSize - kHeapObjectTag); + STATIC_ASSERT(StackHandler::kKindWidth < kPointerSizeLog2); + Lsr(scratch2, state, StackHandler::kKindWidth); + Ldr(scratch2, MemOperand(scratch1, scratch2, LSL, kPointerSizeLog2)); + Add(scratch1, object, Code::kHeaderSize - kHeapObjectTag); + Add(scratch1, scratch1, Operand::UntagSmi(scratch2)); + Br(scratch1); +} + + +void MacroAssembler::InNewSpace(Register object, + Condition cond, + Label* branch) { + ASSERT(cond == eq || cond == ne); + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + And(temp, object, ExternalReference::new_space_mask(isolate())); + Cmp(temp, ExternalReference::new_space_start(isolate())); + B(cond, branch); +} + + +void MacroAssembler::Throw(Register value, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4) { + // Adjust this code if not the case. + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); + STATIC_ASSERT(StackHandlerConstants::kCodeOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 4 * kPointerSize); + + // The handler expects the exception in x0. + ASSERT(value.Is(x0)); + + // Drop the stack pointer to the top of the top handler. + ASSERT(jssp.Is(StackPointer())); + Mov(scratch1, Operand(ExternalReference(Isolate::kHandlerAddress, + isolate()))); + Ldr(jssp, MemOperand(scratch1)); + // Restore the next handler. + Pop(scratch2); + Str(scratch2, MemOperand(scratch1)); + + // Get the code object and state. Restore the context and frame pointer. + Register object = scratch1; + Register state = scratch2; + Pop(object, state, cp, fp); + + // If the handler is a JS frame, restore the context to the frame. + // (kind == ENTRY) == (fp == 0) == (cp == 0), so we could test either fp + // or cp. + Label not_js_frame; + Cbz(cp, ¬_js_frame); + Str(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + Bind(¬_js_frame); + + JumpToHandlerEntry(value, object, state, scratch3, scratch4); +} + + +void MacroAssembler::ThrowUncatchable(Register value, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4) { + // Adjust this code if not the case. + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kCodeOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 4 * kPointerSize); + + // The handler expects the exception in x0. + ASSERT(value.Is(x0)); + + // Drop the stack pointer to the top of the top stack handler. + ASSERT(jssp.Is(StackPointer())); + Mov(scratch1, Operand(ExternalReference(Isolate::kHandlerAddress, + isolate()))); + Ldr(jssp, MemOperand(scratch1)); + + // Unwind the handlers until the ENTRY handler is found. + Label fetch_next, check_kind; + B(&check_kind); + Bind(&fetch_next); + Peek(jssp, StackHandlerConstants::kNextOffset); + + Bind(&check_kind); + STATIC_ASSERT(StackHandler::JS_ENTRY == 0); + Peek(scratch2, StackHandlerConstants::kStateOffset); + TestAndBranchIfAnySet(scratch2, StackHandler::KindField::kMask, &fetch_next); + + // Set the top handler address to next handler past the top ENTRY handler. + Pop(scratch2); + Str(scratch2, MemOperand(scratch1)); + + // Get the code object and state. Clear the context and frame pointer (0 was + // saved in the handler). + Register object = scratch1; + Register state = scratch2; + Pop(object, state, cp, fp); + + JumpToHandlerEntry(value, object, state, scratch3, scratch4); +} + + +void MacroAssembler::SmiAbs(const Register& smi, Label* slow) { + ASSERT(smi.Is64Bits()); + Abs(smi, smi, slow); +} + + +void MacroAssembler::AssertSmi(Register object, BailoutReason reason) { + if (emit_debug_code()) { + STATIC_ASSERT(kSmiTag == 0); + Tst(object, kSmiTagMask); + Check(eq, reason); + } +} + + +void MacroAssembler::AssertNotSmi(Register object, BailoutReason reason) { + if (emit_debug_code()) { + STATIC_ASSERT(kSmiTag == 0); + Tst(object, kSmiTagMask); + Check(ne, reason); + } +} + + +void MacroAssembler::AssertName(Register object) { + if (emit_debug_code()) { + AssertNotSmi(object, kOperandIsASmiAndNotAName); + + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + + Ldr(temp, FieldMemOperand(object, HeapObject::kMapOffset)); + CompareInstanceType(temp, temp, LAST_NAME_TYPE); + Check(ls, kOperandIsNotAName); + } +} + + +void MacroAssembler::AssertUndefinedOrAllocationSite(Register object, + Register scratch) { + if (emit_debug_code()) { + Label done_checking; + AssertNotSmi(object); + JumpIfRoot(object, Heap::kUndefinedValueRootIndex, &done_checking); + Ldr(scratch, FieldMemOperand(object, HeapObject::kMapOffset)); + CompareRoot(scratch, Heap::kAllocationSiteMapRootIndex); + Assert(eq, kExpectedUndefinedOrCell); + Bind(&done_checking); + } +} + + +void MacroAssembler::AssertString(Register object) { + if (emit_debug_code()) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + STATIC_ASSERT(kSmiTag == 0); + Tst(object, kSmiTagMask); + Check(ne, kOperandIsASmiAndNotAString); + Ldr(temp, FieldMemOperand(object, HeapObject::kMapOffset)); + CompareInstanceType(temp, temp, FIRST_NONSTRING_TYPE); + Check(lo, kOperandIsNotAString); + } +} + + +void MacroAssembler::CallStub(CodeStub* stub, TypeFeedbackId ast_id) { + ASSERT(AllowThisStubCall(stub)); // Stub calls are not allowed in some stubs. + Call(stub->GetCode(), RelocInfo::CODE_TARGET, ast_id); +} + + +void MacroAssembler::TailCallStub(CodeStub* stub) { + Jump(stub->GetCode(), RelocInfo::CODE_TARGET); +} + + +void MacroAssembler::CallRuntime(const Runtime::Function* f, + int num_arguments, + SaveFPRegsMode save_doubles) { + // All arguments must be on the stack before this function is called. + // x0 holds the return value after the call. + + // Check that the number of arguments matches what the function expects. + // If f->nargs is -1, the function can accept a variable number of arguments. + CHECK(f->nargs < 0 || f->nargs == num_arguments); + + // Place the necessary arguments. + Mov(x0, num_arguments); + Mov(x1, ExternalReference(f, isolate())); + + CEntryStub stub(isolate(), 1, save_doubles); + CallStub(&stub); +} + + +static int AddressOffset(ExternalReference ref0, ExternalReference ref1) { + return ref0.address() - ref1.address(); +} + + +void MacroAssembler::CallApiFunctionAndReturn( + Register function_address, + ExternalReference thunk_ref, + int stack_space, + int spill_offset, + MemOperand return_value_operand, + MemOperand* context_restore_operand) { + ASM_LOCATION("CallApiFunctionAndReturn"); + ExternalReference next_address = + ExternalReference::handle_scope_next_address(isolate()); + const int kNextOffset = 0; + const int kLimitOffset = AddressOffset( + ExternalReference::handle_scope_limit_address(isolate()), + next_address); + const int kLevelOffset = AddressOffset( + ExternalReference::handle_scope_level_address(isolate()), + next_address); + + ASSERT(function_address.is(x1) || function_address.is(x2)); + + Label profiler_disabled; + Label end_profiler_check; + Mov(x10, ExternalReference::is_profiling_address(isolate())); + Ldrb(w10, MemOperand(x10)); + Cbz(w10, &profiler_disabled); + Mov(x3, thunk_ref); + B(&end_profiler_check); + + Bind(&profiler_disabled); + Mov(x3, function_address); + Bind(&end_profiler_check); + + // Save the callee-save registers we are going to use. + // TODO(all): Is this necessary? ARM doesn't do it. + STATIC_ASSERT(kCallApiFunctionSpillSpace == 4); + Poke(x19, (spill_offset + 0) * kXRegSize); + Poke(x20, (spill_offset + 1) * kXRegSize); + Poke(x21, (spill_offset + 2) * kXRegSize); + Poke(x22, (spill_offset + 3) * kXRegSize); + + // Allocate HandleScope in callee-save registers. + // We will need to restore the HandleScope after the call to the API function, + // by allocating it in callee-save registers they will be preserved by C code. + Register handle_scope_base = x22; + Register next_address_reg = x19; + Register limit_reg = x20; + Register level_reg = w21; + + Mov(handle_scope_base, next_address); + Ldr(next_address_reg, MemOperand(handle_scope_base, kNextOffset)); + Ldr(limit_reg, MemOperand(handle_scope_base, kLimitOffset)); + Ldr(level_reg, MemOperand(handle_scope_base, kLevelOffset)); + Add(level_reg, level_reg, 1); + Str(level_reg, MemOperand(handle_scope_base, kLevelOffset)); + + if (FLAG_log_timer_events) { + FrameScope frame(this, StackFrame::MANUAL); + PushSafepointRegisters(); + Mov(x0, ExternalReference::isolate_address(isolate())); + CallCFunction(ExternalReference::log_enter_external_function(isolate()), 1); + PopSafepointRegisters(); + } + + // Native call returns to the DirectCEntry stub which redirects to the + // return address pushed on stack (could have moved after GC). + // DirectCEntry stub itself is generated early and never moves. + DirectCEntryStub stub(isolate()); + stub.GenerateCall(this, x3); + + if (FLAG_log_timer_events) { + FrameScope frame(this, StackFrame::MANUAL); + PushSafepointRegisters(); + Mov(x0, ExternalReference::isolate_address(isolate())); + CallCFunction(ExternalReference::log_leave_external_function(isolate()), 1); + PopSafepointRegisters(); + } + + Label promote_scheduled_exception; + Label exception_handled; + Label delete_allocated_handles; + Label leave_exit_frame; + Label return_value_loaded; + + // Load value from ReturnValue. + Ldr(x0, return_value_operand); + Bind(&return_value_loaded); + // No more valid handles (the result handle was the last one). Restore + // previous handle scope. + Str(next_address_reg, MemOperand(handle_scope_base, kNextOffset)); + if (emit_debug_code()) { + Ldr(w1, MemOperand(handle_scope_base, kLevelOffset)); + Cmp(w1, level_reg); + Check(eq, kUnexpectedLevelAfterReturnFromApiCall); + } + Sub(level_reg, level_reg, 1); + Str(level_reg, MemOperand(handle_scope_base, kLevelOffset)); + Ldr(x1, MemOperand(handle_scope_base, kLimitOffset)); + Cmp(limit_reg, x1); + B(ne, &delete_allocated_handles); + + Bind(&leave_exit_frame); + // Restore callee-saved registers. + Peek(x19, (spill_offset + 0) * kXRegSize); + Peek(x20, (spill_offset + 1) * kXRegSize); + Peek(x21, (spill_offset + 2) * kXRegSize); + Peek(x22, (spill_offset + 3) * kXRegSize); + + // Check if the function scheduled an exception. + Mov(x5, ExternalReference::scheduled_exception_address(isolate())); + Ldr(x5, MemOperand(x5)); + JumpIfNotRoot(x5, Heap::kTheHoleValueRootIndex, &promote_scheduled_exception); + Bind(&exception_handled); + + bool restore_context = context_restore_operand != NULL; + if (restore_context) { + Ldr(cp, *context_restore_operand); + } + + LeaveExitFrame(false, x1, !restore_context); + Drop(stack_space); + Ret(); + + Bind(&promote_scheduled_exception); + { + FrameScope frame(this, StackFrame::INTERNAL); + CallExternalReference( + ExternalReference( + Runtime::kHiddenPromoteScheduledException, isolate()), 0); + } + B(&exception_handled); + + // HandleScope limit has changed. Delete allocated extensions. + Bind(&delete_allocated_handles); + Str(limit_reg, MemOperand(handle_scope_base, kLimitOffset)); + // Save the return value in a callee-save register. + Register saved_result = x19; + Mov(saved_result, x0); + Mov(x0, ExternalReference::isolate_address(isolate())); + CallCFunction( + ExternalReference::delete_handle_scope_extensions(isolate()), 1); + Mov(x0, saved_result); + B(&leave_exit_frame); +} + + +void MacroAssembler::CallExternalReference(const ExternalReference& ext, + int num_arguments) { + Mov(x0, num_arguments); + Mov(x1, ext); + + CEntryStub stub(isolate(), 1); + CallStub(&stub); +} + + +void MacroAssembler::JumpToExternalReference(const ExternalReference& builtin) { + Mov(x1, builtin); + CEntryStub stub(isolate(), 1); + Jump(stub.GetCode(), RelocInfo::CODE_TARGET); +} + + +void MacroAssembler::GetBuiltinFunction(Register target, + Builtins::JavaScript id) { + // Load the builtins object into target register. + Ldr(target, GlobalObjectMemOperand()); + Ldr(target, FieldMemOperand(target, GlobalObject::kBuiltinsOffset)); + // Load the JavaScript builtin function from the builtins object. + Ldr(target, FieldMemOperand(target, + JSBuiltinsObject::OffsetOfFunctionWithId(id))); +} + + +void MacroAssembler::GetBuiltinEntry(Register target, + Register function, + Builtins::JavaScript id) { + ASSERT(!AreAliased(target, function)); + GetBuiltinFunction(function, id); + // Load the code entry point from the builtins object. + Ldr(target, FieldMemOperand(function, JSFunction::kCodeEntryOffset)); +} + + +void MacroAssembler::InvokeBuiltin(Builtins::JavaScript id, + InvokeFlag flag, + const CallWrapper& call_wrapper) { + ASM_LOCATION("MacroAssembler::InvokeBuiltin"); + // You can't call a builtin without a valid frame. + ASSERT(flag == JUMP_FUNCTION || has_frame()); + + // Get the builtin entry in x2 and setup the function object in x1. + GetBuiltinEntry(x2, x1, id); + if (flag == CALL_FUNCTION) { + call_wrapper.BeforeCall(CallSize(x2)); + Call(x2); + call_wrapper.AfterCall(); + } else { + ASSERT(flag == JUMP_FUNCTION); + Jump(x2); + } +} + + +void MacroAssembler::TailCallExternalReference(const ExternalReference& ext, + int num_arguments, + int result_size) { + // TODO(1236192): Most runtime routines don't need the number of + // arguments passed in because it is constant. At some point we + // should remove this need and make the runtime routine entry code + // smarter. + Mov(x0, num_arguments); + JumpToExternalReference(ext); +} + + +void MacroAssembler::TailCallRuntime(Runtime::FunctionId fid, + int num_arguments, + int result_size) { + TailCallExternalReference(ExternalReference(fid, isolate()), + num_arguments, + result_size); +} + + +void MacroAssembler::InitializeNewString(Register string, + Register length, + Heap::RootListIndex map_index, + Register scratch1, + Register scratch2) { + ASSERT(!AreAliased(string, length, scratch1, scratch2)); + LoadRoot(scratch2, map_index); + SmiTag(scratch1, length); + Str(scratch2, FieldMemOperand(string, HeapObject::kMapOffset)); + + Mov(scratch2, String::kEmptyHashField); + Str(scratch1, FieldMemOperand(string, String::kLengthOffset)); + Str(scratch2, FieldMemOperand(string, String::kHashFieldOffset)); +} + + +int MacroAssembler::ActivationFrameAlignment() { +#if V8_HOST_ARCH_ARM64 + // Running on the real platform. Use the alignment as mandated by the local + // environment. + // Note: This will break if we ever start generating snapshots on one ARM + // platform for another ARM platform with a different alignment. + return OS::ActivationFrameAlignment(); +#else // V8_HOST_ARCH_ARM64 + // If we are using the simulator then we should always align to the expected + // alignment. As the simulator is used to generate snapshots we do not know + // if the target platform will need alignment, so this is controlled from a + // flag. + return FLAG_sim_stack_alignment; +#endif // V8_HOST_ARCH_ARM64 +} + + +void MacroAssembler::CallCFunction(ExternalReference function, + int num_of_reg_args) { + CallCFunction(function, num_of_reg_args, 0); +} + + +void MacroAssembler::CallCFunction(ExternalReference function, + int num_of_reg_args, + int num_of_double_args) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Mov(temp, function); + CallCFunction(temp, num_of_reg_args, num_of_double_args); +} + + +void MacroAssembler::CallCFunction(Register function, + int num_of_reg_args, + int num_of_double_args) { + ASSERT(has_frame()); + // We can pass 8 integer arguments in registers. If we need to pass more than + // that, we'll need to implement support for passing them on the stack. + ASSERT(num_of_reg_args <= 8); + + // If we're passing doubles, we're limited to the following prototypes + // (defined by ExternalReference::Type): + // BUILTIN_COMPARE_CALL: int f(double, double) + // BUILTIN_FP_FP_CALL: double f(double, double) + // BUILTIN_FP_CALL: double f(double) + // BUILTIN_FP_INT_CALL: double f(double, int) + if (num_of_double_args > 0) { + ASSERT(num_of_reg_args <= 1); + ASSERT((num_of_double_args + num_of_reg_args) <= 2); + } + + + // If the stack pointer is not csp, we need to derive an aligned csp from the + // current stack pointer. + const Register old_stack_pointer = StackPointer(); + if (!csp.Is(old_stack_pointer)) { + AssertStackConsistency(); + + int sp_alignment = ActivationFrameAlignment(); + // The ABI mandates at least 16-byte alignment. + ASSERT(sp_alignment >= 16); + ASSERT(IsPowerOf2(sp_alignment)); + + // The current stack pointer is a callee saved register, and is preserved + // across the call. + ASSERT(kCalleeSaved.IncludesAliasOf(old_stack_pointer)); + + // Align and synchronize the system stack pointer with jssp. + Bic(csp, old_stack_pointer, sp_alignment - 1); + SetStackPointer(csp); + } + + // Call directly. The function called cannot cause a GC, or allow preemption, + // so the return address in the link register stays correct. + Call(function); + + if (!csp.Is(old_stack_pointer)) { + if (emit_debug_code()) { + // Because the stack pointer must be aligned on a 16-byte boundary, the + // aligned csp can be up to 12 bytes below the jssp. This is the case + // where we only pushed one W register on top of an aligned jssp. + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + ASSERT(ActivationFrameAlignment() == 16); + Sub(temp, csp, old_stack_pointer); + // We want temp <= 0 && temp >= -12. + Cmp(temp, 0); + Ccmp(temp, -12, NFlag, le); + Check(ge, kTheStackWasCorruptedByMacroAssemblerCall); + } + SetStackPointer(old_stack_pointer); + } +} + + +void MacroAssembler::Jump(Register target) { + Br(target); +} + + +void MacroAssembler::Jump(intptr_t target, RelocInfo::Mode rmode) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Mov(temp, Operand(target, rmode)); + Br(temp); +} + + +void MacroAssembler::Jump(Address target, RelocInfo::Mode rmode) { + ASSERT(!RelocInfo::IsCodeTarget(rmode)); + Jump(reinterpret_cast<intptr_t>(target), rmode); +} + + +void MacroAssembler::Jump(Handle<Code> code, RelocInfo::Mode rmode) { + ASSERT(RelocInfo::IsCodeTarget(rmode)); + AllowDeferredHandleDereference embedding_raw_address; + Jump(reinterpret_cast<intptr_t>(code.location()), rmode); +} + + +void MacroAssembler::Call(Register target) { + BlockPoolsScope scope(this); +#ifdef DEBUG + Label start_call; + Bind(&start_call); +#endif + + Blr(target); + +#ifdef DEBUG + AssertSizeOfCodeGeneratedSince(&start_call, CallSize(target)); +#endif +} + + +void MacroAssembler::Call(Label* target) { + BlockPoolsScope scope(this); +#ifdef DEBUG + Label start_call; + Bind(&start_call); +#endif + + Bl(target); + +#ifdef DEBUG + AssertSizeOfCodeGeneratedSince(&start_call, CallSize(target)); +#endif +} + + +// MacroAssembler::CallSize is sensitive to changes in this function, as it +// requires to know how many instructions are used to branch to the target. +void MacroAssembler::Call(Address target, RelocInfo::Mode rmode) { + BlockPoolsScope scope(this); +#ifdef DEBUG + Label start_call; + Bind(&start_call); +#endif + // Statement positions are expected to be recorded when the target + // address is loaded. + positions_recorder()->WriteRecordedPositions(); + + // Addresses always have 64 bits, so we shouldn't encounter NONE32. + ASSERT(rmode != RelocInfo::NONE32); + + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + + if (rmode == RelocInfo::NONE64) { + // Addresses are 48 bits so we never need to load the upper 16 bits. + uint64_t imm = reinterpret_cast<uint64_t>(target); + // If we don't use ARM tagged addresses, the 16 higher bits must be 0. + ASSERT(((imm >> 48) & 0xffff) == 0); + movz(temp, (imm >> 0) & 0xffff, 0); + movk(temp, (imm >> 16) & 0xffff, 16); + movk(temp, (imm >> 32) & 0xffff, 32); + } else { + Ldr(temp, Immediate(reinterpret_cast<intptr_t>(target), rmode)); + } + Blr(temp); +#ifdef DEBUG + AssertSizeOfCodeGeneratedSince(&start_call, CallSize(target, rmode)); +#endif +} + + +void MacroAssembler::Call(Handle<Code> code, + RelocInfo::Mode rmode, + TypeFeedbackId ast_id) { +#ifdef DEBUG + Label start_call; + Bind(&start_call); +#endif + + if ((rmode == RelocInfo::CODE_TARGET) && (!ast_id.IsNone())) { + SetRecordedAstId(ast_id); + rmode = RelocInfo::CODE_TARGET_WITH_ID; + } + + AllowDeferredHandleDereference embedding_raw_address; + Call(reinterpret_cast<Address>(code.location()), rmode); + +#ifdef DEBUG + // Check the size of the code generated. + AssertSizeOfCodeGeneratedSince(&start_call, CallSize(code, rmode, ast_id)); +#endif +} + + +int MacroAssembler::CallSize(Register target) { + USE(target); + return kInstructionSize; +} + + +int MacroAssembler::CallSize(Label* target) { + USE(target); + return kInstructionSize; +} + + +int MacroAssembler::CallSize(Address target, RelocInfo::Mode rmode) { + USE(target); + + // Addresses always have 64 bits, so we shouldn't encounter NONE32. + ASSERT(rmode != RelocInfo::NONE32); + + if (rmode == RelocInfo::NONE64) { + return kCallSizeWithoutRelocation; + } else { + return kCallSizeWithRelocation; + } +} + + +int MacroAssembler::CallSize(Handle<Code> code, + RelocInfo::Mode rmode, + TypeFeedbackId ast_id) { + USE(code); + USE(ast_id); + + // Addresses always have 64 bits, so we shouldn't encounter NONE32. + ASSERT(rmode != RelocInfo::NONE32); + + if (rmode == RelocInfo::NONE64) { + return kCallSizeWithoutRelocation; + } else { + return kCallSizeWithRelocation; + } +} + + + + + +void MacroAssembler::JumpForHeapNumber(Register object, + Register heap_number_map, + Label* on_heap_number, + Label* on_not_heap_number) { + ASSERT(on_heap_number || on_not_heap_number); + AssertNotSmi(object); + + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + + // Load the HeapNumber map if it is not passed. + if (heap_number_map.Is(NoReg)) { + heap_number_map = temps.AcquireX(); + LoadRoot(heap_number_map, Heap::kHeapNumberMapRootIndex); + } else { + AssertRegisterIsRoot(heap_number_map, Heap::kHeapNumberMapRootIndex); + } + + ASSERT(!AreAliased(temp, heap_number_map)); + + Ldr(temp, FieldMemOperand(object, HeapObject::kMapOffset)); + Cmp(temp, heap_number_map); + + if (on_heap_number) { + B(eq, on_heap_number); + } + if (on_not_heap_number) { + B(ne, on_not_heap_number); + } +} + + +void MacroAssembler::JumpIfHeapNumber(Register object, + Label* on_heap_number, + Register heap_number_map) { + JumpForHeapNumber(object, + heap_number_map, + on_heap_number, + NULL); +} + + +void MacroAssembler::JumpIfNotHeapNumber(Register object, + Label* on_not_heap_number, + Register heap_number_map) { + JumpForHeapNumber(object, + heap_number_map, + NULL, + on_not_heap_number); +} + + +void MacroAssembler::LookupNumberStringCache(Register object, + Register result, + Register scratch1, + Register scratch2, + Register scratch3, + Label* not_found) { + ASSERT(!AreAliased(object, result, scratch1, scratch2, scratch3)); + + // Use of registers. Register result is used as a temporary. + Register number_string_cache = result; + Register mask = scratch3; + + // Load the number string cache. + LoadRoot(number_string_cache, Heap::kNumberStringCacheRootIndex); + + // Make the hash mask from the length of the number string cache. It + // contains two elements (number and string) for each cache entry. + Ldrsw(mask, UntagSmiFieldMemOperand(number_string_cache, + FixedArray::kLengthOffset)); + Asr(mask, mask, 1); // Divide length by two. + Sub(mask, mask, 1); // Make mask. + + // Calculate the entry in the number string cache. The hash value in the + // number string cache for smis is just the smi value, and the hash for + // doubles is the xor of the upper and lower words. See + // Heap::GetNumberStringCache. + Label is_smi; + Label load_result_from_cache; + + JumpIfSmi(object, &is_smi); + CheckMap(object, scratch1, Heap::kHeapNumberMapRootIndex, not_found, + DONT_DO_SMI_CHECK); + + STATIC_ASSERT(kDoubleSize == (kWRegSize * 2)); + Add(scratch1, object, HeapNumber::kValueOffset - kHeapObjectTag); + Ldp(scratch1.W(), scratch2.W(), MemOperand(scratch1)); + Eor(scratch1, scratch1, scratch2); + And(scratch1, scratch1, mask); + + // Calculate address of entry in string cache: each entry consists of two + // pointer sized fields. + Add(scratch1, number_string_cache, + Operand(scratch1, LSL, kPointerSizeLog2 + 1)); + + Register probe = mask; + Ldr(probe, FieldMemOperand(scratch1, FixedArray::kHeaderSize)); + JumpIfSmi(probe, not_found); + Ldr(d0, FieldMemOperand(object, HeapNumber::kValueOffset)); + Ldr(d1, FieldMemOperand(probe, HeapNumber::kValueOffset)); + Fcmp(d0, d1); + B(ne, not_found); + B(&load_result_from_cache); + + Bind(&is_smi); + Register scratch = scratch1; + And(scratch, mask, Operand::UntagSmi(object)); + // Calculate address of entry in string cache: each entry consists + // of two pointer sized fields. + Add(scratch, number_string_cache, + Operand(scratch, LSL, kPointerSizeLog2 + 1)); + + // Check if the entry is the smi we are looking for. + Ldr(probe, FieldMemOperand(scratch, FixedArray::kHeaderSize)); + Cmp(object, probe); + B(ne, not_found); + + // Get the result from the cache. + Bind(&load_result_from_cache); + Ldr(result, FieldMemOperand(scratch, FixedArray::kHeaderSize + kPointerSize)); + IncrementCounter(isolate()->counters()->number_to_string_native(), 1, + scratch1, scratch2); +} + + +void MacroAssembler::TryRepresentDoubleAsInt(Register as_int, + FPRegister value, + FPRegister scratch_d, + Label* on_successful_conversion, + Label* on_failed_conversion) { + // Convert to an int and back again, then compare with the original value. + Fcvtzs(as_int, value); + Scvtf(scratch_d, as_int); + Fcmp(value, scratch_d); + + if (on_successful_conversion) { + B(on_successful_conversion, eq); + } + if (on_failed_conversion) { + B(on_failed_conversion, ne); + } +} + + +void MacroAssembler::TestForMinusZero(DoubleRegister input) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + // Floating point -0.0 is kMinInt as an integer, so subtracting 1 (cmp) will + // cause overflow. + Fmov(temp, input); + Cmp(temp, 1); +} + + +void MacroAssembler::JumpIfMinusZero(DoubleRegister input, + Label* on_negative_zero) { + TestForMinusZero(input); + B(vs, on_negative_zero); +} + + +void MacroAssembler::JumpIfMinusZero(Register input, + Label* on_negative_zero) { + ASSERT(input.Is64Bits()); + // Floating point value is in an integer register. Detect -0.0 by subtracting + // 1 (cmp), which will cause overflow. + Cmp(input, 1); + B(vs, on_negative_zero); +} + + +void MacroAssembler::ClampInt32ToUint8(Register output, Register input) { + // Clamp the value to [0..255]. + Cmp(input.W(), Operand(input.W(), UXTB)); + // If input < input & 0xff, it must be < 0, so saturate to 0. + Csel(output.W(), wzr, input.W(), lt); + // If input <= input & 0xff, it must be <= 255. Otherwise, saturate to 255. + Csel(output.W(), output.W(), 255, le); +} + + +void MacroAssembler::ClampInt32ToUint8(Register in_out) { + ClampInt32ToUint8(in_out, in_out); +} + + +void MacroAssembler::ClampDoubleToUint8(Register output, + DoubleRegister input, + DoubleRegister dbl_scratch) { + // This conversion follows the WebIDL "[Clamp]" rules for PIXEL types: + // - Inputs lower than 0 (including -infinity) produce 0. + // - Inputs higher than 255 (including +infinity) produce 255. + // Also, it seems that PIXEL types use round-to-nearest rather than + // round-towards-zero. + + // Squash +infinity before the conversion, since Fcvtnu will normally + // convert it to 0. + Fmov(dbl_scratch, 255); + Fmin(dbl_scratch, dbl_scratch, input); + + // Convert double to unsigned integer. Values less than zero become zero. + // Values greater than 255 have already been clamped to 255. + Fcvtnu(output, dbl_scratch); +} + + +void MacroAssembler::CopyFieldsLoopPairsHelper(Register dst, + Register src, + unsigned count, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4, + Register scratch5) { + // Untag src and dst into scratch registers. + // Copy src->dst in a tight loop. + ASSERT(!AreAliased(dst, src, + scratch1, scratch2, scratch3, scratch4, scratch5)); + ASSERT(count >= 2); + + const Register& remaining = scratch3; + Mov(remaining, count / 2); + + const Register& dst_untagged = scratch1; + const Register& src_untagged = scratch2; + Sub(dst_untagged, dst, kHeapObjectTag); + Sub(src_untagged, src, kHeapObjectTag); + + // Copy fields in pairs. + Label loop; + Bind(&loop); + Ldp(scratch4, scratch5, + MemOperand(src_untagged, kXRegSize* 2, PostIndex)); + Stp(scratch4, scratch5, + MemOperand(dst_untagged, kXRegSize* 2, PostIndex)); + Sub(remaining, remaining, 1); + Cbnz(remaining, &loop); + + // Handle the leftovers. + if (count & 1) { + Ldr(scratch4, MemOperand(src_untagged)); + Str(scratch4, MemOperand(dst_untagged)); + } +} + + +void MacroAssembler::CopyFieldsUnrolledPairsHelper(Register dst, + Register src, + unsigned count, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4) { + // Untag src and dst into scratch registers. + // Copy src->dst in an unrolled loop. + ASSERT(!AreAliased(dst, src, scratch1, scratch2, scratch3, scratch4)); + + const Register& dst_untagged = scratch1; + const Register& src_untagged = scratch2; + sub(dst_untagged, dst, kHeapObjectTag); + sub(src_untagged, src, kHeapObjectTag); + + // Copy fields in pairs. + for (unsigned i = 0; i < count / 2; i++) { + Ldp(scratch3, scratch4, MemOperand(src_untagged, kXRegSize * 2, PostIndex)); + Stp(scratch3, scratch4, MemOperand(dst_untagged, kXRegSize * 2, PostIndex)); + } + + // Handle the leftovers. + if (count & 1) { + Ldr(scratch3, MemOperand(src_untagged)); + Str(scratch3, MemOperand(dst_untagged)); + } +} + + +void MacroAssembler::CopyFieldsUnrolledHelper(Register dst, + Register src, + unsigned count, + Register scratch1, + Register scratch2, + Register scratch3) { + // Untag src and dst into scratch registers. + // Copy src->dst in an unrolled loop. + ASSERT(!AreAliased(dst, src, scratch1, scratch2, scratch3)); + + const Register& dst_untagged = scratch1; + const Register& src_untagged = scratch2; + Sub(dst_untagged, dst, kHeapObjectTag); + Sub(src_untagged, src, kHeapObjectTag); + + // Copy fields one by one. + for (unsigned i = 0; i < count; i++) { + Ldr(scratch3, MemOperand(src_untagged, kXRegSize, PostIndex)); + Str(scratch3, MemOperand(dst_untagged, kXRegSize, PostIndex)); + } +} + + +void MacroAssembler::CopyFields(Register dst, Register src, CPURegList temps, + unsigned count) { + // One of two methods is used: + // + // For high 'count' values where many scratch registers are available: + // Untag src and dst into scratch registers. + // Copy src->dst in a tight loop. + // + // For low 'count' values or where few scratch registers are available: + // Untag src and dst into scratch registers. + // Copy src->dst in an unrolled loop. + // + // In both cases, fields are copied in pairs if possible, and left-overs are + // handled separately. + ASSERT(!AreAliased(dst, src)); + ASSERT(!temps.IncludesAliasOf(dst)); + ASSERT(!temps.IncludesAliasOf(src)); + ASSERT(!temps.IncludesAliasOf(xzr)); + + if (emit_debug_code()) { + Cmp(dst, src); + Check(ne, kTheSourceAndDestinationAreTheSame); + } + + // The value of 'count' at which a loop will be generated (if there are + // enough scratch registers). + static const unsigned kLoopThreshold = 8; + + UseScratchRegisterScope masm_temps(this); + if ((temps.Count() >= 3) && (count >= kLoopThreshold)) { + CopyFieldsLoopPairsHelper(dst, src, count, + Register(temps.PopLowestIndex()), + Register(temps.PopLowestIndex()), + Register(temps.PopLowestIndex()), + masm_temps.AcquireX(), + masm_temps.AcquireX()); + } else if (temps.Count() >= 2) { + CopyFieldsUnrolledPairsHelper(dst, src, count, + Register(temps.PopLowestIndex()), + Register(temps.PopLowestIndex()), + masm_temps.AcquireX(), + masm_temps.AcquireX()); + } else if (temps.Count() == 1) { + CopyFieldsUnrolledHelper(dst, src, count, + Register(temps.PopLowestIndex()), + masm_temps.AcquireX(), + masm_temps.AcquireX()); + } else { + UNREACHABLE(); + } +} + + +void MacroAssembler::CopyBytes(Register dst, + Register src, + Register length, + Register scratch, + CopyHint hint) { + UseScratchRegisterScope temps(this); + Register tmp1 = temps.AcquireX(); + Register tmp2 = temps.AcquireX(); + ASSERT(!AreAliased(src, dst, length, scratch, tmp1, tmp2)); + ASSERT(!AreAliased(src, dst, csp)); + + if (emit_debug_code()) { + // Check copy length. + Cmp(length, 0); + Assert(ge, kUnexpectedNegativeValue); + + // Check src and dst buffers don't overlap. + Add(scratch, src, length); // Calculate end of src buffer. + Cmp(scratch, dst); + Add(scratch, dst, length); // Calculate end of dst buffer. + Ccmp(scratch, src, ZFlag, gt); + Assert(le, kCopyBuffersOverlap); + } + + Label short_copy, short_loop, bulk_loop, done; + + if ((hint == kCopyLong || hint == kCopyUnknown) && !FLAG_optimize_for_size) { + Register bulk_length = scratch; + int pair_size = 2 * kXRegSize; + int pair_mask = pair_size - 1; + + Bic(bulk_length, length, pair_mask); + Cbz(bulk_length, &short_copy); + Bind(&bulk_loop); + Sub(bulk_length, bulk_length, pair_size); + Ldp(tmp1, tmp2, MemOperand(src, pair_size, PostIndex)); + Stp(tmp1, tmp2, MemOperand(dst, pair_size, PostIndex)); + Cbnz(bulk_length, &bulk_loop); + + And(length, length, pair_mask); + } + + Bind(&short_copy); + Cbz(length, &done); + Bind(&short_loop); + Sub(length, length, 1); + Ldrb(tmp1, MemOperand(src, 1, PostIndex)); + Strb(tmp1, MemOperand(dst, 1, PostIndex)); + Cbnz(length, &short_loop); + + + Bind(&done); +} + + +void MacroAssembler::FillFields(Register dst, + Register field_count, + Register filler) { + ASSERT(!dst.Is(csp)); + UseScratchRegisterScope temps(this); + Register field_ptr = temps.AcquireX(); + Register counter = temps.AcquireX(); + Label done; + + // Decrement count. If the result < zero, count was zero, and there's nothing + // to do. If count was one, flags are set to fail the gt condition at the end + // of the pairs loop. + Subs(counter, field_count, 1); + B(lt, &done); + + // There's at least one field to fill, so do this unconditionally. + Str(filler, MemOperand(dst, kPointerSize, PostIndex)); + + // If the bottom bit of counter is set, there are an even number of fields to + // fill, so pull the start pointer back by one field, allowing the pairs loop + // to overwrite the field that was stored above. + And(field_ptr, counter, 1); + Sub(field_ptr, dst, Operand(field_ptr, LSL, kPointerSizeLog2)); + + // Store filler to memory in pairs. + Label entry, loop; + B(&entry); + Bind(&loop); + Stp(filler, filler, MemOperand(field_ptr, 2 * kPointerSize, PostIndex)); + Subs(counter, counter, 2); + Bind(&entry); + B(gt, &loop); + + Bind(&done); +} + + +void MacroAssembler::JumpIfEitherIsNotSequentialAsciiStrings( + Register first, + Register second, + Register scratch1, + Register scratch2, + Label* failure, + SmiCheckType smi_check) { + + if (smi_check == DO_SMI_CHECK) { + JumpIfEitherSmi(first, second, failure); + } else if (emit_debug_code()) { + ASSERT(smi_check == DONT_DO_SMI_CHECK); + Label not_smi; + JumpIfEitherSmi(first, second, NULL, ¬_smi); + + // At least one input is a smi, but the flags indicated a smi check wasn't + // needed. + Abort(kUnexpectedSmi); + + Bind(¬_smi); + } + + // Test that both first and second are sequential ASCII strings. + Ldr(scratch1, FieldMemOperand(first, HeapObject::kMapOffset)); + Ldr(scratch2, FieldMemOperand(second, HeapObject::kMapOffset)); + Ldrb(scratch1, FieldMemOperand(scratch1, Map::kInstanceTypeOffset)); + Ldrb(scratch2, FieldMemOperand(scratch2, Map::kInstanceTypeOffset)); + + JumpIfEitherInstanceTypeIsNotSequentialAscii(scratch1, + scratch2, + scratch1, + scratch2, + failure); +} + + +void MacroAssembler::JumpIfEitherInstanceTypeIsNotSequentialAscii( + Register first, + Register second, + Register scratch1, + Register scratch2, + Label* failure) { + ASSERT(!AreAliased(scratch1, second)); + ASSERT(!AreAliased(scratch1, scratch2)); + static const int kFlatAsciiStringMask = + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask; + static const int kFlatAsciiStringTag = ASCII_STRING_TYPE; + And(scratch1, first, kFlatAsciiStringMask); + And(scratch2, second, kFlatAsciiStringMask); + Cmp(scratch1, kFlatAsciiStringTag); + Ccmp(scratch2, kFlatAsciiStringTag, NoFlag, eq); + B(ne, failure); +} + + +void MacroAssembler::JumpIfInstanceTypeIsNotSequentialAscii(Register type, + Register scratch, + Label* failure) { + const int kFlatAsciiStringMask = + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask; + const int kFlatAsciiStringTag = + kStringTag | kOneByteStringTag | kSeqStringTag; + And(scratch, type, kFlatAsciiStringMask); + Cmp(scratch, kFlatAsciiStringTag); + B(ne, failure); +} + + +void MacroAssembler::JumpIfBothInstanceTypesAreNotSequentialAscii( + Register first, + Register second, + Register scratch1, + Register scratch2, + Label* failure) { + ASSERT(!AreAliased(first, second, scratch1, scratch2)); + const int kFlatAsciiStringMask = + kIsNotStringMask | kStringEncodingMask | kStringRepresentationMask; + const int kFlatAsciiStringTag = + kStringTag | kOneByteStringTag | kSeqStringTag; + And(scratch1, first, kFlatAsciiStringMask); + And(scratch2, second, kFlatAsciiStringMask); + Cmp(scratch1, kFlatAsciiStringTag); + Ccmp(scratch2, kFlatAsciiStringTag, NoFlag, eq); + B(ne, failure); +} + + +void MacroAssembler::JumpIfNotUniqueName(Register type, + Label* not_unique_name) { + STATIC_ASSERT((kInternalizedTag == 0) && (kStringTag == 0)); + // if ((type is string && type is internalized) || type == SYMBOL_TYPE) { + // continue + // } else { + // goto not_unique_name + // } + Tst(type, kIsNotStringMask | kIsNotInternalizedMask); + Ccmp(type, SYMBOL_TYPE, ZFlag, ne); + B(ne, not_unique_name); +} + + +void MacroAssembler::InvokePrologue(const ParameterCount& expected, + const ParameterCount& actual, + Handle<Code> code_constant, + Register code_reg, + Label* done, + InvokeFlag flag, + bool* definitely_mismatches, + const CallWrapper& call_wrapper) { + bool definitely_matches = false; + *definitely_mismatches = false; + Label regular_invoke; + + // Check whether the expected and actual arguments count match. If not, + // setup registers according to contract with ArgumentsAdaptorTrampoline: + // x0: actual arguments count. + // x1: function (passed through to callee). + // x2: expected arguments count. + + // The code below is made a lot easier because the calling code already sets + // up actual and expected registers according to the contract if values are + // passed in registers. + ASSERT(actual.is_immediate() || actual.reg().is(x0)); + ASSERT(expected.is_immediate() || expected.reg().is(x2)); + ASSERT((!code_constant.is_null() && code_reg.is(no_reg)) || code_reg.is(x3)); + + if (expected.is_immediate()) { + ASSERT(actual.is_immediate()); + if (expected.immediate() == actual.immediate()) { + definitely_matches = true; + + } else { + Mov(x0, actual.immediate()); + if (expected.immediate() == + SharedFunctionInfo::kDontAdaptArgumentsSentinel) { + // Don't worry about adapting arguments for builtins that + // don't want that done. Skip adaption code by making it look + // like we have a match between expected and actual number of + // arguments. + definitely_matches = true; + } else { + *definitely_mismatches = true; + // Set up x2 for the argument adaptor. + Mov(x2, expected.immediate()); + } + } + + } else { // expected is a register. + Operand actual_op = actual.is_immediate() ? Operand(actual.immediate()) + : Operand(actual.reg()); + // If actual == expected perform a regular invocation. + Cmp(expected.reg(), actual_op); + B(eq, ®ular_invoke); + // Otherwise set up x0 for the argument adaptor. + Mov(x0, actual_op); + } + + // If the argument counts may mismatch, generate a call to the argument + // adaptor. + if (!definitely_matches) { + if (!code_constant.is_null()) { + Mov(x3, Operand(code_constant)); + Add(x3, x3, Code::kHeaderSize - kHeapObjectTag); + } + + Handle<Code> adaptor = + isolate()->builtins()->ArgumentsAdaptorTrampoline(); + if (flag == CALL_FUNCTION) { + call_wrapper.BeforeCall(CallSize(adaptor)); + Call(adaptor); + call_wrapper.AfterCall(); + if (!*definitely_mismatches) { + // If the arg counts don't match, no extra code is emitted by + // MAsm::InvokeCode and we can just fall through. + B(done); + } + } else { + Jump(adaptor, RelocInfo::CODE_TARGET); + } + } + Bind(®ular_invoke); +} + + +void MacroAssembler::InvokeCode(Register code, + const ParameterCount& expected, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper) { + // You can't call a function without a valid frame. + ASSERT(flag == JUMP_FUNCTION || has_frame()); + + Label done; + + bool definitely_mismatches = false; + InvokePrologue(expected, actual, Handle<Code>::null(), code, &done, flag, + &definitely_mismatches, call_wrapper); + + // If we are certain that actual != expected, then we know InvokePrologue will + // have handled the call through the argument adaptor mechanism. + // The called function expects the call kind in x5. + if (!definitely_mismatches) { + if (flag == CALL_FUNCTION) { + call_wrapper.BeforeCall(CallSize(code)); + Call(code); + call_wrapper.AfterCall(); + } else { + ASSERT(flag == JUMP_FUNCTION); + Jump(code); + } + } + + // Continue here if InvokePrologue does handle the invocation due to + // mismatched parameter counts. + Bind(&done); +} + + +void MacroAssembler::InvokeFunction(Register function, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper) { + // You can't call a function without a valid frame. + ASSERT(flag == JUMP_FUNCTION || has_frame()); + + // Contract with called JS functions requires that function is passed in x1. + // (See FullCodeGenerator::Generate().) + ASSERT(function.is(x1)); + + Register expected_reg = x2; + Register code_reg = x3; + + Ldr(cp, FieldMemOperand(function, JSFunction::kContextOffset)); + // The number of arguments is stored as an int32_t, and -1 is a marker + // (SharedFunctionInfo::kDontAdaptArgumentsSentinel), so we need sign + // extension to correctly handle it. + Ldr(expected_reg, FieldMemOperand(function, + JSFunction::kSharedFunctionInfoOffset)); + Ldrsw(expected_reg, + FieldMemOperand(expected_reg, + SharedFunctionInfo::kFormalParameterCountOffset)); + Ldr(code_reg, + FieldMemOperand(function, JSFunction::kCodeEntryOffset)); + + ParameterCount expected(expected_reg); + InvokeCode(code_reg, expected, actual, flag, call_wrapper); +} + + +void MacroAssembler::InvokeFunction(Register function, + const ParameterCount& expected, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper) { + // You can't call a function without a valid frame. + ASSERT(flag == JUMP_FUNCTION || has_frame()); + + // Contract with called JS functions requires that function is passed in x1. + // (See FullCodeGenerator::Generate().) + ASSERT(function.Is(x1)); + + Register code_reg = x3; + + // Set up the context. + Ldr(cp, FieldMemOperand(function, JSFunction::kContextOffset)); + + // We call indirectly through the code field in the function to + // allow recompilation to take effect without changing any of the + // call sites. + Ldr(code_reg, FieldMemOperand(function, JSFunction::kCodeEntryOffset)); + InvokeCode(code_reg, expected, actual, flag, call_wrapper); +} + + +void MacroAssembler::InvokeFunction(Handle<JSFunction> function, + const ParameterCount& expected, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper) { + // Contract with called JS functions requires that function is passed in x1. + // (See FullCodeGenerator::Generate().) + __ LoadObject(x1, function); + InvokeFunction(x1, expected, actual, flag, call_wrapper); +} + + +void MacroAssembler::TryConvertDoubleToInt64(Register result, + DoubleRegister double_input, + Label* done) { + // Try to convert with an FPU convert instruction. It's trivial to compute + // the modulo operation on an integer register so we convert to a 64-bit + // integer. + // + // Fcvtzs will saturate to INT64_MIN (0x800...00) or INT64_MAX (0x7ff...ff) + // when the double is out of range. NaNs and infinities will be converted to 0 + // (as ECMA-262 requires). + Fcvtzs(result.X(), double_input); + + // The values INT64_MIN (0x800...00) or INT64_MAX (0x7ff...ff) are not + // representable using a double, so if the result is one of those then we know + // that saturation occured, and we need to manually handle the conversion. + // + // It is easy to detect INT64_MIN and INT64_MAX because adding or subtracting + // 1 will cause signed overflow. + Cmp(result.X(), 1); + Ccmp(result.X(), -1, VFlag, vc); + + B(vc, done); +} + + +void MacroAssembler::TruncateDoubleToI(Register result, + DoubleRegister double_input) { + Label done; + ASSERT(jssp.Is(StackPointer())); + + // Try to convert the double to an int64. If successful, the bottom 32 bits + // contain our truncated int32 result. + TryConvertDoubleToInt64(result, double_input, &done); + + // If we fell through then inline version didn't succeed - call stub instead. + Push(lr); + Push(double_input); // Put input on stack. + + DoubleToIStub stub(isolate(), + jssp, + result, + 0, + true, // is_truncating + true); // skip_fastpath + CallStub(&stub); // DoubleToIStub preserves any registers it needs to clobber + + Drop(1, kDoubleSize); // Drop the double input on the stack. + Pop(lr); + + Bind(&done); +} + + +void MacroAssembler::TruncateHeapNumberToI(Register result, + Register object) { + Label done; + ASSERT(!result.is(object)); + ASSERT(jssp.Is(StackPointer())); + + Ldr(fp_scratch, FieldMemOperand(object, HeapNumber::kValueOffset)); + + // Try to convert the double to an int64. If successful, the bottom 32 bits + // contain our truncated int32 result. + TryConvertDoubleToInt64(result, fp_scratch, &done); + + // If we fell through then inline version didn't succeed - call stub instead. + Push(lr); + DoubleToIStub stub(isolate(), + object, + result, + HeapNumber::kValueOffset - kHeapObjectTag, + true, // is_truncating + true); // skip_fastpath + CallStub(&stub); // DoubleToIStub preserves any registers it needs to clobber + Pop(lr); + + Bind(&done); +} + + +void MacroAssembler::StubPrologue() { + ASSERT(StackPointer().Is(jssp)); + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + __ Mov(temp, Smi::FromInt(StackFrame::STUB)); + // Compiled stubs don't age, and so they don't need the predictable code + // ageing sequence. + __ Push(lr, fp, cp, temp); + __ Add(fp, jssp, StandardFrameConstants::kFixedFrameSizeFromFp); +} + + +void MacroAssembler::Prologue(bool code_pre_aging) { + if (code_pre_aging) { + Code* stub = Code::GetPreAgedCodeAgeStub(isolate()); + __ EmitCodeAgeSequence(stub); + } else { + __ EmitFrameSetupForCodeAgePatching(); + } +} + + +void MacroAssembler::EnterFrame(StackFrame::Type type) { + ASSERT(jssp.Is(StackPointer())); + UseScratchRegisterScope temps(this); + Register type_reg = temps.AcquireX(); + Register code_reg = temps.AcquireX(); + + Push(lr, fp, cp); + Mov(type_reg, Smi::FromInt(type)); + Mov(code_reg, Operand(CodeObject())); + Push(type_reg, code_reg); + // jssp[4] : lr + // jssp[3] : fp + // jssp[2] : cp + // jssp[1] : type + // jssp[0] : code object + + // Adjust FP to point to saved FP. + Add(fp, jssp, StandardFrameConstants::kFixedFrameSizeFromFp + kPointerSize); +} + + +void MacroAssembler::LeaveFrame(StackFrame::Type type) { + ASSERT(jssp.Is(StackPointer())); + // Drop the execution stack down to the frame pointer and restore + // the caller frame pointer and return address. + Mov(jssp, fp); + AssertStackConsistency(); + Pop(fp, lr); +} + + +void MacroAssembler::ExitFramePreserveFPRegs() { + PushCPURegList(kCallerSavedFP); +} + + +void MacroAssembler::ExitFrameRestoreFPRegs() { + // Read the registers from the stack without popping them. The stack pointer + // will be reset as part of the unwinding process. + CPURegList saved_fp_regs = kCallerSavedFP; + ASSERT(saved_fp_regs.Count() % 2 == 0); + + int offset = ExitFrameConstants::kLastExitFrameField; + while (!saved_fp_regs.IsEmpty()) { + const CPURegister& dst0 = saved_fp_regs.PopHighestIndex(); + const CPURegister& dst1 = saved_fp_regs.PopHighestIndex(); + offset -= 2 * kDRegSize; + Ldp(dst1, dst0, MemOperand(fp, offset)); + } +} + + +void MacroAssembler::EnterExitFrame(bool save_doubles, + const Register& scratch, + int extra_space) { + ASSERT(jssp.Is(StackPointer())); + + // Set up the new stack frame. + Mov(scratch, Operand(CodeObject())); + Push(lr, fp); + Mov(fp, StackPointer()); + Push(xzr, scratch); + // fp[8]: CallerPC (lr) + // fp -> fp[0]: CallerFP (old fp) + // fp[-8]: Space reserved for SPOffset. + // jssp -> fp[-16]: CodeObject() + STATIC_ASSERT((2 * kPointerSize) == + ExitFrameConstants::kCallerSPDisplacement); + STATIC_ASSERT((1 * kPointerSize) == ExitFrameConstants::kCallerPCOffset); + STATIC_ASSERT((0 * kPointerSize) == ExitFrameConstants::kCallerFPOffset); + STATIC_ASSERT((-1 * kPointerSize) == ExitFrameConstants::kSPOffset); + STATIC_ASSERT((-2 * kPointerSize) == ExitFrameConstants::kCodeOffset); + + // Save the frame pointer and context pointer in the top frame. + Mov(scratch, Operand(ExternalReference(Isolate::kCEntryFPAddress, + isolate()))); + Str(fp, MemOperand(scratch)); + Mov(scratch, Operand(ExternalReference(Isolate::kContextAddress, + isolate()))); + Str(cp, MemOperand(scratch)); + + STATIC_ASSERT((-2 * kPointerSize) == + ExitFrameConstants::kLastExitFrameField); + if (save_doubles) { + ExitFramePreserveFPRegs(); + } + + // Reserve space for the return address and for user requested memory. + // We do this before aligning to make sure that we end up correctly + // aligned with the minimum of wasted space. + Claim(extra_space + 1, kXRegSize); + // fp[8]: CallerPC (lr) + // fp -> fp[0]: CallerFP (old fp) + // fp[-8]: Space reserved for SPOffset. + // fp[-16]: CodeObject() + // fp[-16 - fp_size]: Saved doubles (if save_doubles is true). + // jssp[8]: Extra space reserved for caller (if extra_space != 0). + // jssp -> jssp[0]: Space reserved for the return address. + + // Align and synchronize the system stack pointer with jssp. + AlignAndSetCSPForFrame(); + ASSERT(csp.Is(StackPointer())); + + // fp[8]: CallerPC (lr) + // fp -> fp[0]: CallerFP (old fp) + // fp[-8]: Space reserved for SPOffset. + // fp[-16]: CodeObject() + // fp[-16 - fp_size]: Saved doubles (if save_doubles is true). + // csp[8]: Memory reserved for the caller if extra_space != 0. + // Alignment padding, if necessary. + // csp -> csp[0]: Space reserved for the return address. + + // ExitFrame::GetStateForFramePointer expects to find the return address at + // the memory address immediately below the pointer stored in SPOffset. + // It is not safe to derive much else from SPOffset, because the size of the + // padding can vary. + Add(scratch, csp, kXRegSize); + Str(scratch, MemOperand(fp, ExitFrameConstants::kSPOffset)); +} + + +// Leave the current exit frame. +void MacroAssembler::LeaveExitFrame(bool restore_doubles, + const Register& scratch, + bool restore_context) { + ASSERT(csp.Is(StackPointer())); + + if (restore_doubles) { + ExitFrameRestoreFPRegs(); + } + + // Restore the context pointer from the top frame. + if (restore_context) { + Mov(scratch, Operand(ExternalReference(Isolate::kContextAddress, + isolate()))); + Ldr(cp, MemOperand(scratch)); + } + + if (emit_debug_code()) { + // Also emit debug code to clear the cp in the top frame. + Mov(scratch, Operand(ExternalReference(Isolate::kContextAddress, + isolate()))); + Str(xzr, MemOperand(scratch)); + } + // Clear the frame pointer from the top frame. + Mov(scratch, Operand(ExternalReference(Isolate::kCEntryFPAddress, + isolate()))); + Str(xzr, MemOperand(scratch)); + + // Pop the exit frame. + // fp[8]: CallerPC (lr) + // fp -> fp[0]: CallerFP (old fp) + // fp[...]: The rest of the frame. + Mov(jssp, fp); + SetStackPointer(jssp); + AssertStackConsistency(); + Pop(fp, lr); +} + + +void MacroAssembler::SetCounter(StatsCounter* counter, int value, + Register scratch1, Register scratch2) { + if (FLAG_native_code_counters && counter->Enabled()) { + Mov(scratch1, value); + Mov(scratch2, ExternalReference(counter)); + Str(scratch1, MemOperand(scratch2)); + } +} + + +void MacroAssembler::IncrementCounter(StatsCounter* counter, int value, + Register scratch1, Register scratch2) { + ASSERT(value != 0); + if (FLAG_native_code_counters && counter->Enabled()) { + Mov(scratch2, ExternalReference(counter)); + Ldr(scratch1, MemOperand(scratch2)); + Add(scratch1, scratch1, value); + Str(scratch1, MemOperand(scratch2)); + } +} + + +void MacroAssembler::DecrementCounter(StatsCounter* counter, int value, + Register scratch1, Register scratch2) { + IncrementCounter(counter, -value, scratch1, scratch2); +} + + +void MacroAssembler::LoadContext(Register dst, int context_chain_length) { + if (context_chain_length > 0) { + // Move up the chain of contexts to the context containing the slot. + Ldr(dst, MemOperand(cp, Context::SlotOffset(Context::PREVIOUS_INDEX))); + for (int i = 1; i < context_chain_length; i++) { + Ldr(dst, MemOperand(dst, Context::SlotOffset(Context::PREVIOUS_INDEX))); + } + } else { + // Slot is in the current function context. Move it into the + // destination register in case we store into it (the write barrier + // cannot be allowed to destroy the context in cp). + Mov(dst, cp); + } +} + + +void MacroAssembler::DebugBreak() { + Mov(x0, 0); + Mov(x1, ExternalReference(Runtime::kDebugBreak, isolate())); + CEntryStub ces(isolate(), 1); + ASSERT(AllowThisStubCall(&ces)); + Call(ces.GetCode(), RelocInfo::DEBUG_BREAK); +} + + +void MacroAssembler::PushTryHandler(StackHandler::Kind kind, + int handler_index) { + ASSERT(jssp.Is(StackPointer())); + // Adjust this code if the asserts don't hold. + STATIC_ASSERT(StackHandlerConstants::kSize == 5 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kCodeOffset == 1 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kStateOffset == 2 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kContextOffset == 3 * kPointerSize); + STATIC_ASSERT(StackHandlerConstants::kFPOffset == 4 * kPointerSize); + + // For the JSEntry handler, we must preserve the live registers x0-x4. + // (See JSEntryStub::GenerateBody().) + + unsigned state = + StackHandler::IndexField::encode(handler_index) | + StackHandler::KindField::encode(kind); + + // Set up the code object and the state for pushing. + Mov(x10, Operand(CodeObject())); + Mov(x11, state); + + // Push the frame pointer, context, state, and code object. + if (kind == StackHandler::JS_ENTRY) { + ASSERT(Smi::FromInt(0) == 0); + Push(xzr, xzr, x11, x10); + } else { + Push(fp, cp, x11, x10); + } + + // Link the current handler as the next handler. + Mov(x11, ExternalReference(Isolate::kHandlerAddress, isolate())); + Ldr(x10, MemOperand(x11)); + Push(x10); + // Set this new handler as the current one. + Str(jssp, MemOperand(x11)); +} + + +void MacroAssembler::PopTryHandler() { + STATIC_ASSERT(StackHandlerConstants::kNextOffset == 0); + Pop(x10); + Mov(x11, ExternalReference(Isolate::kHandlerAddress, isolate())); + Drop(StackHandlerConstants::kSize - kXRegSize, kByteSizeInBytes); + Str(x10, MemOperand(x11)); +} + + +void MacroAssembler::Allocate(int object_size, + Register result, + Register scratch1, + Register scratch2, + Label* gc_required, + AllocationFlags flags) { + ASSERT(object_size <= Page::kMaxRegularHeapObjectSize); + if (!FLAG_inline_new) { + if (emit_debug_code()) { + // Trash the registers to simulate an allocation failure. + // We apply salt to the original zap value to easily spot the values. + Mov(result, (kDebugZapValue & ~0xffL) | 0x11L); + Mov(scratch1, (kDebugZapValue & ~0xffL) | 0x21L); + Mov(scratch2, (kDebugZapValue & ~0xffL) | 0x21L); + } + B(gc_required); + return; + } + + UseScratchRegisterScope temps(this); + Register scratch3 = temps.AcquireX(); + + ASSERT(!AreAliased(result, scratch1, scratch2, scratch3)); + ASSERT(result.Is64Bits() && scratch1.Is64Bits() && scratch2.Is64Bits()); + + // Make object size into bytes. + if ((flags & SIZE_IN_WORDS) != 0) { + object_size *= kPointerSize; + } + ASSERT(0 == (object_size & kObjectAlignmentMask)); + + // Check relative positions of allocation top and limit addresses. + // The values must be adjacent in memory to allow the use of LDP. + ExternalReference heap_allocation_top = + AllocationUtils::GetAllocationTopReference(isolate(), flags); + ExternalReference heap_allocation_limit = + AllocationUtils::GetAllocationLimitReference(isolate(), flags); + intptr_t top = reinterpret_cast<intptr_t>(heap_allocation_top.address()); + intptr_t limit = reinterpret_cast<intptr_t>(heap_allocation_limit.address()); + ASSERT((limit - top) == kPointerSize); + + // Set up allocation top address and object size registers. + Register top_address = scratch1; + Register allocation_limit = scratch2; + Mov(top_address, Operand(heap_allocation_top)); + + if ((flags & RESULT_CONTAINS_TOP) == 0) { + // Load allocation top into result and the allocation limit. + Ldp(result, allocation_limit, MemOperand(top_address)); + } else { + if (emit_debug_code()) { + // Assert that result actually contains top on entry. + Ldr(scratch3, MemOperand(top_address)); + Cmp(result, scratch3); + Check(eq, kUnexpectedAllocationTop); + } + // Load the allocation limit. 'result' already contains the allocation top. + Ldr(allocation_limit, MemOperand(top_address, limit - top)); + } + + // We can ignore DOUBLE_ALIGNMENT flags here because doubles and pointers have + // the same alignment on ARM64. + STATIC_ASSERT(kPointerAlignment == kDoubleAlignment); + + // Calculate new top and bail out if new space is exhausted. + Adds(scratch3, result, object_size); + Ccmp(scratch3, allocation_limit, CFlag, cc); + B(hi, gc_required); + Str(scratch3, MemOperand(top_address)); + + // Tag the object if requested. + if ((flags & TAG_OBJECT) != 0) { + ObjectTag(result, result); + } +} + + +void MacroAssembler::Allocate(Register object_size, + Register result, + Register scratch1, + Register scratch2, + Label* gc_required, + AllocationFlags flags) { + if (!FLAG_inline_new) { + if (emit_debug_code()) { + // Trash the registers to simulate an allocation failure. + // We apply salt to the original zap value to easily spot the values. + Mov(result, (kDebugZapValue & ~0xffL) | 0x11L); + Mov(scratch1, (kDebugZapValue & ~0xffL) | 0x21L); + Mov(scratch2, (kDebugZapValue & ~0xffL) | 0x21L); + } + B(gc_required); + return; + } + + UseScratchRegisterScope temps(this); + Register scratch3 = temps.AcquireX(); + + ASSERT(!AreAliased(object_size, result, scratch1, scratch2, scratch3)); + ASSERT(object_size.Is64Bits() && result.Is64Bits() && + scratch1.Is64Bits() && scratch2.Is64Bits()); + + // Check relative positions of allocation top and limit addresses. + // The values must be adjacent in memory to allow the use of LDP. + ExternalReference heap_allocation_top = + AllocationUtils::GetAllocationTopReference(isolate(), flags); + ExternalReference heap_allocation_limit = + AllocationUtils::GetAllocationLimitReference(isolate(), flags); + intptr_t top = reinterpret_cast<intptr_t>(heap_allocation_top.address()); + intptr_t limit = reinterpret_cast<intptr_t>(heap_allocation_limit.address()); + ASSERT((limit - top) == kPointerSize); + + // Set up allocation top address and object size registers. + Register top_address = scratch1; + Register allocation_limit = scratch2; + Mov(top_address, heap_allocation_top); + + if ((flags & RESULT_CONTAINS_TOP) == 0) { + // Load allocation top into result and the allocation limit. + Ldp(result, allocation_limit, MemOperand(top_address)); + } else { + if (emit_debug_code()) { + // Assert that result actually contains top on entry. + Ldr(scratch3, MemOperand(top_address)); + Cmp(result, scratch3); + Check(eq, kUnexpectedAllocationTop); + } + // Load the allocation limit. 'result' already contains the allocation top. + Ldr(allocation_limit, MemOperand(top_address, limit - top)); + } + + // We can ignore DOUBLE_ALIGNMENT flags here because doubles and pointers have + // the same alignment on ARM64. + STATIC_ASSERT(kPointerAlignment == kDoubleAlignment); + + // Calculate new top and bail out if new space is exhausted + if ((flags & SIZE_IN_WORDS) != 0) { + Adds(scratch3, result, Operand(object_size, LSL, kPointerSizeLog2)); + } else { + Adds(scratch3, result, object_size); + } + + if (emit_debug_code()) { + Tst(scratch3, kObjectAlignmentMask); + Check(eq, kUnalignedAllocationInNewSpace); + } + + Ccmp(scratch3, allocation_limit, CFlag, cc); + B(hi, gc_required); + Str(scratch3, MemOperand(top_address)); + + // Tag the object if requested. + if ((flags & TAG_OBJECT) != 0) { + ObjectTag(result, result); + } +} + + +void MacroAssembler::UndoAllocationInNewSpace(Register object, + Register scratch) { + ExternalReference new_space_allocation_top = + ExternalReference::new_space_allocation_top_address(isolate()); + + // Make sure the object has no tag before resetting top. + Bic(object, object, kHeapObjectTagMask); +#ifdef DEBUG + // Check that the object un-allocated is below the current top. + Mov(scratch, new_space_allocation_top); + Ldr(scratch, MemOperand(scratch)); + Cmp(object, scratch); + Check(lt, kUndoAllocationOfNonAllocatedMemory); +#endif + // Write the address of the object to un-allocate as the current top. + Mov(scratch, new_space_allocation_top); + Str(object, MemOperand(scratch)); +} + + +void MacroAssembler::AllocateTwoByteString(Register result, + Register length, + Register scratch1, + Register scratch2, + Register scratch3, + Label* gc_required) { + ASSERT(!AreAliased(result, length, scratch1, scratch2, scratch3)); + // Calculate the number of bytes needed for the characters in the string while + // observing object alignment. + STATIC_ASSERT((SeqTwoByteString::kHeaderSize & kObjectAlignmentMask) == 0); + Add(scratch1, length, length); // Length in bytes, not chars. + Add(scratch1, scratch1, kObjectAlignmentMask + SeqTwoByteString::kHeaderSize); + Bic(scratch1, scratch1, kObjectAlignmentMask); + + // Allocate two-byte string in new space. + Allocate(scratch1, + result, + scratch2, + scratch3, + gc_required, + TAG_OBJECT); + + // Set the map, length and hash field. + InitializeNewString(result, + length, + Heap::kStringMapRootIndex, + scratch1, + scratch2); +} + + +void MacroAssembler::AllocateAsciiString(Register result, + Register length, + Register scratch1, + Register scratch2, + Register scratch3, + Label* gc_required) { + ASSERT(!AreAliased(result, length, scratch1, scratch2, scratch3)); + // Calculate the number of bytes needed for the characters in the string while + // observing object alignment. + STATIC_ASSERT((SeqOneByteString::kHeaderSize & kObjectAlignmentMask) == 0); + STATIC_ASSERT(kCharSize == 1); + Add(scratch1, length, kObjectAlignmentMask + SeqOneByteString::kHeaderSize); + Bic(scratch1, scratch1, kObjectAlignmentMask); + + // Allocate ASCII string in new space. + Allocate(scratch1, + result, + scratch2, + scratch3, + gc_required, + TAG_OBJECT); + + // Set the map, length and hash field. + InitializeNewString(result, + length, + Heap::kAsciiStringMapRootIndex, + scratch1, + scratch2); +} + + +void MacroAssembler::AllocateTwoByteConsString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required) { + Allocate(ConsString::kSize, result, scratch1, scratch2, gc_required, + TAG_OBJECT); + + InitializeNewString(result, + length, + Heap::kConsStringMapRootIndex, + scratch1, + scratch2); +} + + +void MacroAssembler::AllocateAsciiConsString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required) { + Allocate(ConsString::kSize, + result, + scratch1, + scratch2, + gc_required, + TAG_OBJECT); + + InitializeNewString(result, + length, + Heap::kConsAsciiStringMapRootIndex, + scratch1, + scratch2); +} + + +void MacroAssembler::AllocateTwoByteSlicedString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required) { + ASSERT(!AreAliased(result, length, scratch1, scratch2)); + Allocate(SlicedString::kSize, result, scratch1, scratch2, gc_required, + TAG_OBJECT); + + InitializeNewString(result, + length, + Heap::kSlicedStringMapRootIndex, + scratch1, + scratch2); +} + + +void MacroAssembler::AllocateAsciiSlicedString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required) { + ASSERT(!AreAliased(result, length, scratch1, scratch2)); + Allocate(SlicedString::kSize, result, scratch1, scratch2, gc_required, + TAG_OBJECT); + + InitializeNewString(result, + length, + Heap::kSlicedAsciiStringMapRootIndex, + scratch1, + scratch2); +} + + +// Allocates a heap number or jumps to the need_gc label if the young space +// is full and a scavenge is needed. +void MacroAssembler::AllocateHeapNumber(Register result, + Label* gc_required, + Register scratch1, + Register scratch2, + CPURegister value, + CPURegister heap_number_map) { + ASSERT(!value.IsValid() || value.Is64Bits()); + UseScratchRegisterScope temps(this); + + // Allocate an object in the heap for the heap number and tag it as a heap + // object. + Allocate(HeapNumber::kSize, result, scratch1, scratch2, gc_required, + NO_ALLOCATION_FLAGS); + + // Prepare the heap number map. + if (!heap_number_map.IsValid()) { + // If we have a valid value register, use the same type of register to store + // the map so we can use STP to store both in one instruction. + if (value.IsValid() && value.IsFPRegister()) { + heap_number_map = temps.AcquireD(); + } else { + heap_number_map = scratch1; + } + LoadRoot(heap_number_map, Heap::kHeapNumberMapRootIndex); + } + if (emit_debug_code()) { + Register map; + if (heap_number_map.IsFPRegister()) { + map = scratch1; + Fmov(map, DoubleRegister(heap_number_map)); + } else { + map = Register(heap_number_map); + } + AssertRegisterIsRoot(map, Heap::kHeapNumberMapRootIndex); + } + + // Store the heap number map and the value in the allocated object. + if (value.IsSameSizeAndType(heap_number_map)) { + STATIC_ASSERT(HeapObject::kMapOffset + kPointerSize == + HeapNumber::kValueOffset); + Stp(heap_number_map, value, MemOperand(result, HeapObject::kMapOffset)); + } else { + Str(heap_number_map, MemOperand(result, HeapObject::kMapOffset)); + if (value.IsValid()) { + Str(value, MemOperand(result, HeapNumber::kValueOffset)); + } + } + ObjectTag(result, result); +} + + +void MacroAssembler::JumpIfObjectType(Register object, + Register map, + Register type_reg, + InstanceType type, + Label* if_cond_pass, + Condition cond) { + CompareObjectType(object, map, type_reg, type); + B(cond, if_cond_pass); +} + + +void MacroAssembler::JumpIfNotObjectType(Register object, + Register map, + Register type_reg, + InstanceType type, + Label* if_not_object) { + JumpIfObjectType(object, map, type_reg, type, if_not_object, ne); +} + + +// Sets condition flags based on comparison, and returns type in type_reg. +void MacroAssembler::CompareObjectType(Register object, + Register map, + Register type_reg, + InstanceType type) { + Ldr(map, FieldMemOperand(object, HeapObject::kMapOffset)); + CompareInstanceType(map, type_reg, type); +} + + +// Sets condition flags based on comparison, and returns type in type_reg. +void MacroAssembler::CompareInstanceType(Register map, + Register type_reg, + InstanceType type) { + Ldrb(type_reg, FieldMemOperand(map, Map::kInstanceTypeOffset)); + Cmp(type_reg, type); +} + + +void MacroAssembler::CompareMap(Register obj, + Register scratch, + Handle<Map> map) { + Ldr(scratch, FieldMemOperand(obj, HeapObject::kMapOffset)); + CompareMap(scratch, map); +} + + +void MacroAssembler::CompareMap(Register obj_map, + Handle<Map> map) { + Cmp(obj_map, Operand(map)); +} + + +void MacroAssembler::CheckMap(Register obj, + Register scratch, + Handle<Map> map, + Label* fail, + SmiCheckType smi_check_type) { + if (smi_check_type == DO_SMI_CHECK) { + JumpIfSmi(obj, fail); + } + + CompareMap(obj, scratch, map); + B(ne, fail); +} + + +void MacroAssembler::CheckMap(Register obj, + Register scratch, + Heap::RootListIndex index, + Label* fail, + SmiCheckType smi_check_type) { + if (smi_check_type == DO_SMI_CHECK) { + JumpIfSmi(obj, fail); + } + Ldr(scratch, FieldMemOperand(obj, HeapObject::kMapOffset)); + JumpIfNotRoot(scratch, index, fail); +} + + +void MacroAssembler::CheckMap(Register obj_map, + Handle<Map> map, + Label* fail, + SmiCheckType smi_check_type) { + if (smi_check_type == DO_SMI_CHECK) { + JumpIfSmi(obj_map, fail); + } + + CompareMap(obj_map, map); + B(ne, fail); +} + + +void MacroAssembler::DispatchMap(Register obj, + Register scratch, + Handle<Map> map, + Handle<Code> success, + SmiCheckType smi_check_type) { + Label fail; + if (smi_check_type == DO_SMI_CHECK) { + JumpIfSmi(obj, &fail); + } + Ldr(scratch, FieldMemOperand(obj, HeapObject::kMapOffset)); + Cmp(scratch, Operand(map)); + B(ne, &fail); + Jump(success, RelocInfo::CODE_TARGET); + Bind(&fail); +} + + +void MacroAssembler::TestMapBitfield(Register object, uint64_t mask) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Ldr(temp, FieldMemOperand(object, HeapObject::kMapOffset)); + Ldrb(temp, FieldMemOperand(temp, Map::kBitFieldOffset)); + Tst(temp, mask); +} + + +void MacroAssembler::LoadElementsKindFromMap(Register result, Register map) { + // Load the map's "bit field 2". + __ Ldrb(result, FieldMemOperand(map, Map::kBitField2Offset)); + // Retrieve elements_kind from bit field 2. + DecodeField<Map::ElementsKindBits>(result); +} + + +void MacroAssembler::TryGetFunctionPrototype(Register function, + Register result, + Register scratch, + Label* miss, + BoundFunctionAction action) { + ASSERT(!AreAliased(function, result, scratch)); + + // Check that the receiver isn't a smi. + JumpIfSmi(function, miss); + + // Check that the function really is a function. Load map into result reg. + JumpIfNotObjectType(function, result, scratch, JS_FUNCTION_TYPE, miss); + + if (action == kMissOnBoundFunction) { + Register scratch_w = scratch.W(); + Ldr(scratch, + FieldMemOperand(function, JSFunction::kSharedFunctionInfoOffset)); + // On 64-bit platforms, compiler hints field is not a smi. See definition of + // kCompilerHintsOffset in src/objects.h. + Ldr(scratch_w, + FieldMemOperand(scratch, SharedFunctionInfo::kCompilerHintsOffset)); + Tbnz(scratch, SharedFunctionInfo::kBoundFunction, miss); + } + + // Make sure that the function has an instance prototype. + Label non_instance; + Ldrb(scratch, FieldMemOperand(result, Map::kBitFieldOffset)); + Tbnz(scratch, Map::kHasNonInstancePrototype, &non_instance); + + // Get the prototype or initial map from the function. + Ldr(result, + FieldMemOperand(function, JSFunction::kPrototypeOrInitialMapOffset)); + + // If the prototype or initial map is the hole, don't return it and simply + // miss the cache instead. This will allow us to allocate a prototype object + // on-demand in the runtime system. + JumpIfRoot(result, Heap::kTheHoleValueRootIndex, miss); + + // If the function does not have an initial map, we're done. + Label done; + JumpIfNotObjectType(result, scratch, scratch, MAP_TYPE, &done); + + // Get the prototype from the initial map. + Ldr(result, FieldMemOperand(result, Map::kPrototypeOffset)); + B(&done); + + // Non-instance prototype: fetch prototype from constructor field in initial + // map. + Bind(&non_instance); + Ldr(result, FieldMemOperand(result, Map::kConstructorOffset)); + + // All done. + Bind(&done); +} + + +void MacroAssembler::CompareRoot(const Register& obj, + Heap::RootListIndex index) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + ASSERT(!AreAliased(obj, temp)); + LoadRoot(temp, index); + Cmp(obj, temp); +} + + +void MacroAssembler::JumpIfRoot(const Register& obj, + Heap::RootListIndex index, + Label* if_equal) { + CompareRoot(obj, index); + B(eq, if_equal); +} + + +void MacroAssembler::JumpIfNotRoot(const Register& obj, + Heap::RootListIndex index, + Label* if_not_equal) { + CompareRoot(obj, index); + B(ne, if_not_equal); +} + + +void MacroAssembler::CompareAndSplit(const Register& lhs, + const Operand& rhs, + Condition cond, + Label* if_true, + Label* if_false, + Label* fall_through) { + if ((if_true == if_false) && (if_false == fall_through)) { + // Fall through. + } else if (if_true == if_false) { + B(if_true); + } else if (if_false == fall_through) { + CompareAndBranch(lhs, rhs, cond, if_true); + } else if (if_true == fall_through) { + CompareAndBranch(lhs, rhs, NegateCondition(cond), if_false); + } else { + CompareAndBranch(lhs, rhs, cond, if_true); + B(if_false); + } +} + + +void MacroAssembler::TestAndSplit(const Register& reg, + uint64_t bit_pattern, + Label* if_all_clear, + Label* if_any_set, + Label* fall_through) { + if ((if_all_clear == if_any_set) && (if_any_set == fall_through)) { + // Fall through. + } else if (if_all_clear == if_any_set) { + B(if_all_clear); + } else if (if_all_clear == fall_through) { + TestAndBranchIfAnySet(reg, bit_pattern, if_any_set); + } else if (if_any_set == fall_through) { + TestAndBranchIfAllClear(reg, bit_pattern, if_all_clear); + } else { + TestAndBranchIfAnySet(reg, bit_pattern, if_any_set); + B(if_all_clear); + } +} + + +void MacroAssembler::CheckFastElements(Register map, + Register scratch, + Label* fail) { + STATIC_ASSERT(FAST_SMI_ELEMENTS == 0); + STATIC_ASSERT(FAST_HOLEY_SMI_ELEMENTS == 1); + STATIC_ASSERT(FAST_ELEMENTS == 2); + STATIC_ASSERT(FAST_HOLEY_ELEMENTS == 3); + Ldrb(scratch, FieldMemOperand(map, Map::kBitField2Offset)); + Cmp(scratch, Map::kMaximumBitField2FastHoleyElementValue); + B(hi, fail); +} + + +void MacroAssembler::CheckFastObjectElements(Register map, + Register scratch, + Label* fail) { + STATIC_ASSERT(FAST_SMI_ELEMENTS == 0); + STATIC_ASSERT(FAST_HOLEY_SMI_ELEMENTS == 1); + STATIC_ASSERT(FAST_ELEMENTS == 2); + STATIC_ASSERT(FAST_HOLEY_ELEMENTS == 3); + Ldrb(scratch, FieldMemOperand(map, Map::kBitField2Offset)); + Cmp(scratch, Operand(Map::kMaximumBitField2FastHoleySmiElementValue)); + // If cond==ls, set cond=hi, otherwise compare. + Ccmp(scratch, + Operand(Map::kMaximumBitField2FastHoleyElementValue), CFlag, hi); + B(hi, fail); +} + + +// Note: The ARM version of this clobbers elements_reg, but this version does +// not. Some uses of this in ARM64 assume that elements_reg will be preserved. +void MacroAssembler::StoreNumberToDoubleElements(Register value_reg, + Register key_reg, + Register elements_reg, + Register scratch1, + FPRegister fpscratch1, + Label* fail, + int elements_offset) { + ASSERT(!AreAliased(value_reg, key_reg, elements_reg, scratch1)); + Label store_num; + + // Speculatively convert the smi to a double - all smis can be exactly + // represented as a double. + SmiUntagToDouble(fpscratch1, value_reg, kSpeculativeUntag); + + // If value_reg is a smi, we're done. + JumpIfSmi(value_reg, &store_num); + + // Ensure that the object is a heap number. + CheckMap(value_reg, scratch1, isolate()->factory()->heap_number_map(), + fail, DONT_DO_SMI_CHECK); + + Ldr(fpscratch1, FieldMemOperand(value_reg, HeapNumber::kValueOffset)); + + // Canonicalize NaNs. + CanonicalizeNaN(fpscratch1); + + // Store the result. + Bind(&store_num); + Add(scratch1, elements_reg, + Operand::UntagSmiAndScale(key_reg, kDoubleSizeLog2)); + Str(fpscratch1, + FieldMemOperand(scratch1, + FixedDoubleArray::kHeaderSize - elements_offset)); +} + + +bool MacroAssembler::AllowThisStubCall(CodeStub* stub) { + return has_frame_ || !stub->SometimesSetsUpAFrame(); +} + + +void MacroAssembler::IndexFromHash(Register hash, Register index) { + // If the hash field contains an array index pick it out. The assert checks + // that the constants for the maximum number of digits for an array index + // cached in the hash field and the number of bits reserved for it does not + // conflict. + ASSERT(TenToThe(String::kMaxCachedArrayIndexLength) < + (1 << String::kArrayIndexValueBits)); + DecodeField<String::ArrayIndexValueBits>(index, hash); + SmiTag(index, index); +} + + +void MacroAssembler::EmitSeqStringSetCharCheck( + Register string, + Register index, + SeqStringSetCharCheckIndexType index_type, + Register scratch, + uint32_t encoding_mask) { + ASSERT(!AreAliased(string, index, scratch)); + + if (index_type == kIndexIsSmi) { + AssertSmi(index); + } + + // Check that string is an object. + AssertNotSmi(string, kNonObject); + + // Check that string has an appropriate map. + Ldr(scratch, FieldMemOperand(string, HeapObject::kMapOffset)); + Ldrb(scratch, FieldMemOperand(scratch, Map::kInstanceTypeOffset)); + + And(scratch, scratch, kStringRepresentationMask | kStringEncodingMask); + Cmp(scratch, encoding_mask); + Check(eq, kUnexpectedStringType); + + Ldr(scratch, FieldMemOperand(string, String::kLengthOffset)); + Cmp(index, index_type == kIndexIsSmi ? scratch : Operand::UntagSmi(scratch)); + Check(lt, kIndexIsTooLarge); + + ASSERT_EQ(0, Smi::FromInt(0)); + Cmp(index, 0); + Check(ge, kIndexIsNegative); +} + + +void MacroAssembler::CheckAccessGlobalProxy(Register holder_reg, + Register scratch1, + Register scratch2, + Label* miss) { + ASSERT(!AreAliased(holder_reg, scratch1, scratch2)); + Label same_contexts; + + // Load current lexical context from the stack frame. + Ldr(scratch1, MemOperand(fp, StandardFrameConstants::kContextOffset)); + // In debug mode, make sure the lexical context is set. +#ifdef DEBUG + Cmp(scratch1, 0); + Check(ne, kWeShouldNotHaveAnEmptyLexicalContext); +#endif + + // Load the native context of the current context. + int offset = + Context::kHeaderSize + Context::GLOBAL_OBJECT_INDEX * kPointerSize; + Ldr(scratch1, FieldMemOperand(scratch1, offset)); + Ldr(scratch1, FieldMemOperand(scratch1, GlobalObject::kNativeContextOffset)); + + // Check the context is a native context. + if (emit_debug_code()) { + // Read the first word and compare to the global_context_map. + Ldr(scratch2, FieldMemOperand(scratch1, HeapObject::kMapOffset)); + CompareRoot(scratch2, Heap::kNativeContextMapRootIndex); + Check(eq, kExpectedNativeContext); + } + + // Check if both contexts are the same. + Ldr(scratch2, FieldMemOperand(holder_reg, + JSGlobalProxy::kNativeContextOffset)); + Cmp(scratch1, scratch2); + B(&same_contexts, eq); + + // Check the context is a native context. + if (emit_debug_code()) { + // We're short on scratch registers here, so use holder_reg as a scratch. + Push(holder_reg); + Register scratch3 = holder_reg; + + CompareRoot(scratch2, Heap::kNullValueRootIndex); + Check(ne, kExpectedNonNullContext); + + Ldr(scratch3, FieldMemOperand(scratch2, HeapObject::kMapOffset)); + CompareRoot(scratch3, Heap::kNativeContextMapRootIndex); + Check(eq, kExpectedNativeContext); + Pop(holder_reg); + } + + // Check that the security token in the calling global object is + // compatible with the security token in the receiving global + // object. + int token_offset = Context::kHeaderSize + + Context::SECURITY_TOKEN_INDEX * kPointerSize; + + Ldr(scratch1, FieldMemOperand(scratch1, token_offset)); + Ldr(scratch2, FieldMemOperand(scratch2, token_offset)); + Cmp(scratch1, scratch2); + B(miss, ne); + + Bind(&same_contexts); +} + + +// Compute the hash code from the untagged key. This must be kept in sync with +// ComputeIntegerHash in utils.h and KeyedLoadGenericElementStub in +// code-stub-hydrogen.cc +void MacroAssembler::GetNumberHash(Register key, Register scratch) { + ASSERT(!AreAliased(key, scratch)); + + // Xor original key with a seed. + LoadRoot(scratch, Heap::kHashSeedRootIndex); + Eor(key, key, Operand::UntagSmi(scratch)); + + // The algorithm uses 32-bit integer values. + key = key.W(); + scratch = scratch.W(); + + // Compute the hash code from the untagged key. This must be kept in sync + // with ComputeIntegerHash in utils.h. + // + // hash = ~hash + (hash <<1 15); + Mvn(scratch, key); + Add(key, scratch, Operand(key, LSL, 15)); + // hash = hash ^ (hash >> 12); + Eor(key, key, Operand(key, LSR, 12)); + // hash = hash + (hash << 2); + Add(key, key, Operand(key, LSL, 2)); + // hash = hash ^ (hash >> 4); + Eor(key, key, Operand(key, LSR, 4)); + // hash = hash * 2057; + Mov(scratch, Operand(key, LSL, 11)); + Add(key, key, Operand(key, LSL, 3)); + Add(key, key, scratch); + // hash = hash ^ (hash >> 16); + Eor(key, key, Operand(key, LSR, 16)); +} + + +void MacroAssembler::LoadFromNumberDictionary(Label* miss, + Register elements, + Register key, + Register result, + Register scratch0, + Register scratch1, + Register scratch2, + Register scratch3) { + ASSERT(!AreAliased(elements, key, scratch0, scratch1, scratch2, scratch3)); + + Label done; + + SmiUntag(scratch0, key); + GetNumberHash(scratch0, scratch1); + + // Compute the capacity mask. + Ldrsw(scratch1, + UntagSmiFieldMemOperand(elements, + SeededNumberDictionary::kCapacityOffset)); + Sub(scratch1, scratch1, 1); + + // Generate an unrolled loop that performs a few probes before giving up. + for (int i = 0; i < kNumberDictionaryProbes; i++) { + // Compute the masked index: (hash + i + i * i) & mask. + if (i > 0) { + Add(scratch2, scratch0, SeededNumberDictionary::GetProbeOffset(i)); + } else { + Mov(scratch2, scratch0); + } + And(scratch2, scratch2, scratch1); + + // Scale the index by multiplying by the element size. + ASSERT(SeededNumberDictionary::kEntrySize == 3); + Add(scratch2, scratch2, Operand(scratch2, LSL, 1)); + + // Check if the key is identical to the name. + Add(scratch2, elements, Operand(scratch2, LSL, kPointerSizeLog2)); + Ldr(scratch3, + FieldMemOperand(scratch2, + SeededNumberDictionary::kElementsStartOffset)); + Cmp(key, scratch3); + if (i != (kNumberDictionaryProbes - 1)) { + B(eq, &done); + } else { + B(ne, miss); + } + } + + Bind(&done); + // Check that the value is a normal property. + const int kDetailsOffset = + SeededNumberDictionary::kElementsStartOffset + 2 * kPointerSize; + Ldrsw(scratch1, UntagSmiFieldMemOperand(scratch2, kDetailsOffset)); + TestAndBranchIfAnySet(scratch1, PropertyDetails::TypeField::kMask, miss); + + // Get the value at the masked, scaled index and return. + const int kValueOffset = + SeededNumberDictionary::kElementsStartOffset + kPointerSize; + Ldr(result, FieldMemOperand(scratch2, kValueOffset)); +} + + +void MacroAssembler::RememberedSetHelper(Register object, // For debug tests. + Register address, + Register scratch1, + SaveFPRegsMode fp_mode, + RememberedSetFinalAction and_then) { + ASSERT(!AreAliased(object, address, scratch1)); + Label done, store_buffer_overflow; + if (emit_debug_code()) { + Label ok; + JumpIfNotInNewSpace(object, &ok); + Abort(kRememberedSetPointerInNewSpace); + bind(&ok); + } + UseScratchRegisterScope temps(this); + Register scratch2 = temps.AcquireX(); + + // Load store buffer top. + Mov(scratch2, ExternalReference::store_buffer_top(isolate())); + Ldr(scratch1, MemOperand(scratch2)); + // Store pointer to buffer and increment buffer top. + Str(address, MemOperand(scratch1, kPointerSize, PostIndex)); + // Write back new top of buffer. + Str(scratch1, MemOperand(scratch2)); + // Call stub on end of buffer. + // Check for end of buffer. + ASSERT(StoreBuffer::kStoreBufferOverflowBit == + (1 << (14 + kPointerSizeLog2))); + if (and_then == kFallThroughAtEnd) { + Tbz(scratch1, (14 + kPointerSizeLog2), &done); + } else { + ASSERT(and_then == kReturnAtEnd); + Tbnz(scratch1, (14 + kPointerSizeLog2), &store_buffer_overflow); + Ret(); + } + + Bind(&store_buffer_overflow); + Push(lr); + StoreBufferOverflowStub store_buffer_overflow_stub = + StoreBufferOverflowStub(isolate(), fp_mode); + CallStub(&store_buffer_overflow_stub); + Pop(lr); + + Bind(&done); + if (and_then == kReturnAtEnd) { + Ret(); + } +} + + +void MacroAssembler::PopSafepointRegisters() { + const int num_unsaved = kNumSafepointRegisters - kNumSafepointSavedRegisters; + PopXRegList(kSafepointSavedRegisters); + Drop(num_unsaved); +} + + +void MacroAssembler::PushSafepointRegisters() { + // Safepoints expect a block of kNumSafepointRegisters values on the stack, so + // adjust the stack for unsaved registers. + const int num_unsaved = kNumSafepointRegisters - kNumSafepointSavedRegisters; + ASSERT(num_unsaved >= 0); + Claim(num_unsaved); + PushXRegList(kSafepointSavedRegisters); +} + + +void MacroAssembler::PushSafepointRegistersAndDoubles() { + PushSafepointRegisters(); + PushCPURegList(CPURegList(CPURegister::kFPRegister, kDRegSizeInBits, + FPRegister::kAllocatableFPRegisters)); +} + + +void MacroAssembler::PopSafepointRegistersAndDoubles() { + PopCPURegList(CPURegList(CPURegister::kFPRegister, kDRegSizeInBits, + FPRegister::kAllocatableFPRegisters)); + PopSafepointRegisters(); +} + + +int MacroAssembler::SafepointRegisterStackIndex(int reg_code) { + // Make sure the safepoint registers list is what we expect. + ASSERT(CPURegList::GetSafepointSavedRegisters().list() == 0x6ffcffff); + + // Safepoint registers are stored contiguously on the stack, but not all the + // registers are saved. The following registers are excluded: + // - x16 and x17 (ip0 and ip1) because they shouldn't be preserved outside of + // the macro assembler. + // - x28 (jssp) because JS stack pointer doesn't need to be included in + // safepoint registers. + // - x31 (csp) because the system stack pointer doesn't need to be included + // in safepoint registers. + // + // This function implements the mapping of register code to index into the + // safepoint register slots. + if ((reg_code >= 0) && (reg_code <= 15)) { + return reg_code; + } else if ((reg_code >= 18) && (reg_code <= 27)) { + // Skip ip0 and ip1. + return reg_code - 2; + } else if ((reg_code == 29) || (reg_code == 30)) { + // Also skip jssp. + return reg_code - 3; + } else { + // This register has no safepoint register slot. + UNREACHABLE(); + return -1; + } +} + + +void MacroAssembler::CheckPageFlagSet(const Register& object, + const Register& scratch, + int mask, + Label* if_any_set) { + And(scratch, object, ~Page::kPageAlignmentMask); + Ldr(scratch, MemOperand(scratch, MemoryChunk::kFlagsOffset)); + TestAndBranchIfAnySet(scratch, mask, if_any_set); +} + + +void MacroAssembler::CheckPageFlagClear(const Register& object, + const Register& scratch, + int mask, + Label* if_all_clear) { + And(scratch, object, ~Page::kPageAlignmentMask); + Ldr(scratch, MemOperand(scratch, MemoryChunk::kFlagsOffset)); + TestAndBranchIfAllClear(scratch, mask, if_all_clear); +} + + +void MacroAssembler::RecordWriteField( + Register object, + int offset, + Register value, + Register scratch, + LinkRegisterStatus lr_status, + SaveFPRegsMode save_fp, + RememberedSetAction remembered_set_action, + SmiCheck smi_check, + PointersToHereCheck pointers_to_here_check_for_value) { + // First, check if a write barrier is even needed. The tests below + // catch stores of Smis. + Label done; + + // Skip the barrier if writing a smi. + if (smi_check == INLINE_SMI_CHECK) { + JumpIfSmi(value, &done); + } + + // Although the object register is tagged, the offset is relative to the start + // of the object, so offset must be a multiple of kPointerSize. + ASSERT(IsAligned(offset, kPointerSize)); + + Add(scratch, object, offset - kHeapObjectTag); + if (emit_debug_code()) { + Label ok; + Tst(scratch, (1 << kPointerSizeLog2) - 1); + B(eq, &ok); + Abort(kUnalignedCellInWriteBarrier); + Bind(&ok); + } + + RecordWrite(object, + scratch, + value, + lr_status, + save_fp, + remembered_set_action, + OMIT_SMI_CHECK, + pointers_to_here_check_for_value); + + Bind(&done); + + // Clobber clobbered input registers when running with the debug-code flag + // turned on to provoke errors. + if (emit_debug_code()) { + Mov(value, Operand(BitCast<int64_t>(kZapValue + 4))); + Mov(scratch, Operand(BitCast<int64_t>(kZapValue + 8))); + } +} + + +// Will clobber: object, map, dst. +// If lr_status is kLRHasBeenSaved, lr will also be clobbered. +void MacroAssembler::RecordWriteForMap(Register object, + Register map, + Register dst, + LinkRegisterStatus lr_status, + SaveFPRegsMode fp_mode) { + ASM_LOCATION("MacroAssembler::RecordWrite"); + ASSERT(!AreAliased(object, map)); + + if (emit_debug_code()) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + + CompareMap(map, temp, isolate()->factory()->meta_map()); + Check(eq, kWrongAddressOrValuePassedToRecordWrite); + } + + if (!FLAG_incremental_marking) { + return; + } + + if (emit_debug_code()) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + + Ldr(temp, FieldMemOperand(object, HeapObject::kMapOffset)); + Cmp(temp, map); + Check(eq, kWrongAddressOrValuePassedToRecordWrite); + } + + // Count number of write barriers in generated code. + isolate()->counters()->write_barriers_static()->Increment(); + // TODO(mstarzinger): Dynamic counter missing. + + // First, check if a write barrier is even needed. The tests below + // catch stores of smis and stores into the young generation. + Label done; + + // A single check of the map's pages interesting flag suffices, since it is + // only set during incremental collection, and then it's also guaranteed that + // the from object's page's interesting flag is also set. This optimization + // relies on the fact that maps can never be in new space. + CheckPageFlagClear(map, + map, // Used as scratch. + MemoryChunk::kPointersToHereAreInterestingMask, + &done); + + // Record the actual write. + if (lr_status == kLRHasNotBeenSaved) { + Push(lr); + } + Add(dst, object, HeapObject::kMapOffset - kHeapObjectTag); + RecordWriteStub stub(isolate(), object, map, dst, OMIT_REMEMBERED_SET, + fp_mode); + CallStub(&stub); + if (lr_status == kLRHasNotBeenSaved) { + Pop(lr); + } + + Bind(&done); + + // Clobber clobbered registers when running with the debug-code flag + // turned on to provoke errors. + if (emit_debug_code()) { + Mov(dst, Operand(BitCast<int64_t>(kZapValue + 12))); + Mov(map, Operand(BitCast<int64_t>(kZapValue + 16))); + } +} + + +// Will clobber: object, address, value. +// If lr_status is kLRHasBeenSaved, lr will also be clobbered. +// +// The register 'object' contains a heap object pointer. The heap object tag is +// shifted away. +void MacroAssembler::RecordWrite( + Register object, + Register address, + Register value, + LinkRegisterStatus lr_status, + SaveFPRegsMode fp_mode, + RememberedSetAction remembered_set_action, + SmiCheck smi_check, + PointersToHereCheck pointers_to_here_check_for_value) { + ASM_LOCATION("MacroAssembler::RecordWrite"); + ASSERT(!AreAliased(object, value)); + + if (emit_debug_code()) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + + Ldr(temp, MemOperand(address)); + Cmp(temp, value); + Check(eq, kWrongAddressOrValuePassedToRecordWrite); + } + + // Count number of write barriers in generated code. + isolate()->counters()->write_barriers_static()->Increment(); + // TODO(mstarzinger): Dynamic counter missing. + + // First, check if a write barrier is even needed. The tests below + // catch stores of smis and stores into the young generation. + Label done; + + if (smi_check == INLINE_SMI_CHECK) { + ASSERT_EQ(0, kSmiTag); + JumpIfSmi(value, &done); + } + + if (pointers_to_here_check_for_value != kPointersToHereAreAlwaysInteresting) { + CheckPageFlagClear(value, + value, // Used as scratch. + MemoryChunk::kPointersToHereAreInterestingMask, + &done); + } + CheckPageFlagClear(object, + value, // Used as scratch. + MemoryChunk::kPointersFromHereAreInterestingMask, + &done); + + // Record the actual write. + if (lr_status == kLRHasNotBeenSaved) { + Push(lr); + } + RecordWriteStub stub(isolate(), object, value, address, remembered_set_action, + fp_mode); + CallStub(&stub); + if (lr_status == kLRHasNotBeenSaved) { + Pop(lr); + } + + Bind(&done); + + // Clobber clobbered registers when running with the debug-code flag + // turned on to provoke errors. + if (emit_debug_code()) { + Mov(address, Operand(BitCast<int64_t>(kZapValue + 12))); + Mov(value, Operand(BitCast<int64_t>(kZapValue + 16))); + } +} + + +void MacroAssembler::AssertHasValidColor(const Register& reg) { + if (emit_debug_code()) { + // The bit sequence is backward. The first character in the string + // represents the least significant bit. + ASSERT(strcmp(Marking::kImpossibleBitPattern, "01") == 0); + + Label color_is_valid; + Tbnz(reg, 0, &color_is_valid); + Tbz(reg, 1, &color_is_valid); + Abort(kUnexpectedColorFound); + Bind(&color_is_valid); + } +} + + +void MacroAssembler::GetMarkBits(Register addr_reg, + Register bitmap_reg, + Register shift_reg) { + ASSERT(!AreAliased(addr_reg, bitmap_reg, shift_reg)); + ASSERT(addr_reg.Is64Bits() && bitmap_reg.Is64Bits() && shift_reg.Is64Bits()); + // addr_reg is divided into fields: + // |63 page base 20|19 high 8|7 shift 3|2 0| + // 'high' gives the index of the cell holding color bits for the object. + // 'shift' gives the offset in the cell for this object's color. + const int kShiftBits = kPointerSizeLog2 + Bitmap::kBitsPerCellLog2; + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Ubfx(temp, addr_reg, kShiftBits, kPageSizeBits - kShiftBits); + Bic(bitmap_reg, addr_reg, Page::kPageAlignmentMask); + Add(bitmap_reg, bitmap_reg, Operand(temp, LSL, Bitmap::kBytesPerCellLog2)); + // bitmap_reg: + // |63 page base 20|19 zeros 15|14 high 3|2 0| + Ubfx(shift_reg, addr_reg, kPointerSizeLog2, Bitmap::kBitsPerCellLog2); +} + + +void MacroAssembler::HasColor(Register object, + Register bitmap_scratch, + Register shift_scratch, + Label* has_color, + int first_bit, + int second_bit) { + // See mark-compact.h for color definitions. + ASSERT(!AreAliased(object, bitmap_scratch, shift_scratch)); + + GetMarkBits(object, bitmap_scratch, shift_scratch); + Ldr(bitmap_scratch, MemOperand(bitmap_scratch, MemoryChunk::kHeaderSize)); + // Shift the bitmap down to get the color of the object in bits [1:0]. + Lsr(bitmap_scratch, bitmap_scratch, shift_scratch); + + AssertHasValidColor(bitmap_scratch); + + // These bit sequences are backwards. The first character in the string + // represents the least significant bit. + ASSERT(strcmp(Marking::kWhiteBitPattern, "00") == 0); + ASSERT(strcmp(Marking::kBlackBitPattern, "10") == 0); + ASSERT(strcmp(Marking::kGreyBitPattern, "11") == 0); + + // Check for the color. + if (first_bit == 0) { + // Checking for white. + ASSERT(second_bit == 0); + // We only need to test the first bit. + Tbz(bitmap_scratch, 0, has_color); + } else { + Label other_color; + // Checking for grey or black. + Tbz(bitmap_scratch, 0, &other_color); + if (second_bit == 0) { + Tbz(bitmap_scratch, 1, has_color); + } else { + Tbnz(bitmap_scratch, 1, has_color); + } + Bind(&other_color); + } + + // Fall through if it does not have the right color. +} + + +void MacroAssembler::CheckMapDeprecated(Handle<Map> map, + Register scratch, + Label* if_deprecated) { + if (map->CanBeDeprecated()) { + Mov(scratch, Operand(map)); + Ldrsw(scratch, FieldMemOperand(scratch, Map::kBitField3Offset)); + TestAndBranchIfAnySet(scratch, Map::Deprecated::kMask, if_deprecated); + } +} + + +void MacroAssembler::JumpIfBlack(Register object, + Register scratch0, + Register scratch1, + Label* on_black) { + ASSERT(strcmp(Marking::kBlackBitPattern, "10") == 0); + HasColor(object, scratch0, scratch1, on_black, 1, 0); // kBlackBitPattern. +} + + +void MacroAssembler::JumpIfDictionaryInPrototypeChain( + Register object, + Register scratch0, + Register scratch1, + Label* found) { + ASSERT(!AreAliased(object, scratch0, scratch1)); + Factory* factory = isolate()->factory(); + Register current = scratch0; + Label loop_again; + + // Scratch contains elements pointer. + Mov(current, object); + + // Loop based on the map going up the prototype chain. + Bind(&loop_again); + Ldr(current, FieldMemOperand(current, HeapObject::kMapOffset)); + Ldrb(scratch1, FieldMemOperand(current, Map::kBitField2Offset)); + DecodeField<Map::ElementsKindBits>(scratch1); + CompareAndBranch(scratch1, DICTIONARY_ELEMENTS, eq, found); + Ldr(current, FieldMemOperand(current, Map::kPrototypeOffset)); + CompareAndBranch(current, Operand(factory->null_value()), ne, &loop_again); +} + + +void MacroAssembler::GetRelocatedValueLocation(Register ldr_location, + Register result) { + ASSERT(!result.Is(ldr_location)); + const uint32_t kLdrLitOffset_lsb = 5; + const uint32_t kLdrLitOffset_width = 19; + Ldr(result, MemOperand(ldr_location)); + if (emit_debug_code()) { + And(result, result, LoadLiteralFMask); + Cmp(result, LoadLiteralFixed); + Check(eq, kTheInstructionToPatchShouldBeAnLdrLiteral); + // The instruction was clobbered. Reload it. + Ldr(result, MemOperand(ldr_location)); + } + Sbfx(result, result, kLdrLitOffset_lsb, kLdrLitOffset_width); + Add(result, ldr_location, Operand(result, LSL, kWordSizeInBytesLog2)); +} + + +void MacroAssembler::EnsureNotWhite( + Register value, + Register bitmap_scratch, + Register shift_scratch, + Register load_scratch, + Register length_scratch, + Label* value_is_white_and_not_data) { + ASSERT(!AreAliased( + value, bitmap_scratch, shift_scratch, load_scratch, length_scratch)); + + // These bit sequences are backwards. The first character in the string + // represents the least significant bit. + ASSERT(strcmp(Marking::kWhiteBitPattern, "00") == 0); + ASSERT(strcmp(Marking::kBlackBitPattern, "10") == 0); + ASSERT(strcmp(Marking::kGreyBitPattern, "11") == 0); + + GetMarkBits(value, bitmap_scratch, shift_scratch); + Ldr(load_scratch, MemOperand(bitmap_scratch, MemoryChunk::kHeaderSize)); + Lsr(load_scratch, load_scratch, shift_scratch); + + AssertHasValidColor(load_scratch); + + // If the value is black or grey we don't need to do anything. + // Since both black and grey have a 1 in the first position and white does + // not have a 1 there we only need to check one bit. + Label done; + Tbnz(load_scratch, 0, &done); + + // Value is white. We check whether it is data that doesn't need scanning. + Register map = load_scratch; // Holds map while checking type. + Label is_data_object; + + // Check for heap-number. + Ldr(map, FieldMemOperand(value, HeapObject::kMapOffset)); + Mov(length_scratch, HeapNumber::kSize); + JumpIfRoot(map, Heap::kHeapNumberMapRootIndex, &is_data_object); + + // Check for strings. + ASSERT(kIsIndirectStringTag == 1 && kIsIndirectStringMask == 1); + ASSERT(kNotStringTag == 0x80 && kIsNotStringMask == 0x80); + // If it's a string and it's not a cons string then it's an object containing + // no GC pointers. + Register instance_type = load_scratch; + Ldrb(instance_type, FieldMemOperand(map, Map::kInstanceTypeOffset)); + TestAndBranchIfAnySet(instance_type, + kIsIndirectStringMask | kIsNotStringMask, + value_is_white_and_not_data); + + // It's a non-indirect (non-cons and non-slice) string. + // If it's external, the length is just ExternalString::kSize. + // Otherwise it's String::kHeaderSize + string->length() * (1 or 2). + // External strings are the only ones with the kExternalStringTag bit + // set. + ASSERT_EQ(0, kSeqStringTag & kExternalStringTag); + ASSERT_EQ(0, kConsStringTag & kExternalStringTag); + Mov(length_scratch, ExternalString::kSize); + TestAndBranchIfAnySet(instance_type, kExternalStringTag, &is_data_object); + + // Sequential string, either ASCII or UC16. + // For ASCII (char-size of 1) we shift the smi tag away to get the length. + // For UC16 (char-size of 2) we just leave the smi tag in place, thereby + // getting the length multiplied by 2. + ASSERT(kOneByteStringTag == 4 && kStringEncodingMask == 4); + Ldrsw(length_scratch, UntagSmiFieldMemOperand(value, + String::kLengthOffset)); + Tst(instance_type, kStringEncodingMask); + Cset(load_scratch, eq); + Lsl(length_scratch, length_scratch, load_scratch); + Add(length_scratch, + length_scratch, + SeqString::kHeaderSize + kObjectAlignmentMask); + Bic(length_scratch, length_scratch, kObjectAlignmentMask); + + Bind(&is_data_object); + // Value is a data object, and it is white. Mark it black. Since we know + // that the object is white we can make it black by flipping one bit. + Register mask = shift_scratch; + Mov(load_scratch, 1); + Lsl(mask, load_scratch, shift_scratch); + + Ldr(load_scratch, MemOperand(bitmap_scratch, MemoryChunk::kHeaderSize)); + Orr(load_scratch, load_scratch, mask); + Str(load_scratch, MemOperand(bitmap_scratch, MemoryChunk::kHeaderSize)); + + Bic(bitmap_scratch, bitmap_scratch, Page::kPageAlignmentMask); + Ldr(load_scratch, MemOperand(bitmap_scratch, MemoryChunk::kLiveBytesOffset)); + Add(load_scratch, load_scratch, length_scratch); + Str(load_scratch, MemOperand(bitmap_scratch, MemoryChunk::kLiveBytesOffset)); + + Bind(&done); +} + + +void MacroAssembler::Assert(Condition cond, BailoutReason reason) { + if (emit_debug_code()) { + Check(cond, reason); + } +} + + + +void MacroAssembler::AssertRegisterIsClear(Register reg, BailoutReason reason) { + if (emit_debug_code()) { + CheckRegisterIsClear(reg, reason); + } +} + + +void MacroAssembler::AssertRegisterIsRoot(Register reg, + Heap::RootListIndex index, + BailoutReason reason) { + if (emit_debug_code()) { + CompareRoot(reg, index); + Check(eq, reason); + } +} + + +void MacroAssembler::AssertFastElements(Register elements) { + if (emit_debug_code()) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + Label ok; + Ldr(temp, FieldMemOperand(elements, HeapObject::kMapOffset)); + JumpIfRoot(temp, Heap::kFixedArrayMapRootIndex, &ok); + JumpIfRoot(temp, Heap::kFixedDoubleArrayMapRootIndex, &ok); + JumpIfRoot(temp, Heap::kFixedCOWArrayMapRootIndex, &ok); + Abort(kJSObjectWithFastElementsMapHasSlowElements); + Bind(&ok); + } +} + + +void MacroAssembler::AssertIsString(const Register& object) { + if (emit_debug_code()) { + UseScratchRegisterScope temps(this); + Register temp = temps.AcquireX(); + STATIC_ASSERT(kSmiTag == 0); + Tst(object, kSmiTagMask); + Check(ne, kOperandIsNotAString); + Ldr(temp, FieldMemOperand(object, HeapObject::kMapOffset)); + CompareInstanceType(temp, temp, FIRST_NONSTRING_TYPE); + Check(lo, kOperandIsNotAString); + } +} + + +void MacroAssembler::Check(Condition cond, BailoutReason reason) { + Label ok; + B(cond, &ok); + Abort(reason); + // Will not return here. + Bind(&ok); +} + + +void MacroAssembler::CheckRegisterIsClear(Register reg, BailoutReason reason) { + Label ok; + Cbz(reg, &ok); + Abort(reason); + // Will not return here. + Bind(&ok); +} + + +void MacroAssembler::Abort(BailoutReason reason) { +#ifdef DEBUG + RecordComment("Abort message: "); + RecordComment(GetBailoutReason(reason)); + + if (FLAG_trap_on_abort) { + Brk(0); + return; + } +#endif + + // Abort is used in some contexts where csp is the stack pointer. In order to + // simplify the CallRuntime code, make sure that jssp is the stack pointer. + // There is no risk of register corruption here because Abort doesn't return. + Register old_stack_pointer = StackPointer(); + SetStackPointer(jssp); + Mov(jssp, old_stack_pointer); + + // We need some scratch registers for the MacroAssembler, so make sure we have + // some. This is safe here because Abort never returns. + RegList old_tmp_list = TmpList()->list(); + TmpList()->Combine(MacroAssembler::DefaultTmpList()); + + if (use_real_aborts()) { + // Avoid infinite recursion; Push contains some assertions that use Abort. + NoUseRealAbortsScope no_real_aborts(this); + + Mov(x0, Smi::FromInt(reason)); + Push(x0); + + if (!has_frame_) { + // We don't actually want to generate a pile of code for this, so just + // claim there is a stack frame, without generating one. + FrameScope scope(this, StackFrame::NONE); + CallRuntime(Runtime::kAbort, 1); + } else { + CallRuntime(Runtime::kAbort, 1); + } + } else { + // Load the string to pass to Printf. + Label msg_address; + Adr(x0, &msg_address); + + // Call Printf directly to report the error. + CallPrintf(); + + // We need a way to stop execution on both the simulator and real hardware, + // and Unreachable() is the best option. + Unreachable(); + + // Emit the message string directly in the instruction stream. + { + BlockPoolsScope scope(this); + Bind(&msg_address); + EmitStringData(GetBailoutReason(reason)); + } + } + + SetStackPointer(old_stack_pointer); + TmpList()->set_list(old_tmp_list); +} + + +void MacroAssembler::LoadTransitionedArrayMapConditional( + ElementsKind expected_kind, + ElementsKind transitioned_kind, + Register map_in_out, + Register scratch1, + Register scratch2, + Label* no_map_match) { + // Load the global or builtins object from the current context. + Ldr(scratch1, GlobalObjectMemOperand()); + Ldr(scratch1, FieldMemOperand(scratch1, GlobalObject::kNativeContextOffset)); + + // Check that the function's map is the same as the expected cached map. + Ldr(scratch1, ContextMemOperand(scratch1, Context::JS_ARRAY_MAPS_INDEX)); + size_t offset = (expected_kind * kPointerSize) + FixedArrayBase::kHeaderSize; + Ldr(scratch2, FieldMemOperand(scratch1, offset)); + Cmp(map_in_out, scratch2); + B(ne, no_map_match); + + // Use the transitioned cached map. + offset = (transitioned_kind * kPointerSize) + FixedArrayBase::kHeaderSize; + Ldr(map_in_out, FieldMemOperand(scratch1, offset)); +} + + +void MacroAssembler::LoadGlobalFunction(int index, Register function) { + // Load the global or builtins object from the current context. + Ldr(function, GlobalObjectMemOperand()); + // Load the native context from the global or builtins object. + Ldr(function, FieldMemOperand(function, + GlobalObject::kNativeContextOffset)); + // Load the function from the native context. + Ldr(function, ContextMemOperand(function, index)); +} + + +void MacroAssembler::LoadGlobalFunctionInitialMap(Register function, + Register map, + Register scratch) { + // Load the initial map. The global functions all have initial maps. + Ldr(map, FieldMemOperand(function, JSFunction::kPrototypeOrInitialMapOffset)); + if (emit_debug_code()) { + Label ok, fail; + CheckMap(map, scratch, Heap::kMetaMapRootIndex, &fail, DO_SMI_CHECK); + B(&ok); + Bind(&fail); + Abort(kGlobalFunctionsMustHaveInitialMap); + Bind(&ok); + } +} + + +// This is the main Printf implementation. All other Printf variants call +// PrintfNoPreserve after setting up one or more PreserveRegisterScopes. +void MacroAssembler::PrintfNoPreserve(const char * format, + const CPURegister& arg0, + const CPURegister& arg1, + const CPURegister& arg2, + const CPURegister& arg3) { + // We cannot handle a caller-saved stack pointer. It doesn't make much sense + // in most cases anyway, so this restriction shouldn't be too serious. + ASSERT(!kCallerSaved.IncludesAliasOf(__ StackPointer())); + + // The provided arguments, and their proper procedure-call standard registers. + CPURegister args[kPrintfMaxArgCount] = {arg0, arg1, arg2, arg3}; + CPURegister pcs[kPrintfMaxArgCount] = {NoReg, NoReg, NoReg, NoReg}; + + int arg_count = kPrintfMaxArgCount; + + // The PCS varargs registers for printf. Note that x0 is used for the printf + // format string. + static const CPURegList kPCSVarargs = + CPURegList(CPURegister::kRegister, kXRegSizeInBits, 1, arg_count); + static const CPURegList kPCSVarargsFP = + CPURegList(CPURegister::kFPRegister, kDRegSizeInBits, 0, arg_count - 1); + + // We can use caller-saved registers as scratch values, except for the + // arguments and the PCS registers where they might need to go. + CPURegList tmp_list = kCallerSaved; + tmp_list.Remove(x0); // Used to pass the format string. + tmp_list.Remove(kPCSVarargs); + tmp_list.Remove(arg0, arg1, arg2, arg3); + + CPURegList fp_tmp_list = kCallerSavedFP; + fp_tmp_list.Remove(kPCSVarargsFP); + fp_tmp_list.Remove(arg0, arg1, arg2, arg3); + + // Override the MacroAssembler's scratch register list. The lists will be + // reset automatically at the end of the UseScratchRegisterScope. + UseScratchRegisterScope temps(this); + TmpList()->set_list(tmp_list.list()); + FPTmpList()->set_list(fp_tmp_list.list()); + + // Copies of the printf vararg registers that we can pop from. + CPURegList pcs_varargs = kPCSVarargs; + CPURegList pcs_varargs_fp = kPCSVarargsFP; + + // Place the arguments. There are lots of clever tricks and optimizations we + // could use here, but Printf is a debug tool so instead we just try to keep + // it simple: Move each input that isn't already in the right place to a + // scratch register, then move everything back. + for (unsigned i = 0; i < kPrintfMaxArgCount; i++) { + // Work out the proper PCS register for this argument. + if (args[i].IsRegister()) { + pcs[i] = pcs_varargs.PopLowestIndex().X(); + // We might only need a W register here. We need to know the size of the + // argument so we can properly encode it for the simulator call. + if (args[i].Is32Bits()) pcs[i] = pcs[i].W(); + } else if (args[i].IsFPRegister()) { + // In C, floats are always cast to doubles for varargs calls. + pcs[i] = pcs_varargs_fp.PopLowestIndex().D(); + } else { + ASSERT(args[i].IsNone()); + arg_count = i; + break; + } + + // If the argument is already in the right place, leave it where it is. + if (args[i].Aliases(pcs[i])) continue; + + // Otherwise, if the argument is in a PCS argument register, allocate an + // appropriate scratch register and then move it out of the way. + if (kPCSVarargs.IncludesAliasOf(args[i]) || + kPCSVarargsFP.IncludesAliasOf(args[i])) { + if (args[i].IsRegister()) { + Register old_arg = Register(args[i]); + Register new_arg = temps.AcquireSameSizeAs(old_arg); + Mov(new_arg, old_arg); + args[i] = new_arg; + } else { + FPRegister old_arg = FPRegister(args[i]); + FPRegister new_arg = temps.AcquireSameSizeAs(old_arg); + Fmov(new_arg, old_arg); + args[i] = new_arg; + } + } + } + + // Do a second pass to move values into their final positions and perform any + // conversions that may be required. + for (int i = 0; i < arg_count; i++) { + ASSERT(pcs[i].type() == args[i].type()); + if (pcs[i].IsRegister()) { + Mov(Register(pcs[i]), Register(args[i]), kDiscardForSameWReg); + } else { + ASSERT(pcs[i].IsFPRegister()); + if (pcs[i].SizeInBytes() == args[i].SizeInBytes()) { + Fmov(FPRegister(pcs[i]), FPRegister(args[i])); + } else { + Fcvt(FPRegister(pcs[i]), FPRegister(args[i])); + } + } + } + + // Load the format string into x0, as per the procedure-call standard. + // + // To make the code as portable as possible, the format string is encoded + // directly in the instruction stream. It might be cleaner to encode it in a + // literal pool, but since Printf is usually used for debugging, it is + // beneficial for it to be minimally dependent on other features. + Label format_address; + Adr(x0, &format_address); + + // Emit the format string directly in the instruction stream. + { BlockPoolsScope scope(this); + Label after_data; + B(&after_data); + Bind(&format_address); + EmitStringData(format); + Unreachable(); + Bind(&after_data); + } + + // We don't pass any arguments on the stack, but we still need to align the C + // stack pointer to a 16-byte boundary for PCS compliance. + if (!csp.Is(StackPointer())) { + Bic(csp, StackPointer(), 0xf); + } + + CallPrintf(arg_count, pcs); +} + + +void MacroAssembler::CallPrintf(int arg_count, const CPURegister * args) { + // A call to printf needs special handling for the simulator, since the system + // printf function will use a different instruction set and the procedure-call + // standard will not be compatible. +#ifdef USE_SIMULATOR + { InstructionAccurateScope scope(this, kPrintfLength / kInstructionSize); + hlt(kImmExceptionIsPrintf); + dc32(arg_count); // kPrintfArgCountOffset + + // Determine the argument pattern. + uint32_t arg_pattern_list = 0; + for (int i = 0; i < arg_count; i++) { + uint32_t arg_pattern; + if (args[i].IsRegister()) { + arg_pattern = args[i].Is32Bits() ? kPrintfArgW : kPrintfArgX; + } else { + ASSERT(args[i].Is64Bits()); + arg_pattern = kPrintfArgD; + } + ASSERT(arg_pattern < (1 << kPrintfArgPatternBits)); + arg_pattern_list |= (arg_pattern << (kPrintfArgPatternBits * i)); + } + dc32(arg_pattern_list); // kPrintfArgPatternListOffset + } +#else + Call(FUNCTION_ADDR(printf), RelocInfo::EXTERNAL_REFERENCE); +#endif +} + + +void MacroAssembler::Printf(const char * format, + CPURegister arg0, + CPURegister arg1, + CPURegister arg2, + CPURegister arg3) { + // We can only print sp if it is the current stack pointer. + if (!csp.Is(StackPointer())) { + ASSERT(!csp.Aliases(arg0)); + ASSERT(!csp.Aliases(arg1)); + ASSERT(!csp.Aliases(arg2)); + ASSERT(!csp.Aliases(arg3)); + } + + // Printf is expected to preserve all registers, so make sure that none are + // available as scratch registers until we've preserved them. + RegList old_tmp_list = TmpList()->list(); + RegList old_fp_tmp_list = FPTmpList()->list(); + TmpList()->set_list(0); + FPTmpList()->set_list(0); + + // Preserve all caller-saved registers as well as NZCV. + // If csp is the stack pointer, PushCPURegList asserts that the size of each + // list is a multiple of 16 bytes. + PushCPURegList(kCallerSaved); + PushCPURegList(kCallerSavedFP); + + // We can use caller-saved registers as scratch values (except for argN). + CPURegList tmp_list = kCallerSaved; + CPURegList fp_tmp_list = kCallerSavedFP; + tmp_list.Remove(arg0, arg1, arg2, arg3); + fp_tmp_list.Remove(arg0, arg1, arg2, arg3); + TmpList()->set_list(tmp_list.list()); + FPTmpList()->set_list(fp_tmp_list.list()); + + { UseScratchRegisterScope temps(this); + // If any of the arguments are the current stack pointer, allocate a new + // register for them, and adjust the value to compensate for pushing the + // caller-saved registers. + bool arg0_sp = StackPointer().Aliases(arg0); + bool arg1_sp = StackPointer().Aliases(arg1); + bool arg2_sp = StackPointer().Aliases(arg2); + bool arg3_sp = StackPointer().Aliases(arg3); + if (arg0_sp || arg1_sp || arg2_sp || arg3_sp) { + // Allocate a register to hold the original stack pointer value, to pass + // to PrintfNoPreserve as an argument. + Register arg_sp = temps.AcquireX(); + Add(arg_sp, StackPointer(), + kCallerSaved.TotalSizeInBytes() + kCallerSavedFP.TotalSizeInBytes()); + if (arg0_sp) arg0 = Register::Create(arg_sp.code(), arg0.SizeInBits()); + if (arg1_sp) arg1 = Register::Create(arg_sp.code(), arg1.SizeInBits()); + if (arg2_sp) arg2 = Register::Create(arg_sp.code(), arg2.SizeInBits()); + if (arg3_sp) arg3 = Register::Create(arg_sp.code(), arg3.SizeInBits()); + } + + // Preserve NZCV. + { UseScratchRegisterScope temps(this); + Register tmp = temps.AcquireX(); + Mrs(tmp, NZCV); + Push(tmp, xzr); + } + + PrintfNoPreserve(format, arg0, arg1, arg2, arg3); + + // Restore NZCV. + { UseScratchRegisterScope temps(this); + Register tmp = temps.AcquireX(); + Pop(xzr, tmp); + Msr(NZCV, tmp); + } + } + + PopCPURegList(kCallerSavedFP); + PopCPURegList(kCallerSaved); + + TmpList()->set_list(old_tmp_list); + FPTmpList()->set_list(old_fp_tmp_list); +} + + +void MacroAssembler::EmitFrameSetupForCodeAgePatching() { + // TODO(jbramley): Other architectures use the internal memcpy to copy the + // sequence. If this is a performance bottleneck, we should consider caching + // the sequence and copying it in the same way. + InstructionAccurateScope scope(this, + kNoCodeAgeSequenceLength / kInstructionSize); + ASSERT(jssp.Is(StackPointer())); + EmitFrameSetupForCodeAgePatching(this); +} + + + +void MacroAssembler::EmitCodeAgeSequence(Code* stub) { + InstructionAccurateScope scope(this, + kNoCodeAgeSequenceLength / kInstructionSize); + ASSERT(jssp.Is(StackPointer())); + EmitCodeAgeSequence(this, stub); +} + + +#undef __ +#define __ assm-> + + +void MacroAssembler::EmitFrameSetupForCodeAgePatching(Assembler * assm) { + Label start; + __ bind(&start); + + // We can do this sequence using four instructions, but the code ageing + // sequence that patches it needs five, so we use the extra space to try to + // simplify some addressing modes and remove some dependencies (compared to + // using two stp instructions with write-back). + __ sub(jssp, jssp, 4 * kXRegSize); + __ sub(csp, csp, 4 * kXRegSize); + __ stp(x1, cp, MemOperand(jssp, 0 * kXRegSize)); + __ stp(fp, lr, MemOperand(jssp, 2 * kXRegSize)); + __ add(fp, jssp, StandardFrameConstants::kFixedFrameSizeFromFp); + + __ AssertSizeOfCodeGeneratedSince(&start, kNoCodeAgeSequenceLength); +} + + +void MacroAssembler::EmitCodeAgeSequence(Assembler * assm, + Code * stub) { + Label start; + __ bind(&start); + // When the stub is called, the sequence is replaced with the young sequence + // (as in EmitFrameSetupForCodeAgePatching). After the code is replaced, the + // stub jumps to &start, stored in x0. The young sequence does not call the + // stub so there is no infinite loop here. + // + // A branch (br) is used rather than a call (blr) because this code replaces + // the frame setup code that would normally preserve lr. + __ ldr_pcrel(ip0, kCodeAgeStubEntryOffset >> kLoadLiteralScaleLog2); + __ adr(x0, &start); + __ br(ip0); + // IsCodeAgeSequence in codegen-arm64.cc assumes that the code generated up + // until now (kCodeAgeStubEntryOffset) is the same for all code age sequences. + __ AssertSizeOfCodeGeneratedSince(&start, kCodeAgeStubEntryOffset); + if (stub) { + __ dc64(reinterpret_cast<uint64_t>(stub->instruction_start())); + __ AssertSizeOfCodeGeneratedSince(&start, kNoCodeAgeSequenceLength); + } +} + + +bool MacroAssembler::IsYoungSequence(Isolate* isolate, byte* sequence) { + bool is_young = isolate->code_aging_helper()->IsYoung(sequence); + ASSERT(is_young || + isolate->code_aging_helper()->IsOld(sequence)); + return is_young; +} + + +void MacroAssembler::TruncatingDiv(Register result, + Register dividend, + int32_t divisor) { + ASSERT(!AreAliased(result, dividend)); + ASSERT(result.Is32Bits() && dividend.Is32Bits()); + MultiplierAndShift ms(divisor); + Mov(result, ms.multiplier()); + Smull(result.X(), dividend, result); + Asr(result.X(), result.X(), 32); + if (divisor > 0 && ms.multiplier() < 0) Add(result, result, dividend); + if (divisor < 0 && ms.multiplier() > 0) Sub(result, result, dividend); + if (ms.shift() > 0) Asr(result, result, ms.shift()); + Add(result, result, Operand(dividend, LSR, 31)); +} + + +#undef __ + + +UseScratchRegisterScope::~UseScratchRegisterScope() { + available_->set_list(old_available_); + availablefp_->set_list(old_availablefp_); +} + + +Register UseScratchRegisterScope::AcquireSameSizeAs(const Register& reg) { + int code = AcquireNextAvailable(available_).code(); + return Register::Create(code, reg.SizeInBits()); +} + + +FPRegister UseScratchRegisterScope::AcquireSameSizeAs(const FPRegister& reg) { + int code = AcquireNextAvailable(availablefp_).code(); + return FPRegister::Create(code, reg.SizeInBits()); +} + + +CPURegister UseScratchRegisterScope::AcquireNextAvailable( + CPURegList* available) { + CHECK(!available->IsEmpty()); + CPURegister result = available->PopLowestIndex(); + ASSERT(!AreAliased(result, xzr, csp)); + return result; +} + + +CPURegister UseScratchRegisterScope::UnsafeAcquire(CPURegList* available, + const CPURegister& reg) { + ASSERT(available->IncludesAliasOf(reg)); + available->Remove(reg); + return reg; +} + + +#define __ masm-> + + +void InlineSmiCheckInfo::Emit(MacroAssembler* masm, const Register& reg, + const Label* smi_check) { + Assembler::BlockPoolsScope scope(masm); + if (reg.IsValid()) { + ASSERT(smi_check->is_bound()); + ASSERT(reg.Is64Bits()); + + // Encode the register (x0-x30) in the lowest 5 bits, then the offset to + // 'check' in the other bits. The possible offset is limited in that we + // use BitField to pack the data, and the underlying data type is a + // uint32_t. + uint32_t delta = __ InstructionsGeneratedSince(smi_check); + __ InlineData(RegisterBits::encode(reg.code()) | DeltaBits::encode(delta)); + } else { + ASSERT(!smi_check->is_bound()); + + // An offset of 0 indicates that there is no patch site. + __ InlineData(0); + } +} + + +InlineSmiCheckInfo::InlineSmiCheckInfo(Address info) + : reg_(NoReg), smi_check_(NULL) { + InstructionSequence* inline_data = InstructionSequence::At(info); + ASSERT(inline_data->IsInlineData()); + if (inline_data->IsInlineData()) { + uint64_t payload = inline_data->InlineData(); + // We use BitField to decode the payload, and BitField can only handle + // 32-bit values. + ASSERT(is_uint32(payload)); + if (payload != 0) { + int reg_code = RegisterBits::decode(payload); + reg_ = Register::XRegFromCode(reg_code); + uint64_t smi_check_delta = DeltaBits::decode(payload); + ASSERT(smi_check_delta != 0); + smi_check_ = inline_data->preceding(smi_check_delta); + } + } +} + + +#undef __ + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/macro-assembler-arm64.h b/chromium/v8/src/arm64/macro-assembler-arm64.h new file mode 100644 index 00000000000..34182c02707 --- /dev/null +++ b/chromium/v8/src/arm64/macro-assembler-arm64.h @@ -0,0 +1,2327 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_MACRO_ASSEMBLER_ARM64_H_ +#define V8_ARM64_MACRO_ASSEMBLER_ARM64_H_ + +#include <vector> + +#include "src/globals.h" + +#include "src/arm64/assembler-arm64-inl.h" + +namespace v8 { +namespace internal { + +#define LS_MACRO_LIST(V) \ + V(Ldrb, Register&, rt, LDRB_w) \ + V(Strb, Register&, rt, STRB_w) \ + V(Ldrsb, Register&, rt, rt.Is64Bits() ? LDRSB_x : LDRSB_w) \ + V(Ldrh, Register&, rt, LDRH_w) \ + V(Strh, Register&, rt, STRH_w) \ + V(Ldrsh, Register&, rt, rt.Is64Bits() ? LDRSH_x : LDRSH_w) \ + V(Ldr, CPURegister&, rt, LoadOpFor(rt)) \ + V(Str, CPURegister&, rt, StoreOpFor(rt)) \ + V(Ldrsw, Register&, rt, LDRSW_x) + + +// ---------------------------------------------------------------------------- +// Static helper functions + +// Generate a MemOperand for loading a field from an object. +inline MemOperand FieldMemOperand(Register object, int offset); +inline MemOperand UntagSmiFieldMemOperand(Register object, int offset); + +// Generate a MemOperand for loading a SMI from memory. +inline MemOperand UntagSmiMemOperand(Register object, int offset); + + +// ---------------------------------------------------------------------------- +// MacroAssembler + +enum BranchType { + // Copies of architectural conditions. + // The associated conditions can be used in place of those, the code will + // take care of reinterpreting them with the correct type. + integer_eq = eq, + integer_ne = ne, + integer_hs = hs, + integer_lo = lo, + integer_mi = mi, + integer_pl = pl, + integer_vs = vs, + integer_vc = vc, + integer_hi = hi, + integer_ls = ls, + integer_ge = ge, + integer_lt = lt, + integer_gt = gt, + integer_le = le, + integer_al = al, + integer_nv = nv, + + // These two are *different* from the architectural codes al and nv. + // 'always' is used to generate unconditional branches. + // 'never' is used to not generate a branch (generally as the inverse + // branch type of 'always). + always, never, + // cbz and cbnz + reg_zero, reg_not_zero, + // tbz and tbnz + reg_bit_clear, reg_bit_set, + + // Aliases. + kBranchTypeFirstCondition = eq, + kBranchTypeLastCondition = nv, + kBranchTypeFirstUsingReg = reg_zero, + kBranchTypeFirstUsingBit = reg_bit_clear +}; + +inline BranchType InvertBranchType(BranchType type) { + if (kBranchTypeFirstCondition <= type && type <= kBranchTypeLastCondition) { + return static_cast<BranchType>( + NegateCondition(static_cast<Condition>(type))); + } else { + return static_cast<BranchType>(type ^ 1); + } +} + +enum RememberedSetAction { EMIT_REMEMBERED_SET, OMIT_REMEMBERED_SET }; +enum SmiCheck { INLINE_SMI_CHECK, OMIT_SMI_CHECK }; +enum PointersToHereCheck { + kPointersToHereMaybeInteresting, + kPointersToHereAreAlwaysInteresting +}; +enum LinkRegisterStatus { kLRHasNotBeenSaved, kLRHasBeenSaved }; +enum TargetAddressStorageMode { + CAN_INLINE_TARGET_ADDRESS, + NEVER_INLINE_TARGET_ADDRESS +}; +enum UntagMode { kNotSpeculativeUntag, kSpeculativeUntag }; +enum ArrayHasHoles { kArrayCantHaveHoles, kArrayCanHaveHoles }; +enum CopyHint { kCopyUnknown, kCopyShort, kCopyLong }; +enum DiscardMoveMode { kDontDiscardForSameWReg, kDiscardForSameWReg }; +enum SeqStringSetCharCheckIndexType { kIndexIsSmi, kIndexIsInteger32 }; + +class MacroAssembler : public Assembler { + public: + MacroAssembler(Isolate* isolate, byte * buffer, unsigned buffer_size); + + inline Handle<Object> CodeObject(); + + // Instruction set functions ------------------------------------------------ + // Logical macros. + inline void And(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Ands(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Bic(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Bics(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Orr(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Orn(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Eor(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Eon(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Tst(const Register& rn, const Operand& operand); + void LogicalMacro(const Register& rd, + const Register& rn, + const Operand& operand, + LogicalOp op); + + // Add and sub macros. + inline void Add(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Adds(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Sub(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Subs(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Cmn(const Register& rn, const Operand& operand); + inline void Cmp(const Register& rn, const Operand& operand); + inline void Neg(const Register& rd, + const Operand& operand); + inline void Negs(const Register& rd, + const Operand& operand); + + void AddSubMacro(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubOp op); + + // Add/sub with carry macros. + inline void Adc(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Adcs(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Sbc(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Sbcs(const Register& rd, + const Register& rn, + const Operand& operand); + inline void Ngc(const Register& rd, + const Operand& operand); + inline void Ngcs(const Register& rd, + const Operand& operand); + void AddSubWithCarryMacro(const Register& rd, + const Register& rn, + const Operand& operand, + FlagsUpdate S, + AddSubWithCarryOp op); + + // Move macros. + void Mov(const Register& rd, + const Operand& operand, + DiscardMoveMode discard_mode = kDontDiscardForSameWReg); + void Mov(const Register& rd, uint64_t imm); + inline void Mvn(const Register& rd, uint64_t imm); + void Mvn(const Register& rd, const Operand& operand); + static bool IsImmMovn(uint64_t imm, unsigned reg_size); + static bool IsImmMovz(uint64_t imm, unsigned reg_size); + static unsigned CountClearHalfWords(uint64_t imm, unsigned reg_size); + + // Conditional macros. + inline void Ccmp(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond); + inline void Ccmn(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond); + void ConditionalCompareMacro(const Register& rn, + const Operand& operand, + StatusFlags nzcv, + Condition cond, + ConditionalCompareOp op); + void Csel(const Register& rd, + const Register& rn, + const Operand& operand, + Condition cond); + + // Load/store macros. +#define DECLARE_FUNCTION(FN, REGTYPE, REG, OP) \ + inline void FN(const REGTYPE REG, const MemOperand& addr); + LS_MACRO_LIST(DECLARE_FUNCTION) +#undef DECLARE_FUNCTION + + void LoadStoreMacro(const CPURegister& rt, + const MemOperand& addr, + LoadStoreOp op); + + // V8-specific load/store helpers. + void Load(const Register& rt, const MemOperand& addr, Representation r); + void Store(const Register& rt, const MemOperand& addr, Representation r); + + enum AdrHint { + // The target must be within the immediate range of adr. + kAdrNear, + // The target may be outside of the immediate range of adr. Additional + // instructions may be emitted. + kAdrFar + }; + void Adr(const Register& rd, Label* label, AdrHint = kAdrNear); + + // Remaining instructions are simple pass-through calls to the assembler. + inline void Asr(const Register& rd, const Register& rn, unsigned shift); + inline void Asr(const Register& rd, const Register& rn, const Register& rm); + + // Branch type inversion relies on these relations. + STATIC_ASSERT((reg_zero == (reg_not_zero ^ 1)) && + (reg_bit_clear == (reg_bit_set ^ 1)) && + (always == (never ^ 1))); + + void B(Label* label, BranchType type, Register reg = NoReg, int bit = -1); + + inline void B(Label* label); + inline void B(Condition cond, Label* label); + void B(Label* label, Condition cond); + inline void Bfi(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width); + inline void Bfxil(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width); + inline void Bind(Label* label); + inline void Bl(Label* label); + inline void Blr(const Register& xn); + inline void Br(const Register& xn); + inline void Brk(int code); + void Cbnz(const Register& rt, Label* label); + void Cbz(const Register& rt, Label* label); + inline void Cinc(const Register& rd, const Register& rn, Condition cond); + inline void Cinv(const Register& rd, const Register& rn, Condition cond); + inline void Cls(const Register& rd, const Register& rn); + inline void Clz(const Register& rd, const Register& rn); + inline void Cneg(const Register& rd, const Register& rn, Condition cond); + inline void CzeroX(const Register& rd, Condition cond); + inline void CmovX(const Register& rd, const Register& rn, Condition cond); + inline void Cset(const Register& rd, Condition cond); + inline void Csetm(const Register& rd, Condition cond); + inline void Csinc(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond); + inline void Csinv(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond); + inline void Csneg(const Register& rd, + const Register& rn, + const Register& rm, + Condition cond); + inline void Dmb(BarrierDomain domain, BarrierType type); + inline void Dsb(BarrierDomain domain, BarrierType type); + inline void Debug(const char* message, uint32_t code, Instr params = BREAK); + inline void Extr(const Register& rd, + const Register& rn, + const Register& rm, + unsigned lsb); + inline void Fabs(const FPRegister& fd, const FPRegister& fn); + inline void Fadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Fccmp(const FPRegister& fn, + const FPRegister& fm, + StatusFlags nzcv, + Condition cond); + inline void Fcmp(const FPRegister& fn, const FPRegister& fm); + inline void Fcmp(const FPRegister& fn, double value); + inline void Fcsel(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + Condition cond); + inline void Fcvt(const FPRegister& fd, const FPRegister& fn); + inline void Fcvtas(const Register& rd, const FPRegister& fn); + inline void Fcvtau(const Register& rd, const FPRegister& fn); + inline void Fcvtms(const Register& rd, const FPRegister& fn); + inline void Fcvtmu(const Register& rd, const FPRegister& fn); + inline void Fcvtns(const Register& rd, const FPRegister& fn); + inline void Fcvtnu(const Register& rd, const FPRegister& fn); + inline void Fcvtzs(const Register& rd, const FPRegister& fn); + inline void Fcvtzu(const Register& rd, const FPRegister& fn); + inline void Fdiv(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Fmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + inline void Fmax(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Fmaxnm(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Fmin(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Fminnm(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Fmov(FPRegister fd, FPRegister fn); + inline void Fmov(FPRegister fd, Register rn); + // Provide explicit double and float interfaces for FP immediate moves, rather + // than relying on implicit C++ casts. This allows signalling NaNs to be + // preserved when the immediate matches the format of fd. Most systems convert + // signalling NaNs to quiet NaNs when converting between float and double. + inline void Fmov(FPRegister fd, double imm); + inline void Fmov(FPRegister fd, float imm); + // Provide a template to allow other types to be converted automatically. + template<typename T> + void Fmov(FPRegister fd, T imm) { + ASSERT(allow_macro_instructions_); + Fmov(fd, static_cast<double>(imm)); + } + inline void Fmov(Register rd, FPRegister fn); + inline void Fmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + inline void Fmul(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Fneg(const FPRegister& fd, const FPRegister& fn); + inline void Fnmadd(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + inline void Fnmsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm, + const FPRegister& fa); + inline void Frinta(const FPRegister& fd, const FPRegister& fn); + inline void Frintm(const FPRegister& fd, const FPRegister& fn); + inline void Frintn(const FPRegister& fd, const FPRegister& fn); + inline void Frintz(const FPRegister& fd, const FPRegister& fn); + inline void Fsqrt(const FPRegister& fd, const FPRegister& fn); + inline void Fsub(const FPRegister& fd, + const FPRegister& fn, + const FPRegister& fm); + inline void Hint(SystemHint code); + inline void Hlt(int code); + inline void Isb(); + inline void Ldnp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& src); + inline void Ldp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& src); + inline void Ldpsw(const Register& rt, + const Register& rt2, + const MemOperand& src); + // Load a literal from the inline constant pool. + inline void Ldr(const CPURegister& rt, const Immediate& imm); + // Helper function for double immediate. + inline void Ldr(const CPURegister& rt, double imm); + inline void Lsl(const Register& rd, const Register& rn, unsigned shift); + inline void Lsl(const Register& rd, const Register& rn, const Register& rm); + inline void Lsr(const Register& rd, const Register& rn, unsigned shift); + inline void Lsr(const Register& rd, const Register& rn, const Register& rm); + inline void Madd(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + inline void Mneg(const Register& rd, const Register& rn, const Register& rm); + inline void Mov(const Register& rd, const Register& rm); + inline void Movk(const Register& rd, uint64_t imm, int shift = -1); + inline void Mrs(const Register& rt, SystemRegister sysreg); + inline void Msr(SystemRegister sysreg, const Register& rt); + inline void Msub(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + inline void Mul(const Register& rd, const Register& rn, const Register& rm); + inline void Nop() { nop(); } + inline void Rbit(const Register& rd, const Register& rn); + inline void Ret(const Register& xn = lr); + inline void Rev(const Register& rd, const Register& rn); + inline void Rev16(const Register& rd, const Register& rn); + inline void Rev32(const Register& rd, const Register& rn); + inline void Ror(const Register& rd, const Register& rs, unsigned shift); + inline void Ror(const Register& rd, const Register& rn, const Register& rm); + inline void Sbfiz(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width); + inline void Sbfx(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width); + inline void Scvtf(const FPRegister& fd, + const Register& rn, + unsigned fbits = 0); + inline void Sdiv(const Register& rd, const Register& rn, const Register& rm); + inline void Smaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + inline void Smsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + inline void Smull(const Register& rd, + const Register& rn, + const Register& rm); + inline void Smulh(const Register& rd, + const Register& rn, + const Register& rm); + inline void Stnp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& dst); + inline void Stp(const CPURegister& rt, + const CPURegister& rt2, + const MemOperand& dst); + inline void Sxtb(const Register& rd, const Register& rn); + inline void Sxth(const Register& rd, const Register& rn); + inline void Sxtw(const Register& rd, const Register& rn); + void Tbnz(const Register& rt, unsigned bit_pos, Label* label); + void Tbz(const Register& rt, unsigned bit_pos, Label* label); + inline void Ubfiz(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width); + inline void Ubfx(const Register& rd, + const Register& rn, + unsigned lsb, + unsigned width); + inline void Ucvtf(const FPRegister& fd, + const Register& rn, + unsigned fbits = 0); + inline void Udiv(const Register& rd, const Register& rn, const Register& rm); + inline void Umaddl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + inline void Umsubl(const Register& rd, + const Register& rn, + const Register& rm, + const Register& ra); + inline void Uxtb(const Register& rd, const Register& rn); + inline void Uxth(const Register& rd, const Register& rn); + inline void Uxtw(const Register& rd, const Register& rn); + + // Pseudo-instructions ------------------------------------------------------ + + // Compute rd = abs(rm). + // This function clobbers the condition flags. On output the overflow flag is + // set iff the negation overflowed. + // + // If rm is the minimum representable value, the result is not representable. + // Handlers for each case can be specified using the relevant labels. + void Abs(const Register& rd, const Register& rm, + Label * is_not_representable = NULL, + Label * is_representable = NULL); + + // Push or pop up to 4 registers of the same width to or from the stack, + // using the current stack pointer as set by SetStackPointer. + // + // If an argument register is 'NoReg', all further arguments are also assumed + // to be 'NoReg', and are thus not pushed or popped. + // + // Arguments are ordered such that "Push(a, b);" is functionally equivalent + // to "Push(a); Push(b);". + // + // It is valid to push the same register more than once, and there is no + // restriction on the order in which registers are specified. + // + // It is not valid to pop into the same register more than once in one + // operation, not even into the zero register. + // + // If the current stack pointer (as set by SetStackPointer) is csp, then it + // must be aligned to 16 bytes on entry and the total size of the specified + // registers must also be a multiple of 16 bytes. + // + // Even if the current stack pointer is not the system stack pointer (csp), + // Push (and derived methods) will still modify the system stack pointer in + // order to comply with ABI rules about accessing memory below the system + // stack pointer. + // + // Other than the registers passed into Pop, the stack pointer and (possibly) + // the system stack pointer, these methods do not modify any other registers. + void Push(const CPURegister& src0, const CPURegister& src1 = NoReg, + const CPURegister& src2 = NoReg, const CPURegister& src3 = NoReg); + void Push(const CPURegister& src0, const CPURegister& src1, + const CPURegister& src2, const CPURegister& src3, + const CPURegister& src4, const CPURegister& src5 = NoReg, + const CPURegister& src6 = NoReg, const CPURegister& src7 = NoReg); + void Pop(const CPURegister& dst0, const CPURegister& dst1 = NoReg, + const CPURegister& dst2 = NoReg, const CPURegister& dst3 = NoReg); + + // Alternative forms of Push and Pop, taking a RegList or CPURegList that + // specifies the registers that are to be pushed or popped. Higher-numbered + // registers are associated with higher memory addresses (as in the A32 push + // and pop instructions). + // + // (Push|Pop)SizeRegList allow you to specify the register size as a + // parameter. Only kXRegSizeInBits, kWRegSizeInBits, kDRegSizeInBits and + // kSRegSizeInBits are supported. + // + // Otherwise, (Push|Pop)(CPU|X|W|D|S)RegList is preferred. + void PushCPURegList(CPURegList registers); + void PopCPURegList(CPURegList registers); + + inline void PushSizeRegList(RegList registers, unsigned reg_size, + CPURegister::RegisterType type = CPURegister::kRegister) { + PushCPURegList(CPURegList(type, reg_size, registers)); + } + inline void PopSizeRegList(RegList registers, unsigned reg_size, + CPURegister::RegisterType type = CPURegister::kRegister) { + PopCPURegList(CPURegList(type, reg_size, registers)); + } + inline void PushXRegList(RegList regs) { + PushSizeRegList(regs, kXRegSizeInBits); + } + inline void PopXRegList(RegList regs) { + PopSizeRegList(regs, kXRegSizeInBits); + } + inline void PushWRegList(RegList regs) { + PushSizeRegList(regs, kWRegSizeInBits); + } + inline void PopWRegList(RegList regs) { + PopSizeRegList(regs, kWRegSizeInBits); + } + inline void PushDRegList(RegList regs) { + PushSizeRegList(regs, kDRegSizeInBits, CPURegister::kFPRegister); + } + inline void PopDRegList(RegList regs) { + PopSizeRegList(regs, kDRegSizeInBits, CPURegister::kFPRegister); + } + inline void PushSRegList(RegList regs) { + PushSizeRegList(regs, kSRegSizeInBits, CPURegister::kFPRegister); + } + inline void PopSRegList(RegList regs) { + PopSizeRegList(regs, kSRegSizeInBits, CPURegister::kFPRegister); + } + + // Push the specified register 'count' times. + void PushMultipleTimes(CPURegister src, Register count); + void PushMultipleTimes(CPURegister src, int count); + + // This is a convenience method for pushing a single Handle<Object>. + inline void Push(Handle<Object> handle); + void Push(Smi* smi) { Push(Handle<Smi>(smi, isolate())); } + + // Aliases of Push and Pop, required for V8 compatibility. + inline void push(Register src) { + Push(src); + } + inline void pop(Register dst) { + Pop(dst); + } + + // Sometimes callers need to push or pop multiple registers in a way that is + // difficult to structure efficiently for fixed Push or Pop calls. This scope + // allows push requests to be queued up, then flushed at once. The + // MacroAssembler will try to generate the most efficient sequence required. + // + // Unlike the other Push and Pop macros, PushPopQueue can handle mixed sets of + // register sizes and types. + class PushPopQueue { + public: + explicit PushPopQueue(MacroAssembler* masm) : masm_(masm), size_(0) { } + + ~PushPopQueue() { + ASSERT(queued_.empty()); + } + + void Queue(const CPURegister& rt) { + size_ += rt.SizeInBytes(); + queued_.push_back(rt); + } + + enum PreambleDirective { + WITH_PREAMBLE, + SKIP_PREAMBLE + }; + void PushQueued(PreambleDirective preamble_directive = WITH_PREAMBLE); + void PopQueued(); + + private: + MacroAssembler* masm_; + int size_; + std::vector<CPURegister> queued_; + }; + + // Poke 'src' onto the stack. The offset is in bytes. + // + // If the current stack pointer (according to StackPointer()) is csp, then + // csp must be aligned to 16 bytes. + void Poke(const CPURegister& src, const Operand& offset); + + // Peek at a value on the stack, and put it in 'dst'. The offset is in bytes. + // + // If the current stack pointer (according to StackPointer()) is csp, then + // csp must be aligned to 16 bytes. + void Peek(const CPURegister& dst, const Operand& offset); + + // Poke 'src1' and 'src2' onto the stack. The values written will be adjacent + // with 'src2' at a higher address than 'src1'. The offset is in bytes. + // + // If the current stack pointer (according to StackPointer()) is csp, then + // csp must be aligned to 16 bytes. + void PokePair(const CPURegister& src1, const CPURegister& src2, int offset); + + // Peek at two values on the stack, and put them in 'dst1' and 'dst2'. The + // values peeked will be adjacent, with the value in 'dst2' being from a + // higher address than 'dst1'. The offset is in bytes. + // + // If the current stack pointer (according to StackPointer()) is csp, then + // csp must be aligned to 16 bytes. + void PeekPair(const CPURegister& dst1, const CPURegister& dst2, int offset); + + // Claim or drop stack space without actually accessing memory. + // + // In debug mode, both of these will write invalid data into the claimed or + // dropped space. + // + // If the current stack pointer (according to StackPointer()) is csp, then it + // must be aligned to 16 bytes and the size claimed or dropped must be a + // multiple of 16 bytes. + // + // Note that unit_size must be specified in bytes. For variants which take a + // Register count, the unit size must be a power of two. + inline void Claim(uint64_t count, uint64_t unit_size = kXRegSize); + inline void Claim(const Register& count, + uint64_t unit_size = kXRegSize); + inline void Drop(uint64_t count, uint64_t unit_size = kXRegSize); + inline void Drop(const Register& count, + uint64_t unit_size = kXRegSize); + + // Variants of Claim and Drop, where the 'count' parameter is a SMI held in a + // register. + inline void ClaimBySMI(const Register& count_smi, + uint64_t unit_size = kXRegSize); + inline void DropBySMI(const Register& count_smi, + uint64_t unit_size = kXRegSize); + + // Compare a register with an operand, and branch to label depending on the + // condition. May corrupt the status flags. + inline void CompareAndBranch(const Register& lhs, + const Operand& rhs, + Condition cond, + Label* label); + + // Test the bits of register defined by bit_pattern, and branch if ANY of + // those bits are set. May corrupt the status flags. + inline void TestAndBranchIfAnySet(const Register& reg, + const uint64_t bit_pattern, + Label* label); + + // Test the bits of register defined by bit_pattern, and branch if ALL of + // those bits are clear (ie. not set.) May corrupt the status flags. + inline void TestAndBranchIfAllClear(const Register& reg, + const uint64_t bit_pattern, + Label* label); + + // Insert one or more instructions into the instruction stream that encode + // some caller-defined data. The instructions used will be executable with no + // side effects. + inline void InlineData(uint64_t data); + + // Insert an instrumentation enable marker into the instruction stream. + inline void EnableInstrumentation(); + + // Insert an instrumentation disable marker into the instruction stream. + inline void DisableInstrumentation(); + + // Insert an instrumentation event marker into the instruction stream. These + // will be picked up by the instrumentation system to annotate an instruction + // profile. The argument marker_name must be a printable two character string; + // it will be encoded in the event marker. + inline void AnnotateInstrumentation(const char* marker_name); + + // If emit_debug_code() is true, emit a run-time check to ensure that + // StackPointer() does not point below the system stack pointer. + // + // Whilst it is architecturally legal for StackPointer() to point below csp, + // it can be evidence of a potential bug because the ABI forbids accesses + // below csp. + // + // If StackPointer() is the system stack pointer (csp) or ALWAYS_ALIGN_CSP is + // enabled, then csp will be dereferenced to cause the processor + // (or simulator) to abort if it is not properly aligned. + // + // If emit_debug_code() is false, this emits no code. + void AssertStackConsistency(); + + // Preserve the callee-saved registers (as defined by AAPCS64). + // + // Higher-numbered registers are pushed before lower-numbered registers, and + // thus get higher addresses. + // Floating-point registers are pushed before general-purpose registers, and + // thus get higher addresses. + // + // Note that registers are not checked for invalid values. Use this method + // only if you know that the GC won't try to examine the values on the stack. + // + // This method must not be called unless the current stack pointer (as set by + // SetStackPointer) is the system stack pointer (csp), and is aligned to + // ActivationFrameAlignment(). + void PushCalleeSavedRegisters(); + + // Restore the callee-saved registers (as defined by AAPCS64). + // + // Higher-numbered registers are popped after lower-numbered registers, and + // thus come from higher addresses. + // Floating-point registers are popped after general-purpose registers, and + // thus come from higher addresses. + // + // This method must not be called unless the current stack pointer (as set by + // SetStackPointer) is the system stack pointer (csp), and is aligned to + // ActivationFrameAlignment(). + void PopCalleeSavedRegisters(); + + // Set the current stack pointer, but don't generate any code. + inline void SetStackPointer(const Register& stack_pointer) { + ASSERT(!TmpList()->IncludesAliasOf(stack_pointer)); + sp_ = stack_pointer; + } + + // Return the current stack pointer, as set by SetStackPointer. + inline const Register& StackPointer() const { + return sp_; + } + + // Align csp for a frame, as per ActivationFrameAlignment, and make it the + // current stack pointer. + inline void AlignAndSetCSPForFrame() { + int sp_alignment = ActivationFrameAlignment(); + // AAPCS64 mandates at least 16-byte alignment. + ASSERT(sp_alignment >= 16); + ASSERT(IsPowerOf2(sp_alignment)); + Bic(csp, StackPointer(), sp_alignment - 1); + SetStackPointer(csp); + } + + // Push the system stack pointer (csp) down to allow the same to be done to + // the current stack pointer (according to StackPointer()). This must be + // called _before_ accessing the memory. + // + // This is necessary when pushing or otherwise adding things to the stack, to + // satisfy the AAPCS64 constraint that the memory below the system stack + // pointer is not accessed. The amount pushed will be increased as necessary + // to ensure csp remains aligned to 16 bytes. + // + // This method asserts that StackPointer() is not csp, since the call does + // not make sense in that context. + inline void BumpSystemStackPointer(const Operand& space); + + // Re-synchronizes the system stack pointer (csp) with the current stack + // pointer (according to StackPointer()). This function will ensure the + // new value of the system stack pointer is remains aligned to 16 bytes, and + // is lower than or equal to the value of the current stack pointer. + // + // This method asserts that StackPointer() is not csp, since the call does + // not make sense in that context. + inline void SyncSystemStackPointer(); + + // Helpers ------------------------------------------------------------------ + // Root register. + inline void InitializeRootRegister(); + + void AssertFPCRState(Register fpcr = NoReg); + void ConfigureFPCR(); + void CanonicalizeNaN(const FPRegister& dst, const FPRegister& src); + void CanonicalizeNaN(const FPRegister& reg) { + CanonicalizeNaN(reg, reg); + } + + // Load an object from the root table. + void LoadRoot(CPURegister destination, + Heap::RootListIndex index); + // Store an object to the root table. + void StoreRoot(Register source, + Heap::RootListIndex index); + + // Load both TrueValue and FalseValue roots. + void LoadTrueFalseRoots(Register true_root, Register false_root); + + void LoadHeapObject(Register dst, Handle<HeapObject> object); + + void LoadObject(Register result, Handle<Object> object) { + AllowDeferredHandleDereference heap_object_check; + if (object->IsHeapObject()) { + LoadHeapObject(result, Handle<HeapObject>::cast(object)); + } else { + ASSERT(object->IsSmi()); + Mov(result, Operand(object)); + } + } + + static int SafepointRegisterStackIndex(int reg_code); + + // This is required for compatibility with architecture independant code. + // Remove if not needed. + inline void Move(Register dst, Register src) { Mov(dst, src); } + + void LoadInstanceDescriptors(Register map, + Register descriptors); + void EnumLengthUntagged(Register dst, Register map); + void EnumLengthSmi(Register dst, Register map); + void NumberOfOwnDescriptors(Register dst, Register map); + + template<typename Field> + void DecodeField(Register dst, Register src) { + static const uint64_t shift = Field::kShift; + static const uint64_t setbits = CountSetBits(Field::kMask, 32); + Ubfx(dst, src, shift, setbits); + } + + template<typename Field> + void DecodeField(Register reg) { + DecodeField<Field>(reg, reg); + } + + // ---- SMI and Number Utilities ---- + + inline void SmiTag(Register dst, Register src); + inline void SmiTag(Register smi); + inline void SmiUntag(Register dst, Register src); + inline void SmiUntag(Register smi); + inline void SmiUntagToDouble(FPRegister dst, + Register src, + UntagMode mode = kNotSpeculativeUntag); + inline void SmiUntagToFloat(FPRegister dst, + Register src, + UntagMode mode = kNotSpeculativeUntag); + + // Tag and push in one step. + inline void SmiTagAndPush(Register src); + inline void SmiTagAndPush(Register src1, Register src2); + + // Compute the absolute value of 'smi' and leave the result in 'smi' + // register. If 'smi' is the most negative SMI, the absolute value cannot + // be represented as a SMI and a jump to 'slow' is done. + void SmiAbs(const Register& smi, Label* slow); + + inline void JumpIfSmi(Register value, + Label* smi_label, + Label* not_smi_label = NULL); + inline void JumpIfNotSmi(Register value, Label* not_smi_label); + inline void JumpIfBothSmi(Register value1, + Register value2, + Label* both_smi_label, + Label* not_smi_label = NULL); + inline void JumpIfEitherSmi(Register value1, + Register value2, + Label* either_smi_label, + Label* not_smi_label = NULL); + inline void JumpIfEitherNotSmi(Register value1, + Register value2, + Label* not_smi_label); + inline void JumpIfBothNotSmi(Register value1, + Register value2, + Label* not_smi_label); + + // Abort execution if argument is a smi, enabled via --debug-code. + void AssertNotSmi(Register object, BailoutReason reason = kOperandIsASmi); + void AssertSmi(Register object, BailoutReason reason = kOperandIsNotASmi); + + inline void ObjectTag(Register tagged_obj, Register obj); + inline void ObjectUntag(Register untagged_obj, Register obj); + + // Abort execution if argument is not a name, enabled via --debug-code. + void AssertName(Register object); + + // Abort execution if argument is not undefined or an AllocationSite, enabled + // via --debug-code. + void AssertUndefinedOrAllocationSite(Register object, Register scratch); + + // Abort execution if argument is not a string, enabled via --debug-code. + void AssertString(Register object); + + void JumpForHeapNumber(Register object, + Register heap_number_map, + Label* on_heap_number, + Label* on_not_heap_number = NULL); + void JumpIfHeapNumber(Register object, + Label* on_heap_number, + Register heap_number_map = NoReg); + void JumpIfNotHeapNumber(Register object, + Label* on_not_heap_number, + Register heap_number_map = NoReg); + + // Sets the vs flag if the input is -0.0. + void TestForMinusZero(DoubleRegister input); + + // Jump to label if the input double register contains -0.0. + void JumpIfMinusZero(DoubleRegister input, Label* on_negative_zero); + + // Jump to label if the input integer register contains the double precision + // floating point representation of -0.0. + void JumpIfMinusZero(Register input, Label* on_negative_zero); + + // Generate code to do a lookup in the number string cache. If the number in + // the register object is found in the cache the generated code falls through + // with the result in the result register. The object and the result register + // can be the same. If the number is not found in the cache the code jumps to + // the label not_found with only the content of register object unchanged. + void LookupNumberStringCache(Register object, + Register result, + Register scratch1, + Register scratch2, + Register scratch3, + Label* not_found); + + // Saturate a signed 32-bit integer in input to an unsigned 8-bit integer in + // output. + void ClampInt32ToUint8(Register in_out); + void ClampInt32ToUint8(Register output, Register input); + + // Saturate a double in input to an unsigned 8-bit integer in output. + void ClampDoubleToUint8(Register output, + DoubleRegister input, + DoubleRegister dbl_scratch); + + // Try to represent a double as a signed 32-bit int. + // This succeeds if the result compares equal to the input, so inputs of -0.0 + // are represented as 0 and handled as a success. + // + // On output the Z flag is set if the operation was successful. + void TryRepresentDoubleAsInt32(Register as_int, + FPRegister value, + FPRegister scratch_d, + Label* on_successful_conversion = NULL, + Label* on_failed_conversion = NULL) { + ASSERT(as_int.Is32Bits()); + TryRepresentDoubleAsInt(as_int, value, scratch_d, on_successful_conversion, + on_failed_conversion); + } + + // Try to represent a double as a signed 64-bit int. + // This succeeds if the result compares equal to the input, so inputs of -0.0 + // are represented as 0 and handled as a success. + // + // On output the Z flag is set if the operation was successful. + void TryRepresentDoubleAsInt64(Register as_int, + FPRegister value, + FPRegister scratch_d, + Label* on_successful_conversion = NULL, + Label* on_failed_conversion = NULL) { + ASSERT(as_int.Is64Bits()); + TryRepresentDoubleAsInt(as_int, value, scratch_d, on_successful_conversion, + on_failed_conversion); + } + + // ---- Object Utilities ---- + + // Copy fields from 'src' to 'dst', where both are tagged objects. + // The 'temps' list is a list of X registers which can be used for scratch + // values. The temps list must include at least one register. + // + // Currently, CopyFields cannot make use of more than three registers from + // the 'temps' list. + // + // CopyFields expects to be able to take at least two registers from + // MacroAssembler::TmpList(). + void CopyFields(Register dst, Register src, CPURegList temps, unsigned count); + + // Starting at address in dst, initialize field_count 64-bit fields with + // 64-bit value in register filler. Register dst is corrupted. + void FillFields(Register dst, + Register field_count, + Register filler); + + // Copies a number of bytes from src to dst. All passed registers are + // clobbered. On exit src and dst will point to the place just after where the + // last byte was read or written and length will be zero. Hint may be used to + // determine which is the most efficient algorithm to use for copying. + void CopyBytes(Register dst, + Register src, + Register length, + Register scratch, + CopyHint hint = kCopyUnknown); + + // ---- String Utilities ---- + + + // Jump to label if either object is not a sequential ASCII string. + // Optionally perform a smi check on the objects first. + void JumpIfEitherIsNotSequentialAsciiStrings( + Register first, + Register second, + Register scratch1, + Register scratch2, + Label* failure, + SmiCheckType smi_check = DO_SMI_CHECK); + + // Check if instance type is sequential ASCII string and jump to label if + // it is not. + void JumpIfInstanceTypeIsNotSequentialAscii(Register type, + Register scratch, + Label* failure); + + // Checks if both instance types are sequential ASCII strings and jumps to + // label if either is not. + void JumpIfEitherInstanceTypeIsNotSequentialAscii( + Register first_object_instance_type, + Register second_object_instance_type, + Register scratch1, + Register scratch2, + Label* failure); + + // Checks if both instance types are sequential ASCII strings and jumps to + // label if either is not. + void JumpIfBothInstanceTypesAreNotSequentialAscii( + Register first_object_instance_type, + Register second_object_instance_type, + Register scratch1, + Register scratch2, + Label* failure); + + void JumpIfNotUniqueName(Register type, Label* not_unique_name); + + // ---- Calling / Jumping helpers ---- + + // This is required for compatibility in architecture indepenedant code. + inline void jmp(Label* L) { B(L); } + + // Passes thrown value to the handler of top of the try handler chain. + // Register value must be x0. + void Throw(Register value, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4); + + // Propagates an uncatchable exception to the top of the current JS stack's + // handler chain. Register value must be x0. + void ThrowUncatchable(Register value, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4); + + void CallStub(CodeStub* stub, TypeFeedbackId ast_id = TypeFeedbackId::None()); + void TailCallStub(CodeStub* stub); + + void CallRuntime(const Runtime::Function* f, + int num_arguments, + SaveFPRegsMode save_doubles = kDontSaveFPRegs); + + void CallRuntime(Runtime::FunctionId id, + int num_arguments, + SaveFPRegsMode save_doubles = kDontSaveFPRegs) { + CallRuntime(Runtime::FunctionForId(id), num_arguments, save_doubles); + } + + void CallRuntimeSaveDoubles(Runtime::FunctionId id) { + const Runtime::Function* function = Runtime::FunctionForId(id); + CallRuntime(function, function->nargs, kSaveFPRegs); + } + + void TailCallRuntime(Runtime::FunctionId fid, + int num_arguments, + int result_size); + + int ActivationFrameAlignment(); + + // Calls a C function. + // The called function is not allowed to trigger a + // garbage collection, since that might move the code and invalidate the + // return address (unless this is somehow accounted for by the called + // function). + void CallCFunction(ExternalReference function, + int num_reg_arguments); + void CallCFunction(ExternalReference function, + int num_reg_arguments, + int num_double_arguments); + void CallCFunction(Register function, + int num_reg_arguments, + int num_double_arguments); + + // Calls an API function. Allocates HandleScope, extracts returned value + // from handle and propagates exceptions. + // 'stack_space' is the space to be unwound on exit (includes the call JS + // arguments space and the additional space allocated for the fast call). + // 'spill_offset' is the offset from the stack pointer where + // CallApiFunctionAndReturn can spill registers. + void CallApiFunctionAndReturn(Register function_address, + ExternalReference thunk_ref, + int stack_space, + int spill_offset, + MemOperand return_value_operand, + MemOperand* context_restore_operand); + + // The number of register that CallApiFunctionAndReturn will need to save on + // the stack. The space for these registers need to be allocated in the + // ExitFrame before calling CallApiFunctionAndReturn. + static const int kCallApiFunctionSpillSpace = 4; + + // Jump to a runtime routine. + void JumpToExternalReference(const ExternalReference& builtin); + // Tail call of a runtime routine (jump). + // Like JumpToExternalReference, but also takes care of passing the number + // of parameters. + void TailCallExternalReference(const ExternalReference& ext, + int num_arguments, + int result_size); + void CallExternalReference(const ExternalReference& ext, + int num_arguments); + + + // Invoke specified builtin JavaScript function. Adds an entry to + // the unresolved list if the name does not resolve. + void InvokeBuiltin(Builtins::JavaScript id, + InvokeFlag flag, + const CallWrapper& call_wrapper = NullCallWrapper()); + + // Store the code object for the given builtin in the target register and + // setup the function in the function register. + void GetBuiltinEntry(Register target, + Register function, + Builtins::JavaScript id); + + // Store the function for the given builtin in the target register. + void GetBuiltinFunction(Register target, Builtins::JavaScript id); + + void Jump(Register target); + void Jump(Address target, RelocInfo::Mode rmode); + void Jump(Handle<Code> code, RelocInfo::Mode rmode); + void Jump(intptr_t target, RelocInfo::Mode rmode); + + void Call(Register target); + void Call(Label* target); + void Call(Address target, RelocInfo::Mode rmode); + void Call(Handle<Code> code, + RelocInfo::Mode rmode = RelocInfo::CODE_TARGET, + TypeFeedbackId ast_id = TypeFeedbackId::None()); + + // For every Call variant, there is a matching CallSize function that returns + // the size (in bytes) of the call sequence. + static int CallSize(Register target); + static int CallSize(Label* target); + static int CallSize(Address target, RelocInfo::Mode rmode); + static int CallSize(Handle<Code> code, + RelocInfo::Mode rmode = RelocInfo::CODE_TARGET, + TypeFeedbackId ast_id = TypeFeedbackId::None()); + + // Registers used through the invocation chain are hard-coded. + // We force passing the parameters to ensure the contracts are correctly + // honoured by the caller. + // 'function' must be x1. + // 'actual' must use an immediate or x0. + // 'expected' must use an immediate or x2. + // 'call_kind' must be x5. + void InvokePrologue(const ParameterCount& expected, + const ParameterCount& actual, + Handle<Code> code_constant, + Register code_reg, + Label* done, + InvokeFlag flag, + bool* definitely_mismatches, + const CallWrapper& call_wrapper); + void InvokeCode(Register code, + const ParameterCount& expected, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper); + // Invoke the JavaScript function in the given register. + // Changes the current context to the context in the function before invoking. + void InvokeFunction(Register function, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper); + void InvokeFunction(Register function, + const ParameterCount& expected, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper); + void InvokeFunction(Handle<JSFunction> function, + const ParameterCount& expected, + const ParameterCount& actual, + InvokeFlag flag, + const CallWrapper& call_wrapper); + + + // ---- Floating point helpers ---- + + // Perform a conversion from a double to a signed int64. If the input fits in + // range of the 64-bit result, execution branches to done. Otherwise, + // execution falls through, and the sign of the result can be used to + // determine if overflow was towards positive or negative infinity. + // + // On successful conversion, the least significant 32 bits of the result are + // equivalent to the ECMA-262 operation "ToInt32". + // + // Only public for the test code in test-code-stubs-arm64.cc. + void TryConvertDoubleToInt64(Register result, + DoubleRegister input, + Label* done); + + // Performs a truncating conversion of a floating point number as used by + // the JS bitwise operations. See ECMA-262 9.5: ToInt32. + // Exits with 'result' holding the answer. + void TruncateDoubleToI(Register result, DoubleRegister double_input); + + // Performs a truncating conversion of a heap number as used by + // the JS bitwise operations. See ECMA-262 9.5: ToInt32. 'result' and 'input' + // must be different registers. Exits with 'result' holding the answer. + void TruncateHeapNumberToI(Register result, Register object); + + // Converts the smi or heap number in object to an int32 using the rules + // for ToInt32 as described in ECMAScript 9.5.: the value is truncated + // and brought into the range -2^31 .. +2^31 - 1. 'result' and 'input' must be + // different registers. + void TruncateNumberToI(Register object, + Register result, + Register heap_number_map, + Label* not_int32); + + // ---- Code generation helpers ---- + + void set_generating_stub(bool value) { generating_stub_ = value; } + bool generating_stub() const { return generating_stub_; } +#if DEBUG + void set_allow_macro_instructions(bool value) { + allow_macro_instructions_ = value; + } + bool allow_macro_instructions() const { return allow_macro_instructions_; } +#endif + bool use_real_aborts() const { return use_real_aborts_; } + void set_has_frame(bool value) { has_frame_ = value; } + bool has_frame() const { return has_frame_; } + bool AllowThisStubCall(CodeStub* stub); + + class NoUseRealAbortsScope { + public: + explicit NoUseRealAbortsScope(MacroAssembler* masm) : + saved_(masm->use_real_aborts_), masm_(masm) { + masm_->use_real_aborts_ = false; + } + ~NoUseRealAbortsScope() { + masm_->use_real_aborts_ = saved_; + } + private: + bool saved_; + MacroAssembler* masm_; + }; + + // --------------------------------------------------------------------------- + // Debugger Support + + void DebugBreak(); + + // --------------------------------------------------------------------------- + // Exception handling + + // Push a new try handler and link into try handler chain. + void PushTryHandler(StackHandler::Kind kind, int handler_index); + + // Unlink the stack handler on top of the stack from the try handler chain. + // Must preserve the result register. + void PopTryHandler(); + + + // --------------------------------------------------------------------------- + // Allocation support + + // Allocate an object in new space or old pointer space. The object_size is + // specified either in bytes or in words if the allocation flag SIZE_IN_WORDS + // is passed. The allocated object is returned in result. + // + // If the new space is exhausted control continues at the gc_required label. + // In this case, the result and scratch registers may still be clobbered. + // If flags includes TAG_OBJECT, the result is tagged as as a heap object. + void Allocate(Register object_size, + Register result, + Register scratch1, + Register scratch2, + Label* gc_required, + AllocationFlags flags); + + void Allocate(int object_size, + Register result, + Register scratch1, + Register scratch2, + Label* gc_required, + AllocationFlags flags); + + // Undo allocation in new space. The object passed and objects allocated after + // it will no longer be allocated. The caller must make sure that no pointers + // are left to the object(s) no longer allocated as they would be invalid when + // allocation is undone. + void UndoAllocationInNewSpace(Register object, Register scratch); + + void AllocateTwoByteString(Register result, + Register length, + Register scratch1, + Register scratch2, + Register scratch3, + Label* gc_required); + void AllocateAsciiString(Register result, + Register length, + Register scratch1, + Register scratch2, + Register scratch3, + Label* gc_required); + void AllocateTwoByteConsString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required); + void AllocateAsciiConsString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required); + void AllocateTwoByteSlicedString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required); + void AllocateAsciiSlicedString(Register result, + Register length, + Register scratch1, + Register scratch2, + Label* gc_required); + + // Allocates a heap number or jumps to the gc_required label if the young + // space is full and a scavenge is needed. + // All registers are clobbered. + // If no heap_number_map register is provided, the function will take care of + // loading it. + void AllocateHeapNumber(Register result, + Label* gc_required, + Register scratch1, + Register scratch2, + CPURegister value = NoFPReg, + CPURegister heap_number_map = NoReg); + + // --------------------------------------------------------------------------- + // Support functions. + + // Try to get function prototype of a function and puts the value in the + // result register. Checks that the function really is a function and jumps + // to the miss label if the fast checks fail. The function register will be + // untouched; the other registers may be clobbered. + enum BoundFunctionAction { + kMissOnBoundFunction, + kDontMissOnBoundFunction + }; + + void TryGetFunctionPrototype(Register function, + Register result, + Register scratch, + Label* miss, + BoundFunctionAction action = + kDontMissOnBoundFunction); + + // Compare object type for heap object. heap_object contains a non-Smi + // whose object type should be compared with the given type. This both + // sets the flags and leaves the object type in the type_reg register. + // It leaves the map in the map register (unless the type_reg and map register + // are the same register). It leaves the heap object in the heap_object + // register unless the heap_object register is the same register as one of the + // other registers. + void CompareObjectType(Register heap_object, + Register map, + Register type_reg, + InstanceType type); + + + // Compare object type for heap object, and branch if equal (or not.) + // heap_object contains a non-Smi whose object type should be compared with + // the given type. This both sets the flags and leaves the object type in + // the type_reg register. It leaves the map in the map register (unless the + // type_reg and map register are the same register). It leaves the heap + // object in the heap_object register unless the heap_object register is the + // same register as one of the other registers. + void JumpIfObjectType(Register object, + Register map, + Register type_reg, + InstanceType type, + Label* if_cond_pass, + Condition cond = eq); + + void JumpIfNotObjectType(Register object, + Register map, + Register type_reg, + InstanceType type, + Label* if_not_object); + + // Compare instance type in a map. map contains a valid map object whose + // object type should be compared with the given type. This both + // sets the flags and leaves the object type in the type_reg register. + void CompareInstanceType(Register map, + Register type_reg, + InstanceType type); + + // Compare an object's map with the specified map. Condition flags are set + // with result of map compare. + void CompareMap(Register obj, + Register scratch, + Handle<Map> map); + + // As above, but the map of the object is already loaded into the register + // which is preserved by the code generated. + void CompareMap(Register obj_map, + Handle<Map> map); + + // Check if the map of an object is equal to a specified map and branch to + // label if not. Skip the smi check if not required (object is known to be a + // heap object). If mode is ALLOW_ELEMENT_TRANSITION_MAPS, then also match + // against maps that are ElementsKind transition maps of the specified map. + void CheckMap(Register obj, + Register scratch, + Handle<Map> map, + Label* fail, + SmiCheckType smi_check_type); + + + void CheckMap(Register obj, + Register scratch, + Heap::RootListIndex index, + Label* fail, + SmiCheckType smi_check_type); + + // As above, but the map of the object is already loaded into obj_map, and is + // preserved. + void CheckMap(Register obj_map, + Handle<Map> map, + Label* fail, + SmiCheckType smi_check_type); + + // Check if the map of an object is equal to a specified map and branch to a + // specified target if equal. Skip the smi check if not required (object is + // known to be a heap object) + void DispatchMap(Register obj, + Register scratch, + Handle<Map> map, + Handle<Code> success, + SmiCheckType smi_check_type); + + // Test the bitfield of the heap object map with mask and set the condition + // flags. The object register is preserved. + void TestMapBitfield(Register object, uint64_t mask); + + // Load the elements kind field from a map, and return it in the result + // register. + void LoadElementsKindFromMap(Register result, Register map); + + // Compare the object in a register to a value from the root list. + void CompareRoot(const Register& obj, Heap::RootListIndex index); + + // Compare the object in a register to a value and jump if they are equal. + void JumpIfRoot(const Register& obj, + Heap::RootListIndex index, + Label* if_equal); + + // Compare the object in a register to a value and jump if they are not equal. + void JumpIfNotRoot(const Register& obj, + Heap::RootListIndex index, + Label* if_not_equal); + + // Load and check the instance type of an object for being a unique name. + // Loads the type into the second argument register. + // The object and type arguments can be the same register; in that case it + // will be overwritten with the type. + // Fall-through if the object was a string and jump on fail otherwise. + inline void IsObjectNameType(Register object, Register type, Label* fail); + + inline void IsObjectJSObjectType(Register heap_object, + Register map, + Register scratch, + Label* fail); + + // Check the instance type in the given map to see if it corresponds to a + // JS object type. Jump to the fail label if this is not the case and fall + // through otherwise. However if fail label is NULL, no branch will be + // performed and the flag will be updated. You can test the flag for "le" + // condition to test if it is a valid JS object type. + inline void IsInstanceJSObjectType(Register map, + Register scratch, + Label* fail); + + // Load and check the instance type of an object for being a string. + // Loads the type into the second argument register. + // The object and type arguments can be the same register; in that case it + // will be overwritten with the type. + // Jumps to not_string or string appropriate. If the appropriate label is + // NULL, fall through. + inline void IsObjectJSStringType(Register object, Register type, + Label* not_string, Label* string = NULL); + + // Compare the contents of a register with an operand, and branch to true, + // false or fall through, depending on condition. + void CompareAndSplit(const Register& lhs, + const Operand& rhs, + Condition cond, + Label* if_true, + Label* if_false, + Label* fall_through); + + // Test the bits of register defined by bit_pattern, and branch to + // if_any_set, if_all_clear or fall_through accordingly. + void TestAndSplit(const Register& reg, + uint64_t bit_pattern, + Label* if_all_clear, + Label* if_any_set, + Label* fall_through); + + // Check if a map for a JSObject indicates that the object has fast elements. + // Jump to the specified label if it does not. + void CheckFastElements(Register map, Register scratch, Label* fail); + + // Check if a map for a JSObject indicates that the object can have both smi + // and HeapObject elements. Jump to the specified label if it does not. + void CheckFastObjectElements(Register map, Register scratch, Label* fail); + + // Check to see if number can be stored as a double in FastDoubleElements. + // If it can, store it at the index specified by key_reg in the array, + // otherwise jump to fail. + void StoreNumberToDoubleElements(Register value_reg, + Register key_reg, + Register elements_reg, + Register scratch1, + FPRegister fpscratch1, + Label* fail, + int elements_offset = 0); + + // Picks out an array index from the hash field. + // Register use: + // hash - holds the index's hash. Clobbered. + // index - holds the overwritten index on exit. + void IndexFromHash(Register hash, Register index); + + // --------------------------------------------------------------------------- + // Inline caching support. + + void EmitSeqStringSetCharCheck(Register string, + Register index, + SeqStringSetCharCheckIndexType index_type, + Register scratch, + uint32_t encoding_mask); + + // Generate code for checking access rights - used for security checks + // on access to global objects across environments. The holder register + // is left untouched, whereas both scratch registers are clobbered. + void CheckAccessGlobalProxy(Register holder_reg, + Register scratch1, + Register scratch2, + Label* miss); + + // Hash the interger value in 'key' register. + // It uses the same algorithm as ComputeIntegerHash in utils.h. + void GetNumberHash(Register key, Register scratch); + + // Load value from the dictionary. + // + // elements - holds the slow-case elements of the receiver on entry. + // Unchanged unless 'result' is the same register. + // + // key - holds the smi key on entry. + // Unchanged unless 'result' is the same register. + // + // result - holds the result on exit if the load succeeded. + // Allowed to be the same as 'key' or 'result'. + // Unchanged on bailout so 'key' or 'result' can be used + // in further computation. + void LoadFromNumberDictionary(Label* miss, + Register elements, + Register key, + Register result, + Register scratch0, + Register scratch1, + Register scratch2, + Register scratch3); + + // --------------------------------------------------------------------------- + // Frames. + + // Activation support. + void EnterFrame(StackFrame::Type type); + void LeaveFrame(StackFrame::Type type); + + // Returns map with validated enum cache in object register. + void CheckEnumCache(Register object, + Register null_value, + Register scratch0, + Register scratch1, + Register scratch2, + Register scratch3, + Label* call_runtime); + + // AllocationMemento support. Arrays may have an associated + // AllocationMemento object that can be checked for in order to pretransition + // to another type. + // On entry, receiver should point to the array object. + // If allocation info is present, the Z flag is set (so that the eq + // condition will pass). + void TestJSArrayForAllocationMemento(Register receiver, + Register scratch1, + Register scratch2, + Label* no_memento_found); + + void JumpIfJSArrayHasAllocationMemento(Register receiver, + Register scratch1, + Register scratch2, + Label* memento_found) { + Label no_memento_found; + TestJSArrayForAllocationMemento(receiver, scratch1, scratch2, + &no_memento_found); + B(eq, memento_found); + Bind(&no_memento_found); + } + + // The stack pointer has to switch between csp and jssp when setting up and + // destroying the exit frame. Hence preserving/restoring the registers is + // slightly more complicated than simple push/pop operations. + void ExitFramePreserveFPRegs(); + void ExitFrameRestoreFPRegs(); + + // Generates function and stub prologue code. + void StubPrologue(); + void Prologue(bool code_pre_aging); + + // Enter exit frame. Exit frames are used when calling C code from generated + // (JavaScript) code. + // + // The stack pointer must be jssp on entry, and will be set to csp by this + // function. The frame pointer is also configured, but the only other + // registers modified by this function are the provided scratch register, and + // jssp. + // + // The 'extra_space' argument can be used to allocate some space in the exit + // frame that will be ignored by the GC. This space will be reserved in the + // bottom of the frame immediately above the return address slot. + // + // Set up a stack frame and registers as follows: + // fp[8]: CallerPC (lr) + // fp -> fp[0]: CallerFP (old fp) + // fp[-8]: SPOffset (new csp) + // fp[-16]: CodeObject() + // fp[-16 - fp-size]: Saved doubles, if saved_doubles is true. + // csp[8]: Memory reserved for the caller if extra_space != 0. + // Alignment padding, if necessary. + // csp -> csp[0]: Space reserved for the return address. + // + // This function also stores the new frame information in the top frame, so + // that the new frame becomes the current frame. + void EnterExitFrame(bool save_doubles, + const Register& scratch, + int extra_space = 0); + + // Leave the current exit frame, after a C function has returned to generated + // (JavaScript) code. + // + // This effectively unwinds the operation of EnterExitFrame: + // * Preserved doubles are restored (if restore_doubles is true). + // * The frame information is removed from the top frame. + // * The exit frame is dropped. + // * The stack pointer is reset to jssp. + // + // The stack pointer must be csp on entry. + void LeaveExitFrame(bool save_doubles, + const Register& scratch, + bool restore_context); + + void LoadContext(Register dst, int context_chain_length); + + // Emit code for a truncating division by a constant. The dividend register is + // unchanged. Dividend and result must be different. + void TruncatingDiv(Register result, Register dividend, int32_t divisor); + + // --------------------------------------------------------------------------- + // StatsCounter support + + void SetCounter(StatsCounter* counter, int value, Register scratch1, + Register scratch2); + void IncrementCounter(StatsCounter* counter, int value, Register scratch1, + Register scratch2); + void DecrementCounter(StatsCounter* counter, int value, Register scratch1, + Register scratch2); + + // --------------------------------------------------------------------------- + // Garbage collector support (GC). + + enum RememberedSetFinalAction { + kReturnAtEnd, + kFallThroughAtEnd + }; + + // Record in the remembered set the fact that we have a pointer to new space + // at the address pointed to by the addr register. Only works if addr is not + // in new space. + void RememberedSetHelper(Register object, // Used for debug code. + Register addr, + Register scratch1, + SaveFPRegsMode save_fp, + RememberedSetFinalAction and_then); + + // Push and pop the registers that can hold pointers, as defined by the + // RegList constant kSafepointSavedRegisters. + void PushSafepointRegisters(); + void PopSafepointRegisters(); + + void PushSafepointRegistersAndDoubles(); + void PopSafepointRegistersAndDoubles(); + + // Store value in register src in the safepoint stack slot for register dst. + void StoreToSafepointRegisterSlot(Register src, Register dst) { + Poke(src, SafepointRegisterStackIndex(dst.code()) * kPointerSize); + } + + // Load the value of the src register from its safepoint stack slot + // into register dst. + void LoadFromSafepointRegisterSlot(Register dst, Register src) { + Peek(src, SafepointRegisterStackIndex(dst.code()) * kPointerSize); + } + + void CheckPageFlagSet(const Register& object, + const Register& scratch, + int mask, + Label* if_any_set); + + void CheckPageFlagClear(const Register& object, + const Register& scratch, + int mask, + Label* if_all_clear); + + void CheckMapDeprecated(Handle<Map> map, + Register scratch, + Label* if_deprecated); + + // Check if object is in new space and jump accordingly. + // Register 'object' is preserved. + void JumpIfNotInNewSpace(Register object, + Label* branch) { + InNewSpace(object, ne, branch); + } + + void JumpIfInNewSpace(Register object, + Label* branch) { + InNewSpace(object, eq, branch); + } + + // Notify the garbage collector that we wrote a pointer into an object. + // |object| is the object being stored into, |value| is the object being + // stored. value and scratch registers are clobbered by the operation. + // The offset is the offset from the start of the object, not the offset from + // the tagged HeapObject pointer. For use with FieldOperand(reg, off). + void RecordWriteField( + Register object, + int offset, + Register value, + Register scratch, + LinkRegisterStatus lr_status, + SaveFPRegsMode save_fp, + RememberedSetAction remembered_set_action = EMIT_REMEMBERED_SET, + SmiCheck smi_check = INLINE_SMI_CHECK, + PointersToHereCheck pointers_to_here_check_for_value = + kPointersToHereMaybeInteresting); + + // As above, but the offset has the tag presubtracted. For use with + // MemOperand(reg, off). + inline void RecordWriteContextSlot( + Register context, + int offset, + Register value, + Register scratch, + LinkRegisterStatus lr_status, + SaveFPRegsMode save_fp, + RememberedSetAction remembered_set_action = EMIT_REMEMBERED_SET, + SmiCheck smi_check = INLINE_SMI_CHECK, + PointersToHereCheck pointers_to_here_check_for_value = + kPointersToHereMaybeInteresting) { + RecordWriteField(context, + offset + kHeapObjectTag, + value, + scratch, + lr_status, + save_fp, + remembered_set_action, + smi_check, + pointers_to_here_check_for_value); + } + + void RecordWriteForMap( + Register object, + Register map, + Register dst, + LinkRegisterStatus lr_status, + SaveFPRegsMode save_fp); + + // For a given |object| notify the garbage collector that the slot |address| + // has been written. |value| is the object being stored. The value and + // address registers are clobbered by the operation. + void RecordWrite( + Register object, + Register address, + Register value, + LinkRegisterStatus lr_status, + SaveFPRegsMode save_fp, + RememberedSetAction remembered_set_action = EMIT_REMEMBERED_SET, + SmiCheck smi_check = INLINE_SMI_CHECK, + PointersToHereCheck pointers_to_here_check_for_value = + kPointersToHereMaybeInteresting); + + // Checks the color of an object. If the object is already grey or black + // then we just fall through, since it is already live. If it is white and + // we can determine that it doesn't need to be scanned, then we just mark it + // black and fall through. For the rest we jump to the label so the + // incremental marker can fix its assumptions. + void EnsureNotWhite(Register object, + Register scratch1, + Register scratch2, + Register scratch3, + Register scratch4, + Label* object_is_white_and_not_data); + + // Detects conservatively whether an object is data-only, i.e. it does need to + // be scanned by the garbage collector. + void JumpIfDataObject(Register value, + Register scratch, + Label* not_data_object); + + // Helper for finding the mark bits for an address. + // Note that the behaviour slightly differs from other architectures. + // On exit: + // - addr_reg is unchanged. + // - The bitmap register points at the word with the mark bits. + // - The shift register contains the index of the first color bit for this + // object in the bitmap. + inline void GetMarkBits(Register addr_reg, + Register bitmap_reg, + Register shift_reg); + + // Check if an object has a given incremental marking color. + void HasColor(Register object, + Register scratch0, + Register scratch1, + Label* has_color, + int first_bit, + int second_bit); + + void JumpIfBlack(Register object, + Register scratch0, + Register scratch1, + Label* on_black); + + + // Get the location of a relocated constant (its address in the constant pool) + // from its load site. + void GetRelocatedValueLocation(Register ldr_location, + Register result); + + + // --------------------------------------------------------------------------- + // Debugging. + + // Calls Abort(msg) if the condition cond is not satisfied. + // Use --debug_code to enable. + void Assert(Condition cond, BailoutReason reason); + void AssertRegisterIsClear(Register reg, BailoutReason reason); + void AssertRegisterIsRoot( + Register reg, + Heap::RootListIndex index, + BailoutReason reason = kRegisterDidNotMatchExpectedRoot); + void AssertFastElements(Register elements); + + // Abort if the specified register contains the invalid color bit pattern. + // The pattern must be in bits [1:0] of 'reg' register. + // + // If emit_debug_code() is false, this emits no code. + void AssertHasValidColor(const Register& reg); + + // Abort if 'object' register doesn't point to a string object. + // + // If emit_debug_code() is false, this emits no code. + void AssertIsString(const Register& object); + + // Like Assert(), but always enabled. + void Check(Condition cond, BailoutReason reason); + void CheckRegisterIsClear(Register reg, BailoutReason reason); + + // Print a message to stderr and abort execution. + void Abort(BailoutReason reason); + + // Conditionally load the cached Array transitioned map of type + // transitioned_kind from the native context if the map in register + // map_in_out is the cached Array map in the native context of + // expected_kind. + void LoadTransitionedArrayMapConditional( + ElementsKind expected_kind, + ElementsKind transitioned_kind, + Register map_in_out, + Register scratch1, + Register scratch2, + Label* no_map_match); + + void LoadGlobalFunction(int index, Register function); + + // Load the initial map from the global function. The registers function and + // map can be the same, function is then overwritten. + void LoadGlobalFunctionInitialMap(Register function, + Register map, + Register scratch); + + CPURegList* TmpList() { return &tmp_list_; } + CPURegList* FPTmpList() { return &fptmp_list_; } + + static CPURegList DefaultTmpList(); + static CPURegList DefaultFPTmpList(); + + // Like printf, but print at run-time from generated code. + // + // The caller must ensure that arguments for floating-point placeholders + // (such as %e, %f or %g) are FPRegisters, and that arguments for integer + // placeholders are Registers. + // + // At the moment it is only possible to print the value of csp if it is the + // current stack pointer. Otherwise, the MacroAssembler will automatically + // update csp on every push (using BumpSystemStackPointer), so determining its + // value is difficult. + // + // Format placeholders that refer to more than one argument, or to a specific + // argument, are not supported. This includes formats like "%1$d" or "%.*d". + // + // This function automatically preserves caller-saved registers so that + // calling code can use Printf at any point without having to worry about + // corruption. The preservation mechanism generates a lot of code. If this is + // a problem, preserve the important registers manually and then call + // PrintfNoPreserve. Callee-saved registers are not used by Printf, and are + // implicitly preserved. + void Printf(const char * format, + CPURegister arg0 = NoCPUReg, + CPURegister arg1 = NoCPUReg, + CPURegister arg2 = NoCPUReg, + CPURegister arg3 = NoCPUReg); + + // Like Printf, but don't preserve any caller-saved registers, not even 'lr'. + // + // The return code from the system printf call will be returned in x0. + void PrintfNoPreserve(const char * format, + const CPURegister& arg0 = NoCPUReg, + const CPURegister& arg1 = NoCPUReg, + const CPURegister& arg2 = NoCPUReg, + const CPURegister& arg3 = NoCPUReg); + + // Code ageing support functions. + + // Code ageing on ARM64 works similarly to on ARM. When V8 wants to mark a + // function as old, it replaces some of the function prologue (generated by + // FullCodeGenerator::Generate) with a call to a special stub (ultimately + // generated by GenerateMakeCodeYoungAgainCommon). The stub restores the + // function prologue to its initial young state (indicating that it has been + // recently run) and continues. A young function is therefore one which has a + // normal frame setup sequence, and an old function has a code age sequence + // which calls a code ageing stub. + + // Set up a basic stack frame for young code (or code exempt from ageing) with + // type FUNCTION. It may be patched later for code ageing support. This is + // done by to Code::PatchPlatformCodeAge and EmitCodeAgeSequence. + // + // This function takes an Assembler so it can be called from either a + // MacroAssembler or a PatchingAssembler context. + static void EmitFrameSetupForCodeAgePatching(Assembler* assm); + + // Call EmitFrameSetupForCodeAgePatching from a MacroAssembler context. + void EmitFrameSetupForCodeAgePatching(); + + // Emit a code age sequence that calls the relevant code age stub. The code + // generated by this sequence is expected to replace the code generated by + // EmitFrameSetupForCodeAgePatching, and represents an old function. + // + // If stub is NULL, this function generates the code age sequence but omits + // the stub address that is normally embedded in the instruction stream. This + // can be used by debug code to verify code age sequences. + static void EmitCodeAgeSequence(Assembler* assm, Code* stub); + + // Call EmitCodeAgeSequence from a MacroAssembler context. + void EmitCodeAgeSequence(Code* stub); + + // Return true if the sequence is a young sequence geneated by + // EmitFrameSetupForCodeAgePatching. Otherwise, this method asserts that the + // sequence is a code age sequence (emitted by EmitCodeAgeSequence). + static bool IsYoungSequence(Isolate* isolate, byte* sequence); + + // Jumps to found label if a prototype map has dictionary elements. + void JumpIfDictionaryInPrototypeChain(Register object, Register scratch0, + Register scratch1, Label* found); + + // Perform necessary maintenance operations before a push or after a pop. + // + // Note that size is specified in bytes. + void PushPreamble(Operand total_size); + void PopPostamble(Operand total_size); + + void PushPreamble(int count, int size) { PushPreamble(count * size); } + void PopPostamble(int count, int size) { PopPostamble(count * size); } + + private: + // Helpers for CopyFields. + // These each implement CopyFields in a different way. + void CopyFieldsLoopPairsHelper(Register dst, Register src, unsigned count, + Register scratch1, Register scratch2, + Register scratch3, Register scratch4, + Register scratch5); + void CopyFieldsUnrolledPairsHelper(Register dst, Register src, unsigned count, + Register scratch1, Register scratch2, + Register scratch3, Register scratch4); + void CopyFieldsUnrolledHelper(Register dst, Register src, unsigned count, + Register scratch1, Register scratch2, + Register scratch3); + + // The actual Push and Pop implementations. These don't generate any code + // other than that required for the push or pop. This allows + // (Push|Pop)CPURegList to bundle together run-time assertions for a large + // block of registers. + // + // Note that size is per register, and is specified in bytes. + void PushHelper(int count, int size, + const CPURegister& src0, const CPURegister& src1, + const CPURegister& src2, const CPURegister& src3); + void PopHelper(int count, int size, + const CPURegister& dst0, const CPURegister& dst1, + const CPURegister& dst2, const CPURegister& dst3); + + // Call Printf. On a native build, a simple call will be generated, but if the + // simulator is being used then a suitable pseudo-instruction is used. The + // arguments and stack (csp) must be prepared by the caller as for a normal + // AAPCS64 call to 'printf'. + // + // The 'args' argument should point to an array of variable arguments in their + // proper PCS registers (and in calling order). The argument registers can + // have mixed types. The format string (x0) should not be included. + void CallPrintf(int arg_count = 0, const CPURegister * args = NULL); + + // Helper for throwing exceptions. Compute a handler address and jump to + // it. See the implementation for register usage. + void JumpToHandlerEntry(Register exception, + Register object, + Register state, + Register scratch1, + Register scratch2); + + // Helper for implementing JumpIfNotInNewSpace and JumpIfInNewSpace. + void InNewSpace(Register object, + Condition cond, // eq for new space, ne otherwise. + Label* branch); + + // Try to represent a double as an int so that integer fast-paths may be + // used. Not every valid integer value is guaranteed to be caught. + // It supports both 32-bit and 64-bit integers depending whether 'as_int' + // is a W or X register. + // + // This does not distinguish between +0 and -0, so if this distinction is + // important it must be checked separately. + // + // On output the Z flag is set if the operation was successful. + void TryRepresentDoubleAsInt(Register as_int, + FPRegister value, + FPRegister scratch_d, + Label* on_successful_conversion = NULL, + Label* on_failed_conversion = NULL); + + bool generating_stub_; +#if DEBUG + // Tell whether any of the macro instruction can be used. When false the + // MacroAssembler will assert if a method which can emit a variable number + // of instructions is called. + bool allow_macro_instructions_; +#endif + bool has_frame_; + + // The Abort method should call a V8 runtime function, but the CallRuntime + // mechanism depends on CEntryStub. If use_real_aborts is false, Abort will + // use a simpler abort mechanism that doesn't depend on CEntryStub. + // + // The purpose of this is to allow Aborts to be compiled whilst CEntryStub is + // being generated. + bool use_real_aborts_; + + // This handle will be patched with the code object on installation. + Handle<Object> code_object_; + + // The register to use as a stack pointer for stack operations. + Register sp_; + + // Scratch registers available for use by the MacroAssembler. + CPURegList tmp_list_; + CPURegList fptmp_list_; + + void InitializeNewString(Register string, + Register length, + Heap::RootListIndex map_index, + Register scratch1, + Register scratch2); + + public: + // Far branches resolving. + // + // The various classes of branch instructions with immediate offsets have + // different ranges. While the Assembler will fail to assemble a branch + // exceeding its range, the MacroAssembler offers a mechanism to resolve + // branches to too distant targets, either by tweaking the generated code to + // use branch instructions with wider ranges or generating veneers. + // + // Currently branches to distant targets are resolved using unconditional + // branch isntructions with a range of +-128MB. If that becomes too little + // (!), the mechanism can be extended to generate special veneers for really + // far targets. + + // Helps resolve branching to labels potentially out of range. + // If the label is not bound, it registers the information necessary to later + // be able to emit a veneer for this branch if necessary. + // If the label is bound, it returns true if the label (or the previous link + // in the label chain) is out of range. In that case the caller is responsible + // for generating appropriate code. + // Otherwise it returns false. + // This function also checks wether veneers need to be emitted. + bool NeedExtraInstructionsOrRegisterBranch(Label *label, + ImmBranchType branch_type); +}; + + +// Use this scope when you need a one-to-one mapping bewteen methods and +// instructions. This scope prevents the MacroAssembler from being called and +// literal pools from being emitted. It also asserts the number of instructions +// emitted is what you specified when creating the scope. +class InstructionAccurateScope BASE_EMBEDDED { + public: + InstructionAccurateScope(MacroAssembler* masm, size_t count = 0) + : masm_(masm) +#ifdef DEBUG + , + size_(count * kInstructionSize) +#endif + { + // Before blocking the const pool, see if it needs to be emitted. + masm_->CheckConstPool(false, true); + masm_->CheckVeneerPool(false, true); + + masm_->StartBlockPools(); +#ifdef DEBUG + if (count != 0) { + masm_->bind(&start_); + } + previous_allow_macro_instructions_ = masm_->allow_macro_instructions(); + masm_->set_allow_macro_instructions(false); +#endif + } + + ~InstructionAccurateScope() { + masm_->EndBlockPools(); +#ifdef DEBUG + if (start_.is_bound()) { + ASSERT(masm_->SizeOfCodeGeneratedSince(&start_) == size_); + } + masm_->set_allow_macro_instructions(previous_allow_macro_instructions_); +#endif + } + + private: + MacroAssembler* masm_; +#ifdef DEBUG + size_t size_; + Label start_; + bool previous_allow_macro_instructions_; +#endif +}; + + +// This scope utility allows scratch registers to be managed safely. The +// MacroAssembler's TmpList() (and FPTmpList()) is used as a pool of scratch +// registers. These registers can be allocated on demand, and will be returned +// at the end of the scope. +// +// When the scope ends, the MacroAssembler's lists will be restored to their +// original state, even if the lists were modified by some other means. +class UseScratchRegisterScope { + public: + explicit UseScratchRegisterScope(MacroAssembler* masm) + : available_(masm->TmpList()), + availablefp_(masm->FPTmpList()), + old_available_(available_->list()), + old_availablefp_(availablefp_->list()) { + ASSERT(available_->type() == CPURegister::kRegister); + ASSERT(availablefp_->type() == CPURegister::kFPRegister); + } + + ~UseScratchRegisterScope(); + + // Take a register from the appropriate temps list. It will be returned + // automatically when the scope ends. + Register AcquireW() { return AcquireNextAvailable(available_).W(); } + Register AcquireX() { return AcquireNextAvailable(available_).X(); } + FPRegister AcquireS() { return AcquireNextAvailable(availablefp_).S(); } + FPRegister AcquireD() { return AcquireNextAvailable(availablefp_).D(); } + + Register UnsafeAcquire(const Register& reg) { + return Register(UnsafeAcquire(available_, reg)); + } + + Register AcquireSameSizeAs(const Register& reg); + FPRegister AcquireSameSizeAs(const FPRegister& reg); + + private: + static CPURegister AcquireNextAvailable(CPURegList* available); + static CPURegister UnsafeAcquire(CPURegList* available, + const CPURegister& reg); + + // Available scratch registers. + CPURegList* available_; // kRegister + CPURegList* availablefp_; // kFPRegister + + // The state of the available lists at the start of this scope. + RegList old_available_; // kRegister + RegList old_availablefp_; // kFPRegister +}; + + +inline MemOperand ContextMemOperand(Register context, int index) { + return MemOperand(context, Context::SlotOffset(index)); +} + +inline MemOperand GlobalObjectMemOperand() { + return ContextMemOperand(cp, Context::GLOBAL_OBJECT_INDEX); +} + + +// Encode and decode information about patchable inline SMI checks. +class InlineSmiCheckInfo { + public: + explicit InlineSmiCheckInfo(Address info); + + bool HasSmiCheck() const { + return smi_check_ != NULL; + } + + const Register& SmiRegister() const { + return reg_; + } + + Instruction* SmiCheck() const { + return smi_check_; + } + + // Use MacroAssembler::InlineData to emit information about patchable inline + // SMI checks. The caller may specify 'reg' as NoReg and an unbound 'site' to + // indicate that there is no inline SMI check. Note that 'reg' cannot be csp. + // + // The generated patch information can be read using the InlineSMICheckInfo + // class. + static void Emit(MacroAssembler* masm, const Register& reg, + const Label* smi_check); + + // Emit information to indicate that there is no inline SMI check. + static void EmitNotInlined(MacroAssembler* masm) { + Label unbound; + Emit(masm, NoReg, &unbound); + } + + private: + Register reg_; + Instruction* smi_check_; + + // Fields in the data encoded by InlineData. + + // A width of 5 (Rd_width) for the SMI register preclues the use of csp, + // since kSPRegInternalCode is 63. However, csp should never hold a SMI or be + // used in a patchable check. The Emit() method checks this. + // + // Note that the total size of the fields is restricted by the underlying + // storage size handled by the BitField class, which is a uint32_t. + class RegisterBits : public BitField<unsigned, 0, 5> {}; + class DeltaBits : public BitField<uint32_t, 5, 32-5> {}; +}; + +} } // namespace v8::internal + +#ifdef GENERATED_CODE_COVERAGE +#error "Unsupported option" +#define CODE_COVERAGE_STRINGIFY(x) #x +#define CODE_COVERAGE_TOSTRING(x) CODE_COVERAGE_STRINGIFY(x) +#define __FILE_LINE__ __FILE__ ":" CODE_COVERAGE_TOSTRING(__LINE__) +#define ACCESS_MASM(masm) masm->stop(__FILE_LINE__); masm-> +#else +#define ACCESS_MASM(masm) masm-> +#endif + +#endif // V8_ARM64_MACRO_ASSEMBLER_ARM64_H_ diff --git a/chromium/v8/src/arm64/regexp-macro-assembler-arm64.cc b/chromium/v8/src/arm64/regexp-macro-assembler-arm64.cc new file mode 100644 index 00000000000..a772ef26408 --- /dev/null +++ b/chromium/v8/src/arm64/regexp-macro-assembler-arm64.cc @@ -0,0 +1,1707 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/cpu-profiler.h" +#include "src/unicode.h" +#include "src/log.h" +#include "src/code-stubs.h" +#include "src/regexp-stack.h" +#include "src/macro-assembler.h" +#include "src/regexp-macro-assembler.h" +#include "src/arm64/regexp-macro-assembler-arm64.h" + +namespace v8 { +namespace internal { + +#ifndef V8_INTERPRETED_REGEXP +/* + * This assembler uses the following register assignment convention: + * - w19 : Used to temporarely store a value before a call to C code. + * See CheckNotBackReferenceIgnoreCase. + * - x20 : Pointer to the current code object (Code*), + * it includes the heap object tag. + * - w21 : Current position in input, as negative offset from + * the end of the string. Please notice that this is + * the byte offset, not the character offset! + * - w22 : Currently loaded character. Must be loaded using + * LoadCurrentCharacter before using any of the dispatch methods. + * - x23 : Points to tip of backtrack stack. + * - w24 : Position of the first character minus one: non_position_value. + * Used to initialize capture registers. + * - x25 : Address at the end of the input string: input_end. + * Points to byte after last character in input. + * - x26 : Address at the start of the input string: input_start. + * - w27 : Where to start in the input string. + * - x28 : Output array pointer. + * - x29/fp : Frame pointer. Used to access arguments, local variables and + * RegExp registers. + * - x16/x17 : IP registers, used by assembler. Very volatile. + * - csp : Points to tip of C stack. + * + * - x0-x7 : Used as a cache to store 32 bit capture registers. These + * registers need to be retained every time a call to C code + * is done. + * + * The remaining registers are free for computations. + * Each call to a public method should retain this convention. + * + * The stack will have the following structure: + * + * Location Name Description + * (as referred to in + * the code) + * + * - fp[104] isolate Address of the current isolate. + * - fp[96] return_address Secondary link/return address + * used by an exit frame if this is a + * native call. + * ^^^ csp when called ^^^ + * - fp[88] lr Return from the RegExp code. + * - fp[80] r29 Old frame pointer (CalleeSaved). + * - fp[0..72] r19-r28 Backup of CalleeSaved registers. + * - fp[-8] direct_call 1 => Direct call from JavaScript code. + * 0 => Call through the runtime system. + * - fp[-16] stack_base High end of the memory area to use as + * the backtracking stack. + * - fp[-24] output_size Output may fit multiple sets of matches. + * - fp[-32] input Handle containing the input string. + * - fp[-40] success_counter + * ^^^^^^^^^^^^^ From here and downwards we store 32 bit values ^^^^^^^^^^^^^ + * - fp[-44] register N Capture registers initialized with + * - fp[-48] register N + 1 non_position_value. + * ... The first kNumCachedRegisters (N) registers + * ... are cached in x0 to x7. + * ... Only positions must be stored in the first + * - ... num_saved_registers_ registers. + * - ... + * - register N + num_registers - 1 + * ^^^^^^^^^ csp ^^^^^^^^^ + * + * The first num_saved_registers_ registers are initialized to point to + * "character -1" in the string (i.e., char_size() bytes before the first + * character of the string). The remaining registers start out as garbage. + * + * The data up to the return address must be placed there by the calling + * code and the remaining arguments are passed in registers, e.g. by calling the + * code entry as cast to a function with the signature: + * int (*match)(String* input, + * int start_offset, + * Address input_start, + * Address input_end, + * int* output, + * int output_size, + * Address stack_base, + * bool direct_call = false, + * Address secondary_return_address, // Only used by native call. + * Isolate* isolate) + * The call is performed by NativeRegExpMacroAssembler::Execute() + * (in regexp-macro-assembler.cc) via the CALL_GENERATED_REGEXP_CODE macro + * in arm64/simulator-arm64.h. + * When calling as a non-direct call (i.e., from C++ code), the return address + * area is overwritten with the LR register by the RegExp code. When doing a + * direct call from generated code, the return address is placed there by + * the calling code, as in a normal exit frame. + */ + +#define __ ACCESS_MASM(masm_) + +RegExpMacroAssemblerARM64::RegExpMacroAssemblerARM64( + Mode mode, + int registers_to_save, + Zone* zone) + : NativeRegExpMacroAssembler(zone), + masm_(new MacroAssembler(zone->isolate(), NULL, kRegExpCodeSize)), + mode_(mode), + num_registers_(registers_to_save), + num_saved_registers_(registers_to_save), + entry_label_(), + start_label_(), + success_label_(), + backtrack_label_(), + exit_label_() { + __ SetStackPointer(csp); + ASSERT_EQ(0, registers_to_save % 2); + // We can cache at most 16 W registers in x0-x7. + STATIC_ASSERT(kNumCachedRegisters <= 16); + STATIC_ASSERT((kNumCachedRegisters % 2) == 0); + __ B(&entry_label_); // We'll write the entry code later. + __ Bind(&start_label_); // And then continue from here. +} + + +RegExpMacroAssemblerARM64::~RegExpMacroAssemblerARM64() { + delete masm_; + // Unuse labels in case we throw away the assembler without calling GetCode. + entry_label_.Unuse(); + start_label_.Unuse(); + success_label_.Unuse(); + backtrack_label_.Unuse(); + exit_label_.Unuse(); + check_preempt_label_.Unuse(); + stack_overflow_label_.Unuse(); +} + +int RegExpMacroAssemblerARM64::stack_limit_slack() { + return RegExpStack::kStackLimitSlack; +} + + +void RegExpMacroAssemblerARM64::AdvanceCurrentPosition(int by) { + if (by != 0) { + __ Add(current_input_offset(), + current_input_offset(), by * char_size()); + } +} + + +void RegExpMacroAssemblerARM64::AdvanceRegister(int reg, int by) { + ASSERT((reg >= 0) && (reg < num_registers_)); + if (by != 0) { + Register to_advance; + RegisterState register_state = GetRegisterState(reg); + switch (register_state) { + case STACKED: + __ Ldr(w10, register_location(reg)); + __ Add(w10, w10, by); + __ Str(w10, register_location(reg)); + break; + case CACHED_LSW: + to_advance = GetCachedRegister(reg); + __ Add(to_advance, to_advance, by); + break; + case CACHED_MSW: + to_advance = GetCachedRegister(reg); + __ Add(to_advance, to_advance, + static_cast<int64_t>(by) << kWRegSizeInBits); + break; + default: + UNREACHABLE(); + break; + } + } +} + + +void RegExpMacroAssemblerARM64::Backtrack() { + CheckPreemption(); + Pop(w10); + __ Add(x10, code_pointer(), Operand(w10, UXTW)); + __ Br(x10); +} + + +void RegExpMacroAssemblerARM64::Bind(Label* label) { + __ Bind(label); +} + + +void RegExpMacroAssemblerARM64::CheckCharacter(uint32_t c, Label* on_equal) { + CompareAndBranchOrBacktrack(current_character(), c, eq, on_equal); +} + + +void RegExpMacroAssemblerARM64::CheckCharacterGT(uc16 limit, + Label* on_greater) { + CompareAndBranchOrBacktrack(current_character(), limit, hi, on_greater); +} + + +void RegExpMacroAssemblerARM64::CheckAtStart(Label* on_at_start) { + Label not_at_start; + // Did we start the match at the start of the input string? + CompareAndBranchOrBacktrack(start_offset(), 0, ne, ¬_at_start); + // If we did, are we still at the start of the input string? + __ Add(x10, input_end(), Operand(current_input_offset(), SXTW)); + __ Cmp(x10, input_start()); + BranchOrBacktrack(eq, on_at_start); + __ Bind(¬_at_start); +} + + +void RegExpMacroAssemblerARM64::CheckNotAtStart(Label* on_not_at_start) { + // Did we start the match at the start of the input string? + CompareAndBranchOrBacktrack(start_offset(), 0, ne, on_not_at_start); + // If we did, are we still at the start of the input string? + __ Add(x10, input_end(), Operand(current_input_offset(), SXTW)); + __ Cmp(x10, input_start()); + BranchOrBacktrack(ne, on_not_at_start); +} + + +void RegExpMacroAssemblerARM64::CheckCharacterLT(uc16 limit, Label* on_less) { + CompareAndBranchOrBacktrack(current_character(), limit, lo, on_less); +} + + +void RegExpMacroAssemblerARM64::CheckCharacters(Vector<const uc16> str, + int cp_offset, + Label* on_failure, + bool check_end_of_string) { + // This method is only ever called from the cctests. + + if (check_end_of_string) { + // Is last character of required match inside string. + CheckPosition(cp_offset + str.length() - 1, on_failure); + } + + Register characters_address = x11; + + __ Add(characters_address, + input_end(), + Operand(current_input_offset(), SXTW)); + if (cp_offset != 0) { + __ Add(characters_address, characters_address, cp_offset * char_size()); + } + + for (int i = 0; i < str.length(); i++) { + if (mode_ == ASCII) { + __ Ldrb(w10, MemOperand(characters_address, 1, PostIndex)); + ASSERT(str[i] <= String::kMaxOneByteCharCode); + } else { + __ Ldrh(w10, MemOperand(characters_address, 2, PostIndex)); + } + CompareAndBranchOrBacktrack(w10, str[i], ne, on_failure); + } +} + + +void RegExpMacroAssemblerARM64::CheckGreedyLoop(Label* on_equal) { + __ Ldr(w10, MemOperand(backtrack_stackpointer())); + __ Cmp(current_input_offset(), w10); + __ Cset(x11, eq); + __ Add(backtrack_stackpointer(), + backtrack_stackpointer(), Operand(x11, LSL, kWRegSizeLog2)); + BranchOrBacktrack(eq, on_equal); +} + +void RegExpMacroAssemblerARM64::CheckNotBackReferenceIgnoreCase( + int start_reg, + Label* on_no_match) { + Label fallthrough; + + Register capture_start_offset = w10; + // Save the capture length in a callee-saved register so it will + // be preserved if we call a C helper. + Register capture_length = w19; + ASSERT(kCalleeSaved.IncludesAliasOf(capture_length)); + + // Find length of back-referenced capture. + ASSERT((start_reg % 2) == 0); + if (start_reg < kNumCachedRegisters) { + __ Mov(capture_start_offset.X(), GetCachedRegister(start_reg)); + __ Lsr(x11, GetCachedRegister(start_reg), kWRegSizeInBits); + } else { + __ Ldp(w11, capture_start_offset, capture_location(start_reg, x10)); + } + __ Sub(capture_length, w11, capture_start_offset); // Length to check. + // Succeed on empty capture (including no capture). + __ Cbz(capture_length, &fallthrough); + + // Check that there are enough characters left in the input. + __ Cmn(capture_length, current_input_offset()); + BranchOrBacktrack(gt, on_no_match); + + if (mode_ == ASCII) { + Label success; + Label fail; + Label loop_check; + + Register capture_start_address = x12; + Register capture_end_addresss = x13; + Register current_position_address = x14; + + __ Add(capture_start_address, + input_end(), + Operand(capture_start_offset, SXTW)); + __ Add(capture_end_addresss, + capture_start_address, + Operand(capture_length, SXTW)); + __ Add(current_position_address, + input_end(), + Operand(current_input_offset(), SXTW)); + + Label loop; + __ Bind(&loop); + __ Ldrb(w10, MemOperand(capture_start_address, 1, PostIndex)); + __ Ldrb(w11, MemOperand(current_position_address, 1, PostIndex)); + __ Cmp(w10, w11); + __ B(eq, &loop_check); + + // Mismatch, try case-insensitive match (converting letters to lower-case). + __ Orr(w10, w10, 0x20); // Convert capture character to lower-case. + __ Orr(w11, w11, 0x20); // Also convert input character. + __ Cmp(w11, w10); + __ B(ne, &fail); + __ Sub(w10, w10, 'a'); + __ Cmp(w10, 'z' - 'a'); // Is w10 a lowercase letter? + __ B(ls, &loop_check); // In range 'a'-'z'. + // Latin-1: Check for values in range [224,254] but not 247. + __ Sub(w10, w10, 224 - 'a'); + __ Cmp(w10, 254 - 224); + __ Ccmp(w10, 247 - 224, ZFlag, ls); // Check for 247. + __ B(eq, &fail); // Weren't Latin-1 letters. + + __ Bind(&loop_check); + __ Cmp(capture_start_address, capture_end_addresss); + __ B(lt, &loop); + __ B(&success); + + __ Bind(&fail); + BranchOrBacktrack(al, on_no_match); + + __ Bind(&success); + // Compute new value of character position after the matched part. + __ Sub(current_input_offset().X(), current_position_address, input_end()); + if (masm_->emit_debug_code()) { + __ Cmp(current_input_offset().X(), Operand(current_input_offset(), SXTW)); + __ Ccmp(current_input_offset(), 0, NoFlag, eq); + // The current input offset should be <= 0, and fit in a W register. + __ Check(le, kOffsetOutOfRange); + } + } else { + ASSERT(mode_ == UC16); + int argument_count = 4; + + // The cached registers need to be retained. + CPURegList cached_registers(CPURegister::kRegister, kXRegSizeInBits, 0, 7); + ASSERT((cached_registers.Count() * 2) == kNumCachedRegisters); + __ PushCPURegList(cached_registers); + + // Put arguments into arguments registers. + // Parameters are + // x0: Address byte_offset1 - Address captured substring's start. + // x1: Address byte_offset2 - Address of current character position. + // w2: size_t byte_length - length of capture in bytes(!) + // x3: Isolate* isolate + + // Address of start of capture. + __ Add(x0, input_end(), Operand(capture_start_offset, SXTW)); + // Length of capture. + __ Mov(w2, capture_length); + // Address of current input position. + __ Add(x1, input_end(), Operand(current_input_offset(), SXTW)); + // Isolate. + __ Mov(x3, ExternalReference::isolate_address(isolate())); + + { + AllowExternalCallThatCantCauseGC scope(masm_); + ExternalReference function = + ExternalReference::re_case_insensitive_compare_uc16(isolate()); + __ CallCFunction(function, argument_count); + } + + // Check if function returned non-zero for success or zero for failure. + CompareAndBranchOrBacktrack(x0, 0, eq, on_no_match); + // On success, increment position by length of capture. + __ Add(current_input_offset(), current_input_offset(), capture_length); + // Reset the cached registers. + __ PopCPURegList(cached_registers); + } + + __ Bind(&fallthrough); +} + +void RegExpMacroAssemblerARM64::CheckNotBackReference( + int start_reg, + Label* on_no_match) { + Label fallthrough; + + Register capture_start_address = x12; + Register capture_end_address = x13; + Register current_position_address = x14; + Register capture_length = w15; + + // Find length of back-referenced capture. + ASSERT((start_reg % 2) == 0); + if (start_reg < kNumCachedRegisters) { + __ Mov(x10, GetCachedRegister(start_reg)); + __ Lsr(x11, GetCachedRegister(start_reg), kWRegSizeInBits); + } else { + __ Ldp(w11, w10, capture_location(start_reg, x10)); + } + __ Sub(capture_length, w11, w10); // Length to check. + // Succeed on empty capture (including no capture). + __ Cbz(capture_length, &fallthrough); + + // Check that there are enough characters left in the input. + __ Cmn(capture_length, current_input_offset()); + BranchOrBacktrack(gt, on_no_match); + + // Compute pointers to match string and capture string + __ Add(capture_start_address, input_end(), Operand(w10, SXTW)); + __ Add(capture_end_address, + capture_start_address, + Operand(capture_length, SXTW)); + __ Add(current_position_address, + input_end(), + Operand(current_input_offset(), SXTW)); + + Label loop; + __ Bind(&loop); + if (mode_ == ASCII) { + __ Ldrb(w10, MemOperand(capture_start_address, 1, PostIndex)); + __ Ldrb(w11, MemOperand(current_position_address, 1, PostIndex)); + } else { + ASSERT(mode_ == UC16); + __ Ldrh(w10, MemOperand(capture_start_address, 2, PostIndex)); + __ Ldrh(w11, MemOperand(current_position_address, 2, PostIndex)); + } + __ Cmp(w10, w11); + BranchOrBacktrack(ne, on_no_match); + __ Cmp(capture_start_address, capture_end_address); + __ B(lt, &loop); + + // Move current character position to position after match. + __ Sub(current_input_offset().X(), current_position_address, input_end()); + if (masm_->emit_debug_code()) { + __ Cmp(current_input_offset().X(), Operand(current_input_offset(), SXTW)); + __ Ccmp(current_input_offset(), 0, NoFlag, eq); + // The current input offset should be <= 0, and fit in a W register. + __ Check(le, kOffsetOutOfRange); + } + __ Bind(&fallthrough); +} + + +void RegExpMacroAssemblerARM64::CheckNotCharacter(unsigned c, + Label* on_not_equal) { + CompareAndBranchOrBacktrack(current_character(), c, ne, on_not_equal); +} + + +void RegExpMacroAssemblerARM64::CheckCharacterAfterAnd(uint32_t c, + uint32_t mask, + Label* on_equal) { + __ And(w10, current_character(), mask); + CompareAndBranchOrBacktrack(w10, c, eq, on_equal); +} + + +void RegExpMacroAssemblerARM64::CheckNotCharacterAfterAnd(unsigned c, + unsigned mask, + Label* on_not_equal) { + __ And(w10, current_character(), mask); + CompareAndBranchOrBacktrack(w10, c, ne, on_not_equal); +} + + +void RegExpMacroAssemblerARM64::CheckNotCharacterAfterMinusAnd( + uc16 c, + uc16 minus, + uc16 mask, + Label* on_not_equal) { + ASSERT(minus < String::kMaxUtf16CodeUnit); + __ Sub(w10, current_character(), minus); + __ And(w10, w10, mask); + CompareAndBranchOrBacktrack(w10, c, ne, on_not_equal); +} + + +void RegExpMacroAssemblerARM64::CheckCharacterInRange( + uc16 from, + uc16 to, + Label* on_in_range) { + __ Sub(w10, current_character(), from); + // Unsigned lower-or-same condition. + CompareAndBranchOrBacktrack(w10, to - from, ls, on_in_range); +} + + +void RegExpMacroAssemblerARM64::CheckCharacterNotInRange( + uc16 from, + uc16 to, + Label* on_not_in_range) { + __ Sub(w10, current_character(), from); + // Unsigned higher condition. + CompareAndBranchOrBacktrack(w10, to - from, hi, on_not_in_range); +} + + +void RegExpMacroAssemblerARM64::CheckBitInTable( + Handle<ByteArray> table, + Label* on_bit_set) { + __ Mov(x11, Operand(table)); + if ((mode_ != ASCII) || (kTableMask != String::kMaxOneByteCharCode)) { + __ And(w10, current_character(), kTableMask); + __ Add(w10, w10, ByteArray::kHeaderSize - kHeapObjectTag); + } else { + __ Add(w10, current_character(), ByteArray::kHeaderSize - kHeapObjectTag); + } + __ Ldrb(w11, MemOperand(x11, w10, UXTW)); + CompareAndBranchOrBacktrack(w11, 0, ne, on_bit_set); +} + + +bool RegExpMacroAssemblerARM64::CheckSpecialCharacterClass(uc16 type, + Label* on_no_match) { + // Range checks (c in min..max) are generally implemented by an unsigned + // (c - min) <= (max - min) check + switch (type) { + case 's': + // Match space-characters + if (mode_ == ASCII) { + // One byte space characters are '\t'..'\r', ' ' and \u00a0. + Label success; + // Check for ' ' or 0x00a0. + __ Cmp(current_character(), ' '); + __ Ccmp(current_character(), 0x00a0, ZFlag, ne); + __ B(eq, &success); + // Check range 0x09..0x0d. + __ Sub(w10, current_character(), '\t'); + CompareAndBranchOrBacktrack(w10, '\r' - '\t', hi, on_no_match); + __ Bind(&success); + return true; + } + return false; + case 'S': + // The emitted code for generic character classes is good enough. + return false; + case 'd': + // Match ASCII digits ('0'..'9'). + __ Sub(w10, current_character(), '0'); + CompareAndBranchOrBacktrack(w10, '9' - '0', hi, on_no_match); + return true; + case 'D': + // Match ASCII non-digits. + __ Sub(w10, current_character(), '0'); + CompareAndBranchOrBacktrack(w10, '9' - '0', ls, on_no_match); + return true; + case '.': { + // Match non-newlines (not 0x0a('\n'), 0x0d('\r'), 0x2028 and 0x2029) + // Here we emit the conditional branch only once at the end to make branch + // prediction more efficient, even though we could branch out of here + // as soon as a character matches. + __ Cmp(current_character(), 0x0a); + __ Ccmp(current_character(), 0x0d, ZFlag, ne); + if (mode_ == UC16) { + __ Sub(w10, current_character(), 0x2028); + // If the Z flag was set we clear the flags to force a branch. + __ Ccmp(w10, 0x2029 - 0x2028, NoFlag, ne); + // ls -> !((C==1) && (Z==0)) + BranchOrBacktrack(ls, on_no_match); + } else { + BranchOrBacktrack(eq, on_no_match); + } + return true; + } + case 'n': { + // Match newlines (0x0a('\n'), 0x0d('\r'), 0x2028 and 0x2029) + // We have to check all 4 newline characters before emitting + // the conditional branch. + __ Cmp(current_character(), 0x0a); + __ Ccmp(current_character(), 0x0d, ZFlag, ne); + if (mode_ == UC16) { + __ Sub(w10, current_character(), 0x2028); + // If the Z flag was set we clear the flags to force a fall-through. + __ Ccmp(w10, 0x2029 - 0x2028, NoFlag, ne); + // hi -> (C==1) && (Z==0) + BranchOrBacktrack(hi, on_no_match); + } else { + BranchOrBacktrack(ne, on_no_match); + } + return true; + } + case 'w': { + if (mode_ != ASCII) { + // Table is 128 entries, so all ASCII characters can be tested. + CompareAndBranchOrBacktrack(current_character(), 'z', hi, on_no_match); + } + ExternalReference map = ExternalReference::re_word_character_map(); + __ Mov(x10, map); + __ Ldrb(w10, MemOperand(x10, current_character(), UXTW)); + CompareAndBranchOrBacktrack(w10, 0, eq, on_no_match); + return true; + } + case 'W': { + Label done; + if (mode_ != ASCII) { + // Table is 128 entries, so all ASCII characters can be tested. + __ Cmp(current_character(), 'z'); + __ B(hi, &done); + } + ExternalReference map = ExternalReference::re_word_character_map(); + __ Mov(x10, map); + __ Ldrb(w10, MemOperand(x10, current_character(), UXTW)); + CompareAndBranchOrBacktrack(w10, 0, ne, on_no_match); + __ Bind(&done); + return true; + } + case '*': + // Match any character. + return true; + // No custom implementation (yet): s(UC16), S(UC16). + default: + return false; + } +} + + +void RegExpMacroAssemblerARM64::Fail() { + __ Mov(w0, FAILURE); + __ B(&exit_label_); +} + + +Handle<HeapObject> RegExpMacroAssemblerARM64::GetCode(Handle<String> source) { + Label return_w0; + // Finalize code - write the entry point code now we know how many + // registers we need. + + // Entry code: + __ Bind(&entry_label_); + + // Arguments on entry: + // x0: String* input + // x1: int start_offset + // x2: byte* input_start + // x3: byte* input_end + // x4: int* output array + // x5: int output array size + // x6: Address stack_base + // x7: int direct_call + + // The stack pointer should be csp on entry. + // csp[8]: address of the current isolate + // csp[0]: secondary link/return address used by native call + + // Tell the system that we have a stack frame. Because the type is MANUAL, no + // code is generated. + FrameScope scope(masm_, StackFrame::MANUAL); + + // Push registers on the stack, only push the argument registers that we need. + CPURegList argument_registers(x0, x5, x6, x7); + + CPURegList registers_to_retain = kCalleeSaved; + ASSERT(kCalleeSaved.Count() == 11); + registers_to_retain.Combine(lr); + + ASSERT(csp.Is(__ StackPointer())); + __ PushCPURegList(registers_to_retain); + __ PushCPURegList(argument_registers); + + // Set frame pointer in place. + __ Add(frame_pointer(), csp, argument_registers.Count() * kPointerSize); + + // Initialize callee-saved registers. + __ Mov(start_offset(), w1); + __ Mov(input_start(), x2); + __ Mov(input_end(), x3); + __ Mov(output_array(), x4); + + // Set the number of registers we will need to allocate, that is: + // - success_counter (X register) + // - (num_registers_ - kNumCachedRegisters) (W registers) + int num_wreg_to_allocate = num_registers_ - kNumCachedRegisters; + // Do not allocate registers on the stack if they can all be cached. + if (num_wreg_to_allocate < 0) { num_wreg_to_allocate = 0; } + // Make room for the success_counter. + num_wreg_to_allocate += 2; + + // Make sure the stack alignment will be respected. + int alignment = masm_->ActivationFrameAlignment(); + ASSERT_EQ(alignment % 16, 0); + int align_mask = (alignment / kWRegSize) - 1; + num_wreg_to_allocate = (num_wreg_to_allocate + align_mask) & ~align_mask; + + // Check if we have space on the stack. + Label stack_limit_hit; + Label stack_ok; + + ExternalReference stack_limit = + ExternalReference::address_of_stack_limit(isolate()); + __ Mov(x10, stack_limit); + __ Ldr(x10, MemOperand(x10)); + __ Subs(x10, csp, x10); + + // Handle it if the stack pointer is already below the stack limit. + __ B(ls, &stack_limit_hit); + + // Check if there is room for the variable number of registers above + // the stack limit. + __ Cmp(x10, num_wreg_to_allocate * kWRegSize); + __ B(hs, &stack_ok); + + // Exit with OutOfMemory exception. There is not enough space on the stack + // for our working registers. + __ Mov(w0, EXCEPTION); + __ B(&return_w0); + + __ Bind(&stack_limit_hit); + CallCheckStackGuardState(x10); + // If returned value is non-zero, we exit with the returned value as result. + __ Cbnz(w0, &return_w0); + + __ Bind(&stack_ok); + + // Allocate space on stack. + __ Claim(num_wreg_to_allocate, kWRegSize); + + // Initialize success_counter with 0. + __ Str(wzr, MemOperand(frame_pointer(), kSuccessCounter)); + + // Find negative length (offset of start relative to end). + __ Sub(x10, input_start(), input_end()); + if (masm_->emit_debug_code()) { + // Check that the input string length is < 2^30. + __ Neg(x11, x10); + __ Cmp(x11, (1<<30) - 1); + __ Check(ls, kInputStringTooLong); + } + __ Mov(current_input_offset(), w10); + + // The non-position value is used as a clearing value for the + // capture registers, it corresponds to the position of the first character + // minus one. + __ Sub(non_position_value(), current_input_offset(), char_size()); + __ Sub(non_position_value(), non_position_value(), + Operand(start_offset(), LSL, (mode_ == UC16) ? 1 : 0)); + // We can store this value twice in an X register for initializing + // on-stack registers later. + __ Orr(twice_non_position_value(), + non_position_value().X(), + Operand(non_position_value().X(), LSL, kWRegSizeInBits)); + + // Initialize code pointer register. + __ Mov(code_pointer(), Operand(masm_->CodeObject())); + + Label load_char_start_regexp, start_regexp; + // Load newline if index is at start, previous character otherwise. + __ Cbnz(start_offset(), &load_char_start_regexp); + __ Mov(current_character(), '\n'); + __ B(&start_regexp); + + // Global regexp restarts matching here. + __ Bind(&load_char_start_regexp); + // Load previous char as initial value of current character register. + LoadCurrentCharacterUnchecked(-1, 1); + __ Bind(&start_regexp); + // Initialize on-stack registers. + if (num_saved_registers_ > 0) { + ClearRegisters(0, num_saved_registers_ - 1); + } + + // Initialize backtrack stack pointer. + __ Ldr(backtrack_stackpointer(), MemOperand(frame_pointer(), kStackBase)); + + // Execute + __ B(&start_label_); + + if (backtrack_label_.is_linked()) { + __ Bind(&backtrack_label_); + Backtrack(); + } + + if (success_label_.is_linked()) { + Register first_capture_start = w15; + + // Save captures when successful. + __ Bind(&success_label_); + + if (num_saved_registers_ > 0) { + // V8 expects the output to be an int32_t array. + Register capture_start = w12; + Register capture_end = w13; + Register input_length = w14; + + // Copy captures to output. + + // Get string length. + __ Sub(x10, input_end(), input_start()); + if (masm_->emit_debug_code()) { + // Check that the input string length is < 2^30. + __ Cmp(x10, (1<<30) - 1); + __ Check(ls, kInputStringTooLong); + } + // input_start has a start_offset offset on entry. We need to include + // it when computing the length of the whole string. + if (mode_ == UC16) { + __ Add(input_length, start_offset(), Operand(w10, LSR, 1)); + } else { + __ Add(input_length, start_offset(), w10); + } + + // Copy the results to the output array from the cached registers first. + for (int i = 0; + (i < num_saved_registers_) && (i < kNumCachedRegisters); + i += 2) { + __ Mov(capture_start.X(), GetCachedRegister(i)); + __ Lsr(capture_end.X(), capture_start.X(), kWRegSizeInBits); + if ((i == 0) && global_with_zero_length_check()) { + // Keep capture start for the zero-length check later. + __ Mov(first_capture_start, capture_start); + } + // Offsets need to be relative to the start of the string. + if (mode_ == UC16) { + __ Add(capture_start, input_length, Operand(capture_start, ASR, 1)); + __ Add(capture_end, input_length, Operand(capture_end, ASR, 1)); + } else { + __ Add(capture_start, input_length, capture_start); + __ Add(capture_end, input_length, capture_end); + } + // The output pointer advances for a possible global match. + __ Stp(capture_start, + capture_end, + MemOperand(output_array(), kPointerSize, PostIndex)); + } + + // Only carry on if there are more than kNumCachedRegisters capture + // registers. + int num_registers_left_on_stack = + num_saved_registers_ - kNumCachedRegisters; + if (num_registers_left_on_stack > 0) { + Register base = x10; + // There are always an even number of capture registers. A couple of + // registers determine one match with two offsets. + ASSERT_EQ(0, num_registers_left_on_stack % 2); + __ Add(base, frame_pointer(), kFirstCaptureOnStack); + + // We can unroll the loop here, we should not unroll for less than 2 + // registers. + STATIC_ASSERT(kNumRegistersToUnroll > 2); + if (num_registers_left_on_stack <= kNumRegistersToUnroll) { + for (int i = 0; i < num_registers_left_on_stack / 2; i++) { + __ Ldp(capture_end, + capture_start, + MemOperand(base, -kPointerSize, PostIndex)); + if ((i == 0) && global_with_zero_length_check()) { + // Keep capture start for the zero-length check later. + __ Mov(first_capture_start, capture_start); + } + // Offsets need to be relative to the start of the string. + if (mode_ == UC16) { + __ Add(capture_start, + input_length, + Operand(capture_start, ASR, 1)); + __ Add(capture_end, input_length, Operand(capture_end, ASR, 1)); + } else { + __ Add(capture_start, input_length, capture_start); + __ Add(capture_end, input_length, capture_end); + } + // The output pointer advances for a possible global match. + __ Stp(capture_start, + capture_end, + MemOperand(output_array(), kPointerSize, PostIndex)); + } + } else { + Label loop, start; + __ Mov(x11, num_registers_left_on_stack); + + __ Ldp(capture_end, + capture_start, + MemOperand(base, -kPointerSize, PostIndex)); + if (global_with_zero_length_check()) { + __ Mov(first_capture_start, capture_start); + } + __ B(&start); + + __ Bind(&loop); + __ Ldp(capture_end, + capture_start, + MemOperand(base, -kPointerSize, PostIndex)); + __ Bind(&start); + if (mode_ == UC16) { + __ Add(capture_start, input_length, Operand(capture_start, ASR, 1)); + __ Add(capture_end, input_length, Operand(capture_end, ASR, 1)); + } else { + __ Add(capture_start, input_length, capture_start); + __ Add(capture_end, input_length, capture_end); + } + // The output pointer advances for a possible global match. + __ Stp(capture_start, + capture_end, + MemOperand(output_array(), kPointerSize, PostIndex)); + __ Sub(x11, x11, 2); + __ Cbnz(x11, &loop); + } + } + } + + if (global()) { + Register success_counter = w0; + Register output_size = x10; + // Restart matching if the regular expression is flagged as global. + + // Increment success counter. + __ Ldr(success_counter, MemOperand(frame_pointer(), kSuccessCounter)); + __ Add(success_counter, success_counter, 1); + __ Str(success_counter, MemOperand(frame_pointer(), kSuccessCounter)); + + // Capture results have been stored, so the number of remaining global + // output registers is reduced by the number of stored captures. + __ Ldr(output_size, MemOperand(frame_pointer(), kOutputSize)); + __ Sub(output_size, output_size, num_saved_registers_); + // Check whether we have enough room for another set of capture results. + __ Cmp(output_size, num_saved_registers_); + __ B(lt, &return_w0); + + // The output pointer is already set to the next field in the output + // array. + // Update output size on the frame before we restart matching. + __ Str(output_size, MemOperand(frame_pointer(), kOutputSize)); + + if (global_with_zero_length_check()) { + // Special case for zero-length matches. + __ Cmp(current_input_offset(), first_capture_start); + // Not a zero-length match, restart. + __ B(ne, &load_char_start_regexp); + // Offset from the end is zero if we already reached the end. + __ Cbz(current_input_offset(), &return_w0); + // Advance current position after a zero-length match. + __ Add(current_input_offset(), + current_input_offset(), + Operand((mode_ == UC16) ? 2 : 1)); + } + + __ B(&load_char_start_regexp); + } else { + __ Mov(w0, SUCCESS); + } + } + + if (exit_label_.is_linked()) { + // Exit and return w0 + __ Bind(&exit_label_); + if (global()) { + __ Ldr(w0, MemOperand(frame_pointer(), kSuccessCounter)); + } + } + + __ Bind(&return_w0); + + // Set stack pointer back to first register to retain + ASSERT(csp.Is(__ StackPointer())); + __ Mov(csp, fp); + __ AssertStackConsistency(); + + // Restore registers. + __ PopCPURegList(registers_to_retain); + + __ Ret(); + + Label exit_with_exception; + // Registers x0 to x7 are used to store the first captures, they need to be + // retained over calls to C++ code. + CPURegList cached_registers(CPURegister::kRegister, kXRegSizeInBits, 0, 7); + ASSERT((cached_registers.Count() * 2) == kNumCachedRegisters); + + if (check_preempt_label_.is_linked()) { + __ Bind(&check_preempt_label_); + SaveLinkRegister(); + // The cached registers need to be retained. + __ PushCPURegList(cached_registers); + CallCheckStackGuardState(x10); + // Returning from the regexp code restores the stack (csp <- fp) + // so we don't need to drop the link register from it before exiting. + __ Cbnz(w0, &return_w0); + // Reset the cached registers. + __ PopCPURegList(cached_registers); + RestoreLinkRegister(); + __ Ret(); + } + + if (stack_overflow_label_.is_linked()) { + __ Bind(&stack_overflow_label_); + SaveLinkRegister(); + // The cached registers need to be retained. + __ PushCPURegList(cached_registers); + // Call GrowStack(backtrack_stackpointer(), &stack_base) + __ Mov(x2, ExternalReference::isolate_address(isolate())); + __ Add(x1, frame_pointer(), kStackBase); + __ Mov(x0, backtrack_stackpointer()); + ExternalReference grow_stack = + ExternalReference::re_grow_stack(isolate()); + __ CallCFunction(grow_stack, 3); + // If return NULL, we have failed to grow the stack, and + // must exit with a stack-overflow exception. + // Returning from the regexp code restores the stack (csp <- fp) + // so we don't need to drop the link register from it before exiting. + __ Cbz(w0, &exit_with_exception); + // Otherwise use return value as new stack pointer. + __ Mov(backtrack_stackpointer(), x0); + // Reset the cached registers. + __ PopCPURegList(cached_registers); + RestoreLinkRegister(); + __ Ret(); + } + + if (exit_with_exception.is_linked()) { + __ Bind(&exit_with_exception); + __ Mov(w0, EXCEPTION); + __ B(&return_w0); + } + + CodeDesc code_desc; + masm_->GetCode(&code_desc); + Handle<Code> code = isolate()->factory()->NewCode( + code_desc, Code::ComputeFlags(Code::REGEXP), masm_->CodeObject()); + PROFILE(masm_->isolate(), RegExpCodeCreateEvent(*code, *source)); + return Handle<HeapObject>::cast(code); +} + + +void RegExpMacroAssemblerARM64::GoTo(Label* to) { + BranchOrBacktrack(al, to); +} + +void RegExpMacroAssemblerARM64::IfRegisterGE(int reg, int comparand, + Label* if_ge) { + Register to_compare = GetRegister(reg, w10); + CompareAndBranchOrBacktrack(to_compare, comparand, ge, if_ge); +} + + +void RegExpMacroAssemblerARM64::IfRegisterLT(int reg, int comparand, + Label* if_lt) { + Register to_compare = GetRegister(reg, w10); + CompareAndBranchOrBacktrack(to_compare, comparand, lt, if_lt); +} + + +void RegExpMacroAssemblerARM64::IfRegisterEqPos(int reg, Label* if_eq) { + Register to_compare = GetRegister(reg, w10); + __ Cmp(to_compare, current_input_offset()); + BranchOrBacktrack(eq, if_eq); +} + +RegExpMacroAssembler::IrregexpImplementation + RegExpMacroAssemblerARM64::Implementation() { + return kARM64Implementation; +} + + +void RegExpMacroAssemblerARM64::LoadCurrentCharacter(int cp_offset, + Label* on_end_of_input, + bool check_bounds, + int characters) { + // TODO(pielan): Make sure long strings are caught before this, and not + // just asserted in debug mode. + ASSERT(cp_offset >= -1); // ^ and \b can look behind one character. + // Be sane! (And ensure that an int32_t can be used to index the string) + ASSERT(cp_offset < (1<<30)); + if (check_bounds) { + CheckPosition(cp_offset + characters - 1, on_end_of_input); + } + LoadCurrentCharacterUnchecked(cp_offset, characters); +} + + +void RegExpMacroAssemblerARM64::PopCurrentPosition() { + Pop(current_input_offset()); +} + + +void RegExpMacroAssemblerARM64::PopRegister(int register_index) { + Pop(w10); + StoreRegister(register_index, w10); +} + + +void RegExpMacroAssemblerARM64::PushBacktrack(Label* label) { + if (label->is_bound()) { + int target = label->pos(); + __ Mov(w10, target + Code::kHeaderSize - kHeapObjectTag); + } else { + __ Adr(x10, label, MacroAssembler::kAdrFar); + __ Sub(x10, x10, code_pointer()); + if (masm_->emit_debug_code()) { + __ Cmp(x10, kWRegMask); + // The code offset has to fit in a W register. + __ Check(ls, kOffsetOutOfRange); + } + } + Push(w10); + CheckStackLimit(); +} + + +void RegExpMacroAssemblerARM64::PushCurrentPosition() { + Push(current_input_offset()); +} + + +void RegExpMacroAssemblerARM64::PushRegister(int register_index, + StackCheckFlag check_stack_limit) { + Register to_push = GetRegister(register_index, w10); + Push(to_push); + if (check_stack_limit) CheckStackLimit(); +} + + +void RegExpMacroAssemblerARM64::ReadCurrentPositionFromRegister(int reg) { + Register cached_register; + RegisterState register_state = GetRegisterState(reg); + switch (register_state) { + case STACKED: + __ Ldr(current_input_offset(), register_location(reg)); + break; + case CACHED_LSW: + cached_register = GetCachedRegister(reg); + __ Mov(current_input_offset(), cached_register.W()); + break; + case CACHED_MSW: + cached_register = GetCachedRegister(reg); + __ Lsr(current_input_offset().X(), cached_register, kWRegSizeInBits); + break; + default: + UNREACHABLE(); + break; + } +} + + +void RegExpMacroAssemblerARM64::ReadStackPointerFromRegister(int reg) { + Register read_from = GetRegister(reg, w10); + __ Ldr(x11, MemOperand(frame_pointer(), kStackBase)); + __ Add(backtrack_stackpointer(), x11, Operand(read_from, SXTW)); +} + + +void RegExpMacroAssemblerARM64::SetCurrentPositionFromEnd(int by) { + Label after_position; + __ Cmp(current_input_offset(), -by * char_size()); + __ B(ge, &after_position); + __ Mov(current_input_offset(), -by * char_size()); + // On RegExp code entry (where this operation is used), the character before + // the current position is expected to be already loaded. + // We have advanced the position, so it's safe to read backwards. + LoadCurrentCharacterUnchecked(-1, 1); + __ Bind(&after_position); +} + + +void RegExpMacroAssemblerARM64::SetRegister(int register_index, int to) { + ASSERT(register_index >= num_saved_registers_); // Reserved for positions! + Register set_to = wzr; + if (to != 0) { + set_to = w10; + __ Mov(set_to, to); + } + StoreRegister(register_index, set_to); +} + + +bool RegExpMacroAssemblerARM64::Succeed() { + __ B(&success_label_); + return global(); +} + + +void RegExpMacroAssemblerARM64::WriteCurrentPositionToRegister(int reg, + int cp_offset) { + Register position = current_input_offset(); + if (cp_offset != 0) { + position = w10; + __ Add(position, current_input_offset(), cp_offset * char_size()); + } + StoreRegister(reg, position); +} + + +void RegExpMacroAssemblerARM64::ClearRegisters(int reg_from, int reg_to) { + ASSERT(reg_from <= reg_to); + int num_registers = reg_to - reg_from + 1; + + // If the first capture register is cached in a hardware register but not + // aligned on a 64-bit one, we need to clear the first one specifically. + if ((reg_from < kNumCachedRegisters) && ((reg_from % 2) != 0)) { + StoreRegister(reg_from, non_position_value()); + num_registers--; + reg_from++; + } + + // Clear cached registers in pairs as far as possible. + while ((num_registers >= 2) && (reg_from < kNumCachedRegisters)) { + ASSERT(GetRegisterState(reg_from) == CACHED_LSW); + __ Mov(GetCachedRegister(reg_from), twice_non_position_value()); + reg_from += 2; + num_registers -= 2; + } + + if ((num_registers % 2) == 1) { + StoreRegister(reg_from, non_position_value()); + num_registers--; + reg_from++; + } + + if (num_registers > 0) { + // If there are some remaining registers, they are stored on the stack. + ASSERT(reg_from >= kNumCachedRegisters); + + // Move down the indexes of the registers on stack to get the correct offset + // in memory. + reg_from -= kNumCachedRegisters; + reg_to -= kNumCachedRegisters; + // We should not unroll the loop for less than 2 registers. + STATIC_ASSERT(kNumRegistersToUnroll > 2); + // We position the base pointer to (reg_from + 1). + int base_offset = kFirstRegisterOnStack - + kWRegSize - (kWRegSize * reg_from); + if (num_registers > kNumRegistersToUnroll) { + Register base = x10; + __ Add(base, frame_pointer(), base_offset); + + Label loop; + __ Mov(x11, num_registers); + __ Bind(&loop); + __ Str(twice_non_position_value(), + MemOperand(base, -kPointerSize, PostIndex)); + __ Sub(x11, x11, 2); + __ Cbnz(x11, &loop); + } else { + for (int i = reg_from; i <= reg_to; i += 2) { + __ Str(twice_non_position_value(), + MemOperand(frame_pointer(), base_offset)); + base_offset -= kWRegSize * 2; + } + } + } +} + + +void RegExpMacroAssemblerARM64::WriteStackPointerToRegister(int reg) { + __ Ldr(x10, MemOperand(frame_pointer(), kStackBase)); + __ Sub(x10, backtrack_stackpointer(), x10); + if (masm_->emit_debug_code()) { + __ Cmp(x10, Operand(w10, SXTW)); + // The stack offset needs to fit in a W register. + __ Check(eq, kOffsetOutOfRange); + } + StoreRegister(reg, w10); +} + + +// Helper function for reading a value out of a stack frame. +template <typename T> +static T& frame_entry(Address re_frame, int frame_offset) { + return *reinterpret_cast<T*>(re_frame + frame_offset); +} + + +int RegExpMacroAssemblerARM64::CheckStackGuardState(Address* return_address, + Code* re_code, + Address re_frame, + int start_offset, + const byte** input_start, + const byte** input_end) { + Isolate* isolate = frame_entry<Isolate*>(re_frame, kIsolate); + StackLimitCheck check(isolate); + if (check.JsHasOverflowed()) { + isolate->StackOverflow(); + return EXCEPTION; + } + + // If not real stack overflow the stack guard was used to interrupt + // execution for another purpose. + + // If this is a direct call from JavaScript retry the RegExp forcing the call + // through the runtime system. Currently the direct call cannot handle a GC. + if (frame_entry<int>(re_frame, kDirectCall) == 1) { + return RETRY; + } + + // Prepare for possible GC. + HandleScope handles(isolate); + Handle<Code> code_handle(re_code); + + Handle<String> subject(frame_entry<String*>(re_frame, kInput)); + + // Current string. + bool is_ascii = subject->IsOneByteRepresentationUnderneath(); + + ASSERT(re_code->instruction_start() <= *return_address); + ASSERT(*return_address <= + re_code->instruction_start() + re_code->instruction_size()); + + Object* result = isolate->stack_guard()->HandleInterrupts(); + + if (*code_handle != re_code) { // Return address no longer valid + int delta = code_handle->address() - re_code->address(); + // Overwrite the return address on the stack. + *return_address += delta; + } + + if (result->IsException()) { + return EXCEPTION; + } + + Handle<String> subject_tmp = subject; + int slice_offset = 0; + + // Extract the underlying string and the slice offset. + if (StringShape(*subject_tmp).IsCons()) { + subject_tmp = Handle<String>(ConsString::cast(*subject_tmp)->first()); + } else if (StringShape(*subject_tmp).IsSliced()) { + SlicedString* slice = SlicedString::cast(*subject_tmp); + subject_tmp = Handle<String>(slice->parent()); + slice_offset = slice->offset(); + } + + // String might have changed. + if (subject_tmp->IsOneByteRepresentation() != is_ascii) { + // If we changed between an ASCII and an UC16 string, the specialized + // code cannot be used, and we need to restart regexp matching from + // scratch (including, potentially, compiling a new version of the code). + return RETRY; + } + + // Otherwise, the content of the string might have moved. It must still + // be a sequential or external string with the same content. + // Update the start and end pointers in the stack frame to the current + // location (whether it has actually moved or not). + ASSERT(StringShape(*subject_tmp).IsSequential() || + StringShape(*subject_tmp).IsExternal()); + + // The original start address of the characters to match. + const byte* start_address = *input_start; + + // Find the current start address of the same character at the current string + // position. + const byte* new_address = StringCharacterPosition(*subject_tmp, + start_offset + slice_offset); + + if (start_address != new_address) { + // If there is a difference, update the object pointer and start and end + // addresses in the RegExp stack frame to match the new value. + const byte* end_address = *input_end; + int byte_length = static_cast<int>(end_address - start_address); + frame_entry<const String*>(re_frame, kInput) = *subject; + *input_start = new_address; + *input_end = new_address + byte_length; + } else if (frame_entry<const String*>(re_frame, kInput) != *subject) { + // Subject string might have been a ConsString that underwent + // short-circuiting during GC. That will not change start_address but + // will change pointer inside the subject handle. + frame_entry<const String*>(re_frame, kInput) = *subject; + } + + return 0; +} + + +void RegExpMacroAssemblerARM64::CheckPosition(int cp_offset, + Label* on_outside_input) { + CompareAndBranchOrBacktrack(current_input_offset(), + -cp_offset * char_size(), + ge, + on_outside_input); +} + + +bool RegExpMacroAssemblerARM64::CanReadUnaligned() { + // TODO(pielan): See whether or not we should disable unaligned accesses. + return !slow_safe(); +} + + +// Private methods: + +void RegExpMacroAssemblerARM64::CallCheckStackGuardState(Register scratch) { + // Allocate space on the stack to store the return address. The + // CheckStackGuardState C++ function will override it if the code + // moved. Allocate extra space for 2 arguments passed by pointers. + // AAPCS64 requires the stack to be 16 byte aligned. + int alignment = masm_->ActivationFrameAlignment(); + ASSERT_EQ(alignment % 16, 0); + int align_mask = (alignment / kXRegSize) - 1; + int xreg_to_claim = (3 + align_mask) & ~align_mask; + + ASSERT(csp.Is(__ StackPointer())); + __ Claim(xreg_to_claim); + + // CheckStackGuardState needs the end and start addresses of the input string. + __ Poke(input_end(), 2 * kPointerSize); + __ Add(x5, csp, 2 * kPointerSize); + __ Poke(input_start(), kPointerSize); + __ Add(x4, csp, kPointerSize); + + __ Mov(w3, start_offset()); + // RegExp code frame pointer. + __ Mov(x2, frame_pointer()); + // Code* of self. + __ Mov(x1, Operand(masm_->CodeObject())); + + // We need to pass a pointer to the return address as first argument. + // The DirectCEntry stub will place the return address on the stack before + // calling so the stack pointer will point to it. + __ Mov(x0, csp); + + ExternalReference check_stack_guard_state = + ExternalReference::re_check_stack_guard_state(isolate()); + __ Mov(scratch, check_stack_guard_state); + DirectCEntryStub stub(isolate()); + stub.GenerateCall(masm_, scratch); + + // The input string may have been moved in memory, we need to reload it. + __ Peek(input_start(), kPointerSize); + __ Peek(input_end(), 2 * kPointerSize); + + ASSERT(csp.Is(__ StackPointer())); + __ Drop(xreg_to_claim); + + // Reload the Code pointer. + __ Mov(code_pointer(), Operand(masm_->CodeObject())); +} + +void RegExpMacroAssemblerARM64::BranchOrBacktrack(Condition condition, + Label* to) { + if (condition == al) { // Unconditional. + if (to == NULL) { + Backtrack(); + return; + } + __ B(to); + return; + } + if (to == NULL) { + to = &backtrack_label_; + } + // TODO(ulan): do direct jump when jump distance is known and fits in imm19. + Condition inverted_condition = NegateCondition(condition); + Label no_branch; + __ B(inverted_condition, &no_branch); + __ B(to); + __ Bind(&no_branch); +} + +void RegExpMacroAssemblerARM64::CompareAndBranchOrBacktrack(Register reg, + int immediate, + Condition condition, + Label* to) { + if ((immediate == 0) && ((condition == eq) || (condition == ne))) { + if (to == NULL) { + to = &backtrack_label_; + } + // TODO(ulan): do direct jump when jump distance is known and fits in imm19. + Label no_branch; + if (condition == eq) { + __ Cbnz(reg, &no_branch); + } else { + __ Cbz(reg, &no_branch); + } + __ B(to); + __ Bind(&no_branch); + } else { + __ Cmp(reg, immediate); + BranchOrBacktrack(condition, to); + } +} + + +void RegExpMacroAssemblerARM64::CheckPreemption() { + // Check for preemption. + ExternalReference stack_limit = + ExternalReference::address_of_stack_limit(isolate()); + __ Mov(x10, stack_limit); + __ Ldr(x10, MemOperand(x10)); + ASSERT(csp.Is(__ StackPointer())); + __ Cmp(csp, x10); + CallIf(&check_preempt_label_, ls); +} + + +void RegExpMacroAssemblerARM64::CheckStackLimit() { + ExternalReference stack_limit = + ExternalReference::address_of_regexp_stack_limit(isolate()); + __ Mov(x10, stack_limit); + __ Ldr(x10, MemOperand(x10)); + __ Cmp(backtrack_stackpointer(), x10); + CallIf(&stack_overflow_label_, ls); +} + + +void RegExpMacroAssemblerARM64::Push(Register source) { + ASSERT(source.Is32Bits()); + ASSERT(!source.is(backtrack_stackpointer())); + __ Str(source, + MemOperand(backtrack_stackpointer(), + -static_cast<int>(kWRegSize), + PreIndex)); +} + + +void RegExpMacroAssemblerARM64::Pop(Register target) { + ASSERT(target.Is32Bits()); + ASSERT(!target.is(backtrack_stackpointer())); + __ Ldr(target, + MemOperand(backtrack_stackpointer(), kWRegSize, PostIndex)); +} + + +Register RegExpMacroAssemblerARM64::GetCachedRegister(int register_index) { + ASSERT(register_index < kNumCachedRegisters); + return Register::Create(register_index / 2, kXRegSizeInBits); +} + + +Register RegExpMacroAssemblerARM64::GetRegister(int register_index, + Register maybe_result) { + ASSERT(maybe_result.Is32Bits()); + ASSERT(register_index >= 0); + if (num_registers_ <= register_index) { + num_registers_ = register_index + 1; + } + Register result; + RegisterState register_state = GetRegisterState(register_index); + switch (register_state) { + case STACKED: + __ Ldr(maybe_result, register_location(register_index)); + result = maybe_result; + break; + case CACHED_LSW: + result = GetCachedRegister(register_index).W(); + break; + case CACHED_MSW: + __ Lsr(maybe_result.X(), GetCachedRegister(register_index), + kWRegSizeInBits); + result = maybe_result; + break; + default: + UNREACHABLE(); + break; + } + ASSERT(result.Is32Bits()); + return result; +} + + +void RegExpMacroAssemblerARM64::StoreRegister(int register_index, + Register source) { + ASSERT(source.Is32Bits()); + ASSERT(register_index >= 0); + if (num_registers_ <= register_index) { + num_registers_ = register_index + 1; + } + + Register cached_register; + RegisterState register_state = GetRegisterState(register_index); + switch (register_state) { + case STACKED: + __ Str(source, register_location(register_index)); + break; + case CACHED_LSW: + cached_register = GetCachedRegister(register_index); + if (!source.Is(cached_register.W())) { + __ Bfi(cached_register, source.X(), 0, kWRegSizeInBits); + } + break; + case CACHED_MSW: + cached_register = GetCachedRegister(register_index); + __ Bfi(cached_register, source.X(), kWRegSizeInBits, kWRegSizeInBits); + break; + default: + UNREACHABLE(); + break; + } +} + + +void RegExpMacroAssemblerARM64::CallIf(Label* to, Condition condition) { + Label skip_call; + if (condition != al) __ B(&skip_call, NegateCondition(condition)); + __ Bl(to); + __ Bind(&skip_call); +} + + +void RegExpMacroAssemblerARM64::RestoreLinkRegister() { + ASSERT(csp.Is(__ StackPointer())); + __ Pop(lr, xzr); + __ Add(lr, lr, Operand(masm_->CodeObject())); +} + + +void RegExpMacroAssemblerARM64::SaveLinkRegister() { + ASSERT(csp.Is(__ StackPointer())); + __ Sub(lr, lr, Operand(masm_->CodeObject())); + __ Push(xzr, lr); +} + + +MemOperand RegExpMacroAssemblerARM64::register_location(int register_index) { + ASSERT(register_index < (1<<30)); + ASSERT(register_index >= kNumCachedRegisters); + if (num_registers_ <= register_index) { + num_registers_ = register_index + 1; + } + register_index -= kNumCachedRegisters; + int offset = kFirstRegisterOnStack - register_index * kWRegSize; + return MemOperand(frame_pointer(), offset); +} + +MemOperand RegExpMacroAssemblerARM64::capture_location(int register_index, + Register scratch) { + ASSERT(register_index < (1<<30)); + ASSERT(register_index < num_saved_registers_); + ASSERT(register_index >= kNumCachedRegisters); + ASSERT_EQ(register_index % 2, 0); + register_index -= kNumCachedRegisters; + int offset = kFirstCaptureOnStack - register_index * kWRegSize; + // capture_location is used with Stp instructions to load/store 2 registers. + // The immediate field in the encoding is limited to 7 bits (signed). + if (is_int7(offset)) { + return MemOperand(frame_pointer(), offset); + } else { + __ Add(scratch, frame_pointer(), offset); + return MemOperand(scratch); + } +} + +void RegExpMacroAssemblerARM64::LoadCurrentCharacterUnchecked(int cp_offset, + int characters) { + Register offset = current_input_offset(); + + // The ldr, str, ldrh, strh instructions can do unaligned accesses, if the CPU + // and the operating system running on the target allow it. + // If unaligned load/stores are not supported then this function must only + // be used to load a single character at a time. + + // ARMv8 supports unaligned accesses but V8 or the kernel can decide to + // disable it. + // TODO(pielan): See whether or not we should disable unaligned accesses. + if (!CanReadUnaligned()) { + ASSERT(characters == 1); + } + + if (cp_offset != 0) { + if (masm_->emit_debug_code()) { + __ Mov(x10, cp_offset * char_size()); + __ Add(x10, x10, Operand(current_input_offset(), SXTW)); + __ Cmp(x10, Operand(w10, SXTW)); + // The offset needs to fit in a W register. + __ Check(eq, kOffsetOutOfRange); + } else { + __ Add(w10, current_input_offset(), cp_offset * char_size()); + } + offset = w10; + } + + if (mode_ == ASCII) { + if (characters == 4) { + __ Ldr(current_character(), MemOperand(input_end(), offset, SXTW)); + } else if (characters == 2) { + __ Ldrh(current_character(), MemOperand(input_end(), offset, SXTW)); + } else { + ASSERT(characters == 1); + __ Ldrb(current_character(), MemOperand(input_end(), offset, SXTW)); + } + } else { + ASSERT(mode_ == UC16); + if (characters == 2) { + __ Ldr(current_character(), MemOperand(input_end(), offset, SXTW)); + } else { + ASSERT(characters == 1); + __ Ldrh(current_character(), MemOperand(input_end(), offset, SXTW)); + } + } +} + +#endif // V8_INTERPRETED_REGEXP + +}} // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/regexp-macro-assembler-arm64.h b/chromium/v8/src/arm64/regexp-macro-assembler-arm64.h new file mode 100644 index 00000000000..c319eae3c59 --- /dev/null +++ b/chromium/v8/src/arm64/regexp-macro-assembler-arm64.h @@ -0,0 +1,292 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_REGEXP_MACRO_ASSEMBLER_ARM64_H_ +#define V8_ARM64_REGEXP_MACRO_ASSEMBLER_ARM64_H_ + +#include "src/arm64/assembler-arm64.h" +#include "src/arm64/assembler-arm64-inl.h" +#include "src/macro-assembler.h" + +namespace v8 { +namespace internal { + + +#ifndef V8_INTERPRETED_REGEXP +class RegExpMacroAssemblerARM64: public NativeRegExpMacroAssembler { + public: + RegExpMacroAssemblerARM64(Mode mode, int registers_to_save, Zone* zone); + virtual ~RegExpMacroAssemblerARM64(); + virtual int stack_limit_slack(); + virtual void AdvanceCurrentPosition(int by); + virtual void AdvanceRegister(int reg, int by); + virtual void Backtrack(); + virtual void Bind(Label* label); + virtual void CheckAtStart(Label* on_at_start); + virtual void CheckCharacter(unsigned c, Label* on_equal); + virtual void CheckCharacterAfterAnd(unsigned c, + unsigned mask, + Label* on_equal); + virtual void CheckCharacterGT(uc16 limit, Label* on_greater); + virtual void CheckCharacterLT(uc16 limit, Label* on_less); + virtual void CheckCharacters(Vector<const uc16> str, + int cp_offset, + Label* on_failure, + bool check_end_of_string); + // A "greedy loop" is a loop that is both greedy and with a simple + // body. It has a particularly simple implementation. + virtual void CheckGreedyLoop(Label* on_tos_equals_current_position); + virtual void CheckNotAtStart(Label* on_not_at_start); + virtual void CheckNotBackReference(int start_reg, Label* on_no_match); + virtual void CheckNotBackReferenceIgnoreCase(int start_reg, + Label* on_no_match); + virtual void CheckNotCharacter(unsigned c, Label* on_not_equal); + virtual void CheckNotCharacterAfterAnd(unsigned c, + unsigned mask, + Label* on_not_equal); + virtual void CheckNotCharacterAfterMinusAnd(uc16 c, + uc16 minus, + uc16 mask, + Label* on_not_equal); + virtual void CheckCharacterInRange(uc16 from, + uc16 to, + Label* on_in_range); + virtual void CheckCharacterNotInRange(uc16 from, + uc16 to, + Label* on_not_in_range); + virtual void CheckBitInTable(Handle<ByteArray> table, Label* on_bit_set); + + // Checks whether the given offset from the current position is before + // the end of the string. + virtual void CheckPosition(int cp_offset, Label* on_outside_input); + virtual bool CheckSpecialCharacterClass(uc16 type, + Label* on_no_match); + virtual void Fail(); + virtual Handle<HeapObject> GetCode(Handle<String> source); + virtual void GoTo(Label* label); + virtual void IfRegisterGE(int reg, int comparand, Label* if_ge); + virtual void IfRegisterLT(int reg, int comparand, Label* if_lt); + virtual void IfRegisterEqPos(int reg, Label* if_eq); + virtual IrregexpImplementation Implementation(); + virtual void LoadCurrentCharacter(int cp_offset, + Label* on_end_of_input, + bool check_bounds = true, + int characters = 1); + virtual void PopCurrentPosition(); + virtual void PopRegister(int register_index); + virtual void PushBacktrack(Label* label); + virtual void PushCurrentPosition(); + virtual void PushRegister(int register_index, + StackCheckFlag check_stack_limit); + virtual void ReadCurrentPositionFromRegister(int reg); + virtual void ReadStackPointerFromRegister(int reg); + virtual void SetCurrentPositionFromEnd(int by); + virtual void SetRegister(int register_index, int to); + virtual bool Succeed(); + virtual void WriteCurrentPositionToRegister(int reg, int cp_offset); + virtual void ClearRegisters(int reg_from, int reg_to); + virtual void WriteStackPointerToRegister(int reg); + virtual bool CanReadUnaligned(); + + // Called from RegExp if the stack-guard is triggered. + // If the code object is relocated, the return address is fixed before + // returning. + static int CheckStackGuardState(Address* return_address, + Code* re_code, + Address re_frame, + int start_offset, + const byte** input_start, + const byte** input_end); + + private: + // Above the frame pointer - Stored registers and stack passed parameters. + // Callee-saved registers x19-x29, where x29 is the old frame pointer. + static const int kCalleeSavedRegisters = 0; + // Return address. + // It is placed above the 11 callee-saved registers. + static const int kReturnAddress = kCalleeSavedRegisters + 11 * kPointerSize; + static const int kSecondaryReturnAddress = kReturnAddress + kPointerSize; + // Stack parameter placed by caller. + static const int kIsolate = kSecondaryReturnAddress + kPointerSize; + + // Below the frame pointer. + // Register parameters stored by setup code. + static const int kDirectCall = kCalleeSavedRegisters - kPointerSize; + static const int kStackBase = kDirectCall - kPointerSize; + static const int kOutputSize = kStackBase - kPointerSize; + static const int kInput = kOutputSize - kPointerSize; + // When adding local variables remember to push space for them in + // the frame in GetCode. + static const int kSuccessCounter = kInput - kPointerSize; + // First position register address on the stack. Following positions are + // below it. A position is a 32 bit value. + static const int kFirstRegisterOnStack = kSuccessCounter - kWRegSize; + // A capture is a 64 bit value holding two position. + static const int kFirstCaptureOnStack = kSuccessCounter - kXRegSize; + + // Initial size of code buffer. + static const size_t kRegExpCodeSize = 1024; + + // When initializing registers to a non-position value we can unroll + // the loop. Set the limit of registers to unroll. + static const int kNumRegistersToUnroll = 16; + + // We are using x0 to x7 as a register cache. Each hardware register must + // contain one capture, that is two 32 bit registers. We can cache at most + // 16 registers. + static const int kNumCachedRegisters = 16; + + // Load a number of characters at the given offset from the + // current position, into the current-character register. + void LoadCurrentCharacterUnchecked(int cp_offset, int character_count); + + // Check whether preemption has been requested. + void CheckPreemption(); + + // Check whether we are exceeding the stack limit on the backtrack stack. + void CheckStackLimit(); + + // Generate a call to CheckStackGuardState. + void CallCheckStackGuardState(Register scratch); + + // Location of a 32 bit position register. + MemOperand register_location(int register_index); + + // Location of a 64 bit capture, combining two position registers. + MemOperand capture_location(int register_index, Register scratch); + + // Register holding the current input position as negative offset from + // the end of the string. + Register current_input_offset() { return w21; } + + // The register containing the current character after LoadCurrentCharacter. + Register current_character() { return w22; } + + // Register holding address of the end of the input string. + Register input_end() { return x25; } + + // Register holding address of the start of the input string. + Register input_start() { return x26; } + + // Register holding the offset from the start of the string where we should + // start matching. + Register start_offset() { return w27; } + + // Pointer to the output array's first element. + Register output_array() { return x28; } + + // Register holding the frame address. Local variables, parameters and + // regexp registers are addressed relative to this. + Register frame_pointer() { return fp; } + + // The register containing the backtrack stack top. Provides a meaningful + // name to the register. + Register backtrack_stackpointer() { return x23; } + + // Register holding pointer to the current code object. + Register code_pointer() { return x20; } + + // Register holding the value used for clearing capture registers. + Register non_position_value() { return w24; } + // The top 32 bit of this register is used to store this value + // twice. This is used for clearing more than one register at a time. + Register twice_non_position_value() { return x24; } + + // Byte size of chars in the string to match (decided by the Mode argument) + int char_size() { return static_cast<int>(mode_); } + + // Equivalent to a conditional branch to the label, unless the label + // is NULL, in which case it is a conditional Backtrack. + void BranchOrBacktrack(Condition condition, Label* to); + + // Compares reg against immmediate before calling BranchOrBacktrack. + // It makes use of the Cbz and Cbnz instructions. + void CompareAndBranchOrBacktrack(Register reg, + int immediate, + Condition condition, + Label* to); + + inline void CallIf(Label* to, Condition condition); + + // Save and restore the link register on the stack in a way that + // is GC-safe. + inline void SaveLinkRegister(); + inline void RestoreLinkRegister(); + + // Pushes the value of a register on the backtrack stack. Decrements the + // stack pointer by a word size and stores the register's value there. + inline void Push(Register source); + + // Pops a value from the backtrack stack. Reads the word at the stack pointer + // and increments it by a word size. + inline void Pop(Register target); + + // This state indicates where the register actually is. + enum RegisterState { + STACKED, // Resides in memory. + CACHED_LSW, // Least Significant Word of a 64 bit hardware register. + CACHED_MSW // Most Significant Word of a 64 bit hardware register. + }; + + RegisterState GetRegisterState(int register_index) { + ASSERT(register_index >= 0); + if (register_index >= kNumCachedRegisters) { + return STACKED; + } else { + if ((register_index % 2) == 0) { + return CACHED_LSW; + } else { + return CACHED_MSW; + } + } + } + + // Store helper that takes the state of the register into account. + inline void StoreRegister(int register_index, Register source); + + // Returns a hardware W register that holds the value of the capture + // register. + // + // This function will try to use an existing cache register (w0-w7) for the + // result. Otherwise, it will load the value into maybe_result. + // + // If the returned register is anything other than maybe_result, calling code + // must not write to it. + inline Register GetRegister(int register_index, Register maybe_result); + + // Returns the harware register (x0-x7) holding the value of the capture + // register. + // This assumes that the state of the register is not STACKED. + inline Register GetCachedRegister(int register_index); + + Isolate* isolate() const { return masm_->isolate(); } + + MacroAssembler* masm_; + + // Which mode to generate code for (ASCII or UC16). + Mode mode_; + + // One greater than maximal register index actually used. + int num_registers_; + + // Number of registers to output at the end (the saved registers + // are always 0..num_saved_registers_-1) + int num_saved_registers_; + + // Labels used internally. + Label entry_label_; + Label start_label_; + Label success_label_; + Label backtrack_label_; + Label exit_label_; + Label check_preempt_label_; + Label stack_overflow_label_; +}; + +#endif // V8_INTERPRETED_REGEXP + + +}} // namespace v8::internal + +#endif // V8_ARM64_REGEXP_MACRO_ASSEMBLER_ARM64_H_ diff --git a/chromium/v8/src/arm64/simulator-arm64.cc b/chromium/v8/src/arm64/simulator-arm64.cc new file mode 100644 index 00000000000..488b91e6235 --- /dev/null +++ b/chromium/v8/src/arm64/simulator-arm64.cc @@ -0,0 +1,3736 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdlib.h> +#include <cmath> +#include <cstdarg> +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/disasm.h" +#include "src/assembler.h" +#include "src/arm64/decoder-arm64-inl.h" +#include "src/arm64/simulator-arm64.h" +#include "src/macro-assembler.h" + +namespace v8 { +namespace internal { + +#if defined(USE_SIMULATOR) + + +// This macro provides a platform independent use of sscanf. The reason for +// SScanF not being implemented in a platform independent way through +// ::v8::internal::OS in the same way as SNPrintF is that the +// Windows C Run-Time Library does not provide vsscanf. +#define SScanF sscanf // NOLINT + + +// Helpers for colors. +// Depending on your terminal configuration, the colour names may not match the +// observed colours. +#define COLOUR(colour_code) "\033[" colour_code "m" +#define BOLD(colour_code) "1;" colour_code +#define NORMAL "" +#define GREY "30" +#define GREEN "32" +#define ORANGE "33" +#define BLUE "34" +#define PURPLE "35" +#define INDIGO "36" +#define WHITE "37" +typedef char const * const TEXT_COLOUR; +TEXT_COLOUR clr_normal = FLAG_log_colour ? COLOUR(NORMAL) : ""; +TEXT_COLOUR clr_flag_name = FLAG_log_colour ? COLOUR(BOLD(GREY)) : ""; +TEXT_COLOUR clr_flag_value = FLAG_log_colour ? COLOUR(BOLD(WHITE)) : ""; +TEXT_COLOUR clr_reg_name = FLAG_log_colour ? COLOUR(BOLD(BLUE)) : ""; +TEXT_COLOUR clr_reg_value = FLAG_log_colour ? COLOUR(BOLD(INDIGO)) : ""; +TEXT_COLOUR clr_fpreg_name = FLAG_log_colour ? COLOUR(BOLD(ORANGE)) : ""; +TEXT_COLOUR clr_fpreg_value = FLAG_log_colour ? COLOUR(BOLD(PURPLE)) : ""; +TEXT_COLOUR clr_memory_value = FLAG_log_colour ? COLOUR(BOLD(GREEN)) : ""; +TEXT_COLOUR clr_memory_address = FLAG_log_colour ? COLOUR(GREEN) : ""; +TEXT_COLOUR clr_debug_number = FLAG_log_colour ? COLOUR(BOLD(ORANGE)) : ""; +TEXT_COLOUR clr_debug_message = FLAG_log_colour ? COLOUR(ORANGE) : ""; +TEXT_COLOUR clr_printf = FLAG_log_colour ? COLOUR(GREEN) : ""; + + +// This is basically the same as PrintF, with a guard for FLAG_trace_sim. +void Simulator::TraceSim(const char* format, ...) { + if (FLAG_trace_sim) { + va_list arguments; + va_start(arguments, format); + OS::VFPrint(stream_, format, arguments); + va_end(arguments); + } +} + + +const Instruction* Simulator::kEndOfSimAddress = NULL; + + +void SimSystemRegister::SetBits(int msb, int lsb, uint32_t bits) { + int width = msb - lsb + 1; + ASSERT(is_uintn(bits, width) || is_intn(bits, width)); + + bits <<= lsb; + uint32_t mask = ((1 << width) - 1) << lsb; + ASSERT((mask & write_ignore_mask_) == 0); + + value_ = (value_ & ~mask) | (bits & mask); +} + + +SimSystemRegister SimSystemRegister::DefaultValueFor(SystemRegister id) { + switch (id) { + case NZCV: + return SimSystemRegister(0x00000000, NZCVWriteIgnoreMask); + case FPCR: + return SimSystemRegister(0x00000000, FPCRWriteIgnoreMask); + default: + UNREACHABLE(); + return SimSystemRegister(); + } +} + + +void Simulator::Initialize(Isolate* isolate) { + if (isolate->simulator_initialized()) return; + isolate->set_simulator_initialized(true); + ExternalReference::set_redirector(isolate, &RedirectExternalReference); +} + + +// Get the active Simulator for the current thread. +Simulator* Simulator::current(Isolate* isolate) { + Isolate::PerIsolateThreadData* isolate_data = + isolate->FindOrAllocatePerThreadDataForThisThread(); + ASSERT(isolate_data != NULL); + + Simulator* sim = isolate_data->simulator(); + if (sim == NULL) { + if (FLAG_trace_sim || FLAG_log_instruction_stats || FLAG_debug_sim) { + sim = new Simulator(new Decoder<DispatchingDecoderVisitor>(), isolate); + } else { + sim = new Decoder<Simulator>(); + sim->isolate_ = isolate; + } + isolate_data->set_simulator(sim); + } + return sim; +} + + +void Simulator::CallVoid(byte* entry, CallArgument* args) { + int index_x = 0; + int index_d = 0; + + std::vector<int64_t> stack_args(0); + for (int i = 0; !args[i].IsEnd(); i++) { + CallArgument arg = args[i]; + if (arg.IsX() && (index_x < 8)) { + set_xreg(index_x++, arg.bits()); + } else if (arg.IsD() && (index_d < 8)) { + set_dreg_bits(index_d++, arg.bits()); + } else { + ASSERT(arg.IsD() || arg.IsX()); + stack_args.push_back(arg.bits()); + } + } + + // Process stack arguments, and make sure the stack is suitably aligned. + uintptr_t original_stack = sp(); + uintptr_t entry_stack = original_stack - + stack_args.size() * sizeof(stack_args[0]); + if (OS::ActivationFrameAlignment() != 0) { + entry_stack &= -OS::ActivationFrameAlignment(); + } + char * stack = reinterpret_cast<char*>(entry_stack); + std::vector<int64_t>::const_iterator it; + for (it = stack_args.begin(); it != stack_args.end(); it++) { + memcpy(stack, &(*it), sizeof(*it)); + stack += sizeof(*it); + } + + ASSERT(reinterpret_cast<uintptr_t>(stack) <= original_stack); + set_sp(entry_stack); + + // Call the generated code. + set_pc(entry); + set_lr(kEndOfSimAddress); + CheckPCSComplianceAndRun(); + + set_sp(original_stack); +} + + +int64_t Simulator::CallInt64(byte* entry, CallArgument* args) { + CallVoid(entry, args); + return xreg(0); +} + + +double Simulator::CallDouble(byte* entry, CallArgument* args) { + CallVoid(entry, args); + return dreg(0); +} + + +int64_t Simulator::CallJS(byte* entry, + byte* function_entry, + JSFunction* func, + Object* revc, + int64_t argc, + Object*** argv) { + CallArgument args[] = { + CallArgument(function_entry), + CallArgument(func), + CallArgument(revc), + CallArgument(argc), + CallArgument(argv), + CallArgument::End() + }; + return CallInt64(entry, args); +} + +int64_t Simulator::CallRegExp(byte* entry, + String* input, + int64_t start_offset, + const byte* input_start, + const byte* input_end, + int* output, + int64_t output_size, + Address stack_base, + int64_t direct_call, + void* return_address, + Isolate* isolate) { + CallArgument args[] = { + CallArgument(input), + CallArgument(start_offset), + CallArgument(input_start), + CallArgument(input_end), + CallArgument(output), + CallArgument(output_size), + CallArgument(stack_base), + CallArgument(direct_call), + CallArgument(return_address), + CallArgument(isolate), + CallArgument::End() + }; + return CallInt64(entry, args); +} + + +void Simulator::CheckPCSComplianceAndRun() { +#ifdef DEBUG + CHECK_EQ(kNumberOfCalleeSavedRegisters, kCalleeSaved.Count()); + CHECK_EQ(kNumberOfCalleeSavedFPRegisters, kCalleeSavedFP.Count()); + + int64_t saved_registers[kNumberOfCalleeSavedRegisters]; + uint64_t saved_fpregisters[kNumberOfCalleeSavedFPRegisters]; + + CPURegList register_list = kCalleeSaved; + CPURegList fpregister_list = kCalleeSavedFP; + + for (int i = 0; i < kNumberOfCalleeSavedRegisters; i++) { + // x31 is not a caller saved register, so no need to specify if we want + // the stack or zero. + saved_registers[i] = xreg(register_list.PopLowestIndex().code()); + } + for (int i = 0; i < kNumberOfCalleeSavedFPRegisters; i++) { + saved_fpregisters[i] = + dreg_bits(fpregister_list.PopLowestIndex().code()); + } + int64_t original_stack = sp(); +#endif + // Start the simulation! + Run(); +#ifdef DEBUG + CHECK_EQ(original_stack, sp()); + // Check that callee-saved registers have been preserved. + register_list = kCalleeSaved; + fpregister_list = kCalleeSavedFP; + for (int i = 0; i < kNumberOfCalleeSavedRegisters; i++) { + CHECK_EQ(saved_registers[i], xreg(register_list.PopLowestIndex().code())); + } + for (int i = 0; i < kNumberOfCalleeSavedFPRegisters; i++) { + ASSERT(saved_fpregisters[i] == + dreg_bits(fpregister_list.PopLowestIndex().code())); + } + + // Corrupt caller saved register minus the return regiters. + + // In theory x0 to x7 can be used for return values, but V8 only uses x0, x1 + // for now . + register_list = kCallerSaved; + register_list.Remove(x0); + register_list.Remove(x1); + + // In theory d0 to d7 can be used for return values, but V8 only uses d0 + // for now . + fpregister_list = kCallerSavedFP; + fpregister_list.Remove(d0); + + CorruptRegisters(®ister_list, kCallerSavedRegisterCorruptionValue); + CorruptRegisters(&fpregister_list, kCallerSavedFPRegisterCorruptionValue); +#endif +} + + +#ifdef DEBUG +// The least significant byte of the curruption value holds the corresponding +// register's code. +void Simulator::CorruptRegisters(CPURegList* list, uint64_t value) { + if (list->type() == CPURegister::kRegister) { + while (!list->IsEmpty()) { + unsigned code = list->PopLowestIndex().code(); + set_xreg(code, value | code); + } + } else { + ASSERT(list->type() == CPURegister::kFPRegister); + while (!list->IsEmpty()) { + unsigned code = list->PopLowestIndex().code(); + set_dreg_bits(code, value | code); + } + } +} + + +void Simulator::CorruptAllCallerSavedCPURegisters() { + // Corrupt alters its parameter so copy them first. + CPURegList register_list = kCallerSaved; + CPURegList fpregister_list = kCallerSavedFP; + + CorruptRegisters(®ister_list, kCallerSavedRegisterCorruptionValue); + CorruptRegisters(&fpregister_list, kCallerSavedFPRegisterCorruptionValue); +} +#endif + + +// Extending the stack by 2 * 64 bits is required for stack alignment purposes. +uintptr_t Simulator::PushAddress(uintptr_t address) { + ASSERT(sizeof(uintptr_t) < 2 * kXRegSize); + intptr_t new_sp = sp() - 2 * kXRegSize; + uintptr_t* alignment_slot = + reinterpret_cast<uintptr_t*>(new_sp + kXRegSize); + memcpy(alignment_slot, &kSlotsZapValue, kPointerSize); + uintptr_t* stack_slot = reinterpret_cast<uintptr_t*>(new_sp); + memcpy(stack_slot, &address, kPointerSize); + set_sp(new_sp); + return new_sp; +} + + +uintptr_t Simulator::PopAddress() { + intptr_t current_sp = sp(); + uintptr_t* stack_slot = reinterpret_cast<uintptr_t*>(current_sp); + uintptr_t address = *stack_slot; + ASSERT(sizeof(uintptr_t) < 2 * kXRegSize); + set_sp(current_sp + 2 * kXRegSize); + return address; +} + + +// Returns the limit of the stack area to enable checking for stack overflows. +uintptr_t Simulator::StackLimit() const { + // Leave a safety margin of 1024 bytes to prevent overrunning the stack when + // pushing values. + return reinterpret_cast<uintptr_t>(stack_limit_) + 1024; +} + + +Simulator::Simulator(Decoder<DispatchingDecoderVisitor>* decoder, + Isolate* isolate, FILE* stream) + : decoder_(decoder), + last_debugger_input_(NULL), + log_parameters_(NO_PARAM), + isolate_(isolate) { + // Setup the decoder. + decoder_->AppendVisitor(this); + + Init(stream); + + if (FLAG_trace_sim) { + decoder_->InsertVisitorBefore(print_disasm_, this); + log_parameters_ = LOG_ALL; + } + + if (FLAG_log_instruction_stats) { + instrument_ = new Instrument(FLAG_log_instruction_file, + FLAG_log_instruction_period); + decoder_->AppendVisitor(instrument_); + } +} + + +Simulator::Simulator() + : decoder_(NULL), + last_debugger_input_(NULL), + log_parameters_(NO_PARAM), + isolate_(NULL) { + Init(stdout); + CHECK(!FLAG_trace_sim && !FLAG_log_instruction_stats); +} + + +void Simulator::Init(FILE* stream) { + ResetState(); + + // Allocate and setup the simulator stack. + stack_size_ = (FLAG_sim_stack_size * KB) + (2 * stack_protection_size_); + stack_ = new byte[stack_size_]; + stack_limit_ = stack_ + stack_protection_size_; + byte* tos = stack_ + stack_size_ - stack_protection_size_; + // The stack pointer must be 16 bytes aligned. + set_sp(reinterpret_cast<int64_t>(tos) & ~0xfUL); + + stream_ = stream; + print_disasm_ = new PrintDisassembler(stream_); + + // The debugger needs to disassemble code without the simulator executing an + // instruction, so we create a dedicated decoder. + disassembler_decoder_ = new Decoder<DispatchingDecoderVisitor>(); + disassembler_decoder_->AppendVisitor(print_disasm_); +} + + +void Simulator::ResetState() { + // Reset the system registers. + nzcv_ = SimSystemRegister::DefaultValueFor(NZCV); + fpcr_ = SimSystemRegister::DefaultValueFor(FPCR); + + // Reset registers to 0. + pc_ = NULL; + for (unsigned i = 0; i < kNumberOfRegisters; i++) { + set_xreg(i, 0xbadbeef); + } + for (unsigned i = 0; i < kNumberOfFPRegisters; i++) { + // Set FP registers to a value that is NaN in both 32-bit and 64-bit FP. + set_dreg_bits(i, 0x7ff000007f800001UL); + } + // Returning to address 0 exits the Simulator. + set_lr(kEndOfSimAddress); + + // Reset debug helpers. + breakpoints_.empty(); + break_on_next_= false; +} + + +Simulator::~Simulator() { + delete[] stack_; + if (FLAG_log_instruction_stats) { + delete instrument_; + } + delete disassembler_decoder_; + delete print_disasm_; + DeleteArray(last_debugger_input_); + delete decoder_; +} + + +void Simulator::Run() { + pc_modified_ = false; + while (pc_ != kEndOfSimAddress) { + ExecuteInstruction(); + } +} + + +void Simulator::RunFrom(Instruction* start) { + set_pc(start); + Run(); +} + + +// When the generated code calls an external reference we need to catch that in +// the simulator. The external reference will be a function compiled for the +// host architecture. We need to call that function instead of trying to +// execute it with the simulator. We do that by redirecting the external +// reference to a svc (Supervisor Call) instruction that is handled by +// the simulator. We write the original destination of the jump just at a known +// offset from the svc instruction so the simulator knows what to call. +class Redirection { + public: + Redirection(void* external_function, ExternalReference::Type type) + : external_function_(external_function), + type_(type), + next_(NULL) { + redirect_call_.SetInstructionBits( + HLT | Assembler::ImmException(kImmExceptionIsRedirectedCall)); + Isolate* isolate = Isolate::Current(); + next_ = isolate->simulator_redirection(); + // TODO(all): Simulator flush I cache + isolate->set_simulator_redirection(this); + } + + void* address_of_redirect_call() { + return reinterpret_cast<void*>(&redirect_call_); + } + + template <typename T> + T external_function() { return reinterpret_cast<T>(external_function_); } + + ExternalReference::Type type() { return type_; } + + static Redirection* Get(void* external_function, + ExternalReference::Type type) { + Isolate* isolate = Isolate::Current(); + Redirection* current = isolate->simulator_redirection(); + for (; current != NULL; current = current->next_) { + if (current->external_function_ == external_function) { + ASSERT_EQ(current->type(), type); + return current; + } + } + return new Redirection(external_function, type); + } + + static Redirection* FromHltInstruction(Instruction* redirect_call) { + char* addr_of_hlt = reinterpret_cast<char*>(redirect_call); + char* addr_of_redirection = + addr_of_hlt - OFFSET_OF(Redirection, redirect_call_); + return reinterpret_cast<Redirection*>(addr_of_redirection); + } + + static void* ReverseRedirection(int64_t reg) { + Redirection* redirection = + FromHltInstruction(reinterpret_cast<Instruction*>(reg)); + return redirection->external_function<void*>(); + } + + private: + void* external_function_; + Instruction redirect_call_; + ExternalReference::Type type_; + Redirection* next_; +}; + + +// Calls into the V8 runtime are based on this very simple interface. +// Note: To be able to return two values from some calls the code in runtime.cc +// uses the ObjectPair structure. +// The simulator assumes all runtime calls return two 64-bits values. If they +// don't, register x1 is clobbered. This is fine because x1 is caller-saved. +struct ObjectPair { + int64_t res0; + int64_t res1; +}; + + +typedef ObjectPair (*SimulatorRuntimeCall)(int64_t arg0, + int64_t arg1, + int64_t arg2, + int64_t arg3, + int64_t arg4, + int64_t arg5, + int64_t arg6, + int64_t arg7); + +typedef int64_t (*SimulatorRuntimeCompareCall)(double arg1, double arg2); +typedef double (*SimulatorRuntimeFPFPCall)(double arg1, double arg2); +typedef double (*SimulatorRuntimeFPCall)(double arg1); +typedef double (*SimulatorRuntimeFPIntCall)(double arg1, int32_t arg2); + +// This signature supports direct call in to API function native callback +// (refer to InvocationCallback in v8.h). +typedef void (*SimulatorRuntimeDirectApiCall)(int64_t arg0); +typedef void (*SimulatorRuntimeProfilingApiCall)(int64_t arg0, void* arg1); + +// This signature supports direct call to accessor getter callback. +typedef void (*SimulatorRuntimeDirectGetterCall)(int64_t arg0, int64_t arg1); +typedef void (*SimulatorRuntimeProfilingGetterCall)(int64_t arg0, int64_t arg1, + void* arg2); + +void Simulator::DoRuntimeCall(Instruction* instr) { + Redirection* redirection = Redirection::FromHltInstruction(instr); + + // The called C code might itself call simulated code, so any + // caller-saved registers (including lr) could still be clobbered by a + // redirected call. + Instruction* return_address = lr(); + + int64_t external = redirection->external_function<int64_t>(); + + TraceSim("Call to host function at %p\n", + redirection->external_function<void*>()); + + // SP must be 16-byte-aligned at the call interface. + bool stack_alignment_exception = ((sp() & 0xf) != 0); + if (stack_alignment_exception) { + TraceSim(" with unaligned stack 0x%016" PRIx64 ".\n", sp()); + FATAL("ALIGNMENT EXCEPTION"); + } + + switch (redirection->type()) { + default: + TraceSim("Type: Unknown.\n"); + UNREACHABLE(); + break; + + case ExternalReference::BUILTIN_CALL: { + // Object* f(v8::internal::Arguments). + TraceSim("Type: BUILTIN_CALL\n"); + SimulatorRuntimeCall target = + reinterpret_cast<SimulatorRuntimeCall>(external); + + // We don't know how many arguments are being passed, but we can + // pass 8 without touching the stack. They will be ignored by the + // host function if they aren't used. + TraceSim("Arguments: " + "0x%016" PRIx64 ", 0x%016" PRIx64 ", " + "0x%016" PRIx64 ", 0x%016" PRIx64 ", " + "0x%016" PRIx64 ", 0x%016" PRIx64 ", " + "0x%016" PRIx64 ", 0x%016" PRIx64, + xreg(0), xreg(1), xreg(2), xreg(3), + xreg(4), xreg(5), xreg(6), xreg(7)); + ObjectPair result = target(xreg(0), xreg(1), xreg(2), xreg(3), + xreg(4), xreg(5), xreg(6), xreg(7)); + TraceSim("Returned: {0x%" PRIx64 ", 0x%" PRIx64 "}\n", + result.res0, result.res1); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + set_xreg(0, result.res0); + set_xreg(1, result.res1); + break; + } + + case ExternalReference::DIRECT_API_CALL: { + // void f(v8::FunctionCallbackInfo&) + TraceSim("Type: DIRECT_API_CALL\n"); + SimulatorRuntimeDirectApiCall target = + reinterpret_cast<SimulatorRuntimeDirectApiCall>(external); + TraceSim("Arguments: 0x%016" PRIx64 "\n", xreg(0)); + target(xreg(0)); + TraceSim("No return value."); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + break; + } + + case ExternalReference::BUILTIN_COMPARE_CALL: { + // int f(double, double) + TraceSim("Type: BUILTIN_COMPARE_CALL\n"); + SimulatorRuntimeCompareCall target = + reinterpret_cast<SimulatorRuntimeCompareCall>(external); + TraceSim("Arguments: %f, %f\n", dreg(0), dreg(1)); + int64_t result = target(dreg(0), dreg(1)); + TraceSim("Returned: %" PRId64 "\n", result); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + set_xreg(0, result); + break; + } + + case ExternalReference::BUILTIN_FP_CALL: { + // double f(double) + TraceSim("Type: BUILTIN_FP_CALL\n"); + SimulatorRuntimeFPCall target = + reinterpret_cast<SimulatorRuntimeFPCall>(external); + TraceSim("Argument: %f\n", dreg(0)); + double result = target(dreg(0)); + TraceSim("Returned: %f\n", result); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + set_dreg(0, result); + break; + } + + case ExternalReference::BUILTIN_FP_FP_CALL: { + // double f(double, double) + TraceSim("Type: BUILTIN_FP_FP_CALL\n"); + SimulatorRuntimeFPFPCall target = + reinterpret_cast<SimulatorRuntimeFPFPCall>(external); + TraceSim("Arguments: %f, %f\n", dreg(0), dreg(1)); + double result = target(dreg(0), dreg(1)); + TraceSim("Returned: %f\n", result); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + set_dreg(0, result); + break; + } + + case ExternalReference::BUILTIN_FP_INT_CALL: { + // double f(double, int) + TraceSim("Type: BUILTIN_FP_INT_CALL\n"); + SimulatorRuntimeFPIntCall target = + reinterpret_cast<SimulatorRuntimeFPIntCall>(external); + TraceSim("Arguments: %f, %d\n", dreg(0), wreg(0)); + double result = target(dreg(0), wreg(0)); + TraceSim("Returned: %f\n", result); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + set_dreg(0, result); + break; + } + + case ExternalReference::DIRECT_GETTER_CALL: { + // void f(Local<String> property, PropertyCallbackInfo& info) + TraceSim("Type: DIRECT_GETTER_CALL\n"); + SimulatorRuntimeDirectGetterCall target = + reinterpret_cast<SimulatorRuntimeDirectGetterCall>(external); + TraceSim("Arguments: 0x%016" PRIx64 ", 0x%016" PRIx64 "\n", + xreg(0), xreg(1)); + target(xreg(0), xreg(1)); + TraceSim("No return value."); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + break; + } + + case ExternalReference::PROFILING_API_CALL: { + // void f(v8::FunctionCallbackInfo&, v8::FunctionCallback) + TraceSim("Type: PROFILING_API_CALL\n"); + SimulatorRuntimeProfilingApiCall target = + reinterpret_cast<SimulatorRuntimeProfilingApiCall>(external); + void* arg1 = Redirection::ReverseRedirection(xreg(1)); + TraceSim("Arguments: 0x%016" PRIx64 ", %p\n", xreg(0), arg1); + target(xreg(0), arg1); + TraceSim("No return value."); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + break; + } + + case ExternalReference::PROFILING_GETTER_CALL: { + // void f(Local<String> property, PropertyCallbackInfo& info, + // AccessorGetterCallback callback) + TraceSim("Type: PROFILING_GETTER_CALL\n"); + SimulatorRuntimeProfilingGetterCall target = + reinterpret_cast<SimulatorRuntimeProfilingGetterCall>( + external); + void* arg2 = Redirection::ReverseRedirection(xreg(2)); + TraceSim("Arguments: 0x%016" PRIx64 ", 0x%016" PRIx64 ", %p\n", + xreg(0), xreg(1), arg2); + target(xreg(0), xreg(1), arg2); + TraceSim("No return value."); +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + break; + } + } + + set_lr(return_address); + set_pc(return_address); +} + + +void* Simulator::RedirectExternalReference(void* external_function, + ExternalReference::Type type) { + Redirection* redirection = Redirection::Get(external_function, type); + return redirection->address_of_redirect_call(); +} + + +const char* Simulator::xreg_names[] = { +"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", +"x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", +"ip0", "ip1", "x18", "x19", "x20", "x21", "x22", "x23", +"x24", "x25", "x26", "cp", "jssp", "fp", "lr", "xzr", "csp"}; + +const char* Simulator::wreg_names[] = { +"w0", "w1", "w2", "w3", "w4", "w5", "w6", "w7", +"w8", "w9", "w10", "w11", "w12", "w13", "w14", "w15", +"w16", "w17", "w18", "w19", "w20", "w21", "w22", "w23", +"w24", "w25", "w26", "wcp", "wjssp", "wfp", "wlr", "wzr", "wcsp"}; + +const char* Simulator::sreg_names[] = { +"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", +"s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", +"s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23", +"s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31"}; + +const char* Simulator::dreg_names[] = { +"d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", +"d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15", +"d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23", +"d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31"}; + +const char* Simulator::vreg_names[] = { +"v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", +"v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", +"v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", +"v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31"}; + + +const char* Simulator::WRegNameForCode(unsigned code, Reg31Mode mode) { + ASSERT(code < kNumberOfRegisters); + // If the code represents the stack pointer, index the name after zr. + if ((code == kZeroRegCode) && (mode == Reg31IsStackPointer)) { + code = kZeroRegCode + 1; + } + return wreg_names[code]; +} + + +const char* Simulator::XRegNameForCode(unsigned code, Reg31Mode mode) { + ASSERT(code < kNumberOfRegisters); + // If the code represents the stack pointer, index the name after zr. + if ((code == kZeroRegCode) && (mode == Reg31IsStackPointer)) { + code = kZeroRegCode + 1; + } + return xreg_names[code]; +} + + +const char* Simulator::SRegNameForCode(unsigned code) { + ASSERT(code < kNumberOfFPRegisters); + return sreg_names[code]; +} + + +const char* Simulator::DRegNameForCode(unsigned code) { + ASSERT(code < kNumberOfFPRegisters); + return dreg_names[code]; +} + + +const char* Simulator::VRegNameForCode(unsigned code) { + ASSERT(code < kNumberOfFPRegisters); + return vreg_names[code]; +} + + +int Simulator::CodeFromName(const char* name) { + for (unsigned i = 0; i < kNumberOfRegisters; i++) { + if ((strcmp(xreg_names[i], name) == 0) || + (strcmp(wreg_names[i], name) == 0)) { + return i; + } + } + for (unsigned i = 0; i < kNumberOfFPRegisters; i++) { + if ((strcmp(vreg_names[i], name) == 0) || + (strcmp(dreg_names[i], name) == 0) || + (strcmp(sreg_names[i], name) == 0)) { + return i; + } + } + if ((strcmp("csp", name) == 0) || (strcmp("wcsp", name) == 0)) { + return kSPRegInternalCode; + } + return -1; +} + + +// Helpers --------------------------------------------------------------------- +template <typename T> +T Simulator::AddWithCarry(bool set_flags, + T src1, + T src2, + T carry_in) { + typedef typename make_unsigned<T>::type unsignedT; + ASSERT((carry_in == 0) || (carry_in == 1)); + + T signed_sum = src1 + src2 + carry_in; + T result = signed_sum; + + bool N, Z, C, V; + + // Compute the C flag + unsignedT u1 = static_cast<unsignedT>(src1); + unsignedT u2 = static_cast<unsignedT>(src2); + unsignedT urest = std::numeric_limits<unsignedT>::max() - u1; + C = (u2 > urest) || (carry_in && (((u2 + 1) > urest) || (u2 > (urest - 1)))); + + // Overflow iff the sign bit is the same for the two inputs and different + // for the result. + V = ((src1 ^ src2) >= 0) && ((src1 ^ result) < 0); + + N = CalcNFlag(result); + Z = CalcZFlag(result); + + if (set_flags) { + nzcv().SetN(N); + nzcv().SetZ(Z); + nzcv().SetC(C); + nzcv().SetV(V); + } + return result; +} + + +template<typename T> +void Simulator::AddSubWithCarry(Instruction* instr) { + T op2 = reg<T>(instr->Rm()); + T new_val; + + if ((instr->Mask(AddSubOpMask) == SUB) || instr->Mask(AddSubOpMask) == SUBS) { + op2 = ~op2; + } + + new_val = AddWithCarry<T>(instr->FlagsUpdate(), + reg<T>(instr->Rn()), + op2, + nzcv().C()); + + set_reg<T>(instr->Rd(), new_val); +} + +template <typename T> +T Simulator::ShiftOperand(T value, Shift shift_type, unsigned amount) { + typedef typename make_unsigned<T>::type unsignedT; + + if (amount == 0) { + return value; + } + + switch (shift_type) { + case LSL: + return value << amount; + case LSR: + return static_cast<unsignedT>(value) >> amount; + case ASR: + return value >> amount; + case ROR: + return (static_cast<unsignedT>(value) >> amount) | + ((value & ((1L << amount) - 1L)) << + (sizeof(unsignedT) * 8 - amount)); + default: + UNIMPLEMENTED(); + return 0; + } +} + + +template <typename T> +T Simulator::ExtendValue(T value, Extend extend_type, unsigned left_shift) { + const unsigned kSignExtendBShift = (sizeof(T) - 1) * 8; + const unsigned kSignExtendHShift = (sizeof(T) - 2) * 8; + const unsigned kSignExtendWShift = (sizeof(T) - 4) * 8; + + switch (extend_type) { + case UXTB: + value &= kByteMask; + break; + case UXTH: + value &= kHalfWordMask; + break; + case UXTW: + value &= kWordMask; + break; + case SXTB: + value = (value << kSignExtendBShift) >> kSignExtendBShift; + break; + case SXTH: + value = (value << kSignExtendHShift) >> kSignExtendHShift; + break; + case SXTW: + value = (value << kSignExtendWShift) >> kSignExtendWShift; + break; + case UXTX: + case SXTX: + break; + default: + UNREACHABLE(); + } + return value << left_shift; +} + + +template <typename T> +void Simulator::Extract(Instruction* instr) { + unsigned lsb = instr->ImmS(); + T op2 = reg<T>(instr->Rm()); + T result = op2; + + if (lsb) { + T op1 = reg<T>(instr->Rn()); + result = op2 >> lsb | (op1 << ((sizeof(T) * 8) - lsb)); + } + set_reg<T>(instr->Rd(), result); +} + + +template<> double Simulator::FPDefaultNaN<double>() const { + return kFP64DefaultNaN; +} + + +template<> float Simulator::FPDefaultNaN<float>() const { + return kFP32DefaultNaN; +} + + +void Simulator::FPCompare(double val0, double val1) { + AssertSupportedFPCR(); + + // TODO(jbramley): This assumes that the C++ implementation handles + // comparisons in the way that we expect (as per AssertSupportedFPCR()). + if ((std::isnan(val0) != 0) || (std::isnan(val1) != 0)) { + nzcv().SetRawValue(FPUnorderedFlag); + } else if (val0 < val1) { + nzcv().SetRawValue(FPLessThanFlag); + } else if (val0 > val1) { + nzcv().SetRawValue(FPGreaterThanFlag); + } else if (val0 == val1) { + nzcv().SetRawValue(FPEqualFlag); + } else { + UNREACHABLE(); + } +} + + +void Simulator::SetBreakpoint(Instruction* location) { + for (unsigned i = 0; i < breakpoints_.size(); i++) { + if (breakpoints_.at(i).location == location) { + PrintF(stream_, + "Existing breakpoint at %p was %s\n", + reinterpret_cast<void*>(location), + breakpoints_.at(i).enabled ? "disabled" : "enabled"); + breakpoints_.at(i).enabled = !breakpoints_.at(i).enabled; + return; + } + } + Breakpoint new_breakpoint = {location, true}; + breakpoints_.push_back(new_breakpoint); + PrintF(stream_, + "Set a breakpoint at %p\n", reinterpret_cast<void*>(location)); +} + + +void Simulator::ListBreakpoints() { + PrintF(stream_, "Breakpoints:\n"); + for (unsigned i = 0; i < breakpoints_.size(); i++) { + PrintF(stream_, "%p : %s\n", + reinterpret_cast<void*>(breakpoints_.at(i).location), + breakpoints_.at(i).enabled ? "enabled" : "disabled"); + } +} + + +void Simulator::CheckBreakpoints() { + bool hit_a_breakpoint = false; + for (unsigned i = 0; i < breakpoints_.size(); i++) { + if ((breakpoints_.at(i).location == pc_) && + breakpoints_.at(i).enabled) { + hit_a_breakpoint = true; + // Disable this breakpoint. + breakpoints_.at(i).enabled = false; + } + } + if (hit_a_breakpoint) { + PrintF(stream_, "Hit and disabled a breakpoint at %p.\n", + reinterpret_cast<void*>(pc_)); + Debug(); + } +} + + +void Simulator::CheckBreakNext() { + // If the current instruction is a BL, insert a breakpoint just after it. + if (break_on_next_ && pc_->IsBranchAndLinkToRegister()) { + SetBreakpoint(pc_->following()); + break_on_next_ = false; + } +} + + +void Simulator::PrintInstructionsAt(Instruction* start, uint64_t count) { + Instruction* end = start->InstructionAtOffset(count * kInstructionSize); + for (Instruction* pc = start; pc < end; pc = pc->following()) { + disassembler_decoder_->Decode(pc); + } +} + + +void Simulator::PrintSystemRegisters(bool print_all) { + static bool first_run = true; + + static SimSystemRegister last_nzcv; + if (print_all || first_run || (last_nzcv.RawValue() != nzcv().RawValue())) { + fprintf(stream_, "# %sFLAGS: %sN:%d Z:%d C:%d V:%d%s\n", + clr_flag_name, + clr_flag_value, + nzcv().N(), nzcv().Z(), nzcv().C(), nzcv().V(), + clr_normal); + } + last_nzcv = nzcv(); + + static SimSystemRegister last_fpcr; + if (print_all || first_run || (last_fpcr.RawValue() != fpcr().RawValue())) { + static const char * rmode[] = { + "0b00 (Round to Nearest)", + "0b01 (Round towards Plus Infinity)", + "0b10 (Round towards Minus Infinity)", + "0b11 (Round towards Zero)" + }; + ASSERT(fpcr().RMode() <= (sizeof(rmode) / sizeof(rmode[0]))); + fprintf(stream_, "# %sFPCR: %sAHP:%d DN:%d FZ:%d RMode:%s%s\n", + clr_flag_name, + clr_flag_value, + fpcr().AHP(), fpcr().DN(), fpcr().FZ(), rmode[fpcr().RMode()], + clr_normal); + } + last_fpcr = fpcr(); + + first_run = false; +} + + +void Simulator::PrintRegisters(bool print_all_regs) { + static bool first_run = true; + static int64_t last_regs[kNumberOfRegisters]; + + for (unsigned i = 0; i < kNumberOfRegisters; i++) { + if (print_all_regs || first_run || + (last_regs[i] != xreg(i, Reg31IsStackPointer))) { + fprintf(stream_, + "# %s%4s:%s 0x%016" PRIx64 "%s\n", + clr_reg_name, + XRegNameForCode(i, Reg31IsStackPointer), + clr_reg_value, + xreg(i, Reg31IsStackPointer), + clr_normal); + } + // Cache the new register value so the next run can detect any changes. + last_regs[i] = xreg(i, Reg31IsStackPointer); + } + first_run = false; +} + + +void Simulator::PrintFPRegisters(bool print_all_regs) { + static bool first_run = true; + static uint64_t last_regs[kNumberOfFPRegisters]; + + // Print as many rows of registers as necessary, keeping each individual + // register in the same column each time (to make it easy to visually scan + // for changes). + for (unsigned i = 0; i < kNumberOfFPRegisters; i++) { + if (print_all_regs || first_run || (last_regs[i] != dreg_bits(i))) { + fprintf(stream_, + "# %s %4s:%s 0x%016" PRIx64 "%s (%s%s:%s %g%s %s:%s %g%s)\n", + clr_fpreg_name, + VRegNameForCode(i), + clr_fpreg_value, + dreg_bits(i), + clr_normal, + clr_fpreg_name, + DRegNameForCode(i), + clr_fpreg_value, + dreg(i), + clr_fpreg_name, + SRegNameForCode(i), + clr_fpreg_value, + sreg(i), + clr_normal); + } + // Cache the new register value so the next run can detect any changes. + last_regs[i] = dreg_bits(i); + } + first_run = false; +} + + +void Simulator::PrintProcessorState() { + PrintSystemRegisters(); + PrintRegisters(); + PrintFPRegisters(); +} + + +void Simulator::PrintWrite(uint8_t* address, + uint64_t value, + unsigned num_bytes) { + // The template is "# value -> address". The template is not directly used + // in the printf since compilers tend to struggle with the parametrized + // width (%0*). + const char* format = "# %s0x%0*" PRIx64 "%s -> %s0x%016" PRIx64 "%s\n"; + fprintf(stream_, + format, + clr_memory_value, + num_bytes * 2, // The width in hexa characters. + value, + clr_normal, + clr_memory_address, + address, + clr_normal); +} + + +// Visitors--------------------------------------------------------------------- + +void Simulator::VisitUnimplemented(Instruction* instr) { + fprintf(stream_, "Unimplemented instruction at %p: 0x%08" PRIx32 "\n", + reinterpret_cast<void*>(instr), instr->InstructionBits()); + UNIMPLEMENTED(); +} + + +void Simulator::VisitUnallocated(Instruction* instr) { + fprintf(stream_, "Unallocated instruction at %p: 0x%08" PRIx32 "\n", + reinterpret_cast<void*>(instr), instr->InstructionBits()); + UNIMPLEMENTED(); +} + + +void Simulator::VisitPCRelAddressing(Instruction* instr) { + switch (instr->Mask(PCRelAddressingMask)) { + case ADR: + set_reg(instr->Rd(), instr->ImmPCOffsetTarget()); + break; + case ADRP: // Not implemented in the assembler. + UNIMPLEMENTED(); + break; + default: + UNREACHABLE(); + break; + } +} + + +void Simulator::VisitUnconditionalBranch(Instruction* instr) { + switch (instr->Mask(UnconditionalBranchMask)) { + case BL: + set_lr(instr->following()); + // Fall through. + case B: + set_pc(instr->ImmPCOffsetTarget()); + break; + default: + UNREACHABLE(); + } +} + + +void Simulator::VisitConditionalBranch(Instruction* instr) { + ASSERT(instr->Mask(ConditionalBranchMask) == B_cond); + if (ConditionPassed(static_cast<Condition>(instr->ConditionBranch()))) { + set_pc(instr->ImmPCOffsetTarget()); + } +} + + +void Simulator::VisitUnconditionalBranchToRegister(Instruction* instr) { + Instruction* target = reg<Instruction*>(instr->Rn()); + switch (instr->Mask(UnconditionalBranchToRegisterMask)) { + case BLR: { + set_lr(instr->following()); + if (instr->Rn() == 31) { + // BLR XZR is used as a guard for the constant pool. We should never hit + // this, but if we do trap to allow debugging. + Debug(); + } + // Fall through. + } + case BR: + case RET: set_pc(target); break; + default: UNIMPLEMENTED(); + } +} + + +void Simulator::VisitTestBranch(Instruction* instr) { + unsigned bit_pos = (instr->ImmTestBranchBit5() << 5) | + instr->ImmTestBranchBit40(); + bool take_branch = ((xreg(instr->Rt()) & (1UL << bit_pos)) == 0); + switch (instr->Mask(TestBranchMask)) { + case TBZ: break; + case TBNZ: take_branch = !take_branch; break; + default: UNIMPLEMENTED(); + } + if (take_branch) { + set_pc(instr->ImmPCOffsetTarget()); + } +} + + +void Simulator::VisitCompareBranch(Instruction* instr) { + unsigned rt = instr->Rt(); + bool take_branch = false; + switch (instr->Mask(CompareBranchMask)) { + case CBZ_w: take_branch = (wreg(rt) == 0); break; + case CBZ_x: take_branch = (xreg(rt) == 0); break; + case CBNZ_w: take_branch = (wreg(rt) != 0); break; + case CBNZ_x: take_branch = (xreg(rt) != 0); break; + default: UNIMPLEMENTED(); + } + if (take_branch) { + set_pc(instr->ImmPCOffsetTarget()); + } +} + + +template<typename T> +void Simulator::AddSubHelper(Instruction* instr, T op2) { + bool set_flags = instr->FlagsUpdate(); + T new_val = 0; + Instr operation = instr->Mask(AddSubOpMask); + + switch (operation) { + case ADD: + case ADDS: { + new_val = AddWithCarry<T>(set_flags, + reg<T>(instr->Rn(), instr->RnMode()), + op2); + break; + } + case SUB: + case SUBS: { + new_val = AddWithCarry<T>(set_flags, + reg<T>(instr->Rn(), instr->RnMode()), + ~op2, + 1); + break; + } + default: UNREACHABLE(); + } + + set_reg<T>(instr->Rd(), new_val, instr->RdMode()); +} + + +void Simulator::VisitAddSubShifted(Instruction* instr) { + Shift shift_type = static_cast<Shift>(instr->ShiftDP()); + unsigned shift_amount = instr->ImmDPShift(); + + if (instr->SixtyFourBits()) { + int64_t op2 = ShiftOperand(xreg(instr->Rm()), shift_type, shift_amount); + AddSubHelper(instr, op2); + } else { + int32_t op2 = ShiftOperand(wreg(instr->Rm()), shift_type, shift_amount); + AddSubHelper(instr, op2); + } +} + + +void Simulator::VisitAddSubImmediate(Instruction* instr) { + int64_t op2 = instr->ImmAddSub() << ((instr->ShiftAddSub() == 1) ? 12 : 0); + if (instr->SixtyFourBits()) { + AddSubHelper<int64_t>(instr, op2); + } else { + AddSubHelper<int32_t>(instr, op2); + } +} + + +void Simulator::VisitAddSubExtended(Instruction* instr) { + Extend ext = static_cast<Extend>(instr->ExtendMode()); + unsigned left_shift = instr->ImmExtendShift(); + if (instr->SixtyFourBits()) { + int64_t op2 = ExtendValue(xreg(instr->Rm()), ext, left_shift); + AddSubHelper(instr, op2); + } else { + int32_t op2 = ExtendValue(wreg(instr->Rm()), ext, left_shift); + AddSubHelper(instr, op2); + } +} + + +void Simulator::VisitAddSubWithCarry(Instruction* instr) { + if (instr->SixtyFourBits()) { + AddSubWithCarry<int64_t>(instr); + } else { + AddSubWithCarry<int32_t>(instr); + } +} + + +void Simulator::VisitLogicalShifted(Instruction* instr) { + Shift shift_type = static_cast<Shift>(instr->ShiftDP()); + unsigned shift_amount = instr->ImmDPShift(); + + if (instr->SixtyFourBits()) { + int64_t op2 = ShiftOperand(xreg(instr->Rm()), shift_type, shift_amount); + op2 = (instr->Mask(NOT) == NOT) ? ~op2 : op2; + LogicalHelper<int64_t>(instr, op2); + } else { + int32_t op2 = ShiftOperand(wreg(instr->Rm()), shift_type, shift_amount); + op2 = (instr->Mask(NOT) == NOT) ? ~op2 : op2; + LogicalHelper<int32_t>(instr, op2); + } +} + + +void Simulator::VisitLogicalImmediate(Instruction* instr) { + if (instr->SixtyFourBits()) { + LogicalHelper<int64_t>(instr, instr->ImmLogical()); + } else { + LogicalHelper<int32_t>(instr, instr->ImmLogical()); + } +} + + +template<typename T> +void Simulator::LogicalHelper(Instruction* instr, T op2) { + T op1 = reg<T>(instr->Rn()); + T result = 0; + bool update_flags = false; + + // Switch on the logical operation, stripping out the NOT bit, as it has a + // different meaning for logical immediate instructions. + switch (instr->Mask(LogicalOpMask & ~NOT)) { + case ANDS: update_flags = true; // Fall through. + case AND: result = op1 & op2; break; + case ORR: result = op1 | op2; break; + case EOR: result = op1 ^ op2; break; + default: + UNIMPLEMENTED(); + } + + if (update_flags) { + nzcv().SetN(CalcNFlag(result)); + nzcv().SetZ(CalcZFlag(result)); + nzcv().SetC(0); + nzcv().SetV(0); + } + + set_reg<T>(instr->Rd(), result, instr->RdMode()); +} + + +void Simulator::VisitConditionalCompareRegister(Instruction* instr) { + if (instr->SixtyFourBits()) { + ConditionalCompareHelper(instr, xreg(instr->Rm())); + } else { + ConditionalCompareHelper(instr, wreg(instr->Rm())); + } +} + + +void Simulator::VisitConditionalCompareImmediate(Instruction* instr) { + if (instr->SixtyFourBits()) { + ConditionalCompareHelper<int64_t>(instr, instr->ImmCondCmp()); + } else { + ConditionalCompareHelper<int32_t>(instr, instr->ImmCondCmp()); + } +} + + +template<typename T> +void Simulator::ConditionalCompareHelper(Instruction* instr, T op2) { + T op1 = reg<T>(instr->Rn()); + + if (ConditionPassed(static_cast<Condition>(instr->Condition()))) { + // If the condition passes, set the status flags to the result of comparing + // the operands. + if (instr->Mask(ConditionalCompareMask) == CCMP) { + AddWithCarry<T>(true, op1, ~op2, 1); + } else { + ASSERT(instr->Mask(ConditionalCompareMask) == CCMN); + AddWithCarry<T>(true, op1, op2, 0); + } + } else { + // If the condition fails, set the status flags to the nzcv immediate. + nzcv().SetFlags(instr->Nzcv()); + } +} + + +void Simulator::VisitLoadStoreUnsignedOffset(Instruction* instr) { + int offset = instr->ImmLSUnsigned() << instr->SizeLS(); + LoadStoreHelper(instr, offset, Offset); +} + + +void Simulator::VisitLoadStoreUnscaledOffset(Instruction* instr) { + LoadStoreHelper(instr, instr->ImmLS(), Offset); +} + + +void Simulator::VisitLoadStorePreIndex(Instruction* instr) { + LoadStoreHelper(instr, instr->ImmLS(), PreIndex); +} + + +void Simulator::VisitLoadStorePostIndex(Instruction* instr) { + LoadStoreHelper(instr, instr->ImmLS(), PostIndex); +} + + +void Simulator::VisitLoadStoreRegisterOffset(Instruction* instr) { + Extend ext = static_cast<Extend>(instr->ExtendMode()); + ASSERT((ext == UXTW) || (ext == UXTX) || (ext == SXTW) || (ext == SXTX)); + unsigned shift_amount = instr->ImmShiftLS() * instr->SizeLS(); + + int64_t offset = ExtendValue(xreg(instr->Rm()), ext, shift_amount); + LoadStoreHelper(instr, offset, Offset); +} + + +void Simulator::LoadStoreHelper(Instruction* instr, + int64_t offset, + AddrMode addrmode) { + unsigned srcdst = instr->Rt(); + unsigned addr_reg = instr->Rn(); + uint8_t* address = LoadStoreAddress(addr_reg, offset, addrmode); + int num_bytes = 1 << instr->SizeLS(); + uint8_t* stack = NULL; + + // Handle the writeback for stores before the store. On a CPU the writeback + // and the store are atomic, but when running on the simulator it is possible + // to be interrupted in between. The simulator is not thread safe and V8 does + // not require it to be to run JavaScript therefore the profiler may sample + // the "simulated" CPU in the middle of load/store with writeback. The code + // below ensures that push operations are safe even when interrupted: the + // stack pointer will be decremented before adding an element to the stack. + if (instr->IsStore()) { + LoadStoreWriteBack(addr_reg, offset, addrmode); + + // For store the address post writeback is used to check access below the + // stack. + stack = reinterpret_cast<uint8_t*>(sp()); + } + + LoadStoreOp op = static_cast<LoadStoreOp>(instr->Mask(LoadStoreOpMask)); + switch (op) { + case LDRB_w: + case LDRH_w: + case LDR_w: + case LDR_x: set_xreg(srcdst, MemoryRead(address, num_bytes)); break; + case STRB_w: + case STRH_w: + case STR_w: + case STR_x: MemoryWrite(address, xreg(srcdst), num_bytes); break; + case LDRSB_w: { + set_wreg(srcdst, ExtendValue<int32_t>(MemoryRead8(address), SXTB)); + break; + } + case LDRSB_x: { + set_xreg(srcdst, ExtendValue<int64_t>(MemoryRead8(address), SXTB)); + break; + } + case LDRSH_w: { + set_wreg(srcdst, ExtendValue<int32_t>(MemoryRead16(address), SXTH)); + break; + } + case LDRSH_x: { + set_xreg(srcdst, ExtendValue<int64_t>(MemoryRead16(address), SXTH)); + break; + } + case LDRSW_x: { + set_xreg(srcdst, ExtendValue<int64_t>(MemoryRead32(address), SXTW)); + break; + } + case LDR_s: set_sreg(srcdst, MemoryReadFP32(address)); break; + case LDR_d: set_dreg(srcdst, MemoryReadFP64(address)); break; + case STR_s: MemoryWriteFP32(address, sreg(srcdst)); break; + case STR_d: MemoryWriteFP64(address, dreg(srcdst)); break; + default: UNIMPLEMENTED(); + } + + // Handle the writeback for loads after the load to ensure safe pop + // operation even when interrupted in the middle of it. The stack pointer + // is only updated after the load so pop(fp) will never break the invariant + // sp <= fp expected while walking the stack in the sampler. + if (instr->IsLoad()) { + // For loads the address pre writeback is used to check access below the + // stack. + stack = reinterpret_cast<uint8_t*>(sp()); + + LoadStoreWriteBack(addr_reg, offset, addrmode); + } + + // Accesses below the stack pointer (but above the platform stack limit) are + // not allowed in the ABI. + CheckMemoryAccess(address, stack); +} + + +void Simulator::VisitLoadStorePairOffset(Instruction* instr) { + LoadStorePairHelper(instr, Offset); +} + + +void Simulator::VisitLoadStorePairPreIndex(Instruction* instr) { + LoadStorePairHelper(instr, PreIndex); +} + + +void Simulator::VisitLoadStorePairPostIndex(Instruction* instr) { + LoadStorePairHelper(instr, PostIndex); +} + + +void Simulator::VisitLoadStorePairNonTemporal(Instruction* instr) { + LoadStorePairHelper(instr, Offset); +} + + +void Simulator::LoadStorePairHelper(Instruction* instr, + AddrMode addrmode) { + unsigned rt = instr->Rt(); + unsigned rt2 = instr->Rt2(); + unsigned addr_reg = instr->Rn(); + int offset = instr->ImmLSPair() << instr->SizeLSPair(); + uint8_t* address = LoadStoreAddress(addr_reg, offset, addrmode); + uint8_t* stack = NULL; + + // Handle the writeback for stores before the store. On a CPU the writeback + // and the store are atomic, but when running on the simulator it is possible + // to be interrupted in between. The simulator is not thread safe and V8 does + // not require it to be to run JavaScript therefore the profiler may sample + // the "simulated" CPU in the middle of load/store with writeback. The code + // below ensures that push operations are safe even when interrupted: the + // stack pointer will be decremented before adding an element to the stack. + if (instr->IsStore()) { + LoadStoreWriteBack(addr_reg, offset, addrmode); + + // For store the address post writeback is used to check access below the + // stack. + stack = reinterpret_cast<uint8_t*>(sp()); + } + + LoadStorePairOp op = + static_cast<LoadStorePairOp>(instr->Mask(LoadStorePairMask)); + + // 'rt' and 'rt2' can only be aliased for stores. + ASSERT(((op & LoadStorePairLBit) == 0) || (rt != rt2)); + + switch (op) { + case LDP_w: { + set_wreg(rt, MemoryRead32(address)); + set_wreg(rt2, MemoryRead32(address + kWRegSize)); + break; + } + case LDP_s: { + set_sreg(rt, MemoryReadFP32(address)); + set_sreg(rt2, MemoryReadFP32(address + kSRegSize)); + break; + } + case LDP_x: { + set_xreg(rt, MemoryRead64(address)); + set_xreg(rt2, MemoryRead64(address + kXRegSize)); + break; + } + case LDP_d: { + set_dreg(rt, MemoryReadFP64(address)); + set_dreg(rt2, MemoryReadFP64(address + kDRegSize)); + break; + } + case LDPSW_x: { + set_xreg(rt, ExtendValue<int64_t>(MemoryRead32(address), SXTW)); + set_xreg(rt2, ExtendValue<int64_t>( + MemoryRead32(address + kWRegSize), SXTW)); + break; + } + case STP_w: { + MemoryWrite32(address, wreg(rt)); + MemoryWrite32(address + kWRegSize, wreg(rt2)); + break; + } + case STP_s: { + MemoryWriteFP32(address, sreg(rt)); + MemoryWriteFP32(address + kSRegSize, sreg(rt2)); + break; + } + case STP_x: { + MemoryWrite64(address, xreg(rt)); + MemoryWrite64(address + kXRegSize, xreg(rt2)); + break; + } + case STP_d: { + MemoryWriteFP64(address, dreg(rt)); + MemoryWriteFP64(address + kDRegSize, dreg(rt2)); + break; + } + default: UNREACHABLE(); + } + + // Handle the writeback for loads after the load to ensure safe pop + // operation even when interrupted in the middle of it. The stack pointer + // is only updated after the load so pop(fp) will never break the invariant + // sp <= fp expected while walking the stack in the sampler. + if (instr->IsLoad()) { + // For loads the address pre writeback is used to check access below the + // stack. + stack = reinterpret_cast<uint8_t*>(sp()); + + LoadStoreWriteBack(addr_reg, offset, addrmode); + } + + // Accesses below the stack pointer (but above the platform stack limit) are + // not allowed in the ABI. + CheckMemoryAccess(address, stack); +} + + +void Simulator::VisitLoadLiteral(Instruction* instr) { + uint8_t* address = instr->LiteralAddress(); + unsigned rt = instr->Rt(); + + switch (instr->Mask(LoadLiteralMask)) { + case LDR_w_lit: set_wreg(rt, MemoryRead32(address)); break; + case LDR_x_lit: set_xreg(rt, MemoryRead64(address)); break; + case LDR_s_lit: set_sreg(rt, MemoryReadFP32(address)); break; + case LDR_d_lit: set_dreg(rt, MemoryReadFP64(address)); break; + default: UNREACHABLE(); + } +} + + +uint8_t* Simulator::LoadStoreAddress(unsigned addr_reg, + int64_t offset, + AddrMode addrmode) { + const unsigned kSPRegCode = kSPRegInternalCode & kRegCodeMask; + int64_t address = xreg(addr_reg, Reg31IsStackPointer); + if ((addr_reg == kSPRegCode) && ((address % 16) != 0)) { + // When the base register is SP the stack pointer is required to be + // quadword aligned prior to the address calculation and write-backs. + // Misalignment will cause a stack alignment fault. + FATAL("ALIGNMENT EXCEPTION"); + } + + if ((addrmode == Offset) || (addrmode == PreIndex)) { + address += offset; + } + + return reinterpret_cast<uint8_t*>(address); +} + + +void Simulator::LoadStoreWriteBack(unsigned addr_reg, + int64_t offset, + AddrMode addrmode) { + if ((addrmode == PreIndex) || (addrmode == PostIndex)) { + ASSERT(offset != 0); + uint64_t address = xreg(addr_reg, Reg31IsStackPointer); + set_reg(addr_reg, address + offset, Reg31IsStackPointer); + } +} + + +void Simulator::CheckMemoryAccess(uint8_t* address, uint8_t* stack) { + if ((address >= stack_limit_) && (address < stack)) { + fprintf(stream_, "ACCESS BELOW STACK POINTER:\n"); + fprintf(stream_, " sp is here: 0x%16p\n", stack); + fprintf(stream_, " access was here: 0x%16p\n", address); + fprintf(stream_, " stack limit is here: 0x%16p\n", stack_limit_); + fprintf(stream_, "\n"); + FATAL("ACCESS BELOW STACK POINTER"); + } +} + + +uint64_t Simulator::MemoryRead(uint8_t* address, unsigned num_bytes) { + ASSERT(address != NULL); + ASSERT((num_bytes > 0) && (num_bytes <= sizeof(uint64_t))); + uint64_t read = 0; + memcpy(&read, address, num_bytes); + return read; +} + + +uint8_t Simulator::MemoryRead8(uint8_t* address) { + return MemoryRead(address, sizeof(uint8_t)); +} + + +uint16_t Simulator::MemoryRead16(uint8_t* address) { + return MemoryRead(address, sizeof(uint16_t)); +} + + +uint32_t Simulator::MemoryRead32(uint8_t* address) { + return MemoryRead(address, sizeof(uint32_t)); +} + + +float Simulator::MemoryReadFP32(uint8_t* address) { + return rawbits_to_float(MemoryRead32(address)); +} + + +uint64_t Simulator::MemoryRead64(uint8_t* address) { + return MemoryRead(address, sizeof(uint64_t)); +} + + +double Simulator::MemoryReadFP64(uint8_t* address) { + return rawbits_to_double(MemoryRead64(address)); +} + + +void Simulator::MemoryWrite(uint8_t* address, + uint64_t value, + unsigned num_bytes) { + ASSERT(address != NULL); + ASSERT((num_bytes > 0) && (num_bytes <= sizeof(uint64_t))); + + LogWrite(address, value, num_bytes); + memcpy(address, &value, num_bytes); +} + + +void Simulator::MemoryWrite32(uint8_t* address, uint32_t value) { + MemoryWrite(address, value, sizeof(uint32_t)); +} + + +void Simulator::MemoryWriteFP32(uint8_t* address, float value) { + MemoryWrite32(address, float_to_rawbits(value)); +} + + +void Simulator::MemoryWrite64(uint8_t* address, uint64_t value) { + MemoryWrite(address, value, sizeof(uint64_t)); +} + + +void Simulator::MemoryWriteFP64(uint8_t* address, double value) { + MemoryWrite64(address, double_to_rawbits(value)); +} + + +void Simulator::VisitMoveWideImmediate(Instruction* instr) { + MoveWideImmediateOp mov_op = + static_cast<MoveWideImmediateOp>(instr->Mask(MoveWideImmediateMask)); + int64_t new_xn_val = 0; + + bool is_64_bits = instr->SixtyFourBits() == 1; + // Shift is limited for W operations. + ASSERT(is_64_bits || (instr->ShiftMoveWide() < 2)); + + // Get the shifted immediate. + int64_t shift = instr->ShiftMoveWide() * 16; + int64_t shifted_imm16 = instr->ImmMoveWide() << shift; + + // Compute the new value. + switch (mov_op) { + case MOVN_w: + case MOVN_x: { + new_xn_val = ~shifted_imm16; + if (!is_64_bits) new_xn_val &= kWRegMask; + break; + } + case MOVK_w: + case MOVK_x: { + unsigned reg_code = instr->Rd(); + int64_t prev_xn_val = is_64_bits ? xreg(reg_code) + : wreg(reg_code); + new_xn_val = (prev_xn_val & ~(0xffffL << shift)) | shifted_imm16; + break; + } + case MOVZ_w: + case MOVZ_x: { + new_xn_val = shifted_imm16; + break; + } + default: + UNREACHABLE(); + } + + // Update the destination register. + set_xreg(instr->Rd(), new_xn_val); +} + + +void Simulator::VisitConditionalSelect(Instruction* instr) { + if (ConditionFailed(static_cast<Condition>(instr->Condition()))) { + uint64_t new_val = xreg(instr->Rm()); + switch (instr->Mask(ConditionalSelectMask)) { + case CSEL_w: set_wreg(instr->Rd(), new_val); break; + case CSEL_x: set_xreg(instr->Rd(), new_val); break; + case CSINC_w: set_wreg(instr->Rd(), new_val + 1); break; + case CSINC_x: set_xreg(instr->Rd(), new_val + 1); break; + case CSINV_w: set_wreg(instr->Rd(), ~new_val); break; + case CSINV_x: set_xreg(instr->Rd(), ~new_val); break; + case CSNEG_w: set_wreg(instr->Rd(), -new_val); break; + case CSNEG_x: set_xreg(instr->Rd(), -new_val); break; + default: UNIMPLEMENTED(); + } + } else { + if (instr->SixtyFourBits()) { + set_xreg(instr->Rd(), xreg(instr->Rn())); + } else { + set_wreg(instr->Rd(), wreg(instr->Rn())); + } + } +} + + +void Simulator::VisitDataProcessing1Source(Instruction* instr) { + unsigned dst = instr->Rd(); + unsigned src = instr->Rn(); + + switch (instr->Mask(DataProcessing1SourceMask)) { + case RBIT_w: set_wreg(dst, ReverseBits(wreg(src), kWRegSizeInBits)); break; + case RBIT_x: set_xreg(dst, ReverseBits(xreg(src), kXRegSizeInBits)); break; + case REV16_w: set_wreg(dst, ReverseBytes(wreg(src), Reverse16)); break; + case REV16_x: set_xreg(dst, ReverseBytes(xreg(src), Reverse16)); break; + case REV_w: set_wreg(dst, ReverseBytes(wreg(src), Reverse32)); break; + case REV32_x: set_xreg(dst, ReverseBytes(xreg(src), Reverse32)); break; + case REV_x: set_xreg(dst, ReverseBytes(xreg(src), Reverse64)); break; + case CLZ_w: set_wreg(dst, CountLeadingZeros(wreg(src), kWRegSizeInBits)); + break; + case CLZ_x: set_xreg(dst, CountLeadingZeros(xreg(src), kXRegSizeInBits)); + break; + case CLS_w: { + set_wreg(dst, CountLeadingSignBits(wreg(src), kWRegSizeInBits)); + break; + } + case CLS_x: { + set_xreg(dst, CountLeadingSignBits(xreg(src), kXRegSizeInBits)); + break; + } + default: UNIMPLEMENTED(); + } +} + + +uint64_t Simulator::ReverseBits(uint64_t value, unsigned num_bits) { + ASSERT((num_bits == kWRegSizeInBits) || (num_bits == kXRegSizeInBits)); + uint64_t result = 0; + for (unsigned i = 0; i < num_bits; i++) { + result = (result << 1) | (value & 1); + value >>= 1; + } + return result; +} + + +uint64_t Simulator::ReverseBytes(uint64_t value, ReverseByteMode mode) { + // Split the 64-bit value into an 8-bit array, where b[0] is the least + // significant byte, and b[7] is the most significant. + uint8_t bytes[8]; + uint64_t mask = 0xff00000000000000UL; + for (int i = 7; i >= 0; i--) { + bytes[i] = (value & mask) >> (i * 8); + mask >>= 8; + } + + // Permutation tables for REV instructions. + // permute_table[Reverse16] is used by REV16_x, REV16_w + // permute_table[Reverse32] is used by REV32_x, REV_w + // permute_table[Reverse64] is used by REV_x + ASSERT((Reverse16 == 0) && (Reverse32 == 1) && (Reverse64 == 2)); + static const uint8_t permute_table[3][8] = { {6, 7, 4, 5, 2, 3, 0, 1}, + {4, 5, 6, 7, 0, 1, 2, 3}, + {0, 1, 2, 3, 4, 5, 6, 7} }; + uint64_t result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= bytes[permute_table[mode][i]]; + } + return result; +} + + +template <typename T> +void Simulator::DataProcessing2Source(Instruction* instr) { + Shift shift_op = NO_SHIFT; + T result = 0; + switch (instr->Mask(DataProcessing2SourceMask)) { + case SDIV_w: + case SDIV_x: { + T rn = reg<T>(instr->Rn()); + T rm = reg<T>(instr->Rm()); + if ((rn == std::numeric_limits<T>::min()) && (rm == -1)) { + result = std::numeric_limits<T>::min(); + } else if (rm == 0) { + // Division by zero can be trapped, but not on A-class processors. + result = 0; + } else { + result = rn / rm; + } + break; + } + case UDIV_w: + case UDIV_x: { + typedef typename make_unsigned<T>::type unsignedT; + unsignedT rn = static_cast<unsignedT>(reg<T>(instr->Rn())); + unsignedT rm = static_cast<unsignedT>(reg<T>(instr->Rm())); + if (rm == 0) { + // Division by zero can be trapped, but not on A-class processors. + result = 0; + } else { + result = rn / rm; + } + break; + } + case LSLV_w: + case LSLV_x: shift_op = LSL; break; + case LSRV_w: + case LSRV_x: shift_op = LSR; break; + case ASRV_w: + case ASRV_x: shift_op = ASR; break; + case RORV_w: + case RORV_x: shift_op = ROR; break; + default: UNIMPLEMENTED(); + } + + if (shift_op != NO_SHIFT) { + // Shift distance encoded in the least-significant five/six bits of the + // register. + unsigned shift = wreg(instr->Rm()); + if (sizeof(T) == kWRegSize) { + shift &= kShiftAmountWRegMask; + } else { + shift &= kShiftAmountXRegMask; + } + result = ShiftOperand(reg<T>(instr->Rn()), shift_op, shift); + } + set_reg<T>(instr->Rd(), result); +} + + +void Simulator::VisitDataProcessing2Source(Instruction* instr) { + if (instr->SixtyFourBits()) { + DataProcessing2Source<int64_t>(instr); + } else { + DataProcessing2Source<int32_t>(instr); + } +} + + +// The algorithm used is described in section 8.2 of +// Hacker's Delight, by Henry S. Warren, Jr. +// It assumes that a right shift on a signed integer is an arithmetic shift. +static int64_t MultiplyHighSigned(int64_t u, int64_t v) { + uint64_t u0, v0, w0; + int64_t u1, v1, w1, w2, t; + + u0 = u & 0xffffffffL; + u1 = u >> 32; + v0 = v & 0xffffffffL; + v1 = v >> 32; + + w0 = u0 * v0; + t = u1 * v0 + (w0 >> 32); + w1 = t & 0xffffffffL; + w2 = t >> 32; + w1 = u0 * v1 + w1; + + return u1 * v1 + w2 + (w1 >> 32); +} + + +void Simulator::VisitDataProcessing3Source(Instruction* instr) { + int64_t result = 0; + // Extract and sign- or zero-extend 32-bit arguments for widening operations. + uint64_t rn_u32 = reg<uint32_t>(instr->Rn()); + uint64_t rm_u32 = reg<uint32_t>(instr->Rm()); + int64_t rn_s32 = reg<int32_t>(instr->Rn()); + int64_t rm_s32 = reg<int32_t>(instr->Rm()); + switch (instr->Mask(DataProcessing3SourceMask)) { + case MADD_w: + case MADD_x: + result = xreg(instr->Ra()) + (xreg(instr->Rn()) * xreg(instr->Rm())); + break; + case MSUB_w: + case MSUB_x: + result = xreg(instr->Ra()) - (xreg(instr->Rn()) * xreg(instr->Rm())); + break; + case SMADDL_x: result = xreg(instr->Ra()) + (rn_s32 * rm_s32); break; + case SMSUBL_x: result = xreg(instr->Ra()) - (rn_s32 * rm_s32); break; + case UMADDL_x: result = xreg(instr->Ra()) + (rn_u32 * rm_u32); break; + case UMSUBL_x: result = xreg(instr->Ra()) - (rn_u32 * rm_u32); break; + case SMULH_x: + ASSERT(instr->Ra() == kZeroRegCode); + result = MultiplyHighSigned(xreg(instr->Rn()), xreg(instr->Rm())); + break; + default: UNIMPLEMENTED(); + } + + if (instr->SixtyFourBits()) { + set_xreg(instr->Rd(), result); + } else { + set_wreg(instr->Rd(), result); + } +} + + +template <typename T> +void Simulator::BitfieldHelper(Instruction* instr) { + typedef typename make_unsigned<T>::type unsignedT; + T reg_size = sizeof(T) * 8; + T R = instr->ImmR(); + T S = instr->ImmS(); + T diff = S - R; + T mask; + if (diff >= 0) { + mask = diff < reg_size - 1 ? (static_cast<T>(1) << (diff + 1)) - 1 + : static_cast<T>(-1); + } else { + mask = ((1L << (S + 1)) - 1); + mask = (static_cast<uint64_t>(mask) >> R) | (mask << (reg_size - R)); + diff += reg_size; + } + + // inzero indicates if the extracted bitfield is inserted into the + // destination register value or in zero. + // If extend is true, extend the sign of the extracted bitfield. + bool inzero = false; + bool extend = false; + switch (instr->Mask(BitfieldMask)) { + case BFM_x: + case BFM_w: + break; + case SBFM_x: + case SBFM_w: + inzero = true; + extend = true; + break; + case UBFM_x: + case UBFM_w: + inzero = true; + break; + default: + UNIMPLEMENTED(); + } + + T dst = inzero ? 0 : reg<T>(instr->Rd()); + T src = reg<T>(instr->Rn()); + // Rotate source bitfield into place. + T result = (static_cast<unsignedT>(src) >> R) | (src << (reg_size - R)); + // Determine the sign extension. + T topbits_preshift = (static_cast<T>(1) << (reg_size - diff - 1)) - 1; + T signbits = (extend && ((src >> S) & 1) ? topbits_preshift : 0) + << (diff + 1); + + // Merge sign extension, dest/zero and bitfield. + result = signbits | (result & mask) | (dst & ~mask); + + set_reg<T>(instr->Rd(), result); +} + + +void Simulator::VisitBitfield(Instruction* instr) { + if (instr->SixtyFourBits()) { + BitfieldHelper<int64_t>(instr); + } else { + BitfieldHelper<int32_t>(instr); + } +} + + +void Simulator::VisitExtract(Instruction* instr) { + if (instr->SixtyFourBits()) { + Extract<uint64_t>(instr); + } else { + Extract<uint32_t>(instr); + } +} + + +void Simulator::VisitFPImmediate(Instruction* instr) { + AssertSupportedFPCR(); + + unsigned dest = instr->Rd(); + switch (instr->Mask(FPImmediateMask)) { + case FMOV_s_imm: set_sreg(dest, instr->ImmFP32()); break; + case FMOV_d_imm: set_dreg(dest, instr->ImmFP64()); break; + default: UNREACHABLE(); + } +} + + +void Simulator::VisitFPIntegerConvert(Instruction* instr) { + AssertSupportedFPCR(); + + unsigned dst = instr->Rd(); + unsigned src = instr->Rn(); + + FPRounding round = fpcr().RMode(); + + switch (instr->Mask(FPIntegerConvertMask)) { + case FCVTAS_ws: set_wreg(dst, FPToInt32(sreg(src), FPTieAway)); break; + case FCVTAS_xs: set_xreg(dst, FPToInt64(sreg(src), FPTieAway)); break; + case FCVTAS_wd: set_wreg(dst, FPToInt32(dreg(src), FPTieAway)); break; + case FCVTAS_xd: set_xreg(dst, FPToInt64(dreg(src), FPTieAway)); break; + case FCVTAU_ws: set_wreg(dst, FPToUInt32(sreg(src), FPTieAway)); break; + case FCVTAU_xs: set_xreg(dst, FPToUInt64(sreg(src), FPTieAway)); break; + case FCVTAU_wd: set_wreg(dst, FPToUInt32(dreg(src), FPTieAway)); break; + case FCVTAU_xd: set_xreg(dst, FPToUInt64(dreg(src), FPTieAway)); break; + case FCVTMS_ws: + set_wreg(dst, FPToInt32(sreg(src), FPNegativeInfinity)); + break; + case FCVTMS_xs: + set_xreg(dst, FPToInt64(sreg(src), FPNegativeInfinity)); + break; + case FCVTMS_wd: + set_wreg(dst, FPToInt32(dreg(src), FPNegativeInfinity)); + break; + case FCVTMS_xd: + set_xreg(dst, FPToInt64(dreg(src), FPNegativeInfinity)); + break; + case FCVTMU_ws: + set_wreg(dst, FPToUInt32(sreg(src), FPNegativeInfinity)); + break; + case FCVTMU_xs: + set_xreg(dst, FPToUInt64(sreg(src), FPNegativeInfinity)); + break; + case FCVTMU_wd: + set_wreg(dst, FPToUInt32(dreg(src), FPNegativeInfinity)); + break; + case FCVTMU_xd: + set_xreg(dst, FPToUInt64(dreg(src), FPNegativeInfinity)); + break; + case FCVTNS_ws: set_wreg(dst, FPToInt32(sreg(src), FPTieEven)); break; + case FCVTNS_xs: set_xreg(dst, FPToInt64(sreg(src), FPTieEven)); break; + case FCVTNS_wd: set_wreg(dst, FPToInt32(dreg(src), FPTieEven)); break; + case FCVTNS_xd: set_xreg(dst, FPToInt64(dreg(src), FPTieEven)); break; + case FCVTNU_ws: set_wreg(dst, FPToUInt32(sreg(src), FPTieEven)); break; + case FCVTNU_xs: set_xreg(dst, FPToUInt64(sreg(src), FPTieEven)); break; + case FCVTNU_wd: set_wreg(dst, FPToUInt32(dreg(src), FPTieEven)); break; + case FCVTNU_xd: set_xreg(dst, FPToUInt64(dreg(src), FPTieEven)); break; + case FCVTZS_ws: set_wreg(dst, FPToInt32(sreg(src), FPZero)); break; + case FCVTZS_xs: set_xreg(dst, FPToInt64(sreg(src), FPZero)); break; + case FCVTZS_wd: set_wreg(dst, FPToInt32(dreg(src), FPZero)); break; + case FCVTZS_xd: set_xreg(dst, FPToInt64(dreg(src), FPZero)); break; + case FCVTZU_ws: set_wreg(dst, FPToUInt32(sreg(src), FPZero)); break; + case FCVTZU_xs: set_xreg(dst, FPToUInt64(sreg(src), FPZero)); break; + case FCVTZU_wd: set_wreg(dst, FPToUInt32(dreg(src), FPZero)); break; + case FCVTZU_xd: set_xreg(dst, FPToUInt64(dreg(src), FPZero)); break; + case FMOV_ws: set_wreg(dst, sreg_bits(src)); break; + case FMOV_xd: set_xreg(dst, dreg_bits(src)); break; + case FMOV_sw: set_sreg_bits(dst, wreg(src)); break; + case FMOV_dx: set_dreg_bits(dst, xreg(src)); break; + + // A 32-bit input can be handled in the same way as a 64-bit input, since + // the sign- or zero-extension will not affect the conversion. + case SCVTF_dx: set_dreg(dst, FixedToDouble(xreg(src), 0, round)); break; + case SCVTF_dw: set_dreg(dst, FixedToDouble(wreg(src), 0, round)); break; + case UCVTF_dx: set_dreg(dst, UFixedToDouble(xreg(src), 0, round)); break; + case UCVTF_dw: { + set_dreg(dst, UFixedToDouble(reg<uint32_t>(src), 0, round)); + break; + } + case SCVTF_sx: set_sreg(dst, FixedToFloat(xreg(src), 0, round)); break; + case SCVTF_sw: set_sreg(dst, FixedToFloat(wreg(src), 0, round)); break; + case UCVTF_sx: set_sreg(dst, UFixedToFloat(xreg(src), 0, round)); break; + case UCVTF_sw: { + set_sreg(dst, UFixedToFloat(reg<uint32_t>(src), 0, round)); + break; + } + + default: UNREACHABLE(); + } +} + + +void Simulator::VisitFPFixedPointConvert(Instruction* instr) { + AssertSupportedFPCR(); + + unsigned dst = instr->Rd(); + unsigned src = instr->Rn(); + int fbits = 64 - instr->FPScale(); + + FPRounding round = fpcr().RMode(); + + switch (instr->Mask(FPFixedPointConvertMask)) { + // A 32-bit input can be handled in the same way as a 64-bit input, since + // the sign- or zero-extension will not affect the conversion. + case SCVTF_dx_fixed: + set_dreg(dst, FixedToDouble(xreg(src), fbits, round)); + break; + case SCVTF_dw_fixed: + set_dreg(dst, FixedToDouble(wreg(src), fbits, round)); + break; + case UCVTF_dx_fixed: + set_dreg(dst, UFixedToDouble(xreg(src), fbits, round)); + break; + case UCVTF_dw_fixed: { + set_dreg(dst, + UFixedToDouble(reg<uint32_t>(src), fbits, round)); + break; + } + case SCVTF_sx_fixed: + set_sreg(dst, FixedToFloat(xreg(src), fbits, round)); + break; + case SCVTF_sw_fixed: + set_sreg(dst, FixedToFloat(wreg(src), fbits, round)); + break; + case UCVTF_sx_fixed: + set_sreg(dst, UFixedToFloat(xreg(src), fbits, round)); + break; + case UCVTF_sw_fixed: { + set_sreg(dst, + UFixedToFloat(reg<uint32_t>(src), fbits, round)); + break; + } + default: UNREACHABLE(); + } +} + + +int32_t Simulator::FPToInt32(double value, FPRounding rmode) { + value = FPRoundInt(value, rmode); + if (value >= kWMaxInt) { + return kWMaxInt; + } else if (value < kWMinInt) { + return kWMinInt; + } + return std::isnan(value) ? 0 : static_cast<int32_t>(value); +} + + +int64_t Simulator::FPToInt64(double value, FPRounding rmode) { + value = FPRoundInt(value, rmode); + if (value >= kXMaxInt) { + return kXMaxInt; + } else if (value < kXMinInt) { + return kXMinInt; + } + return std::isnan(value) ? 0 : static_cast<int64_t>(value); +} + + +uint32_t Simulator::FPToUInt32(double value, FPRounding rmode) { + value = FPRoundInt(value, rmode); + if (value >= kWMaxUInt) { + return kWMaxUInt; + } else if (value < 0.0) { + return 0; + } + return std::isnan(value) ? 0 : static_cast<uint32_t>(value); +} + + +uint64_t Simulator::FPToUInt64(double value, FPRounding rmode) { + value = FPRoundInt(value, rmode); + if (value >= kXMaxUInt) { + return kXMaxUInt; + } else if (value < 0.0) { + return 0; + } + return std::isnan(value) ? 0 : static_cast<uint64_t>(value); +} + + +void Simulator::VisitFPCompare(Instruction* instr) { + AssertSupportedFPCR(); + + unsigned reg_size = (instr->Mask(FP64) == FP64) ? kDRegSizeInBits + : kSRegSizeInBits; + double fn_val = fpreg(reg_size, instr->Rn()); + + switch (instr->Mask(FPCompareMask)) { + case FCMP_s: + case FCMP_d: FPCompare(fn_val, fpreg(reg_size, instr->Rm())); break; + case FCMP_s_zero: + case FCMP_d_zero: FPCompare(fn_val, 0.0); break; + default: UNIMPLEMENTED(); + } +} + + +void Simulator::VisitFPConditionalCompare(Instruction* instr) { + AssertSupportedFPCR(); + + switch (instr->Mask(FPConditionalCompareMask)) { + case FCCMP_s: + case FCCMP_d: { + if (ConditionPassed(static_cast<Condition>(instr->Condition()))) { + // If the condition passes, set the status flags to the result of + // comparing the operands. + unsigned reg_size = (instr->Mask(FP64) == FP64) ? kDRegSizeInBits + : kSRegSizeInBits; + FPCompare(fpreg(reg_size, instr->Rn()), fpreg(reg_size, instr->Rm())); + } else { + // If the condition fails, set the status flags to the nzcv immediate. + nzcv().SetFlags(instr->Nzcv()); + } + break; + } + default: UNIMPLEMENTED(); + } +} + + +void Simulator::VisitFPConditionalSelect(Instruction* instr) { + AssertSupportedFPCR(); + + Instr selected; + if (ConditionPassed(static_cast<Condition>(instr->Condition()))) { + selected = instr->Rn(); + } else { + selected = instr->Rm(); + } + + switch (instr->Mask(FPConditionalSelectMask)) { + case FCSEL_s: set_sreg(instr->Rd(), sreg(selected)); break; + case FCSEL_d: set_dreg(instr->Rd(), dreg(selected)); break; + default: UNIMPLEMENTED(); + } +} + + +void Simulator::VisitFPDataProcessing1Source(Instruction* instr) { + AssertSupportedFPCR(); + + unsigned fd = instr->Rd(); + unsigned fn = instr->Rn(); + + switch (instr->Mask(FPDataProcessing1SourceMask)) { + case FMOV_s: set_sreg(fd, sreg(fn)); break; + case FMOV_d: set_dreg(fd, dreg(fn)); break; + case FABS_s: set_sreg(fd, std::fabs(sreg(fn))); break; + case FABS_d: set_dreg(fd, std::fabs(dreg(fn))); break; + case FNEG_s: set_sreg(fd, -sreg(fn)); break; + case FNEG_d: set_dreg(fd, -dreg(fn)); break; + case FSQRT_s: set_sreg(fd, FPSqrt(sreg(fn))); break; + case FSQRT_d: set_dreg(fd, FPSqrt(dreg(fn))); break; + case FRINTA_s: set_sreg(fd, FPRoundInt(sreg(fn), FPTieAway)); break; + case FRINTA_d: set_dreg(fd, FPRoundInt(dreg(fn), FPTieAway)); break; + case FRINTM_s: + set_sreg(fd, FPRoundInt(sreg(fn), FPNegativeInfinity)); break; + case FRINTM_d: + set_dreg(fd, FPRoundInt(dreg(fn), FPNegativeInfinity)); break; + case FRINTN_s: set_sreg(fd, FPRoundInt(sreg(fn), FPTieEven)); break; + case FRINTN_d: set_dreg(fd, FPRoundInt(dreg(fn), FPTieEven)); break; + case FRINTZ_s: set_sreg(fd, FPRoundInt(sreg(fn), FPZero)); break; + case FRINTZ_d: set_dreg(fd, FPRoundInt(dreg(fn), FPZero)); break; + case FCVT_ds: set_dreg(fd, FPToDouble(sreg(fn))); break; + case FCVT_sd: set_sreg(fd, FPToFloat(dreg(fn), FPTieEven)); break; + default: UNIMPLEMENTED(); + } +} + + +// Assemble the specified IEEE-754 components into the target type and apply +// appropriate rounding. +// sign: 0 = positive, 1 = negative +// exponent: Unbiased IEEE-754 exponent. +// mantissa: The mantissa of the input. The top bit (which is not encoded for +// normal IEEE-754 values) must not be omitted. This bit has the +// value 'pow(2, exponent)'. +// +// The input value is assumed to be a normalized value. That is, the input may +// not be infinity or NaN. If the source value is subnormal, it must be +// normalized before calling this function such that the highest set bit in the +// mantissa has the value 'pow(2, exponent)'. +// +// Callers should use FPRoundToFloat or FPRoundToDouble directly, rather than +// calling a templated FPRound. +template <class T, int ebits, int mbits> +static T FPRound(int64_t sign, int64_t exponent, uint64_t mantissa, + FPRounding round_mode) { + ASSERT((sign == 0) || (sign == 1)); + + // Only the FPTieEven rounding mode is implemented. + ASSERT(round_mode == FPTieEven); + USE(round_mode); + + // Rounding can promote subnormals to normals, and normals to infinities. For + // example, a double with exponent 127 (FLT_MAX_EXP) would appear to be + // encodable as a float, but rounding based on the low-order mantissa bits + // could make it overflow. With ties-to-even rounding, this value would become + // an infinity. + + // ---- Rounding Method ---- + // + // The exponent is irrelevant in the rounding operation, so we treat the + // lowest-order bit that will fit into the result ('onebit') as having + // the value '1'. Similarly, the highest-order bit that won't fit into + // the result ('halfbit') has the value '0.5'. The 'point' sits between + // 'onebit' and 'halfbit': + // + // These bits fit into the result. + // |---------------------| + // mantissa = 0bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + // || + // / | + // / halfbit + // onebit + // + // For subnormal outputs, the range of representable bits is smaller and + // the position of onebit and halfbit depends on the exponent of the + // input, but the method is otherwise similar. + // + // onebit(frac) + // | + // | halfbit(frac) halfbit(adjusted) + // | / / + // | | | + // 0b00.0 (exact) -> 0b00.0 (exact) -> 0b00 + // 0b00.0... -> 0b00.0... -> 0b00 + // 0b00.1 (exact) -> 0b00.0111..111 -> 0b00 + // 0b00.1... -> 0b00.1... -> 0b01 + // 0b01.0 (exact) -> 0b01.0 (exact) -> 0b01 + // 0b01.0... -> 0b01.0... -> 0b01 + // 0b01.1 (exact) -> 0b01.1 (exact) -> 0b10 + // 0b01.1... -> 0b01.1... -> 0b10 + // 0b10.0 (exact) -> 0b10.0 (exact) -> 0b10 + // 0b10.0... -> 0b10.0... -> 0b10 + // 0b10.1 (exact) -> 0b10.0111..111 -> 0b10 + // 0b10.1... -> 0b10.1... -> 0b11 + // 0b11.0 (exact) -> 0b11.0 (exact) -> 0b11 + // ... / | / | + // / | / | + // / | + // adjusted = frac - (halfbit(mantissa) & ~onebit(frac)); / | + // + // mantissa = (mantissa >> shift) + halfbit(adjusted); + + static const int mantissa_offset = 0; + static const int exponent_offset = mantissa_offset + mbits; + static const int sign_offset = exponent_offset + ebits; + STATIC_ASSERT(sign_offset == (sizeof(T) * kByteSize - 1)); + + // Bail out early for zero inputs. + if (mantissa == 0) { + return sign << sign_offset; + } + + // If all bits in the exponent are set, the value is infinite or NaN. + // This is true for all binary IEEE-754 formats. + static const int infinite_exponent = (1 << ebits) - 1; + static const int max_normal_exponent = infinite_exponent - 1; + + // Apply the exponent bias to encode it for the result. Doing this early makes + // it easy to detect values that will be infinite or subnormal. + exponent += max_normal_exponent >> 1; + + if (exponent > max_normal_exponent) { + // Overflow: The input is too large for the result type to represent. The + // FPTieEven rounding mode handles overflows using infinities. + exponent = infinite_exponent; + mantissa = 0; + return (sign << sign_offset) | + (exponent << exponent_offset) | + (mantissa << mantissa_offset); + } + + // Calculate the shift required to move the top mantissa bit to the proper + // place in the destination type. + const int highest_significant_bit = 63 - CountLeadingZeros(mantissa, 64); + int shift = highest_significant_bit - mbits; + + if (exponent <= 0) { + // The output will be subnormal (before rounding). + + // For subnormal outputs, the shift must be adjusted by the exponent. The +1 + // is necessary because the exponent of a subnormal value (encoded as 0) is + // the same as the exponent of the smallest normal value (encoded as 1). + shift += -exponent + 1; + + // Handle inputs that would produce a zero output. + // + // Shifts higher than highest_significant_bit+1 will always produce a zero + // result. A shift of exactly highest_significant_bit+1 might produce a + // non-zero result after rounding. + if (shift > (highest_significant_bit + 1)) { + // The result will always be +/-0.0. + return sign << sign_offset; + } + + // Properly encode the exponent for a subnormal output. + exponent = 0; + } else { + // Clear the topmost mantissa bit, since this is not encoded in IEEE-754 + // normal values. + mantissa &= ~(1UL << highest_significant_bit); + } + + if (shift > 0) { + // We have to shift the mantissa to the right. Some precision is lost, so we + // need to apply rounding. + uint64_t onebit_mantissa = (mantissa >> (shift)) & 1; + uint64_t halfbit_mantissa = (mantissa >> (shift-1)) & 1; + uint64_t adjusted = mantissa - (halfbit_mantissa & ~onebit_mantissa); + T halfbit_adjusted = (adjusted >> (shift-1)) & 1; + + T result = (sign << sign_offset) | + (exponent << exponent_offset) | + ((mantissa >> shift) << mantissa_offset); + + // A very large mantissa can overflow during rounding. If this happens, the + // exponent should be incremented and the mantissa set to 1.0 (encoded as + // 0). Applying halfbit_adjusted after assembling the float has the nice + // side-effect that this case is handled for free. + // + // This also handles cases where a very large finite value overflows to + // infinity, or where a very large subnormal value overflows to become + // normal. + return result + halfbit_adjusted; + } else { + // We have to shift the mantissa to the left (or not at all). The input + // mantissa is exactly representable in the output mantissa, so apply no + // rounding correction. + return (sign << sign_offset) | + (exponent << exponent_offset) | + ((mantissa << -shift) << mantissa_offset); + } +} + + +// See FPRound for a description of this function. +static inline double FPRoundToDouble(int64_t sign, int64_t exponent, + uint64_t mantissa, FPRounding round_mode) { + int64_t bits = + FPRound<int64_t, kDoubleExponentBits, kDoubleMantissaBits>(sign, + exponent, + mantissa, + round_mode); + return rawbits_to_double(bits); +} + + +// See FPRound for a description of this function. +static inline float FPRoundToFloat(int64_t sign, int64_t exponent, + uint64_t mantissa, FPRounding round_mode) { + int32_t bits = + FPRound<int32_t, kFloatExponentBits, kFloatMantissaBits>(sign, + exponent, + mantissa, + round_mode); + return rawbits_to_float(bits); +} + + +double Simulator::FixedToDouble(int64_t src, int fbits, FPRounding round) { + if (src >= 0) { + return UFixedToDouble(src, fbits, round); + } else { + // This works for all negative values, including INT64_MIN. + return -UFixedToDouble(-src, fbits, round); + } +} + + +double Simulator::UFixedToDouble(uint64_t src, int fbits, FPRounding round) { + // An input of 0 is a special case because the result is effectively + // subnormal: The exponent is encoded as 0 and there is no implicit 1 bit. + if (src == 0) { + return 0.0; + } + + // Calculate the exponent. The highest significant bit will have the value + // 2^exponent. + const int highest_significant_bit = 63 - CountLeadingZeros(src, 64); + const int64_t exponent = highest_significant_bit - fbits; + + return FPRoundToDouble(0, exponent, src, round); +} + + +float Simulator::FixedToFloat(int64_t src, int fbits, FPRounding round) { + if (src >= 0) { + return UFixedToFloat(src, fbits, round); + } else { + // This works for all negative values, including INT64_MIN. + return -UFixedToFloat(-src, fbits, round); + } +} + + +float Simulator::UFixedToFloat(uint64_t src, int fbits, FPRounding round) { + // An input of 0 is a special case because the result is effectively + // subnormal: The exponent is encoded as 0 and there is no implicit 1 bit. + if (src == 0) { + return 0.0f; + } + + // Calculate the exponent. The highest significant bit will have the value + // 2^exponent. + const int highest_significant_bit = 63 - CountLeadingZeros(src, 64); + const int32_t exponent = highest_significant_bit - fbits; + + return FPRoundToFloat(0, exponent, src, round); +} + + +double Simulator::FPRoundInt(double value, FPRounding round_mode) { + if ((value == 0.0) || (value == kFP64PositiveInfinity) || + (value == kFP64NegativeInfinity)) { + return value; + } else if (std::isnan(value)) { + return FPProcessNaN(value); + } + + double int_result = floor(value); + double error = value - int_result; + switch (round_mode) { + case FPTieAway: { + // Take care of correctly handling the range ]-0.5, -0.0], which must + // yield -0.0. + if ((-0.5 < value) && (value < 0.0)) { + int_result = -0.0; + + } else if ((error > 0.5) || ((error == 0.5) && (int_result >= 0.0))) { + // If the error is greater than 0.5, or is equal to 0.5 and the integer + // result is positive, round up. + int_result++; + } + break; + } + case FPTieEven: { + // Take care of correctly handling the range [-0.5, -0.0], which must + // yield -0.0. + if ((-0.5 <= value) && (value < 0.0)) { + int_result = -0.0; + + // If the error is greater than 0.5, or is equal to 0.5 and the integer + // result is odd, round up. + } else if ((error > 0.5) || + ((error == 0.5) && (fmod(int_result, 2) != 0))) { + int_result++; + } + break; + } + case FPZero: { + // If value > 0 then we take floor(value) + // otherwise, ceil(value) + if (value < 0) { + int_result = ceil(value); + } + break; + } + case FPNegativeInfinity: { + // We always use floor(value). + break; + } + default: UNIMPLEMENTED(); + } + return int_result; +} + + +double Simulator::FPToDouble(float value) { + switch (std::fpclassify(value)) { + case FP_NAN: { + if (fpcr().DN()) return kFP64DefaultNaN; + + // Convert NaNs as the processor would: + // - The sign is propagated. + // - The payload (mantissa) is transferred entirely, except that the top + // bit is forced to '1', making the result a quiet NaN. The unused + // (low-order) payload bits are set to 0. + uint32_t raw = float_to_rawbits(value); + + uint64_t sign = raw >> 31; + uint64_t exponent = (1 << 11) - 1; + uint64_t payload = unsigned_bitextract_64(21, 0, raw); + payload <<= (52 - 23); // The unused low-order bits should be 0. + payload |= (1L << 51); // Force a quiet NaN. + + return rawbits_to_double((sign << 63) | (exponent << 52) | payload); + } + + case FP_ZERO: + case FP_NORMAL: + case FP_SUBNORMAL: + case FP_INFINITE: { + // All other inputs are preserved in a standard cast, because every value + // representable using an IEEE-754 float is also representable using an + // IEEE-754 double. + return static_cast<double>(value); + } + } + + UNREACHABLE(); + return static_cast<double>(value); +} + + +float Simulator::FPToFloat(double value, FPRounding round_mode) { + // Only the FPTieEven rounding mode is implemented. + ASSERT(round_mode == FPTieEven); + USE(round_mode); + + switch (std::fpclassify(value)) { + case FP_NAN: { + if (fpcr().DN()) return kFP32DefaultNaN; + + // Convert NaNs as the processor would: + // - The sign is propagated. + // - The payload (mantissa) is transferred as much as possible, except + // that the top bit is forced to '1', making the result a quiet NaN. + uint64_t raw = double_to_rawbits(value); + + uint32_t sign = raw >> 63; + uint32_t exponent = (1 << 8) - 1; + uint32_t payload = unsigned_bitextract_64(50, 52 - 23, raw); + payload |= (1 << 22); // Force a quiet NaN. + + return rawbits_to_float((sign << 31) | (exponent << 23) | payload); + } + + case FP_ZERO: + case FP_INFINITE: { + // In a C++ cast, any value representable in the target type will be + // unchanged. This is always the case for +/-0.0 and infinities. + return static_cast<float>(value); + } + + case FP_NORMAL: + case FP_SUBNORMAL: { + // Convert double-to-float as the processor would, assuming that FPCR.FZ + // (flush-to-zero) is not set. + uint64_t raw = double_to_rawbits(value); + // Extract the IEEE-754 double components. + uint32_t sign = raw >> 63; + // Extract the exponent and remove the IEEE-754 encoding bias. + int32_t exponent = unsigned_bitextract_64(62, 52, raw) - 1023; + // Extract the mantissa and add the implicit '1' bit. + uint64_t mantissa = unsigned_bitextract_64(51, 0, raw); + if (std::fpclassify(value) == FP_NORMAL) { + mantissa |= (1UL << 52); + } + return FPRoundToFloat(sign, exponent, mantissa, round_mode); + } + } + + UNREACHABLE(); + return value; +} + + +void Simulator::VisitFPDataProcessing2Source(Instruction* instr) { + AssertSupportedFPCR(); + + unsigned fd = instr->Rd(); + unsigned fn = instr->Rn(); + unsigned fm = instr->Rm(); + + // Fmaxnm and Fminnm have special NaN handling. + switch (instr->Mask(FPDataProcessing2SourceMask)) { + case FMAXNM_s: set_sreg(fd, FPMaxNM(sreg(fn), sreg(fm))); return; + case FMAXNM_d: set_dreg(fd, FPMaxNM(dreg(fn), dreg(fm))); return; + case FMINNM_s: set_sreg(fd, FPMinNM(sreg(fn), sreg(fm))); return; + case FMINNM_d: set_dreg(fd, FPMinNM(dreg(fn), dreg(fm))); return; + default: + break; // Fall through. + } + + if (FPProcessNaNs(instr)) return; + + switch (instr->Mask(FPDataProcessing2SourceMask)) { + case FADD_s: set_sreg(fd, FPAdd(sreg(fn), sreg(fm))); break; + case FADD_d: set_dreg(fd, FPAdd(dreg(fn), dreg(fm))); break; + case FSUB_s: set_sreg(fd, FPSub(sreg(fn), sreg(fm))); break; + case FSUB_d: set_dreg(fd, FPSub(dreg(fn), dreg(fm))); break; + case FMUL_s: set_sreg(fd, FPMul(sreg(fn), sreg(fm))); break; + case FMUL_d: set_dreg(fd, FPMul(dreg(fn), dreg(fm))); break; + case FDIV_s: set_sreg(fd, FPDiv(sreg(fn), sreg(fm))); break; + case FDIV_d: set_dreg(fd, FPDiv(dreg(fn), dreg(fm))); break; + case FMAX_s: set_sreg(fd, FPMax(sreg(fn), sreg(fm))); break; + case FMAX_d: set_dreg(fd, FPMax(dreg(fn), dreg(fm))); break; + case FMIN_s: set_sreg(fd, FPMin(sreg(fn), sreg(fm))); break; + case FMIN_d: set_dreg(fd, FPMin(dreg(fn), dreg(fm))); break; + case FMAXNM_s: + case FMAXNM_d: + case FMINNM_s: + case FMINNM_d: + // These were handled before the standard FPProcessNaNs() stage. + UNREACHABLE(); + default: UNIMPLEMENTED(); + } +} + + +void Simulator::VisitFPDataProcessing3Source(Instruction* instr) { + AssertSupportedFPCR(); + + unsigned fd = instr->Rd(); + unsigned fn = instr->Rn(); + unsigned fm = instr->Rm(); + unsigned fa = instr->Ra(); + + switch (instr->Mask(FPDataProcessing3SourceMask)) { + // fd = fa +/- (fn * fm) + case FMADD_s: set_sreg(fd, FPMulAdd(sreg(fa), sreg(fn), sreg(fm))); break; + case FMSUB_s: set_sreg(fd, FPMulAdd(sreg(fa), -sreg(fn), sreg(fm))); break; + case FMADD_d: set_dreg(fd, FPMulAdd(dreg(fa), dreg(fn), dreg(fm))); break; + case FMSUB_d: set_dreg(fd, FPMulAdd(dreg(fa), -dreg(fn), dreg(fm))); break; + // Negated variants of the above. + case FNMADD_s: + set_sreg(fd, FPMulAdd(-sreg(fa), -sreg(fn), sreg(fm))); + break; + case FNMSUB_s: + set_sreg(fd, FPMulAdd(-sreg(fa), sreg(fn), sreg(fm))); + break; + case FNMADD_d: + set_dreg(fd, FPMulAdd(-dreg(fa), -dreg(fn), dreg(fm))); + break; + case FNMSUB_d: + set_dreg(fd, FPMulAdd(-dreg(fa), dreg(fn), dreg(fm))); + break; + default: UNIMPLEMENTED(); + } +} + + +template <typename T> +T Simulator::FPAdd(T op1, T op2) { + // NaNs should be handled elsewhere. + ASSERT(!std::isnan(op1) && !std::isnan(op2)); + + if (std::isinf(op1) && std::isinf(op2) && (op1 != op2)) { + // inf + -inf returns the default NaN. + return FPDefaultNaN<T>(); + } else { + // Other cases should be handled by standard arithmetic. + return op1 + op2; + } +} + + +template <typename T> +T Simulator::FPDiv(T op1, T op2) { + // NaNs should be handled elsewhere. + ASSERT(!std::isnan(op1) && !std::isnan(op2)); + + if ((std::isinf(op1) && std::isinf(op2)) || ((op1 == 0.0) && (op2 == 0.0))) { + // inf / inf and 0.0 / 0.0 return the default NaN. + return FPDefaultNaN<T>(); + } else { + // Other cases should be handled by standard arithmetic. + return op1 / op2; + } +} + + +template <typename T> +T Simulator::FPMax(T a, T b) { + // NaNs should be handled elsewhere. + ASSERT(!std::isnan(a) && !std::isnan(b)); + + if ((a == 0.0) && (b == 0.0) && + (copysign(1.0, a) != copysign(1.0, b))) { + // a and b are zero, and the sign differs: return +0.0. + return 0.0; + } else { + return (a > b) ? a : b; + } +} + + +template <typename T> +T Simulator::FPMaxNM(T a, T b) { + if (IsQuietNaN(a) && !IsQuietNaN(b)) { + a = kFP64NegativeInfinity; + } else if (!IsQuietNaN(a) && IsQuietNaN(b)) { + b = kFP64NegativeInfinity; + } + + T result = FPProcessNaNs(a, b); + return std::isnan(result) ? result : FPMax(a, b); +} + +template <typename T> +T Simulator::FPMin(T a, T b) { + // NaNs should be handled elsewhere. + ASSERT(!isnan(a) && !isnan(b)); + + if ((a == 0.0) && (b == 0.0) && + (copysign(1.0, a) != copysign(1.0, b))) { + // a and b are zero, and the sign differs: return -0.0. + return -0.0; + } else { + return (a < b) ? a : b; + } +} + + +template <typename T> +T Simulator::FPMinNM(T a, T b) { + if (IsQuietNaN(a) && !IsQuietNaN(b)) { + a = kFP64PositiveInfinity; + } else if (!IsQuietNaN(a) && IsQuietNaN(b)) { + b = kFP64PositiveInfinity; + } + + T result = FPProcessNaNs(a, b); + return std::isnan(result) ? result : FPMin(a, b); +} + + +template <typename T> +T Simulator::FPMul(T op1, T op2) { + // NaNs should be handled elsewhere. + ASSERT(!std::isnan(op1) && !std::isnan(op2)); + + if ((std::isinf(op1) && (op2 == 0.0)) || (std::isinf(op2) && (op1 == 0.0))) { + // inf * 0.0 returns the default NaN. + return FPDefaultNaN<T>(); + } else { + // Other cases should be handled by standard arithmetic. + return op1 * op2; + } +} + + +template<typename T> +T Simulator::FPMulAdd(T a, T op1, T op2) { + T result = FPProcessNaNs3(a, op1, op2); + + T sign_a = copysign(1.0, a); + T sign_prod = copysign(1.0, op1) * copysign(1.0, op2); + bool isinf_prod = std::isinf(op1) || std::isinf(op2); + bool operation_generates_nan = + (std::isinf(op1) && (op2 == 0.0)) || // inf * 0.0 + (std::isinf(op2) && (op1 == 0.0)) || // 0.0 * inf + (std::isinf(a) && isinf_prod && (sign_a != sign_prod)); // inf - inf + + if (std::isnan(result)) { + // Generated NaNs override quiet NaNs propagated from a. + if (operation_generates_nan && IsQuietNaN(a)) { + return FPDefaultNaN<T>(); + } else { + return result; + } + } + + // If the operation would produce a NaN, return the default NaN. + if (operation_generates_nan) { + return FPDefaultNaN<T>(); + } + + // Work around broken fma implementations for exact zero results: The sign of + // exact 0.0 results is positive unless both a and op1 * op2 are negative. + if (((op1 == 0.0) || (op2 == 0.0)) && (a == 0.0)) { + return ((sign_a < 0) && (sign_prod < 0)) ? -0.0 : 0.0; + } + + result = FusedMultiplyAdd(op1, op2, a); + ASSERT(!std::isnan(result)); + + // Work around broken fma implementations for rounded zero results: If a is + // 0.0, the sign of the result is the sign of op1 * op2 before rounding. + if ((a == 0.0) && (result == 0.0)) { + return copysign(0.0, sign_prod); + } + + return result; +} + + +template <typename T> +T Simulator::FPSqrt(T op) { + if (std::isnan(op)) { + return FPProcessNaN(op); + } else if (op < 0.0) { + return FPDefaultNaN<T>(); + } else { + return std::sqrt(op); + } +} + + +template <typename T> +T Simulator::FPSub(T op1, T op2) { + // NaNs should be handled elsewhere. + ASSERT(!std::isnan(op1) && !std::isnan(op2)); + + if (std::isinf(op1) && std::isinf(op2) && (op1 == op2)) { + // inf - inf returns the default NaN. + return FPDefaultNaN<T>(); + } else { + // Other cases should be handled by standard arithmetic. + return op1 - op2; + } +} + + +template <typename T> +T Simulator::FPProcessNaN(T op) { + ASSERT(std::isnan(op)); + return fpcr().DN() ? FPDefaultNaN<T>() : ToQuietNaN(op); +} + + +template <typename T> +T Simulator::FPProcessNaNs(T op1, T op2) { + if (IsSignallingNaN(op1)) { + return FPProcessNaN(op1); + } else if (IsSignallingNaN(op2)) { + return FPProcessNaN(op2); + } else if (std::isnan(op1)) { + ASSERT(IsQuietNaN(op1)); + return FPProcessNaN(op1); + } else if (std::isnan(op2)) { + ASSERT(IsQuietNaN(op2)); + return FPProcessNaN(op2); + } else { + return 0.0; + } +} + + +template <typename T> +T Simulator::FPProcessNaNs3(T op1, T op2, T op3) { + if (IsSignallingNaN(op1)) { + return FPProcessNaN(op1); + } else if (IsSignallingNaN(op2)) { + return FPProcessNaN(op2); + } else if (IsSignallingNaN(op3)) { + return FPProcessNaN(op3); + } else if (std::isnan(op1)) { + ASSERT(IsQuietNaN(op1)); + return FPProcessNaN(op1); + } else if (std::isnan(op2)) { + ASSERT(IsQuietNaN(op2)); + return FPProcessNaN(op2); + } else if (std::isnan(op3)) { + ASSERT(IsQuietNaN(op3)); + return FPProcessNaN(op3); + } else { + return 0.0; + } +} + + +bool Simulator::FPProcessNaNs(Instruction* instr) { + unsigned fd = instr->Rd(); + unsigned fn = instr->Rn(); + unsigned fm = instr->Rm(); + bool done = false; + + if (instr->Mask(FP64) == FP64) { + double result = FPProcessNaNs(dreg(fn), dreg(fm)); + if (std::isnan(result)) { + set_dreg(fd, result); + done = true; + } + } else { + float result = FPProcessNaNs(sreg(fn), sreg(fm)); + if (std::isnan(result)) { + set_sreg(fd, result); + done = true; + } + } + + return done; +} + + +void Simulator::VisitSystem(Instruction* instr) { + // Some system instructions hijack their Op and Cp fields to represent a + // range of immediates instead of indicating a different instruction. This + // makes the decoding tricky. + if (instr->Mask(SystemSysRegFMask) == SystemSysRegFixed) { + switch (instr->Mask(SystemSysRegMask)) { + case MRS: { + switch (instr->ImmSystemRegister()) { + case NZCV: set_xreg(instr->Rt(), nzcv().RawValue()); break; + case FPCR: set_xreg(instr->Rt(), fpcr().RawValue()); break; + default: UNIMPLEMENTED(); + } + break; + } + case MSR: { + switch (instr->ImmSystemRegister()) { + case NZCV: nzcv().SetRawValue(xreg(instr->Rt())); break; + case FPCR: fpcr().SetRawValue(xreg(instr->Rt())); break; + default: UNIMPLEMENTED(); + } + break; + } + } + } else if (instr->Mask(SystemHintFMask) == SystemHintFixed) { + ASSERT(instr->Mask(SystemHintMask) == HINT); + switch (instr->ImmHint()) { + case NOP: break; + default: UNIMPLEMENTED(); + } + } else if (instr->Mask(MemBarrierFMask) == MemBarrierFixed) { + __sync_synchronize(); + } else { + UNIMPLEMENTED(); + } +} + + +bool Simulator::GetValue(const char* desc, int64_t* value) { + int regnum = CodeFromName(desc); + if (regnum >= 0) { + unsigned code = regnum; + if (code == kZeroRegCode) { + // Catch the zero register and return 0. + *value = 0; + return true; + } else if (code == kSPRegInternalCode) { + // Translate the stack pointer code to 31, for Reg31IsStackPointer. + code = 31; + } + if (desc[0] == 'w') { + *value = wreg(code, Reg31IsStackPointer); + } else { + *value = xreg(code, Reg31IsStackPointer); + } + return true; + } else if (strncmp(desc, "0x", 2) == 0) { + return SScanF(desc + 2, "%" SCNx64, + reinterpret_cast<uint64_t*>(value)) == 1; + } else { + return SScanF(desc, "%" SCNu64, + reinterpret_cast<uint64_t*>(value)) == 1; + } +} + + +bool Simulator::PrintValue(const char* desc) { + if (strcmp(desc, "csp") == 0) { + ASSERT(CodeFromName(desc) == static_cast<int>(kSPRegInternalCode)); + PrintF(stream_, "%s csp:%s 0x%016" PRIx64 "%s\n", + clr_reg_name, clr_reg_value, xreg(31, Reg31IsStackPointer), clr_normal); + return true; + } else if (strcmp(desc, "wcsp") == 0) { + ASSERT(CodeFromName(desc) == static_cast<int>(kSPRegInternalCode)); + PrintF(stream_, "%s wcsp:%s 0x%08" PRIx32 "%s\n", + clr_reg_name, clr_reg_value, wreg(31, Reg31IsStackPointer), clr_normal); + return true; + } + + int i = CodeFromName(desc); + STATIC_ASSERT(kNumberOfRegisters == kNumberOfFPRegisters); + if (i < 0 || static_cast<unsigned>(i) >= kNumberOfFPRegisters) return false; + + if (desc[0] == 'v') { + PrintF(stream_, "%s %s:%s 0x%016" PRIx64 "%s (%s%s:%s %g%s %s:%s %g%s)\n", + clr_fpreg_name, VRegNameForCode(i), + clr_fpreg_value, double_to_rawbits(dreg(i)), + clr_normal, + clr_fpreg_name, DRegNameForCode(i), + clr_fpreg_value, dreg(i), + clr_fpreg_name, SRegNameForCode(i), + clr_fpreg_value, sreg(i), + clr_normal); + return true; + } else if (desc[0] == 'd') { + PrintF(stream_, "%s %s:%s %g%s\n", + clr_fpreg_name, DRegNameForCode(i), + clr_fpreg_value, dreg(i), + clr_normal); + return true; + } else if (desc[0] == 's') { + PrintF(stream_, "%s %s:%s %g%s\n", + clr_fpreg_name, SRegNameForCode(i), + clr_fpreg_value, sreg(i), + clr_normal); + return true; + } else if (desc[0] == 'w') { + PrintF(stream_, "%s %s:%s 0x%08" PRIx32 "%s\n", + clr_reg_name, WRegNameForCode(i), clr_reg_value, wreg(i), clr_normal); + return true; + } else { + // X register names have a wide variety of starting characters, but anything + // else will be an X register. + PrintF(stream_, "%s %s:%s 0x%016" PRIx64 "%s\n", + clr_reg_name, XRegNameForCode(i), clr_reg_value, xreg(i), clr_normal); + return true; + } +} + + +void Simulator::Debug() { +#define COMMAND_SIZE 63 +#define ARG_SIZE 255 + +#define STR(a) #a +#define XSTR(a) STR(a) + + char cmd[COMMAND_SIZE + 1]; + char arg1[ARG_SIZE + 1]; + char arg2[ARG_SIZE + 1]; + char* argv[3] = { cmd, arg1, arg2 }; + + // Make sure to have a proper terminating character if reaching the limit. + cmd[COMMAND_SIZE] = 0; + arg1[ARG_SIZE] = 0; + arg2[ARG_SIZE] = 0; + + bool done = false; + bool cleared_log_disasm_bit = false; + + while (!done) { + // Disassemble the next instruction to execute before doing anything else. + PrintInstructionsAt(pc_, 1); + // Read the command line. + char* line = ReadLine("sim> "); + if (line == NULL) { + break; + } else { + // Repeat last command by default. + char* last_input = last_debugger_input(); + if (strcmp(line, "\n") == 0 && (last_input != NULL)) { + DeleteArray(line); + line = last_input; + } else { + // Update the latest command ran + set_last_debugger_input(line); + } + + // Use sscanf to parse the individual parts of the command line. At the + // moment no command expects more than two parameters. + int argc = SScanF(line, + "%" XSTR(COMMAND_SIZE) "s " + "%" XSTR(ARG_SIZE) "s " + "%" XSTR(ARG_SIZE) "s", + cmd, arg1, arg2); + + // stepi / si ------------------------------------------------------------ + if ((strcmp(cmd, "si") == 0) || (strcmp(cmd, "stepi") == 0)) { + // We are about to execute instructions, after which by default we + // should increment the pc_. If it was set when reaching this debug + // instruction, it has not been cleared because this instruction has not + // completed yet. So clear it manually. + pc_modified_ = false; + + if (argc == 1) { + ExecuteInstruction(); + } else { + int64_t number_of_instructions_to_execute = 1; + GetValue(arg1, &number_of_instructions_to_execute); + + set_log_parameters(log_parameters() | LOG_DISASM); + while (number_of_instructions_to_execute-- > 0) { + ExecuteInstruction(); + } + set_log_parameters(log_parameters() & ~LOG_DISASM); + PrintF("\n"); + } + + // If it was necessary, the pc has already been updated or incremented + // when executing the instruction. So we do not want it to be updated + // again. It will be cleared when exiting. + pc_modified_ = true; + + // next / n -------------------------------------------------------------- + } else if ((strcmp(cmd, "next") == 0) || (strcmp(cmd, "n") == 0)) { + // Tell the simulator to break after the next executed BL. + break_on_next_ = true; + // Continue. + done = true; + + // continue / cont / c --------------------------------------------------- + } else if ((strcmp(cmd, "continue") == 0) || + (strcmp(cmd, "cont") == 0) || + (strcmp(cmd, "c") == 0)) { + // Leave the debugger shell. + done = true; + + // disassemble / disasm / di --------------------------------------------- + } else if (strcmp(cmd, "disassemble") == 0 || + strcmp(cmd, "disasm") == 0 || + strcmp(cmd, "di") == 0) { + int64_t n_of_instrs_to_disasm = 10; // default value. + int64_t address = reinterpret_cast<int64_t>(pc_); // default value. + if (argc >= 2) { // disasm <n of instrs> + GetValue(arg1, &n_of_instrs_to_disasm); + } + if (argc >= 3) { // disasm <n of instrs> <address> + GetValue(arg2, &address); + } + + // Disassemble. + PrintInstructionsAt(reinterpret_cast<Instruction*>(address), + n_of_instrs_to_disasm); + PrintF("\n"); + + // print / p ------------------------------------------------------------- + } else if ((strcmp(cmd, "print") == 0) || (strcmp(cmd, "p") == 0)) { + if (argc == 2) { + if (strcmp(arg1, "all") == 0) { + PrintRegisters(true); + PrintFPRegisters(true); + } else { + if (!PrintValue(arg1)) { + PrintF("%s unrecognized\n", arg1); + } + } + } else { + PrintF( + "print <register>\n" + " Print the content of a register. (alias 'p')\n" + " 'print all' will print all registers.\n" + " Use 'printobject' to get more details about the value.\n"); + } + + // printobject / po ------------------------------------------------------ + } else if ((strcmp(cmd, "printobject") == 0) || + (strcmp(cmd, "po") == 0)) { + if (argc == 2) { + int64_t value; + if (GetValue(arg1, &value)) { + Object* obj = reinterpret_cast<Object*>(value); + PrintF("%s: \n", arg1); +#ifdef DEBUG + obj->PrintLn(); +#else + obj->ShortPrint(); + PrintF("\n"); +#endif + } else { + PrintF("%s unrecognized\n", arg1); + } + } else { + PrintF("printobject <value>\n" + "printobject <register>\n" + " Print details about the value. (alias 'po')\n"); + } + + // stack / mem ---------------------------------------------------------- + } else if (strcmp(cmd, "stack") == 0 || strcmp(cmd, "mem") == 0) { + int64_t* cur = NULL; + int64_t* end = NULL; + int next_arg = 1; + + if (strcmp(cmd, "stack") == 0) { + cur = reinterpret_cast<int64_t*>(jssp()); + + } else { // "mem" + int64_t value; + if (!GetValue(arg1, &value)) { + PrintF("%s unrecognized\n", arg1); + continue; + } + cur = reinterpret_cast<int64_t*>(value); + next_arg++; + } + + int64_t words = 0; + if (argc == next_arg) { + words = 10; + } else if (argc == next_arg + 1) { + if (!GetValue(argv[next_arg], &words)) { + PrintF("%s unrecognized\n", argv[next_arg]); + PrintF("Printing 10 double words by default"); + words = 10; + } + } else { + UNREACHABLE(); + } + end = cur + words; + + while (cur < end) { + PrintF(" 0x%016" PRIx64 ": 0x%016" PRIx64 " %10" PRId64, + reinterpret_cast<uint64_t>(cur), *cur, *cur); + HeapObject* obj = reinterpret_cast<HeapObject*>(*cur); + int64_t value = *cur; + Heap* current_heap = v8::internal::Isolate::Current()->heap(); + if (((value & 1) == 0) || current_heap->Contains(obj)) { + PrintF(" ("); + if ((value & kSmiTagMask) == 0) { + STATIC_ASSERT(kSmiValueSize == 32); + int32_t untagged = (value >> kSmiShift) & 0xffffffff; + PrintF("smi %" PRId32, untagged); + } else { + obj->ShortPrint(); + } + PrintF(")"); + } + PrintF("\n"); + cur++; + } + + // trace / t ------------------------------------------------------------- + } else if (strcmp(cmd, "trace") == 0 || strcmp(cmd, "t") == 0) { + if ((log_parameters() & (LOG_DISASM | LOG_REGS)) != + (LOG_DISASM | LOG_REGS)) { + PrintF("Enabling disassembly and registers tracing\n"); + set_log_parameters(log_parameters() | LOG_DISASM | LOG_REGS); + } else { + PrintF("Disabling disassembly and registers tracing\n"); + set_log_parameters(log_parameters() & ~(LOG_DISASM | LOG_REGS)); + } + + // break / b ------------------------------------------------------------- + } else if (strcmp(cmd, "break") == 0 || strcmp(cmd, "b") == 0) { + if (argc == 2) { + int64_t value; + if (GetValue(arg1, &value)) { + SetBreakpoint(reinterpret_cast<Instruction*>(value)); + } else { + PrintF("%s unrecognized\n", arg1); + } + } else { + ListBreakpoints(); + PrintF("Use `break <address>` to set or disable a breakpoint\n"); + } + + // gdb ------------------------------------------------------------------- + } else if (strcmp(cmd, "gdb") == 0) { + PrintF("Relinquishing control to gdb.\n"); + OS::DebugBreak(); + PrintF("Regaining control from gdb.\n"); + + // sysregs --------------------------------------------------------------- + } else if (strcmp(cmd, "sysregs") == 0) { + PrintSystemRegisters(); + + // help / h -------------------------------------------------------------- + } else if (strcmp(cmd, "help") == 0 || strcmp(cmd, "h") == 0) { + PrintF( + "stepi / si\n" + " stepi <n>\n" + " Step <n> instructions.\n" + "next / n\n" + " Continue execution until a BL instruction is reached.\n" + " At this point a breakpoint is set just after this BL.\n" + " Then execution is resumed. It will probably later hit the\n" + " breakpoint just set.\n" + "continue / cont / c\n" + " Continue execution from here.\n" + "disassemble / disasm / di\n" + " disassemble <n> <address>\n" + " Disassemble <n> instructions from current <address>.\n" + " By default <n> is 20 and <address> is the current pc.\n" + "print / p\n" + " print <register>\n" + " Print the content of a register.\n" + " 'print all' will print all registers.\n" + " Use 'printobject' to get more details about the value.\n" + "printobject / po\n" + " printobject <value>\n" + " printobject <register>\n" + " Print details about the value.\n" + "stack\n" + " stack [<words>]\n" + " Dump stack content, default dump 10 words\n" + "mem\n" + " mem <address> [<words>]\n" + " Dump memory content, default dump 10 words\n" + "trace / t\n" + " Toggle disassembly and register tracing\n" + "break / b\n" + " break : list all breakpoints\n" + " break <address> : set / enable / disable a breakpoint.\n" + "gdb\n" + " Enter gdb.\n" + "sysregs\n" + " Print all system registers (including NZCV).\n"); + } else { + PrintF("Unknown command: %s\n", cmd); + PrintF("Use 'help' for more information.\n"); + } + } + if (cleared_log_disasm_bit == true) { + set_log_parameters(log_parameters_ | LOG_DISASM); + } + } +} + + +void Simulator::VisitException(Instruction* instr) { + switch (instr->Mask(ExceptionMask)) { + case HLT: { + if (instr->ImmException() == kImmExceptionIsDebug) { + // Read the arguments encoded inline in the instruction stream. + uint32_t code; + uint32_t parameters; + + memcpy(&code, + pc_->InstructionAtOffset(kDebugCodeOffset), + sizeof(code)); + memcpy(¶meters, + pc_->InstructionAtOffset(kDebugParamsOffset), + sizeof(parameters)); + char const *message = + reinterpret_cast<char const*>( + pc_->InstructionAtOffset(kDebugMessageOffset)); + + // Always print something when we hit a debug point that breaks. + // We are going to break, so printing something is not an issue in + // terms of speed. + if (FLAG_trace_sim_messages || FLAG_trace_sim || (parameters & BREAK)) { + if (message != NULL) { + PrintF(stream_, + "%sDebugger hit %d: %s%s%s\n", + clr_debug_number, + code, + clr_debug_message, + message, + clr_normal); + } else { + PrintF(stream_, + "%sDebugger hit %d.%s\n", + clr_debug_number, + code, + clr_normal); + } + } + + // Other options. + switch (parameters & kDebuggerTracingDirectivesMask) { + case TRACE_ENABLE: + set_log_parameters(log_parameters() | parameters); + if (parameters & LOG_SYS_REGS) { PrintSystemRegisters(); } + if (parameters & LOG_REGS) { PrintRegisters(); } + if (parameters & LOG_FP_REGS) { PrintFPRegisters(); } + break; + case TRACE_DISABLE: + set_log_parameters(log_parameters() & ~parameters); + break; + case TRACE_OVERRIDE: + set_log_parameters(parameters); + break; + default: + // We don't support a one-shot LOG_DISASM. + ASSERT((parameters & LOG_DISASM) == 0); + // Don't print information that is already being traced. + parameters &= ~log_parameters(); + // Print the requested information. + if (parameters & LOG_SYS_REGS) PrintSystemRegisters(true); + if (parameters & LOG_REGS) PrintRegisters(true); + if (parameters & LOG_FP_REGS) PrintFPRegisters(true); + } + + // The stop parameters are inlined in the code. Skip them: + // - Skip to the end of the message string. + size_t size = kDebugMessageOffset + strlen(message) + 1; + pc_ = pc_->InstructionAtOffset(RoundUp(size, kInstructionSize)); + // - Verify that the unreachable marker is present. + ASSERT(pc_->Mask(ExceptionMask) == HLT); + ASSERT(pc_->ImmException() == kImmExceptionIsUnreachable); + // - Skip past the unreachable marker. + set_pc(pc_->following()); + + // Check if the debugger should break. + if (parameters & BREAK) Debug(); + + } else if (instr->ImmException() == kImmExceptionIsRedirectedCall) { + DoRuntimeCall(instr); + } else if (instr->ImmException() == kImmExceptionIsPrintf) { + DoPrintf(instr); + + } else if (instr->ImmException() == kImmExceptionIsUnreachable) { + fprintf(stream_, "Hit UNREACHABLE marker at PC=%p.\n", + reinterpret_cast<void*>(pc_)); + abort(); + + } else { + OS::DebugBreak(); + } + break; + } + + default: + UNIMPLEMENTED(); + } +} + + +void Simulator::DoPrintf(Instruction* instr) { + ASSERT((instr->Mask(ExceptionMask) == HLT) && + (instr->ImmException() == kImmExceptionIsPrintf)); + + // Read the arguments encoded inline in the instruction stream. + uint32_t arg_count; + uint32_t arg_pattern_list; + STATIC_ASSERT(sizeof(*instr) == 1); + memcpy(&arg_count, + instr + kPrintfArgCountOffset, + sizeof(arg_count)); + memcpy(&arg_pattern_list, + instr + kPrintfArgPatternListOffset, + sizeof(arg_pattern_list)); + + ASSERT(arg_count <= kPrintfMaxArgCount); + ASSERT((arg_pattern_list >> (kPrintfArgPatternBits * arg_count)) == 0); + + // We need to call the host printf function with a set of arguments defined by + // arg_pattern_list. Because we don't know the types and sizes of the + // arguments, this is very difficult to do in a robust and portable way. To + // work around the problem, we pick apart the format string, and print one + // format placeholder at a time. + + // Allocate space for the format string. We take a copy, so we can modify it. + // Leave enough space for one extra character per expected argument (plus the + // '\0' termination). + const char * format_base = reg<const char *>(0); + ASSERT(format_base != NULL); + size_t length = strlen(format_base) + 1; + char * const format = new char[length + arg_count]; + + // A list of chunks, each with exactly one format placeholder. + const char * chunks[kPrintfMaxArgCount]; + + // Copy the format string and search for format placeholders. + uint32_t placeholder_count = 0; + char * format_scratch = format; + for (size_t i = 0; i < length; i++) { + if (format_base[i] != '%') { + *format_scratch++ = format_base[i]; + } else { + if (format_base[i + 1] == '%') { + // Ignore explicit "%%" sequences. + *format_scratch++ = format_base[i]; + + if (placeholder_count == 0) { + // The first chunk is passed to printf using "%s", so we need to + // unescape "%%" sequences in this chunk. (Just skip the next '%'.) + i++; + } else { + // Otherwise, pass through "%%" unchanged. + *format_scratch++ = format_base[++i]; + } + } else { + CHECK(placeholder_count < arg_count); + // Insert '\0' before placeholders, and store their locations. + *format_scratch++ = '\0'; + chunks[placeholder_count++] = format_scratch; + *format_scratch++ = format_base[i]; + } + } + } + ASSERT(format_scratch <= (format + length + arg_count)); + CHECK(placeholder_count == arg_count); + + // Finally, call printf with each chunk, passing the appropriate register + // argument. Normally, printf returns the number of bytes transmitted, so we + // can emulate a single printf call by adding the result from each chunk. If + // any call returns a negative (error) value, though, just return that value. + + fprintf(stream_, "%s", clr_printf); + + // Because '\0' is inserted before each placeholder, the first string in + // 'format' contains no format placeholders and should be printed literally. + int result = fprintf(stream_, "%s", format); + int pcs_r = 1; // Start at x1. x0 holds the format string. + int pcs_f = 0; // Start at d0. + if (result >= 0) { + for (uint32_t i = 0; i < placeholder_count; i++) { + int part_result = -1; + + uint32_t arg_pattern = arg_pattern_list >> (i * kPrintfArgPatternBits); + arg_pattern &= (1 << kPrintfArgPatternBits) - 1; + switch (arg_pattern) { + case kPrintfArgW: + part_result = fprintf(stream_, chunks[i], wreg(pcs_r++)); + break; + case kPrintfArgX: + part_result = fprintf(stream_, chunks[i], xreg(pcs_r++)); + break; + case kPrintfArgD: + part_result = fprintf(stream_, chunks[i], dreg(pcs_f++)); + break; + default: UNREACHABLE(); + } + + if (part_result < 0) { + // Handle error values. + result = part_result; + break; + } + + result += part_result; + } + } + + fprintf(stream_, "%s", clr_normal); + +#ifdef DEBUG + CorruptAllCallerSavedCPURegisters(); +#endif + + // Printf returns its result in x0 (just like the C library's printf). + set_xreg(0, result); + + // The printf parameters are inlined in the code, so skip them. + set_pc(instr->InstructionAtOffset(kPrintfLength)); + + // Set LR as if we'd just called a native printf function. + set_lr(pc()); + + delete[] format; +} + + +#endif // USE_SIMULATOR + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/simulator-arm64.h b/chromium/v8/src/arm64/simulator-arm64.h new file mode 100644 index 00000000000..bf74de85fbf --- /dev/null +++ b/chromium/v8/src/arm64/simulator-arm64.h @@ -0,0 +1,837 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_SIMULATOR_ARM64_H_ +#define V8_ARM64_SIMULATOR_ARM64_H_ + +#include <stdarg.h> +#include <vector> + +#include "src/v8.h" + +#include "src/globals.h" +#include "src/utils.h" +#include "src/allocation.h" +#include "src/assembler.h" +#include "src/arm64/assembler-arm64.h" +#include "src/arm64/decoder-arm64.h" +#include "src/arm64/disasm-arm64.h" +#include "src/arm64/instrument-arm64.h" + +#define REGISTER_CODE_LIST(R) \ +R(0) R(1) R(2) R(3) R(4) R(5) R(6) R(7) \ +R(8) R(9) R(10) R(11) R(12) R(13) R(14) R(15) \ +R(16) R(17) R(18) R(19) R(20) R(21) R(22) R(23) \ +R(24) R(25) R(26) R(27) R(28) R(29) R(30) R(31) + +namespace v8 { +namespace internal { + +#if !defined(USE_SIMULATOR) + +// Running without a simulator on a native ARM64 platform. +// When running without a simulator we call the entry directly. +#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \ + (entry(p0, p1, p2, p3, p4)) + +typedef int (*arm64_regexp_matcher)(String* input, + int64_t start_offset, + const byte* input_start, + const byte* input_end, + int* output, + int64_t output_size, + Address stack_base, + int64_t direct_call, + void* return_address, + Isolate* isolate); + +// Call the generated regexp code directly. The code at the entry address +// should act as a function matching the type arm64_regexp_matcher. +// The ninth argument is a dummy that reserves the space used for +// the return address added by the ExitFrame in native calls. +#define CALL_GENERATED_REGEXP_CODE(entry, p0, p1, p2, p3, p4, p5, p6, p7, p8) \ + (FUNCTION_CAST<arm64_regexp_matcher>(entry)( \ + p0, p1, p2, p3, p4, p5, p6, p7, NULL, p8)) + +// Running without a simulator there is nothing to do. +class SimulatorStack : public v8::internal::AllStatic { + public: + static uintptr_t JsLimitFromCLimit(v8::internal::Isolate* isolate, + uintptr_t c_limit) { + USE(isolate); + return c_limit; + } + + static uintptr_t RegisterCTryCatch(uintptr_t try_catch_address) { + return try_catch_address; + } + + static void UnregisterCTryCatch() { } +}; + +#else // !defined(USE_SIMULATOR) + +enum ReverseByteMode { + Reverse16 = 0, + Reverse32 = 1, + Reverse64 = 2 +}; + + +// The proper way to initialize a simulated system register (such as NZCV) is as +// follows: +// SimSystemRegister nzcv = SimSystemRegister::DefaultValueFor(NZCV); +class SimSystemRegister { + public: + // The default constructor represents a register which has no writable bits. + // It is not possible to set its value to anything other than 0. + SimSystemRegister() : value_(0), write_ignore_mask_(0xffffffff) { } + + uint32_t RawValue() const { + return value_; + } + + void SetRawValue(uint32_t new_value) { + value_ = (value_ & write_ignore_mask_) | (new_value & ~write_ignore_mask_); + } + + uint32_t Bits(int msb, int lsb) const { + return unsigned_bitextract_32(msb, lsb, value_); + } + + int32_t SignedBits(int msb, int lsb) const { + return signed_bitextract_32(msb, lsb, value_); + } + + void SetBits(int msb, int lsb, uint32_t bits); + + // Default system register values. + static SimSystemRegister DefaultValueFor(SystemRegister id); + +#define DEFINE_GETTER(Name, HighBit, LowBit, Func, Type) \ + Type Name() const { return static_cast<Type>(Func(HighBit, LowBit)); } \ + void Set##Name(Type bits) { \ + SetBits(HighBit, LowBit, static_cast<Type>(bits)); \ + } +#define DEFINE_WRITE_IGNORE_MASK(Name, Mask) \ + static const uint32_t Name##WriteIgnoreMask = ~static_cast<uint32_t>(Mask); + SYSTEM_REGISTER_FIELDS_LIST(DEFINE_GETTER, DEFINE_WRITE_IGNORE_MASK) +#undef DEFINE_ZERO_BITS +#undef DEFINE_GETTER + + protected: + // Most system registers only implement a few of the bits in the word. Other + // bits are "read-as-zero, write-ignored". The write_ignore_mask argument + // describes the bits which are not modifiable. + SimSystemRegister(uint32_t value, uint32_t write_ignore_mask) + : value_(value), write_ignore_mask_(write_ignore_mask) { } + + uint32_t value_; + uint32_t write_ignore_mask_; +}; + + +// Represent a register (r0-r31, v0-v31). +class SimRegisterBase { + public: + template<typename T> + void Set(T new_value) { + value_ = 0; + memcpy(&value_, &new_value, sizeof(T)); + } + + template<typename T> + T Get() const { + T result; + memcpy(&result, &value_, sizeof(T)); + return result; + } + + protected: + int64_t value_; +}; + + +typedef SimRegisterBase SimRegister; // r0-r31 +typedef SimRegisterBase SimFPRegister; // v0-v31 + + +class Simulator : public DecoderVisitor { + public: + explicit Simulator(Decoder<DispatchingDecoderVisitor>* decoder, + Isolate* isolate = NULL, + FILE* stream = stderr); + Simulator(); + ~Simulator(); + + // System functions. + + static void Initialize(Isolate* isolate); + + static Simulator* current(v8::internal::Isolate* isolate); + + class CallArgument; + + // Call an arbitrary function taking an arbitrary number of arguments. The + // varargs list must be a set of arguments with type CallArgument, and + // terminated by CallArgument::End(). + void CallVoid(byte* entry, CallArgument* args); + + // Like CallVoid, but expect a return value. + int64_t CallInt64(byte* entry, CallArgument* args); + double CallDouble(byte* entry, CallArgument* args); + + // V8 calls into generated JS code with 5 parameters and into + // generated RegExp code with 10 parameters. These are convenience functions, + // which set up the simulator state and grab the result on return. + int64_t CallJS(byte* entry, + byte* function_entry, + JSFunction* func, + Object* revc, + int64_t argc, + Object*** argv); + int64_t CallRegExp(byte* entry, + String* input, + int64_t start_offset, + const byte* input_start, + const byte* input_end, + int* output, + int64_t output_size, + Address stack_base, + int64_t direct_call, + void* return_address, + Isolate* isolate); + + // A wrapper class that stores an argument for one of the above Call + // functions. + // + // Only arguments up to 64 bits in size are supported. + class CallArgument { + public: + template<typename T> + explicit CallArgument(T argument) { + ASSERT(sizeof(argument) <= sizeof(bits_)); + memcpy(&bits_, &argument, sizeof(argument)); + type_ = X_ARG; + } + + explicit CallArgument(double argument) { + ASSERT(sizeof(argument) == sizeof(bits_)); + memcpy(&bits_, &argument, sizeof(argument)); + type_ = D_ARG; + } + + explicit CallArgument(float argument) { + // TODO(all): CallArgument(float) is untested, remove this check once + // tested. + UNIMPLEMENTED(); + // Make the D register a NaN to try to trap errors if the callee expects a + // double. If it expects a float, the callee should ignore the top word. + ASSERT(sizeof(kFP64SignallingNaN) == sizeof(bits_)); + memcpy(&bits_, &kFP64SignallingNaN, sizeof(kFP64SignallingNaN)); + // Write the float payload to the S register. + ASSERT(sizeof(argument) <= sizeof(bits_)); + memcpy(&bits_, &argument, sizeof(argument)); + type_ = D_ARG; + } + + // This indicates the end of the arguments list, so that CallArgument + // objects can be passed into varargs functions. + static CallArgument End() { return CallArgument(); } + + int64_t bits() const { return bits_; } + bool IsEnd() const { return type_ == NO_ARG; } + bool IsX() const { return type_ == X_ARG; } + bool IsD() const { return type_ == D_ARG; } + + private: + enum CallArgumentType { X_ARG, D_ARG, NO_ARG }; + + // All arguments are aligned to at least 64 bits and we don't support + // passing bigger arguments, so the payload size can be fixed at 64 bits. + int64_t bits_; + CallArgumentType type_; + + CallArgument() { type_ = NO_ARG; } + }; + + + // Start the debugging command line. + void Debug(); + + bool GetValue(const char* desc, int64_t* value); + + bool PrintValue(const char* desc); + + // Push an address onto the JS stack. + uintptr_t PushAddress(uintptr_t address); + + // Pop an address from the JS stack. + uintptr_t PopAddress(); + + // Accessor to the internal simulator stack area. + uintptr_t StackLimit() const; + + void ResetState(); + + // Runtime call support. + static void* RedirectExternalReference(void* external_function, + ExternalReference::Type type); + void DoRuntimeCall(Instruction* instr); + + // Run the simulator. + static const Instruction* kEndOfSimAddress; + void DecodeInstruction(); + void Run(); + void RunFrom(Instruction* start); + + // Simulation helpers. + template <typename T> + void set_pc(T new_pc) { + ASSERT(sizeof(T) == sizeof(pc_)); + memcpy(&pc_, &new_pc, sizeof(T)); + pc_modified_ = true; + } + Instruction* pc() { return pc_; } + + void increment_pc() { + if (!pc_modified_) { + pc_ = pc_->following(); + } + + pc_modified_ = false; + } + + virtual void Decode(Instruction* instr) { + decoder_->Decode(instr); + } + + void ExecuteInstruction() { + ASSERT(IsAligned(reinterpret_cast<uintptr_t>(pc_), kInstructionSize)); + CheckBreakNext(); + Decode(pc_); + LogProcessorState(); + increment_pc(); + CheckBreakpoints(); + } + + // Declare all Visitor functions. + #define DECLARE(A) void Visit##A(Instruction* instr); + VISITOR_LIST(DECLARE) + #undef DECLARE + + bool IsZeroRegister(unsigned code, Reg31Mode r31mode) const { + return ((code == 31) && (r31mode == Reg31IsZeroRegister)); + } + + // Register accessors. + // Return 'size' bits of the value of an integer register, as the specified + // type. The value is zero-extended to fill the result. + // + template<typename T> + T reg(unsigned code, Reg31Mode r31mode = Reg31IsZeroRegister) const { + ASSERT(code < kNumberOfRegisters); + if (IsZeroRegister(code, r31mode)) { + return 0; + } + return registers_[code].Get<T>(); + } + + // Common specialized accessors for the reg() template. + int32_t wreg(unsigned code, Reg31Mode r31mode = Reg31IsZeroRegister) const { + return reg<int32_t>(code, r31mode); + } + + int64_t xreg(unsigned code, Reg31Mode r31mode = Reg31IsZeroRegister) const { + return reg<int64_t>(code, r31mode); + } + + // Write 'size' bits of 'value' into an integer register. The value is + // zero-extended. This behaviour matches AArch64 register writes. + + // Like set_reg(), but infer the access size from the template type. + template<typename T> + void set_reg(unsigned code, T value, + Reg31Mode r31mode = Reg31IsZeroRegister) { + ASSERT(code < kNumberOfRegisters); + if (!IsZeroRegister(code, r31mode)) + registers_[code].Set(value); + } + + // Common specialized accessors for the set_reg() template. + void set_wreg(unsigned code, int32_t value, + Reg31Mode r31mode = Reg31IsZeroRegister) { + set_reg(code, value, r31mode); + } + + void set_xreg(unsigned code, int64_t value, + Reg31Mode r31mode = Reg31IsZeroRegister) { + set_reg(code, value, r31mode); + } + + // Commonly-used special cases. + template<typename T> + void set_lr(T value) { + ASSERT(sizeof(T) == kPointerSize); + set_reg(kLinkRegCode, value); + } + + template<typename T> + void set_sp(T value) { + ASSERT(sizeof(T) == kPointerSize); + set_reg(31, value, Reg31IsStackPointer); + } + + int64_t sp() { return xreg(31, Reg31IsStackPointer); } + int64_t jssp() { return xreg(kJSSPCode, Reg31IsStackPointer); } + int64_t fp() { + return xreg(kFramePointerRegCode, Reg31IsStackPointer); + } + Instruction* lr() { return reg<Instruction*>(kLinkRegCode); } + + Address get_sp() { return reg<Address>(31, Reg31IsStackPointer); } + + template<typename T> + T fpreg(unsigned code) const { + ASSERT(code < kNumberOfRegisters); + return fpregisters_[code].Get<T>(); + } + + // Common specialized accessors for the fpreg() template. + float sreg(unsigned code) const { + return fpreg<float>(code); + } + + uint32_t sreg_bits(unsigned code) const { + return fpreg<uint32_t>(code); + } + + double dreg(unsigned code) const { + return fpreg<double>(code); + } + + uint64_t dreg_bits(unsigned code) const { + return fpreg<uint64_t>(code); + } + + double fpreg(unsigned size, unsigned code) const { + switch (size) { + case kSRegSizeInBits: return sreg(code); + case kDRegSizeInBits: return dreg(code); + default: + UNREACHABLE(); + return 0.0; + } + } + + // Write 'value' into a floating-point register. The value is zero-extended. + // This behaviour matches AArch64 register writes. + template<typename T> + void set_fpreg(unsigned code, T value) { + ASSERT((sizeof(value) == kDRegSize) || (sizeof(value) == kSRegSize)); + ASSERT(code < kNumberOfFPRegisters); + fpregisters_[code].Set(value); + } + + // Common specialized accessors for the set_fpreg() template. + void set_sreg(unsigned code, float value) { + set_fpreg(code, value); + } + + void set_sreg_bits(unsigned code, uint32_t value) { + set_fpreg(code, value); + } + + void set_dreg(unsigned code, double value) { + set_fpreg(code, value); + } + + void set_dreg_bits(unsigned code, uint64_t value) { + set_fpreg(code, value); + } + + SimSystemRegister& nzcv() { return nzcv_; } + SimSystemRegister& fpcr() { return fpcr_; } + + // Debug helpers + + // Simulator breakpoints. + struct Breakpoint { + Instruction* location; + bool enabled; + }; + std::vector<Breakpoint> breakpoints_; + void SetBreakpoint(Instruction* breakpoint); + void ListBreakpoints(); + void CheckBreakpoints(); + + // Helpers for the 'next' command. + // When this is set, the Simulator will insert a breakpoint after the next BL + // instruction it meets. + bool break_on_next_; + // Check if the Simulator should insert a break after the current instruction + // for the 'next' command. + void CheckBreakNext(); + + // Disassemble instruction at the given address. + void PrintInstructionsAt(Instruction* pc, uint64_t count); + + void PrintSystemRegisters(bool print_all = false); + void PrintRegisters(bool print_all_regs = false); + void PrintFPRegisters(bool print_all_regs = false); + void PrintProcessorState(); + void PrintWrite(uint8_t* address, uint64_t value, unsigned num_bytes); + void LogSystemRegisters() { + if (log_parameters_ & LOG_SYS_REGS) PrintSystemRegisters(); + } + void LogRegisters() { + if (log_parameters_ & LOG_REGS) PrintRegisters(); + } + void LogFPRegisters() { + if (log_parameters_ & LOG_FP_REGS) PrintFPRegisters(); + } + void LogProcessorState() { + LogSystemRegisters(); + LogRegisters(); + LogFPRegisters(); + } + void LogWrite(uint8_t* address, uint64_t value, unsigned num_bytes) { + if (log_parameters_ & LOG_WRITE) PrintWrite(address, value, num_bytes); + } + + int log_parameters() { return log_parameters_; } + void set_log_parameters(int new_parameters) { + log_parameters_ = new_parameters; + if (!decoder_) { + if (new_parameters & LOG_DISASM) { + PrintF("Run --debug-sim to dynamically turn on disassembler\n"); + } + return; + } + if (new_parameters & LOG_DISASM) { + decoder_->InsertVisitorBefore(print_disasm_, this); + } else { + decoder_->RemoveVisitor(print_disasm_); + } + } + + static inline const char* WRegNameForCode(unsigned code, + Reg31Mode mode = Reg31IsZeroRegister); + static inline const char* XRegNameForCode(unsigned code, + Reg31Mode mode = Reg31IsZeroRegister); + static inline const char* SRegNameForCode(unsigned code); + static inline const char* DRegNameForCode(unsigned code); + static inline const char* VRegNameForCode(unsigned code); + static inline int CodeFromName(const char* name); + + protected: + // Simulation helpers ------------------------------------ + bool ConditionPassed(Condition cond) { + SimSystemRegister& flags = nzcv(); + switch (cond) { + case eq: + return flags.Z(); + case ne: + return !flags.Z(); + case hs: + return flags.C(); + case lo: + return !flags.C(); + case mi: + return flags.N(); + case pl: + return !flags.N(); + case vs: + return flags.V(); + case vc: + return !flags.V(); + case hi: + return flags.C() && !flags.Z(); + case ls: + return !(flags.C() && !flags.Z()); + case ge: + return flags.N() == flags.V(); + case lt: + return flags.N() != flags.V(); + case gt: + return !flags.Z() && (flags.N() == flags.V()); + case le: + return !(!flags.Z() && (flags.N() == flags.V())); + case nv: // Fall through. + case al: + return true; + default: + UNREACHABLE(); + return false; + } + } + + bool ConditionFailed(Condition cond) { + return !ConditionPassed(cond); + } + + template<typename T> + void AddSubHelper(Instruction* instr, T op2); + template<typename T> + T AddWithCarry(bool set_flags, + T src1, + T src2, + T carry_in = 0); + template<typename T> + void AddSubWithCarry(Instruction* instr); + template<typename T> + void LogicalHelper(Instruction* instr, T op2); + template<typename T> + void ConditionalCompareHelper(Instruction* instr, T op2); + void LoadStoreHelper(Instruction* instr, + int64_t offset, + AddrMode addrmode); + void LoadStorePairHelper(Instruction* instr, AddrMode addrmode); + uint8_t* LoadStoreAddress(unsigned addr_reg, + int64_t offset, + AddrMode addrmode); + void LoadStoreWriteBack(unsigned addr_reg, + int64_t offset, + AddrMode addrmode); + void CheckMemoryAccess(uint8_t* address, uint8_t* stack); + + uint64_t MemoryRead(uint8_t* address, unsigned num_bytes); + uint8_t MemoryRead8(uint8_t* address); + uint16_t MemoryRead16(uint8_t* address); + uint32_t MemoryRead32(uint8_t* address); + float MemoryReadFP32(uint8_t* address); + uint64_t MemoryRead64(uint8_t* address); + double MemoryReadFP64(uint8_t* address); + + void MemoryWrite(uint8_t* address, uint64_t value, unsigned num_bytes); + void MemoryWrite32(uint8_t* address, uint32_t value); + void MemoryWriteFP32(uint8_t* address, float value); + void MemoryWrite64(uint8_t* address, uint64_t value); + void MemoryWriteFP64(uint8_t* address, double value); + + + template <typename T> + T ShiftOperand(T value, + Shift shift_type, + unsigned amount); + template <typename T> + T ExtendValue(T value, + Extend extend_type, + unsigned left_shift = 0); + template <typename T> + void Extract(Instruction* instr); + template <typename T> + void DataProcessing2Source(Instruction* instr); + template <typename T> + void BitfieldHelper(Instruction* instr); + + uint64_t ReverseBits(uint64_t value, unsigned num_bits); + uint64_t ReverseBytes(uint64_t value, ReverseByteMode mode); + + template <typename T> + T FPDefaultNaN() const; + + void FPCompare(double val0, double val1); + double FPRoundInt(double value, FPRounding round_mode); + double FPToDouble(float value); + float FPToFloat(double value, FPRounding round_mode); + double FixedToDouble(int64_t src, int fbits, FPRounding round_mode); + double UFixedToDouble(uint64_t src, int fbits, FPRounding round_mode); + float FixedToFloat(int64_t src, int fbits, FPRounding round_mode); + float UFixedToFloat(uint64_t src, int fbits, FPRounding round_mode); + int32_t FPToInt32(double value, FPRounding rmode); + int64_t FPToInt64(double value, FPRounding rmode); + uint32_t FPToUInt32(double value, FPRounding rmode); + uint64_t FPToUInt64(double value, FPRounding rmode); + + template <typename T> + T FPAdd(T op1, T op2); + + template <typename T> + T FPDiv(T op1, T op2); + + template <typename T> + T FPMax(T a, T b); + + template <typename T> + T FPMaxNM(T a, T b); + + template <typename T> + T FPMin(T a, T b); + + template <typename T> + T FPMinNM(T a, T b); + + template <typename T> + T FPMul(T op1, T op2); + + template <typename T> + T FPMulAdd(T a, T op1, T op2); + + template <typename T> + T FPSqrt(T op); + + template <typename T> + T FPSub(T op1, T op2); + + // Standard NaN processing. + template <typename T> + T FPProcessNaN(T op); + + bool FPProcessNaNs(Instruction* instr); + + template <typename T> + T FPProcessNaNs(T op1, T op2); + + template <typename T> + T FPProcessNaNs3(T op1, T op2, T op3); + + void CheckStackAlignment(); + + inline void CheckPCSComplianceAndRun(); + +#ifdef DEBUG + // Corruption values should have their least significant byte cleared to + // allow the code of the register being corrupted to be inserted. + static const uint64_t kCallerSavedRegisterCorruptionValue = + 0xca11edc0de000000UL; + // This value is a NaN in both 32-bit and 64-bit FP. + static const uint64_t kCallerSavedFPRegisterCorruptionValue = + 0x7ff000007f801000UL; + // This value is a mix of 32/64-bits NaN and "verbose" immediate. + static const uint64_t kDefaultCPURegisterCorruptionValue = + 0x7ffbad007f8bad00UL; + + void CorruptRegisters(CPURegList* list, + uint64_t value = kDefaultCPURegisterCorruptionValue); + void CorruptAllCallerSavedCPURegisters(); +#endif + + // Pseudo Printf instruction + void DoPrintf(Instruction* instr); + + // Processor state --------------------------------------- + + // Output stream. + FILE* stream_; + PrintDisassembler* print_disasm_; + void PRINTF_METHOD_CHECKING TraceSim(const char* format, ...); + + // Instrumentation. + Instrument* instrument_; + + // General purpose registers. Register 31 is the stack pointer. + SimRegister registers_[kNumberOfRegisters]; + + // Floating point registers + SimFPRegister fpregisters_[kNumberOfFPRegisters]; + + // Processor state + // bits[31, 27]: Condition flags N, Z, C, and V. + // (Negative, Zero, Carry, Overflow) + SimSystemRegister nzcv_; + + // Floating-Point Control Register + SimSystemRegister fpcr_; + + // Only a subset of FPCR features are supported by the simulator. This helper + // checks that the FPCR settings are supported. + // + // This is checked when floating-point instructions are executed, not when + // FPCR is set. This allows generated code to modify FPCR for external + // functions, or to save and restore it when entering and leaving generated + // code. + void AssertSupportedFPCR() { + ASSERT(fpcr().FZ() == 0); // No flush-to-zero support. + ASSERT(fpcr().RMode() == FPTieEven); // Ties-to-even rounding only. + + // The simulator does not support half-precision operations so fpcr().AHP() + // is irrelevant, and is not checked here. + } + + template <typename T> + static int CalcNFlag(T result) { + return (result >> (sizeof(T) * 8 - 1)) & 1; + } + + static int CalcZFlag(uint64_t result) { + return result == 0; + } + + static const uint32_t kConditionFlagsMask = 0xf0000000; + + // Stack + byte* stack_; + static const intptr_t stack_protection_size_ = KB; + intptr_t stack_size_; + byte* stack_limit_; + + Decoder<DispatchingDecoderVisitor>* decoder_; + Decoder<DispatchingDecoderVisitor>* disassembler_decoder_; + + // Indicates if the pc has been modified by the instruction and should not be + // automatically incremented. + bool pc_modified_; + Instruction* pc_; + + static const char* xreg_names[]; + static const char* wreg_names[]; + static const char* sreg_names[]; + static const char* dreg_names[]; + static const char* vreg_names[]; + + // Debugger input. + void set_last_debugger_input(char* input) { + DeleteArray(last_debugger_input_); + last_debugger_input_ = input; + } + char* last_debugger_input() { return last_debugger_input_; } + char* last_debugger_input_; + + private: + void Init(FILE* stream); + + int log_parameters_; + Isolate* isolate_; +}; + + +// When running with the simulator transition into simulated execution at this +// point. +#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \ + reinterpret_cast<Object*>(Simulator::current(Isolate::Current())->CallJS( \ + FUNCTION_ADDR(entry), \ + p0, p1, p2, p3, p4)) + +#define CALL_GENERATED_REGEXP_CODE(entry, p0, p1, p2, p3, p4, p5, p6, p7, p8) \ + Simulator::current(Isolate::Current())->CallRegExp( \ + entry, \ + p0, p1, p2, p3, p4, p5, p6, p7, NULL, p8) + + +// The simulator has its own stack. Thus it has a different stack limit from +// the C-based native code. +// See also 'class SimulatorStack' in arm/simulator-arm.h. +class SimulatorStack : public v8::internal::AllStatic { + public: + static uintptr_t JsLimitFromCLimit(v8::internal::Isolate* isolate, + uintptr_t c_limit) { + return Simulator::current(isolate)->StackLimit(); + } + + static uintptr_t RegisterCTryCatch(uintptr_t try_catch_address) { + Simulator* sim = Simulator::current(Isolate::Current()); + return sim->PushAddress(try_catch_address); + } + + static void UnregisterCTryCatch() { + Simulator::current(Isolate::Current())->PopAddress(); + } +}; + +#endif // !defined(USE_SIMULATOR) + +} } // namespace v8::internal + +#endif // V8_ARM64_SIMULATOR_ARM64_H_ diff --git a/chromium/v8/src/arm64/stub-cache-arm64.cc b/chromium/v8/src/arm64/stub-cache-arm64.cc new file mode 100644 index 00000000000..b0f58fda229 --- /dev/null +++ b/chromium/v8/src/arm64/stub-cache-arm64.cc @@ -0,0 +1,1477 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/v8.h" + +#if V8_TARGET_ARCH_ARM64 + +#include "src/ic-inl.h" +#include "src/codegen.h" +#include "src/stub-cache.h" + +namespace v8 { +namespace internal { + + +#define __ ACCESS_MASM(masm) + + +void StubCompiler::GenerateDictionaryNegativeLookup(MacroAssembler* masm, + Label* miss_label, + Register receiver, + Handle<Name> name, + Register scratch0, + Register scratch1) { + ASSERT(!AreAliased(receiver, scratch0, scratch1)); + ASSERT(name->IsUniqueName()); + Counters* counters = masm->isolate()->counters(); + __ IncrementCounter(counters->negative_lookups(), 1, scratch0, scratch1); + __ IncrementCounter(counters->negative_lookups_miss(), 1, scratch0, scratch1); + + Label done; + + const int kInterceptorOrAccessCheckNeededMask = + (1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded); + + // Bail out if the receiver has a named interceptor or requires access checks. + Register map = scratch1; + __ Ldr(map, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ Ldrb(scratch0, FieldMemOperand(map, Map::kBitFieldOffset)); + __ Tst(scratch0, kInterceptorOrAccessCheckNeededMask); + __ B(ne, miss_label); + + // Check that receiver is a JSObject. + __ Ldrb(scratch0, FieldMemOperand(map, Map::kInstanceTypeOffset)); + __ Cmp(scratch0, FIRST_SPEC_OBJECT_TYPE); + __ B(lt, miss_label); + + // Load properties array. + Register properties = scratch0; + __ Ldr(properties, FieldMemOperand(receiver, JSObject::kPropertiesOffset)); + // Check that the properties array is a dictionary. + __ Ldr(map, FieldMemOperand(properties, HeapObject::kMapOffset)); + __ JumpIfNotRoot(map, Heap::kHashTableMapRootIndex, miss_label); + + NameDictionaryLookupStub::GenerateNegativeLookup(masm, + miss_label, + &done, + receiver, + properties, + name, + scratch1); + __ Bind(&done); + __ DecrementCounter(counters->negative_lookups_miss(), 1, scratch0, scratch1); +} + + +// Probe primary or secondary table. +// If the entry is found in the cache, the generated code jump to the first +// instruction of the stub in the cache. +// If there is a miss the code fall trough. +// +// 'receiver', 'name' and 'offset' registers are preserved on miss. +static void ProbeTable(Isolate* isolate, + MacroAssembler* masm, + Code::Flags flags, + StubCache::Table table, + Register receiver, + Register name, + Register offset, + Register scratch, + Register scratch2, + Register scratch3) { + // Some code below relies on the fact that the Entry struct contains + // 3 pointers (name, code, map). + STATIC_ASSERT(sizeof(StubCache::Entry) == (3 * kPointerSize)); + + ExternalReference key_offset(isolate->stub_cache()->key_reference(table)); + ExternalReference value_offset(isolate->stub_cache()->value_reference(table)); + ExternalReference map_offset(isolate->stub_cache()->map_reference(table)); + + uintptr_t key_off_addr = reinterpret_cast<uintptr_t>(key_offset.address()); + uintptr_t value_off_addr = + reinterpret_cast<uintptr_t>(value_offset.address()); + uintptr_t map_off_addr = reinterpret_cast<uintptr_t>(map_offset.address()); + + Label miss; + + ASSERT(!AreAliased(name, offset, scratch, scratch2, scratch3)); + + // Multiply by 3 because there are 3 fields per entry. + __ Add(scratch3, offset, Operand(offset, LSL, 1)); + + // Calculate the base address of the entry. + __ Mov(scratch, key_offset); + __ Add(scratch, scratch, Operand(scratch3, LSL, kPointerSizeLog2)); + + // Check that the key in the entry matches the name. + __ Ldr(scratch2, MemOperand(scratch)); + __ Cmp(name, scratch2); + __ B(ne, &miss); + + // Check the map matches. + __ Ldr(scratch2, MemOperand(scratch, map_off_addr - key_off_addr)); + __ Ldr(scratch3, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ Cmp(scratch2, scratch3); + __ B(ne, &miss); + + // Get the code entry from the cache. + __ Ldr(scratch, MemOperand(scratch, value_off_addr - key_off_addr)); + + // Check that the flags match what we're looking for. + __ Ldr(scratch2.W(), FieldMemOperand(scratch, Code::kFlagsOffset)); + __ Bic(scratch2.W(), scratch2.W(), Code::kFlagsNotUsedInLookup); + __ Cmp(scratch2.W(), flags); + __ B(ne, &miss); + +#ifdef DEBUG + if (FLAG_test_secondary_stub_cache && table == StubCache::kPrimary) { + __ B(&miss); + } else if (FLAG_test_primary_stub_cache && table == StubCache::kSecondary) { + __ B(&miss); + } +#endif + + // Jump to the first instruction in the code stub. + __ Add(scratch, scratch, Code::kHeaderSize - kHeapObjectTag); + __ Br(scratch); + + // Miss: fall through. + __ Bind(&miss); +} + + +void StubCache::GenerateProbe(MacroAssembler* masm, + Code::Flags flags, + Register receiver, + Register name, + Register scratch, + Register extra, + Register extra2, + Register extra3) { + Isolate* isolate = masm->isolate(); + Label miss; + + // Make sure the flags does not name a specific type. + ASSERT(Code::ExtractTypeFromFlags(flags) == 0); + + // Make sure that there are no register conflicts. + ASSERT(!AreAliased(receiver, name, scratch, extra, extra2, extra3)); + + // Make sure extra and extra2 registers are valid. + ASSERT(!extra.is(no_reg)); + ASSERT(!extra2.is(no_reg)); + ASSERT(!extra3.is(no_reg)); + + Counters* counters = masm->isolate()->counters(); + __ IncrementCounter(counters->megamorphic_stub_cache_probes(), 1, + extra2, extra3); + + // Check that the receiver isn't a smi. + __ JumpIfSmi(receiver, &miss); + + // Compute the hash for primary table. + __ Ldr(scratch, FieldMemOperand(name, Name::kHashFieldOffset)); + __ Ldr(extra, FieldMemOperand(receiver, HeapObject::kMapOffset)); + __ Add(scratch, scratch, extra); + __ Eor(scratch, scratch, flags); + // We shift out the last two bits because they are not part of the hash. + __ Ubfx(scratch, scratch, kHeapObjectTagSize, + CountTrailingZeros(kPrimaryTableSize, 64)); + + // Probe the primary table. + ProbeTable(isolate, masm, flags, kPrimary, receiver, name, + scratch, extra, extra2, extra3); + + // Primary miss: Compute hash for secondary table. + __ Sub(scratch, scratch, Operand(name, LSR, kHeapObjectTagSize)); + __ Add(scratch, scratch, flags >> kHeapObjectTagSize); + __ And(scratch, scratch, kSecondaryTableSize - 1); + + // Probe the secondary table. + ProbeTable(isolate, masm, flags, kSecondary, receiver, name, + scratch, extra, extra2, extra3); + + // Cache miss: Fall-through and let caller handle the miss by + // entering the runtime system. + __ Bind(&miss); + __ IncrementCounter(counters->megamorphic_stub_cache_misses(), 1, + extra2, extra3); +} + + +void StubCompiler::GenerateLoadGlobalFunctionPrototype(MacroAssembler* masm, + int index, + Register prototype) { + // Load the global or builtins object from the current context. + __ Ldr(prototype, GlobalObjectMemOperand()); + // Load the native context from the global or builtins object. + __ Ldr(prototype, + FieldMemOperand(prototype, GlobalObject::kNativeContextOffset)); + // Load the function from the native context. + __ Ldr(prototype, ContextMemOperand(prototype, index)); + // Load the initial map. The global functions all have initial maps. + __ Ldr(prototype, + FieldMemOperand(prototype, JSFunction::kPrototypeOrInitialMapOffset)); + // Load the prototype from the initial map. + __ Ldr(prototype, FieldMemOperand(prototype, Map::kPrototypeOffset)); +} + + +void StubCompiler::GenerateDirectLoadGlobalFunctionPrototype( + MacroAssembler* masm, + int index, + Register prototype, + Label* miss) { + Isolate* isolate = masm->isolate(); + // Get the global function with the given index. + Handle<JSFunction> function( + JSFunction::cast(isolate->native_context()->get(index))); + + // Check we're still in the same context. + Register scratch = prototype; + __ Ldr(scratch, GlobalObjectMemOperand()); + __ Ldr(scratch, FieldMemOperand(scratch, GlobalObject::kNativeContextOffset)); + __ Ldr(scratch, ContextMemOperand(scratch, index)); + __ Cmp(scratch, Operand(function)); + __ B(ne, miss); + + // Load its initial map. The global functions all have initial maps. + __ Mov(prototype, Operand(Handle<Map>(function->initial_map()))); + // Load the prototype from the initial map. + __ Ldr(prototype, FieldMemOperand(prototype, Map::kPrototypeOffset)); +} + + +void StubCompiler::GenerateFastPropertyLoad(MacroAssembler* masm, + Register dst, + Register src, + bool inobject, + int index, + Representation representation) { + ASSERT(!representation.IsDouble()); + USE(representation); + if (inobject) { + int offset = index * kPointerSize; + __ Ldr(dst, FieldMemOperand(src, offset)); + } else { + // Calculate the offset into the properties array. + int offset = index * kPointerSize + FixedArray::kHeaderSize; + __ Ldr(dst, FieldMemOperand(src, JSObject::kPropertiesOffset)); + __ Ldr(dst, FieldMemOperand(dst, offset)); + } +} + + +void StubCompiler::GenerateLoadArrayLength(MacroAssembler* masm, + Register receiver, + Register scratch, + Label* miss_label) { + ASSERT(!AreAliased(receiver, scratch)); + + // Check that the receiver isn't a smi. + __ JumpIfSmi(receiver, miss_label); + + // Check that the object is a JS array. + __ JumpIfNotObjectType(receiver, scratch, scratch, JS_ARRAY_TYPE, + miss_label); + + // Load length directly from the JS array. + __ Ldr(x0, FieldMemOperand(receiver, JSArray::kLengthOffset)); + __ Ret(); +} + + +void StubCompiler::GenerateLoadFunctionPrototype(MacroAssembler* masm, + Register receiver, + Register scratch1, + Register scratch2, + Label* miss_label) { + __ TryGetFunctionPrototype(receiver, scratch1, scratch2, miss_label); + // TryGetFunctionPrototype can't put the result directly in x0 because the + // 3 inputs registers can't alias and we call this function from + // LoadIC::GenerateFunctionPrototype, where receiver is x0. So we explicitly + // move the result in x0. + __ Mov(x0, scratch1); + __ Ret(); +} + + +// Generate code to check that a global property cell is empty. Create +// the property cell at compilation time if no cell exists for the +// property. +void StubCompiler::GenerateCheckPropertyCell(MacroAssembler* masm, + Handle<JSGlobalObject> global, + Handle<Name> name, + Register scratch, + Label* miss) { + Handle<Cell> cell = JSGlobalObject::EnsurePropertyCell(global, name); + ASSERT(cell->value()->IsTheHole()); + __ Mov(scratch, Operand(cell)); + __ Ldr(scratch, FieldMemOperand(scratch, Cell::kValueOffset)); + __ JumpIfNotRoot(scratch, Heap::kTheHoleValueRootIndex, miss); +} + + +void StoreStubCompiler::GenerateNegativeHolderLookup( + MacroAssembler* masm, + Handle<JSObject> holder, + Register holder_reg, + Handle<Name> name, + Label* miss) { + if (holder->IsJSGlobalObject()) { + GenerateCheckPropertyCell( + masm, Handle<JSGlobalObject>::cast(holder), name, scratch1(), miss); + } else if (!holder->HasFastProperties() && !holder->IsJSGlobalProxy()) { + GenerateDictionaryNegativeLookup( + masm, miss, holder_reg, name, scratch1(), scratch2()); + } +} + + +// Generate StoreTransition code, value is passed in x0 register. +// When leaving generated code after success, the receiver_reg and storage_reg +// may be clobbered. Upon branch to miss_label, the receiver and name registers +// have their original values. +void StoreStubCompiler::GenerateStoreTransition(MacroAssembler* masm, + Handle<JSObject> object, + LookupResult* lookup, + Handle<Map> transition, + Handle<Name> name, + Register receiver_reg, + Register storage_reg, + Register value_reg, + Register scratch1, + Register scratch2, + Register scratch3, + Label* miss_label, + Label* slow) { + Label exit; + + ASSERT(!AreAliased(receiver_reg, storage_reg, value_reg, + scratch1, scratch2, scratch3)); + + // We don't need scratch3. + scratch3 = NoReg; + + int descriptor = transition->LastAdded(); + DescriptorArray* descriptors = transition->instance_descriptors(); + PropertyDetails details = descriptors->GetDetails(descriptor); + Representation representation = details.representation(); + ASSERT(!representation.IsNone()); + + if (details.type() == CONSTANT) { + Handle<Object> constant(descriptors->GetValue(descriptor), masm->isolate()); + __ LoadObject(scratch1, constant); + __ Cmp(value_reg, scratch1); + __ B(ne, miss_label); + } else if (representation.IsSmi()) { + __ JumpIfNotSmi(value_reg, miss_label); + } else if (representation.IsHeapObject()) { + __ JumpIfSmi(value_reg, miss_label); + HeapType* field_type = descriptors->GetFieldType(descriptor); + HeapType::Iterator<Map> it = field_type->Classes(); + if (!it.Done()) { + __ Ldr(scratch1, FieldMemOperand(value_reg, HeapObject::kMapOffset)); + Label do_store; + while (true) { + __ CompareMap(scratch1, it.Current()); + it.Advance(); + if (it.Done()) { + __ B(ne, miss_label); + break; + } + __ B(eq, &do_store); + } + __ Bind(&do_store); + } + } else if (representation.IsDouble()) { + UseScratchRegisterScope temps(masm); + DoubleRegister temp_double = temps.AcquireD(); + __ SmiUntagToDouble(temp_double, value_reg, kSpeculativeUntag); + + Label do_store; + __ JumpIfSmi(value_reg, &do_store); + + __ CheckMap(value_reg, scratch1, Heap::kHeapNumberMapRootIndex, + miss_label, DONT_DO_SMI_CHECK); + __ Ldr(temp_double, FieldMemOperand(value_reg, HeapNumber::kValueOffset)); + + __ Bind(&do_store); + __ AllocateHeapNumber(storage_reg, slow, scratch1, scratch2, temp_double); + } + + // Stub never generated for non-global objects that require access checks. + ASSERT(object->IsJSGlobalProxy() || !object->IsAccessCheckNeeded()); + + // Perform map transition for the receiver if necessary. + if ((details.type() == FIELD) && + (object->map()->unused_property_fields() == 0)) { + // The properties must be extended before we can store the value. + // We jump to a runtime call that extends the properties array. + __ Mov(scratch1, Operand(transition)); + __ Push(receiver_reg, scratch1, value_reg); + __ TailCallExternalReference( + ExternalReference(IC_Utility(IC::kSharedStoreIC_ExtendStorage), + masm->isolate()), + 3, + 1); + return; + } + + // Update the map of the object. + __ Mov(scratch1, Operand(transition)); + __ Str(scratch1, FieldMemOperand(receiver_reg, HeapObject::kMapOffset)); + + // Update the write barrier for the map field. + __ RecordWriteField(receiver_reg, + HeapObject::kMapOffset, + scratch1, + scratch2, + kLRHasNotBeenSaved, + kDontSaveFPRegs, + OMIT_REMEMBERED_SET, + OMIT_SMI_CHECK); + + if (details.type() == CONSTANT) { + ASSERT(value_reg.is(x0)); + __ Ret(); + return; + } + + int index = transition->instance_descriptors()->GetFieldIndex( + transition->LastAdded()); + + // Adjust for the number of properties stored in the object. Even in the + // face of a transition we can use the old map here because the size of the + // object and the number of in-object properties is not going to change. + index -= object->map()->inobject_properties(); + + // TODO(verwaest): Share this code as a code stub. + SmiCheck smi_check = representation.IsTagged() + ? INLINE_SMI_CHECK : OMIT_SMI_CHECK; + Register prop_reg = representation.IsDouble() ? storage_reg : value_reg; + if (index < 0) { + // Set the property straight into the object. + int offset = object->map()->instance_size() + (index * kPointerSize); + __ Str(prop_reg, FieldMemOperand(receiver_reg, offset)); + + if (!representation.IsSmi()) { + // Update the write barrier for the array address. + if (!representation.IsDouble()) { + __ Mov(storage_reg, value_reg); + } + __ RecordWriteField(receiver_reg, + offset, + storage_reg, + scratch1, + kLRHasNotBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + smi_check); + } + } else { + // Write to the properties array. + int offset = index * kPointerSize + FixedArray::kHeaderSize; + // Get the properties array + __ Ldr(scratch1, + FieldMemOperand(receiver_reg, JSObject::kPropertiesOffset)); + __ Str(prop_reg, FieldMemOperand(scratch1, offset)); + + if (!representation.IsSmi()) { + // Update the write barrier for the array address. + if (!representation.IsDouble()) { + __ Mov(storage_reg, value_reg); + } + __ RecordWriteField(scratch1, + offset, + storage_reg, + receiver_reg, + kLRHasNotBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + smi_check); + } + } + + __ Bind(&exit); + // Return the value (register x0). + ASSERT(value_reg.is(x0)); + __ Ret(); +} + + +// Generate StoreField code, value is passed in x0 register. +// When leaving generated code after success, the receiver_reg and name_reg may +// be clobbered. Upon branch to miss_label, the receiver and name registers have +// their original values. +void StoreStubCompiler::GenerateStoreField(MacroAssembler* masm, + Handle<JSObject> object, + LookupResult* lookup, + Register receiver_reg, + Register name_reg, + Register value_reg, + Register scratch1, + Register scratch2, + Label* miss_label) { + // x0 : value + Label exit; + + // Stub never generated for non-global objects that require access + // checks. + ASSERT(object->IsJSGlobalProxy() || !object->IsAccessCheckNeeded()); + + FieldIndex index = lookup->GetFieldIndex(); + + Representation representation = lookup->representation(); + ASSERT(!representation.IsNone()); + if (representation.IsSmi()) { + __ JumpIfNotSmi(value_reg, miss_label); + } else if (representation.IsHeapObject()) { + __ JumpIfSmi(value_reg, miss_label); + HeapType* field_type = lookup->GetFieldType(); + HeapType::Iterator<Map> it = field_type->Classes(); + if (!it.Done()) { + __ Ldr(scratch1, FieldMemOperand(value_reg, HeapObject::kMapOffset)); + Label do_store; + while (true) { + __ CompareMap(scratch1, it.Current()); + it.Advance(); + if (it.Done()) { + __ B(ne, miss_label); + break; + } + __ B(eq, &do_store); + } + __ Bind(&do_store); + } + } else if (representation.IsDouble()) { + UseScratchRegisterScope temps(masm); + DoubleRegister temp_double = temps.AcquireD(); + + __ SmiUntagToDouble(temp_double, value_reg, kSpeculativeUntag); + + // Load the double storage. + if (index.is_inobject()) { + __ Ldr(scratch1, FieldMemOperand(receiver_reg, index.offset())); + } else { + __ Ldr(scratch1, + FieldMemOperand(receiver_reg, JSObject::kPropertiesOffset)); + __ Ldr(scratch1, FieldMemOperand(scratch1, index.offset())); + } + + // Store the value into the storage. + Label do_store, heap_number; + + __ JumpIfSmi(value_reg, &do_store); + + __ CheckMap(value_reg, scratch2, Heap::kHeapNumberMapRootIndex, + miss_label, DONT_DO_SMI_CHECK); + __ Ldr(temp_double, FieldMemOperand(value_reg, HeapNumber::kValueOffset)); + + __ Bind(&do_store); + __ Str(temp_double, FieldMemOperand(scratch1, HeapNumber::kValueOffset)); + + // Return the value (register x0). + ASSERT(value_reg.is(x0)); + __ Ret(); + return; + } + + // TODO(verwaest): Share this code as a code stub. + SmiCheck smi_check = representation.IsTagged() + ? INLINE_SMI_CHECK : OMIT_SMI_CHECK; + if (index.is_inobject()) { + // Set the property straight into the object. + __ Str(value_reg, FieldMemOperand(receiver_reg, index.offset())); + + if (!representation.IsSmi()) { + // Skip updating write barrier if storing a smi. + __ JumpIfSmi(value_reg, &exit); + + // Update the write barrier for the array address. + // Pass the now unused name_reg as a scratch register. + __ Mov(name_reg, value_reg); + __ RecordWriteField(receiver_reg, + index.offset(), + name_reg, + scratch1, + kLRHasNotBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + smi_check); + } + } else { + // Write to the properties array. + // Get the properties array + __ Ldr(scratch1, + FieldMemOperand(receiver_reg, JSObject::kPropertiesOffset)); + __ Str(value_reg, FieldMemOperand(scratch1, index.offset())); + + if (!representation.IsSmi()) { + // Skip updating write barrier if storing a smi. + __ JumpIfSmi(value_reg, &exit); + + // Update the write barrier for the array address. + // Ok to clobber receiver_reg and name_reg, since we return. + __ Mov(name_reg, value_reg); + __ RecordWriteField(scratch1, + index.offset(), + name_reg, + receiver_reg, + kLRHasNotBeenSaved, + kDontSaveFPRegs, + EMIT_REMEMBERED_SET, + smi_check); + } + } + + __ Bind(&exit); + // Return the value (register x0). + ASSERT(value_reg.is(x0)); + __ Ret(); +} + + +void StoreStubCompiler::GenerateRestoreName(MacroAssembler* masm, + Label* label, + Handle<Name> name) { + if (!label->is_unused()) { + __ Bind(label); + __ Mov(this->name(), Operand(name)); + } +} + + +static void PushInterceptorArguments(MacroAssembler* masm, + Register receiver, + Register holder, + Register name, + Handle<JSObject> holder_obj) { + STATIC_ASSERT(StubCache::kInterceptorArgsNameIndex == 0); + STATIC_ASSERT(StubCache::kInterceptorArgsInfoIndex == 1); + STATIC_ASSERT(StubCache::kInterceptorArgsThisIndex == 2); + STATIC_ASSERT(StubCache::kInterceptorArgsHolderIndex == 3); + STATIC_ASSERT(StubCache::kInterceptorArgsLength == 4); + + __ Push(name); + Handle<InterceptorInfo> interceptor(holder_obj->GetNamedInterceptor()); + ASSERT(!masm->isolate()->heap()->InNewSpace(*interceptor)); + Register scratch = name; + __ Mov(scratch, Operand(interceptor)); + __ Push(scratch, receiver, holder); +} + + +static void CompileCallLoadPropertyWithInterceptor( + MacroAssembler* masm, + Register receiver, + Register holder, + Register name, + Handle<JSObject> holder_obj, + IC::UtilityId id) { + PushInterceptorArguments(masm, receiver, holder, name, holder_obj); + + __ CallExternalReference( + ExternalReference(IC_Utility(id), masm->isolate()), + StubCache::kInterceptorArgsLength); +} + + +// Generate call to api function. +void StubCompiler::GenerateFastApiCall(MacroAssembler* masm, + const CallOptimization& optimization, + Handle<Map> receiver_map, + Register receiver, + Register scratch, + bool is_store, + int argc, + Register* values) { + ASSERT(!AreAliased(receiver, scratch)); + + MacroAssembler::PushPopQueue queue(masm); + queue.Queue(receiver); + // Write the arguments to the stack frame. + for (int i = 0; i < argc; i++) { + Register arg = values[argc-1-i]; + ASSERT(!AreAliased(receiver, scratch, arg)); + queue.Queue(arg); + } + queue.PushQueued(); + + ASSERT(optimization.is_simple_api_call()); + + // Abi for CallApiFunctionStub. + Register callee = x0; + Register call_data = x4; + Register holder = x2; + Register api_function_address = x1; + + // Put holder in place. + CallOptimization::HolderLookup holder_lookup; + Handle<JSObject> api_holder = + optimization.LookupHolderOfExpectedType(receiver_map, &holder_lookup); + switch (holder_lookup) { + case CallOptimization::kHolderIsReceiver: + __ Mov(holder, receiver); + break; + case CallOptimization::kHolderFound: + __ LoadObject(holder, api_holder); + break; + case CallOptimization::kHolderNotFound: + UNREACHABLE(); + break; + } + + Isolate* isolate = masm->isolate(); + Handle<JSFunction> function = optimization.constant_function(); + Handle<CallHandlerInfo> api_call_info = optimization.api_call_info(); + Handle<Object> call_data_obj(api_call_info->data(), isolate); + + // Put callee in place. + __ LoadObject(callee, function); + + bool call_data_undefined = false; + // Put call_data in place. + if (isolate->heap()->InNewSpace(*call_data_obj)) { + __ LoadObject(call_data, api_call_info); + __ Ldr(call_data, FieldMemOperand(call_data, CallHandlerInfo::kDataOffset)); + } else if (call_data_obj->IsUndefined()) { + call_data_undefined = true; + __ LoadRoot(call_data, Heap::kUndefinedValueRootIndex); + } else { + __ LoadObject(call_data, call_data_obj); + } + + // Put api_function_address in place. + Address function_address = v8::ToCData<Address>(api_call_info->callback()); + ApiFunction fun(function_address); + ExternalReference ref = ExternalReference(&fun, + ExternalReference::DIRECT_API_CALL, + masm->isolate()); + __ Mov(api_function_address, ref); + + // Jump to stub. + CallApiFunctionStub stub(isolate, is_store, call_data_undefined, argc); + __ TailCallStub(&stub); +} + + +void StubCompiler::GenerateTailCall(MacroAssembler* masm, Handle<Code> code) { + __ Jump(code, RelocInfo::CODE_TARGET); +} + + +#undef __ +#define __ ACCESS_MASM(masm()) + + +Register StubCompiler::CheckPrototypes(Handle<HeapType> type, + Register object_reg, + Handle<JSObject> holder, + Register holder_reg, + Register scratch1, + Register scratch2, + Handle<Name> name, + Label* miss, + PrototypeCheckType check) { + Handle<Map> receiver_map(IC::TypeToMap(*type, isolate())); + + // object_reg and holder_reg registers can alias. + ASSERT(!AreAliased(object_reg, scratch1, scratch2)); + ASSERT(!AreAliased(holder_reg, scratch1, scratch2)); + + // Keep track of the current object in register reg. + Register reg = object_reg; + int depth = 0; + + Handle<JSObject> current = Handle<JSObject>::null(); + if (type->IsConstant()) { + current = Handle<JSObject>::cast(type->AsConstant()->Value()); + } + Handle<JSObject> prototype = Handle<JSObject>::null(); + Handle<Map> current_map = receiver_map; + Handle<Map> holder_map(holder->map()); + // Traverse the prototype chain and check the maps in the prototype chain for + // fast and global objects or do negative lookup for normal objects. + while (!current_map.is_identical_to(holder_map)) { + ++depth; + + // Only global objects and objects that do not require access + // checks are allowed in stubs. + ASSERT(current_map->IsJSGlobalProxyMap() || + !current_map->is_access_check_needed()); + + prototype = handle(JSObject::cast(current_map->prototype())); + if (current_map->is_dictionary_map() && + !current_map->IsJSGlobalObjectMap() && + !current_map->IsJSGlobalProxyMap()) { + if (!name->IsUniqueName()) { + ASSERT(name->IsString()); + name = factory()->InternalizeString(Handle<String>::cast(name)); + } + ASSERT(current.is_null() || + (current->property_dictionary()->FindEntry(name) == + NameDictionary::kNotFound)); + + GenerateDictionaryNegativeLookup(masm(), miss, reg, name, + scratch1, scratch2); + + __ Ldr(scratch1, FieldMemOperand(reg, HeapObject::kMapOffset)); + reg = holder_reg; // From now on the object will be in holder_reg. + __ Ldr(reg, FieldMemOperand(scratch1, Map::kPrototypeOffset)); + } else { + bool need_map = (depth != 1 || check == CHECK_ALL_MAPS) || + heap()->InNewSpace(*prototype); + Register map_reg = NoReg; + if (need_map) { + map_reg = scratch1; + __ Ldr(map_reg, FieldMemOperand(reg, HeapObject::kMapOffset)); + } + + if (depth != 1 || check == CHECK_ALL_MAPS) { + __ CheckMap(map_reg, current_map, miss, DONT_DO_SMI_CHECK); + } + + // Check access rights to the global object. This has to happen after + // the map check so that we know that the object is actually a global + // object. + if (current_map->IsJSGlobalProxyMap()) { + UseScratchRegisterScope temps(masm()); + __ CheckAccessGlobalProxy(reg, scratch2, temps.AcquireX(), miss); + } else if (current_map->IsJSGlobalObjectMap()) { + GenerateCheckPropertyCell( + masm(), Handle<JSGlobalObject>::cast(current), name, + scratch2, miss); + } + + reg = holder_reg; // From now on the object will be in holder_reg. + + if (heap()->InNewSpace(*prototype)) { + // The prototype is in new space; we cannot store a reference to it + // in the code. Load it from the map. + __ Ldr(reg, FieldMemOperand(map_reg, Map::kPrototypeOffset)); + } else { + // The prototype is in old space; load it directly. + __ Mov(reg, Operand(prototype)); + } + } + + // Go to the next object in the prototype chain. + current = prototype; + current_map = handle(current->map()); + } + + // Log the check depth. + LOG(isolate(), IntEvent("check-maps-depth", depth + 1)); + + // Check the holder map. + if (depth != 0 || check == CHECK_ALL_MAPS) { + // Check the holder map. + __ CheckMap(reg, scratch1, current_map, miss, DONT_DO_SMI_CHECK); + } + + // Perform security check for access to the global object. + ASSERT(current_map->IsJSGlobalProxyMap() || + !current_map->is_access_check_needed()); + if (current_map->IsJSGlobalProxyMap()) { + __ CheckAccessGlobalProxy(reg, scratch1, scratch2, miss); + } + + // Return the register containing the holder. + return reg; +} + + +void LoadStubCompiler::HandlerFrontendFooter(Handle<Name> name, Label* miss) { + if (!miss->is_unused()) { + Label success; + __ B(&success); + + __ Bind(miss); + TailCallBuiltin(masm(), MissBuiltin(kind())); + + __ Bind(&success); + } +} + + +void StoreStubCompiler::HandlerFrontendFooter(Handle<Name> name, Label* miss) { + if (!miss->is_unused()) { + Label success; + __ B(&success); + + GenerateRestoreName(masm(), miss, name); + TailCallBuiltin(masm(), MissBuiltin(kind())); + + __ Bind(&success); + } +} + + +Register LoadStubCompiler::CallbackHandlerFrontend(Handle<HeapType> type, + Register object_reg, + Handle<JSObject> holder, + Handle<Name> name, + Handle<Object> callback) { + Label miss; + + Register reg = HandlerFrontendHeader(type, object_reg, holder, name, &miss); + // HandlerFrontendHeader can return its result into scratch1() so do not + // use it. + Register scratch2 = this->scratch2(); + Register scratch3 = this->scratch3(); + Register dictionary = this->scratch4(); + ASSERT(!AreAliased(reg, scratch2, scratch3, dictionary)); + + if (!holder->HasFastProperties() && !holder->IsJSGlobalObject()) { + // Load the properties dictionary. + __ Ldr(dictionary, FieldMemOperand(reg, JSObject::kPropertiesOffset)); + + // Probe the dictionary. + Label probe_done; + NameDictionaryLookupStub::GeneratePositiveLookup(masm(), + &miss, + &probe_done, + dictionary, + this->name(), + scratch2, + scratch3); + __ Bind(&probe_done); + + // If probing finds an entry in the dictionary, scratch3 contains the + // pointer into the dictionary. Check that the value is the callback. + Register pointer = scratch3; + const int kElementsStartOffset = NameDictionary::kHeaderSize + + NameDictionary::kElementsStartIndex * kPointerSize; + const int kValueOffset = kElementsStartOffset + kPointerSize; + __ Ldr(scratch2, FieldMemOperand(pointer, kValueOffset)); + __ Cmp(scratch2, Operand(callback)); + __ B(ne, &miss); + } + + HandlerFrontendFooter(name, &miss); + return reg; +} + + +void LoadStubCompiler::GenerateLoadField(Register reg, + Handle<JSObject> holder, + FieldIndex field, + Representation representation) { + __ Mov(receiver(), reg); + if (kind() == Code::LOAD_IC) { + LoadFieldStub stub(isolate(), field); + GenerateTailCall(masm(), stub.GetCode()); + } else { + KeyedLoadFieldStub stub(isolate(), field); + GenerateTailCall(masm(), stub.GetCode()); + } +} + + +void LoadStubCompiler::GenerateLoadConstant(Handle<Object> value) { + // Return the constant value. + __ LoadObject(x0, value); + __ Ret(); +} + + +void LoadStubCompiler::GenerateLoadCallback( + Register reg, + Handle<ExecutableAccessorInfo> callback) { + ASSERT(!AreAliased(scratch2(), scratch3(), scratch4(), reg)); + + // Build ExecutableAccessorInfo::args_ list on the stack and push property + // name below the exit frame to make GC aware of them and store pointers to + // them. + STATIC_ASSERT(PropertyCallbackArguments::kHolderIndex == 0); + STATIC_ASSERT(PropertyCallbackArguments::kIsolateIndex == 1); + STATIC_ASSERT(PropertyCallbackArguments::kReturnValueDefaultValueIndex == 2); + STATIC_ASSERT(PropertyCallbackArguments::kReturnValueOffset == 3); + STATIC_ASSERT(PropertyCallbackArguments::kDataIndex == 4); + STATIC_ASSERT(PropertyCallbackArguments::kThisIndex == 5); + STATIC_ASSERT(PropertyCallbackArguments::kArgsLength == 6); + + __ Push(receiver()); + + if (heap()->InNewSpace(callback->data())) { + __ Mov(scratch3(), Operand(callback)); + __ Ldr(scratch3(), FieldMemOperand(scratch3(), + ExecutableAccessorInfo::kDataOffset)); + } else { + __ Mov(scratch3(), Operand(Handle<Object>(callback->data(), isolate()))); + } + __ LoadRoot(scratch4(), Heap::kUndefinedValueRootIndex); + __ Mov(scratch2(), Operand(ExternalReference::isolate_address(isolate()))); + __ Push(scratch3(), scratch4(), scratch4(), scratch2(), reg, name()); + + Register args_addr = scratch2(); + __ Add(args_addr, __ StackPointer(), kPointerSize); + + // Stack at this point: + // sp[40] callback data + // sp[32] undefined + // sp[24] undefined + // sp[16] isolate + // args_addr -> sp[8] reg + // sp[0] name + + // Abi for CallApiGetter. + Register getter_address_reg = x2; + + // Set up the call. + Address getter_address = v8::ToCData<Address>(callback->getter()); + ApiFunction fun(getter_address); + ExternalReference::Type type = ExternalReference::DIRECT_GETTER_CALL; + ExternalReference ref = ExternalReference(&fun, type, isolate()); + __ Mov(getter_address_reg, ref); + + CallApiGetterStub stub(isolate()); + __ TailCallStub(&stub); +} + + +void LoadStubCompiler::GenerateLoadInterceptor( + Register holder_reg, + Handle<Object> object, + Handle<JSObject> interceptor_holder, + LookupResult* lookup, + Handle<Name> name) { + ASSERT(!AreAliased(receiver(), this->name(), + scratch1(), scratch2(), scratch3())); + ASSERT(interceptor_holder->HasNamedInterceptor()); + ASSERT(!interceptor_holder->GetNamedInterceptor()->getter()->IsUndefined()); + + // So far the most popular follow ups for interceptor loads are FIELD + // and CALLBACKS, so inline only them, other cases may be added later. + bool compile_followup_inline = false; + if (lookup->IsFound() && lookup->IsCacheable()) { + if (lookup->IsField()) { + compile_followup_inline = true; + } else if (lookup->type() == CALLBACKS && + lookup->GetCallbackObject()->IsExecutableAccessorInfo()) { + ExecutableAccessorInfo* callback = + ExecutableAccessorInfo::cast(lookup->GetCallbackObject()); + compile_followup_inline = callback->getter() != NULL && + callback->IsCompatibleReceiver(*object); + } + } + + if (compile_followup_inline) { + // Compile the interceptor call, followed by inline code to load the + // property from further up the prototype chain if the call fails. + // Check that the maps haven't changed. + ASSERT(holder_reg.is(receiver()) || holder_reg.is(scratch1())); + + // Preserve the receiver register explicitly whenever it is different from + // the holder and it is needed should the interceptor return without any + // result. The CALLBACKS case needs the receiver to be passed into C++ code, + // the FIELD case might cause a miss during the prototype check. + bool must_perfrom_prototype_check = *interceptor_holder != lookup->holder(); + bool must_preserve_receiver_reg = !receiver().Is(holder_reg) && + (lookup->type() == CALLBACKS || must_perfrom_prototype_check); + + // Save necessary data before invoking an interceptor. + // Requires a frame to make GC aware of pushed pointers. + { + FrameScope frame_scope(masm(), StackFrame::INTERNAL); + if (must_preserve_receiver_reg) { + __ Push(receiver(), holder_reg, this->name()); + } else { + __ Push(holder_reg, this->name()); + } + // Invoke an interceptor. Note: map checks from receiver to + // interceptor's holder has been compiled before (see a caller + // of this method.) + CompileCallLoadPropertyWithInterceptor( + masm(), receiver(), holder_reg, this->name(), interceptor_holder, + IC::kLoadPropertyWithInterceptorOnly); + + // Check if interceptor provided a value for property. If it's + // the case, return immediately. + Label interceptor_failed; + __ JumpIfRoot(x0, + Heap::kNoInterceptorResultSentinelRootIndex, + &interceptor_failed); + frame_scope.GenerateLeaveFrame(); + __ Ret(); + + __ Bind(&interceptor_failed); + if (must_preserve_receiver_reg) { + __ Pop(this->name(), holder_reg, receiver()); + } else { + __ Pop(this->name(), holder_reg); + } + // Leave the internal frame. + } + GenerateLoadPostInterceptor(holder_reg, interceptor_holder, name, lookup); + } else { // !compile_followup_inline + // Call the runtime system to load the interceptor. + // Check that the maps haven't changed. + PushInterceptorArguments( + masm(), receiver(), holder_reg, this->name(), interceptor_holder); + + ExternalReference ref = + ExternalReference(IC_Utility(IC::kLoadPropertyWithInterceptor), + isolate()); + __ TailCallExternalReference(ref, StubCache::kInterceptorArgsLength, 1); + } +} + + +Handle<Code> StoreStubCompiler::CompileStoreCallback( + Handle<JSObject> object, + Handle<JSObject> holder, + Handle<Name> name, + Handle<ExecutableAccessorInfo> callback) { + ASM_LOCATION("StoreStubCompiler::CompileStoreCallback"); + Register holder_reg = HandlerFrontend( + IC::CurrentTypeOf(object, isolate()), receiver(), holder, name); + + // Stub never generated for non-global objects that require access checks. + ASSERT(holder->IsJSGlobalProxy() || !holder->IsAccessCheckNeeded()); + + // receiver() and holder_reg can alias. + ASSERT(!AreAliased(receiver(), scratch1(), scratch2(), value())); + ASSERT(!AreAliased(holder_reg, scratch1(), scratch2(), value())); + __ Mov(scratch1(), Operand(callback)); + __ Mov(scratch2(), Operand(name)); + __ Push(receiver(), holder_reg, scratch1(), scratch2(), value()); + + // Do tail-call to the runtime system. + ExternalReference store_callback_property = + ExternalReference(IC_Utility(IC::kStoreCallbackProperty), isolate()); + __ TailCallExternalReference(store_callback_property, 5, 1); + + // Return the generated code. + return GetCode(kind(), Code::FAST, name); +} + + +#undef __ +#define __ ACCESS_MASM(masm) + + +void StoreStubCompiler::GenerateStoreViaSetter( + MacroAssembler* masm, + Handle<HeapType> type, + Register receiver, + Handle<JSFunction> setter) { + // ----------- S t a t e ------------- + // -- lr : return address + // ----------------------------------- + Label miss; + + { + FrameScope scope(masm, StackFrame::INTERNAL); + + // Save value register, so we can restore it later. + __ Push(value()); + + if (!setter.is_null()) { + // Call the JavaScript setter with receiver and value on the stack. + if (IC::TypeToMap(*type, masm->isolate())->IsJSGlobalObjectMap()) { + // Swap in the global receiver. + __ Ldr(receiver, + FieldMemOperand( + receiver, JSGlobalObject::kGlobalReceiverOffset)); + } + __ Push(receiver, value()); + ParameterCount actual(1); + ParameterCount expected(setter); + __ InvokeFunction(setter, expected, actual, + CALL_FUNCTION, NullCallWrapper()); + } else { + // If we generate a global code snippet for deoptimization only, remember + // the place to continue after deoptimization. + masm->isolate()->heap()->SetSetterStubDeoptPCOffset(masm->pc_offset()); + } + + // We have to return the passed value, not the return value of the setter. + __ Pop(x0); + + // Restore context register. + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + } + __ Ret(); +} + + +#undef __ +#define __ ACCESS_MASM(masm()) + + +Handle<Code> StoreStubCompiler::CompileStoreInterceptor( + Handle<JSObject> object, + Handle<Name> name) { + Label miss; + + ASM_LOCATION("StoreStubCompiler::CompileStoreInterceptor"); + + __ Push(receiver(), this->name(), value()); + + // Do tail-call to the runtime system. + ExternalReference store_ic_property = + ExternalReference(IC_Utility(IC::kStoreInterceptorProperty), isolate()); + __ TailCallExternalReference(store_ic_property, 3, 1); + + // Return the generated code. + return GetCode(kind(), Code::FAST, name); +} + + +Handle<Code> LoadStubCompiler::CompileLoadNonexistent(Handle<HeapType> type, + Handle<JSObject> last, + Handle<Name> name) { + NonexistentHandlerFrontend(type, last, name); + + // Return undefined if maps of the full prototype chain are still the + // same and no global property with this name contains a value. + __ LoadRoot(x0, Heap::kUndefinedValueRootIndex); + __ Ret(); + + // Return the generated code. + return GetCode(kind(), Code::FAST, name); +} + + +// TODO(all): The so-called scratch registers are significant in some cases. For +// example, KeyedStoreStubCompiler::registers()[3] (x3) is actually used for +// KeyedStoreCompiler::transition_map(). We should verify which registers are +// actually scratch registers, and which are important. For now, we use the same +// assignments as ARM to remain on the safe side. + +Register* LoadStubCompiler::registers() { + // receiver, name, scratch1, scratch2, scratch3, scratch4. + static Register registers[] = { x0, x2, x3, x1, x4, x5 }; + return registers; +} + + +Register* KeyedLoadStubCompiler::registers() { + // receiver, name/key, scratch1, scratch2, scratch3, scratch4. + static Register registers[] = { x1, x0, x2, x3, x4, x5 }; + return registers; +} + + +Register StoreStubCompiler::value() { + return x0; +} + + +Register* StoreStubCompiler::registers() { + // receiver, value, scratch1, scratch2, scratch3. + static Register registers[] = { x1, x2, x3, x4, x5 }; + return registers; +} + + +Register* KeyedStoreStubCompiler::registers() { + // receiver, name, scratch1, scratch2, scratch3. + static Register registers[] = { x2, x1, x3, x4, x5 }; + return registers; +} + + +#undef __ +#define __ ACCESS_MASM(masm) + +void LoadStubCompiler::GenerateLoadViaGetter(MacroAssembler* masm, + Handle<HeapType> type, + Register receiver, + Handle<JSFunction> getter) { + { + FrameScope scope(masm, StackFrame::INTERNAL); + + if (!getter.is_null()) { + // Call the JavaScript getter with the receiver on the stack. + if (IC::TypeToMap(*type, masm->isolate())->IsJSGlobalObjectMap()) { + // Swap in the global receiver. + __ Ldr(receiver, + FieldMemOperand( + receiver, JSGlobalObject::kGlobalReceiverOffset)); + } + __ Push(receiver); + ParameterCount actual(0); + ParameterCount expected(getter); + __ InvokeFunction(getter, expected, actual, + CALL_FUNCTION, NullCallWrapper()); + } else { + // If we generate a global code snippet for deoptimization only, remember + // the place to continue after deoptimization. + masm->isolate()->heap()->SetGetterStubDeoptPCOffset(masm->pc_offset()); + } + + // Restore context register. + __ Ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); + } + __ Ret(); +} + + +#undef __ +#define __ ACCESS_MASM(masm()) + + +Handle<Code> LoadStubCompiler::CompileLoadGlobal( + Handle<HeapType> type, + Handle<GlobalObject> global, + Handle<PropertyCell> cell, + Handle<Name> name, + bool is_dont_delete) { + Label miss; + HandlerFrontendHeader(type, receiver(), global, name, &miss); + + // Get the value from the cell. + __ Mov(x3, Operand(cell)); + __ Ldr(x4, FieldMemOperand(x3, Cell::kValueOffset)); + + // Check for deleted property if property can actually be deleted. + if (!is_dont_delete) { + __ JumpIfRoot(x4, Heap::kTheHoleValueRootIndex, &miss); + } + + Counters* counters = isolate()->counters(); + __ IncrementCounter(counters->named_load_global_stub(), 1, x1, x3); + __ Mov(x0, x4); + __ Ret(); + + HandlerFrontendFooter(name, &miss); + + // Return the generated code. + return GetCode(kind(), Code::NORMAL, name); +} + + +Handle<Code> BaseLoadStoreStubCompiler::CompilePolymorphicIC( + TypeHandleList* types, + CodeHandleList* handlers, + Handle<Name> name, + Code::StubType type, + IcCheckType check) { + Label miss; + + if (check == PROPERTY && + (kind() == Code::KEYED_LOAD_IC || kind() == Code::KEYED_STORE_IC)) { + __ CompareAndBranch(this->name(), Operand(name), ne, &miss); + } + + Label number_case; + Label* smi_target = IncludesNumberType(types) ? &number_case : &miss; + __ JumpIfSmi(receiver(), smi_target); + + Register map_reg = scratch1(); + __ Ldr(map_reg, FieldMemOperand(receiver(), HeapObject::kMapOffset)); + int receiver_count = types->length(); + int number_of_handled_maps = 0; + for (int current = 0; current < receiver_count; ++current) { + Handle<HeapType> type = types->at(current); + Handle<Map> map = IC::TypeToMap(*type, isolate()); + if (!map->is_deprecated()) { + number_of_handled_maps++; + Label try_next; + __ Cmp(map_reg, Operand(map)); + __ B(ne, &try_next); + if (type->Is(HeapType::Number())) { + ASSERT(!number_case.is_unused()); + __ Bind(&number_case); + } + __ Jump(handlers->at(current), RelocInfo::CODE_TARGET); + __ Bind(&try_next); + } + } + ASSERT(number_of_handled_maps != 0); + + __ Bind(&miss); + TailCallBuiltin(masm(), MissBuiltin(kind())); + + // Return the generated code. + InlineCacheState state = + (number_of_handled_maps > 1) ? POLYMORPHIC : MONOMORPHIC; + return GetICCode(kind(), type, name, state); +} + + +void StoreStubCompiler::GenerateStoreArrayLength() { + // Prepare tail call to StoreIC_ArrayLength. + __ Push(receiver(), value()); + + ExternalReference ref = + ExternalReference(IC_Utility(IC::kStoreIC_ArrayLength), + masm()->isolate()); + __ TailCallExternalReference(ref, 2, 1); +} + + +Handle<Code> KeyedStoreStubCompiler::CompileStorePolymorphic( + MapHandleList* receiver_maps, + CodeHandleList* handler_stubs, + MapHandleList* transitioned_maps) { + Label miss; + + ASM_LOCATION("KeyedStoreStubCompiler::CompileStorePolymorphic"); + + __ JumpIfSmi(receiver(), &miss); + + int receiver_count = receiver_maps->length(); + __ Ldr(scratch1(), FieldMemOperand(receiver(), HeapObject::kMapOffset)); + for (int i = 0; i < receiver_count; i++) { + __ Cmp(scratch1(), Operand(receiver_maps->at(i))); + + Label skip; + __ B(&skip, ne); + if (!transitioned_maps->at(i).is_null()) { + // This argument is used by the handler stub. For example, see + // ElementsTransitionGenerator::GenerateMapChangeElementsTransition. + __ Mov(transition_map(), Operand(transitioned_maps->at(i))); + } + __ Jump(handler_stubs->at(i), RelocInfo::CODE_TARGET); + __ Bind(&skip); + } + + __ Bind(&miss); + TailCallBuiltin(masm(), MissBuiltin(kind())); + + return GetICCode( + kind(), Code::NORMAL, factory()->empty_string(), POLYMORPHIC); +} + + +#undef __ +#define __ ACCESS_MASM(masm) + +void KeyedLoadStubCompiler::GenerateLoadDictionaryElement( + MacroAssembler* masm) { + // ---------- S t a t e -------------- + // -- lr : return address + // -- x0 : key + // -- x1 : receiver + // ----------------------------------- + Label slow, miss; + + Register result = x0; + Register key = x0; + Register receiver = x1; + + __ JumpIfNotSmi(key, &miss); + __ Ldr(x4, FieldMemOperand(receiver, JSObject::kElementsOffset)); + __ LoadFromNumberDictionary(&slow, x4, key, result, x2, x3, x5, x6); + __ Ret(); + + __ Bind(&slow); + __ IncrementCounter( + masm->isolate()->counters()->keyed_load_external_array_slow(), 1, x2, x3); + TailCallBuiltin(masm, Builtins::kKeyedLoadIC_Slow); + + // Miss case, call the runtime. + __ Bind(&miss); + TailCallBuiltin(masm, Builtins::kKeyedLoadIC_Miss); +} + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/utils-arm64.cc b/chromium/v8/src/arm64/utils-arm64.cc new file mode 100644 index 00000000000..0cb4ea5e74f --- /dev/null +++ b/chromium/v8/src/arm64/utils-arm64.cc @@ -0,0 +1,89 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if V8_TARGET_ARCH_ARM64 + +#include "src/arm64/utils-arm64.h" + + +namespace v8 { +namespace internal { + +#define __ assm-> + + +int CountLeadingZeros(uint64_t value, int width) { + // TODO(jbramley): Optimize this for ARM64 hosts. + ASSERT((width == 32) || (width == 64)); + int count = 0; + uint64_t bit_test = 1UL << (width - 1); + while ((count < width) && ((bit_test & value) == 0)) { + count++; + bit_test >>= 1; + } + return count; +} + + +int CountLeadingSignBits(int64_t value, int width) { + // TODO(jbramley): Optimize this for ARM64 hosts. + ASSERT((width == 32) || (width == 64)); + if (value >= 0) { + return CountLeadingZeros(value, width) - 1; + } else { + return CountLeadingZeros(~value, width) - 1; + } +} + + +int CountTrailingZeros(uint64_t value, int width) { + // TODO(jbramley): Optimize this for ARM64 hosts. + ASSERT((width == 32) || (width == 64)); + int count = 0; + while ((count < width) && (((value >> count) & 1) == 0)) { + count++; + } + return count; +} + + +int CountSetBits(uint64_t value, int width) { + // TODO(jbramley): Would it be useful to allow other widths? The + // implementation already supports them. + ASSERT((width == 32) || (width == 64)); + + // Mask out unused bits to ensure that they are not counted. + value &= (0xffffffffffffffffUL >> (64-width)); + + // Add up the set bits. + // The algorithm works by adding pairs of bit fields together iteratively, + // where the size of each bit field doubles each time. + // An example for an 8-bit value: + // Bits: h g f e d c b a + // \ | \ | \ | \ | + // value = h+g f+e d+c b+a + // \ | \ | + // value = h+g+f+e d+c+b+a + // \ | + // value = h+g+f+e+d+c+b+a + 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; +} + + +int MaskToBit(uint64_t mask) { + ASSERT(CountSetBits(mask, 64) == 1); + return CountTrailingZeros(mask, 64); +} + + +} } // namespace v8::internal + +#endif // V8_TARGET_ARCH_ARM64 diff --git a/chromium/v8/src/arm64/utils-arm64.h b/chromium/v8/src/arm64/utils-arm64.h new file mode 100644 index 00000000000..b9407102059 --- /dev/null +++ b/chromium/v8/src/arm64/utils-arm64.h @@ -0,0 +1,112 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ARM64_UTILS_ARM64_H_ +#define V8_ARM64_UTILS_ARM64_H_ + +#include <cmath> +#include "src/v8.h" +#include "src/arm64/constants-arm64.h" + +#define REGISTER_CODE_LIST(R) \ +R(0) R(1) R(2) R(3) R(4) R(5) R(6) R(7) \ +R(8) R(9) R(10) R(11) R(12) R(13) R(14) R(15) \ +R(16) R(17) R(18) R(19) R(20) R(21) R(22) R(23) \ +R(24) R(25) R(26) R(27) R(28) R(29) R(30) R(31) + +namespace v8 { +namespace internal { + +// These are global assumptions in v8. +STATIC_ASSERT((static_cast<int32_t>(-1) >> 1) == -1); +STATIC_ASSERT((static_cast<uint32_t>(-1) >> 1) == 0x7FFFFFFF); + +// Floating point representation. +static inline uint32_t float_to_rawbits(float value) { + uint32_t bits = 0; + memcpy(&bits, &value, 4); + return bits; +} + + +static inline uint64_t double_to_rawbits(double value) { + uint64_t bits = 0; + memcpy(&bits, &value, 8); + return bits; +} + + +static inline float rawbits_to_float(uint32_t bits) { + float value = 0.0; + memcpy(&value, &bits, 4); + return value; +} + + +static inline double rawbits_to_double(uint64_t bits) { + double value = 0.0; + memcpy(&value, &bits, 8); + return value; +} + + +// Bit counting. +int CountLeadingZeros(uint64_t value, int width); +int CountLeadingSignBits(int64_t value, int width); +int CountTrailingZeros(uint64_t value, int width); +int CountSetBits(uint64_t value, int width); +int MaskToBit(uint64_t mask); + + +// NaN tests. +inline bool IsSignallingNaN(double num) { + uint64_t raw = double_to_rawbits(num); + if (std::isnan(num) && ((raw & kDQuietNanMask) == 0)) { + return true; + } + return false; +} + + +inline bool IsSignallingNaN(float num) { + uint32_t raw = float_to_rawbits(num); + if (std::isnan(num) && ((raw & kSQuietNanMask) == 0)) { + return true; + } + return false; +} + + +template <typename T> +inline bool IsQuietNaN(T num) { + return std::isnan(num) && !IsSignallingNaN(num); +} + + +// Convert the NaN in 'num' to a quiet NaN. +inline double ToQuietNaN(double num) { + ASSERT(isnan(num)); + return rawbits_to_double(double_to_rawbits(num) | kDQuietNanMask); +} + + +inline float ToQuietNaN(float num) { + ASSERT(isnan(num)); + return rawbits_to_float(float_to_rawbits(num) | kSQuietNanMask); +} + + +// Fused multiply-add. +inline double FusedMultiplyAdd(double op1, double op2, double a) { + return fma(op1, op2, a); +} + + +inline float FusedMultiplyAdd(float op1, float op2, float a) { + return fmaf(op1, op2, a); +} + +} } // namespace v8::internal + +#endif // V8_ARM64_UTILS_ARM64_H_ |