|  | // 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/base/bits.h" | 
|  | #include "src/base/division-by-constant.h" | 
|  | #include "src/codegen/assembler.h" | 
|  | #include "src/codegen/callable.h" | 
|  | #include "src/codegen/code-factory.h" | 
|  | #include "src/codegen/external-reference-table.h" | 
|  | #include "src/codegen/macro-assembler-inl.h" | 
|  | #include "src/codegen/register-configuration.h" | 
|  | #include "src/debug/debug.h" | 
|  | #include "src/deoptimizer/deoptimizer.h" | 
|  | #include "src/execution/frame-constants.h" | 
|  | #include "src/execution/frames-inl.h" | 
|  | #include "src/heap/memory-chunk.h" | 
|  | #include "src/init/bootstrapper.h" | 
|  | #include "src/logging/counters.h" | 
|  | #include "src/runtime/runtime.h" | 
|  | #include "src/snapshot/embedded/embedded-data.h" | 
|  | #include "src/snapshot/snapshot.h" | 
|  | #include "src/wasm/wasm-code-manager.h" | 
|  |  | 
|  | // Satisfy cpplint check, but don't include platform-specific header. It is | 
|  | // included recursively via macro-assembler.h. | 
|  | #if 0 | 
|  | #include "src/codegen/arm64/macro-assembler-arm64.h" | 
|  | #endif | 
|  |  | 
|  | namespace v8 { | 
|  | namespace internal { | 
|  |  | 
|  | CPURegList TurboAssembler::DefaultTmpList() { return CPURegList(ip0, ip1); } | 
|  |  | 
|  | CPURegList TurboAssembler::DefaultFPTmpList() { | 
|  | return CPURegList(fp_scratch1, fp_scratch2); | 
|  | } | 
|  |  | 
|  | int TurboAssembler::RequiredStackSizeForCallerSaved(SaveFPRegsMode fp_mode, | 
|  | Register exclusion) const { | 
|  | auto list = kCallerSaved; | 
|  | list.Remove(exclusion); | 
|  | list.Align(); | 
|  |  | 
|  | int bytes = list.Count() * kXRegSizeInBits / 8; | 
|  |  | 
|  | if (fp_mode == kSaveFPRegs) { | 
|  | DCHECK_EQ(kCallerSavedV.Count() % 2, 0); | 
|  | bytes += kCallerSavedV.Count() * kDRegSizeInBits / 8; | 
|  | } | 
|  | return bytes; | 
|  | } | 
|  |  | 
|  | int TurboAssembler::PushCallerSaved(SaveFPRegsMode fp_mode, | 
|  | Register exclusion) { | 
|  | auto list = kCallerSaved; | 
|  | list.Remove(exclusion); | 
|  | list.Align(); | 
|  |  | 
|  | PushCPURegList<kDontStoreLR>(list); | 
|  |  | 
|  | int bytes = list.Count() * kXRegSizeInBits / 8; | 
|  |  | 
|  | if (fp_mode == kSaveFPRegs) { | 
|  | DCHECK_EQ(kCallerSavedV.Count() % 2, 0); | 
|  | PushCPURegList(kCallerSavedV); | 
|  | bytes += kCallerSavedV.Count() * kDRegSizeInBits / 8; | 
|  | } | 
|  | return bytes; | 
|  | } | 
|  |  | 
|  | int TurboAssembler::PopCallerSaved(SaveFPRegsMode fp_mode, Register exclusion) { | 
|  | int bytes = 0; | 
|  | if (fp_mode == kSaveFPRegs) { | 
|  | DCHECK_EQ(kCallerSavedV.Count() % 2, 0); | 
|  | PopCPURegList(kCallerSavedV); | 
|  | bytes += kCallerSavedV.Count() * kDRegSizeInBits / 8; | 
|  | } | 
|  |  | 
|  | auto list = kCallerSaved; | 
|  | list.Remove(exclusion); | 
|  | list.Align(); | 
|  |  | 
|  | PopCPURegList<kDontLoadLR>(list); | 
|  | bytes += list.Count() * kXRegSizeInBits / 8; | 
|  |  | 
|  | return bytes; | 
|  | } | 
|  |  | 
|  | void TurboAssembler::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(); | 
|  |  | 
|  | // If the operation is NOT, invert the operation and immediate. | 
|  | if ((op & NOT) == NOT) { | 
|  | op = static_cast<LogicalOp>(op & ~NOT); | 
|  | immediate = ~immediate; | 
|  | } | 
|  |  | 
|  | // Ignore the top 32 bits of an immediate if we're moving to a W register. | 
|  | if (rd.Is32Bits()) { | 
|  | // Check that the top 32 bits are consistent. | 
|  | DCHECK(((immediate >> kWRegSizeInBits) == 0) || | 
|  | ((immediate >> kWRegSizeInBits) == -1)); | 
|  | immediate &= kWRegMask; | 
|  | } | 
|  |  | 
|  | DCHECK(rd.Is64Bits() || is_uint32(immediate)); | 
|  |  | 
|  | // 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); | 
|  |  | 
|  | // If the left-hand input is the stack pointer, we can't pre-shift the | 
|  | // immediate, as the encoding won't allow the subsequent post shift. | 
|  | PreShiftImmMode mode = rn == sp ? kNoShift : kAnyShift; | 
|  | Operand imm_operand = MoveImmediateForShiftedOp(temp, immediate, mode); | 
|  |  | 
|  | if (rd.IsSP()) { | 
|  | // 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, imm_operand, op); | 
|  | Mov(sp, temp); | 
|  | } else { | 
|  | Logical(rd, rn, imm_operand, op); | 
|  | } | 
|  | } | 
|  |  | 
|  | } else if (operand.IsExtendedRegister()) { | 
|  | DCHECK(operand.reg().SizeInBits() <= rd.SizeInBits()); | 
|  | // Add/sub extended supports shift <= 4. We want to support exactly the | 
|  | // same modes here. | 
|  | DCHECK_LE(operand.shift_amount(), 4); | 
|  | DCHECK(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. | 
|  | DCHECK(operand.IsShiftedRegister()); | 
|  | Logical(rd, rn, operand, op); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Mov(const Register& rd, uint64_t imm) { | 
|  | DCHECK(allow_macro_instructions()); | 
|  | DCHECK(is_uint32(imm) || is_int32(imm) || rd.Is64Bits()); | 
|  | DCHECK(!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. | 
|  |  | 
|  | // Try to move the immediate in one instruction, and if that fails, switch to | 
|  | // using multiple instructions. | 
|  | if (!TryOneInstrMoveImmediate(rd, imm)) { | 
|  | unsigned reg_size = rd.SizeInBits(); | 
|  |  | 
|  | // 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. | 
|  | DCHECK_EQ(reg_size % 16, 0); | 
|  | bool first_mov_done = false; | 
|  | for (int 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); | 
|  | } | 
|  | } | 
|  | } | 
|  | DCHECK(first_mov_done); | 
|  |  | 
|  | // Move the temporary if the original destination register was the stack | 
|  | // pointer. | 
|  | if (rd.IsSP()) { | 
|  | mov(rd, temp); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Mov(const Register& rd, const Operand& operand, | 
|  | DiscardMoveMode discard_mode) { | 
|  | DCHECK(allow_macro_instructions()); | 
|  | DCHECK(!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)) { | 
|  | // TODO(jgruber,v8:8887): Also consider a root-relative load when generating | 
|  | // non-isolate-independent code. In many cases it might be cheaper than | 
|  | // embedding the relocatable value. | 
|  | if (root_array_available_ && options().isolate_independent_code) { | 
|  | if (operand.ImmediateRMode() == RelocInfo::EXTERNAL_REFERENCE) { | 
|  | Address addr = static_cast<Address>(operand.ImmediateValue()); | 
|  | ExternalReference reference = bit_cast<ExternalReference>(addr); | 
|  | IndirectLoadExternalReference(rd, reference); | 
|  | return; | 
|  | } else if (RelocInfo::IsEmbeddedObjectMode(operand.ImmediateRMode())) { | 
|  | Handle<HeapObject> x( | 
|  | reinterpret_cast<Address*>(operand.ImmediateValue())); | 
|  | // TODO(v8:9706): Fix-it! This load will always uncompress the value | 
|  | // even when we are loading a compressed embedded object. | 
|  | IndirectLoadConstant(rd.X(), x); | 
|  | return; | 
|  | } | 
|  | } | 
|  | Ldr(dst, operand); | 
|  | } 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 sp is an operand, add #0 is emitted, otherwise, orr #0. | 
|  | if (rd != 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 != rd) { | 
|  | DCHECK(rd.IsSP()); | 
|  | Assembler::mov(rd, dst); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Mov(const Register& rd, Smi smi) { | 
|  | return Mov(rd, Operand(smi)); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Movi16bitHelper(const VRegister& vd, uint64_t imm) { | 
|  | DCHECK(is_uint16(imm)); | 
|  | int byte1 = (imm & 0xFF); | 
|  | int byte2 = ((imm >> 8) & 0xFF); | 
|  | if (byte1 == byte2) { | 
|  | movi(vd.Is64Bits() ? vd.V8B() : vd.V16B(), byte1); | 
|  | } else if (byte1 == 0) { | 
|  | movi(vd, byte2, LSL, 8); | 
|  | } else if (byte2 == 0) { | 
|  | movi(vd, byte1); | 
|  | } else if (byte1 == 0xFF) { | 
|  | mvni(vd, ~byte2 & 0xFF, LSL, 8); | 
|  | } else if (byte2 == 0xFF) { | 
|  | mvni(vd, ~byte1 & 0xFF); | 
|  | } else { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireW(); | 
|  | movz(temp, imm); | 
|  | dup(vd, temp); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Movi32bitHelper(const VRegister& vd, uint64_t imm) { | 
|  | DCHECK(is_uint32(imm)); | 
|  |  | 
|  | uint8_t bytes[sizeof(imm)]; | 
|  | memcpy(bytes, &imm, sizeof(imm)); | 
|  |  | 
|  | // All bytes are either 0x00 or 0xFF. | 
|  | { | 
|  | bool all0orff = true; | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | if ((bytes[i] != 0) && (bytes[i] != 0xFF)) { | 
|  | all0orff = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (all0orff == true) { | 
|  | movi(vd.Is64Bits() ? vd.V1D() : vd.V2D(), ((imm << 32) | imm)); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Of the 4 bytes, only one byte is non-zero. | 
|  | for (int i = 0; i < 4; i++) { | 
|  | if ((imm & (0xFF << (i * 8))) == imm) { | 
|  | movi(vd, bytes[i], LSL, i * 8); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Of the 4 bytes, only one byte is not 0xFF. | 
|  | for (int i = 0; i < 4; i++) { | 
|  | uint32_t mask = ~(0xFF << (i * 8)); | 
|  | if ((imm & mask) == mask) { | 
|  | mvni(vd, ~bytes[i] & 0xFF, LSL, i * 8); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Immediate is of the form 0x00MMFFFF. | 
|  | if ((imm & 0xFF00FFFF) == 0x0000FFFF) { | 
|  | movi(vd, bytes[2], MSL, 16); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Immediate is of the form 0x0000MMFF. | 
|  | if ((imm & 0xFFFF00FF) == 0x000000FF) { | 
|  | movi(vd, bytes[1], MSL, 8); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Immediate is of the form 0xFFMM0000. | 
|  | if ((imm & 0xFF00FFFF) == 0xFF000000) { | 
|  | mvni(vd, ~bytes[2] & 0xFF, MSL, 16); | 
|  | return; | 
|  | } | 
|  | // Immediate is of the form 0xFFFFMM00. | 
|  | if ((imm & 0xFFFF00FF) == 0xFFFF0000) { | 
|  | mvni(vd, ~bytes[1] & 0xFF, MSL, 8); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Top and bottom 16-bits are equal. | 
|  | if (((imm >> 16) & 0xFFFF) == (imm & 0xFFFF)) { | 
|  | Movi16bitHelper(vd.Is64Bits() ? vd.V4H() : vd.V8H(), imm & 0xFFFF); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Default case. | 
|  | { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireW(); | 
|  | Mov(temp, imm); | 
|  | dup(vd, temp); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Movi64bitHelper(const VRegister& vd, uint64_t imm) { | 
|  | // All bytes are either 0x00 or 0xFF. | 
|  | { | 
|  | bool all0orff = true; | 
|  | for (int i = 0; i < 8; ++i) { | 
|  | int byteval = (imm >> (i * 8)) & 0xFF; | 
|  | if (byteval != 0 && byteval != 0xFF) { | 
|  | all0orff = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (all0orff == true) { | 
|  | movi(vd, imm); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Top and bottom 32-bits are equal. | 
|  | if (((imm >> 32) & 0xFFFFFFFF) == (imm & 0xFFFFFFFF)) { | 
|  | Movi32bitHelper(vd.Is64Bits() ? vd.V2S() : vd.V4S(), imm & 0xFFFFFFFF); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Default case. | 
|  | { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | Mov(temp, imm); | 
|  | if (vd.Is1D()) { | 
|  | mov(vd.D(), 0, temp); | 
|  | } else { | 
|  | dup(vd.V2D(), temp); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Movi(const VRegister& vd, uint64_t imm, Shift shift, | 
|  | int shift_amount) { | 
|  | DCHECK(allow_macro_instructions()); | 
|  | if (shift_amount != 0 || shift != LSL) { | 
|  | movi(vd, imm, shift, shift_amount); | 
|  | } else if (vd.Is8B() || vd.Is16B()) { | 
|  | // 8-bit immediate. | 
|  | DCHECK(is_uint8(imm)); | 
|  | movi(vd, imm); | 
|  | } else if (vd.Is4H() || vd.Is8H()) { | 
|  | // 16-bit immediate. | 
|  | Movi16bitHelper(vd, imm); | 
|  | } else if (vd.Is2S() || vd.Is4S()) { | 
|  | // 32-bit immediate. | 
|  | Movi32bitHelper(vd, imm); | 
|  | } else { | 
|  | // 64-bit immediate. | 
|  | Movi64bitHelper(vd, imm); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Movi(const VRegister& vd, uint64_t hi, uint64_t lo) { | 
|  | // TODO(v8:11033): Move 128-bit values in a more efficient way. | 
|  | DCHECK(vd.Is128Bits()); | 
|  | Movi(vd.V2D(), lo); | 
|  | if (lo != hi) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | Mov(temp, hi); | 
|  | Ins(vd.V2D(), 1, temp); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Mvn(const Register& rd, const Operand& operand) { | 
|  | DCHECK(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 TurboAssembler::CountClearHalfWords(uint64_t imm, unsigned reg_size) { | 
|  | DCHECK_EQ(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 TurboAssembler::IsImmMovz(uint64_t imm, unsigned reg_size) { | 
|  | DCHECK((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 TurboAssembler::IsImmMovn(uint64_t imm, unsigned reg_size) { | 
|  | return IsImmMovz(~imm, reg_size); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::ConditionalCompareMacro(const Register& rn, | 
|  | const Operand& operand, | 
|  | StatusFlags nzcv, Condition cond, | 
|  | ConditionalCompareOp op) { | 
|  | DCHECK((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 TurboAssembler::Csel(const Register& rd, const Register& rn, | 
|  | const Operand& operand, Condition cond) { | 
|  | DCHECK(allow_macro_instructions()); | 
|  | DCHECK(!rd.IsZero()); | 
|  | DCHECK((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); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool TurboAssembler::TryOneInstrMoveImmediate(const Register& dst, | 
|  | int64_t imm) { | 
|  | unsigned n, imm_s, imm_r; | 
|  | int reg_size = dst.SizeInBits(); | 
|  | if (IsImmMovz(imm, reg_size) && !dst.IsSP()) { | 
|  | // Immediate can be represented in a move zero instruction. Movz can't write | 
|  | // to the stack pointer. | 
|  | movz(dst, imm); | 
|  | return true; | 
|  | } else if (IsImmMovn(imm, reg_size) && !dst.IsSP()) { | 
|  | // Immediate can be represented in a move not instruction. Movn can't write | 
|  | // to the stack pointer. | 
|  | movn(dst, dst.Is64Bits() ? ~imm : (~imm & kWRegMask)); | 
|  | return true; | 
|  | } else if (IsImmLogical(imm, reg_size, &n, &imm_s, &imm_r)) { | 
|  | // Immediate can be represented in a logical orr instruction. | 
|  | LogicalImmediate(dst, AppropriateZeroRegFor(dst), n, imm_s, imm_r, ORR); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Operand TurboAssembler::MoveImmediateForShiftedOp(const Register& dst, | 
|  | int64_t imm, | 
|  | PreShiftImmMode mode) { | 
|  | int reg_size = dst.SizeInBits(); | 
|  | // Encode the immediate in a single move instruction, if possible. | 
|  | if (TryOneInstrMoveImmediate(dst, imm)) { | 
|  | // The move was successful; nothing to do here. | 
|  | } else { | 
|  | // Pre-shift the immediate to the least-significant bits of the register. | 
|  | int shift_low; | 
|  | if (reg_size == 64) { | 
|  | shift_low = base::bits::CountTrailingZeros(imm); | 
|  | } else { | 
|  | DCHECK_EQ(reg_size, 32); | 
|  | shift_low = base::bits::CountTrailingZeros(static_cast<uint32_t>(imm)); | 
|  | } | 
|  |  | 
|  | if (mode == kLimitShiftForSP) { | 
|  | // When applied to the stack pointer, the subsequent arithmetic operation | 
|  | // can use the extend form to shift left by a maximum of four bits. Right | 
|  | // shifts are not allowed, so we filter them out later before the new | 
|  | // immediate is tested. | 
|  | shift_low = std::min(shift_low, 4); | 
|  | } | 
|  | int64_t imm_low = imm >> shift_low; | 
|  |  | 
|  | // Pre-shift the immediate to the most-significant bits of the register. We | 
|  | // insert set bits in the least-significant bits, as this creates a | 
|  | // different immediate that may be encodable using movn or orr-immediate. | 
|  | // If this new immediate is encodable, the set bits will be eliminated by | 
|  | // the post shift on the following instruction. | 
|  | int shift_high = CountLeadingZeros(imm, reg_size); | 
|  | int64_t imm_high = (imm << shift_high) | ((INT64_C(1) << shift_high) - 1); | 
|  |  | 
|  | if ((mode != kNoShift) && TryOneInstrMoveImmediate(dst, imm_low)) { | 
|  | // The new immediate has been moved into the destination's low bits: | 
|  | // return a new leftward-shifting operand. | 
|  | return Operand(dst, LSL, shift_low); | 
|  | } else if ((mode == kAnyShift) && TryOneInstrMoveImmediate(dst, imm_high)) { | 
|  | // The new immediate has been moved into the destination's high bits: | 
|  | // return a new rightward-shifting operand. | 
|  | return Operand(dst, LSR, shift_high); | 
|  | } else { | 
|  | // Use the generic move operation to set up the immediate. | 
|  | Mov(dst, imm); | 
|  | } | 
|  | } | 
|  | return Operand(dst); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::AddSubMacro(const Register& rd, const Register& rn, | 
|  | const Operand& operand, FlagsUpdate S, | 
|  | AddSubOp op) { | 
|  | if (operand.IsZero() && rd == 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); | 
|  | if (operand.IsImmediate()) { | 
|  | PreShiftImmMode mode = kAnyShift; | 
|  |  | 
|  | // If the destination or source register is the stack pointer, we can | 
|  | // only pre-shift the immediate right by values supported in the add/sub | 
|  | // extend encoding. | 
|  | if (rd == sp) { | 
|  | // If the destination is SP and flags will be set, we can't pre-shift | 
|  | // the immediate at all. | 
|  | mode = (S == SetFlags) ? kNoShift : kLimitShiftForSP; | 
|  | } else if (rn == sp) { | 
|  | mode = kLimitShiftForSP; | 
|  | } | 
|  |  | 
|  | Operand imm_operand = | 
|  | MoveImmediateForShiftedOp(temp, operand.ImmediateValue(), mode); | 
|  | AddSub(rd, rn, imm_operand, S, op); | 
|  | } else { | 
|  | Mov(temp, operand); | 
|  | AddSub(rd, rn, temp, S, op); | 
|  | } | 
|  | } else { | 
|  | AddSub(rd, rn, operand, S, op); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::AddSubWithCarryMacro(const Register& rd, | 
|  | const Register& rn, | 
|  | const Operand& operand, FlagsUpdate S, | 
|  | AddSubWithCarryOp op) { | 
|  | DCHECK(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). | 
|  | DCHECK(operand.reg().SizeInBits() == rd.SizeInBits()); | 
|  | DCHECK(operand.shift() != ROR); | 
|  | DCHECK(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). | 
|  | DCHECK(operand.reg().SizeInBits() <= rd.SizeInBits()); | 
|  | // Add/sub extended supports a shift <= 4. We want to support exactly the | 
|  | // same modes. | 
|  | DCHECK_LE(operand.shift_amount(), 4); | 
|  | DCHECK(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 TurboAssembler::LoadStoreMacro(const CPURegister& rt, | 
|  | const MemOperand& addr, LoadStoreOp op) { | 
|  | int64_t offset = addr.offset(); | 
|  | unsigned 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 TurboAssembler::LoadStorePairMacro(const CPURegister& rt, | 
|  | const CPURegister& rt2, | 
|  | const MemOperand& addr, | 
|  | LoadStorePairOp op) { | 
|  | // TODO(all): Should we support register offset for load-store-pair? | 
|  | DCHECK(!addr.IsRegisterOffset()); | 
|  |  | 
|  | int64_t offset = addr.offset(); | 
|  | unsigned size = CalcLSPairDataSize(op); | 
|  |  | 
|  | // Check if the offset fits in the immediate field of the appropriate | 
|  | // instruction. If not, emit two instructions to perform the operation. | 
|  | if (IsImmLSPair(offset, size)) { | 
|  | // Encodable in one load/store pair instruction. | 
|  | LoadStorePair(rt, rt2, addr, op); | 
|  | } else { | 
|  | Register base = addr.base(); | 
|  | if (addr.IsImmediateOffset()) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireSameSizeAs(base); | 
|  | Add(temp, base, offset); | 
|  | LoadStorePair(rt, rt2, MemOperand(temp), op); | 
|  | } else if (addr.IsPostIndex()) { | 
|  | LoadStorePair(rt, rt2, MemOperand(base), op); | 
|  | Add(base, base, offset); | 
|  | } else { | 
|  | DCHECK(addr.IsPreIndex()); | 
|  | Add(base, base, offset); | 
|  | LoadStorePair(rt, rt2, MemOperand(base), op); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool TurboAssembler::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_ = std::min( | 
|  | next_veneer_pool_check_, max_reachable_pc - kVeneerDistanceCheckMargin); | 
|  | } | 
|  | return need_longer_range; | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Adr(const Register& rd, Label* label, AdrHint hint) { | 
|  | DCHECK(allow_macro_instructions()); | 
|  | DCHECK(!rd.IsZero()); | 
|  |  | 
|  | if (hint == kAdrNear) { | 
|  | adr(rd, label); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(hint, kAdrFar); | 
|  | if (label->is_bound()) { | 
|  | int label_offset = label->pos() - pc_offset(); | 
|  | if (Instruction::IsValidPCRelOffset(label_offset)) { | 
|  | adr(rd, label); | 
|  | } else { | 
|  | DCHECK_LE(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 { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  |  | 
|  | InstructionAccurateScope scope(this, | 
|  | PatchingAssembler::kAdrFarPatchableNInstrs); | 
|  | adr(rd, label); | 
|  | for (int i = 0; i < PatchingAssembler::kAdrFarPatchableNNops; ++i) { | 
|  | nop(ADR_FAR_NOP); | 
|  | } | 
|  | movz(scratch, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::B(Label* label, BranchType type, Register reg, int bit) { | 
|  | DCHECK((reg == 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 TurboAssembler::B(Label* label, Condition cond) { | 
|  | DCHECK(allow_macro_instructions()); | 
|  | DCHECK((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 TurboAssembler::Tbnz(const Register& rt, unsigned bit_pos, Label* label) { | 
|  | DCHECK(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 TurboAssembler::Tbz(const Register& rt, unsigned bit_pos, Label* label) { | 
|  | DCHECK(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 TurboAssembler::Cbnz(const Register& rt, Label* label) { | 
|  | DCHECK(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 TurboAssembler::Cbz(const Register& rt, Label* label) { | 
|  | DCHECK(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 TurboAssembler::Abs(const Register& rd, const Register& rm, | 
|  | Label* is_not_representable, Label* is_representable) { | 
|  | DCHECK(allow_macro_instructions()); | 
|  | DCHECK(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 != nullptr) && (is_representable != nullptr)) { | 
|  | B(is_not_representable, vs); | 
|  | B(is_representable); | 
|  | } else if (is_not_representable != nullptr) { | 
|  | B(is_not_representable, vs); | 
|  | } else if (is_representable != nullptr) { | 
|  | B(is_representable, vc); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Abstracted stack operations. | 
|  |  | 
|  | void TurboAssembler::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) { | 
|  | DCHECK(AreSameSizeAndType(src0, src1, src2, src3, src4, src5, src6, src7)); | 
|  |  | 
|  | int count = 5 + src5.is_valid() + src6.is_valid() + src6.is_valid(); | 
|  | int size = src0.SizeInBytes(); | 
|  | DCHECK_EQ(0, (size * count) % 16); | 
|  |  | 
|  | PushHelper(4, size, src0, src1, src2, src3); | 
|  | PushHelper(count - 4, size, src4, src5, src6, src7); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1, | 
|  | const CPURegister& dst2, const CPURegister& dst3, | 
|  | const CPURegister& dst4, const CPURegister& dst5, | 
|  | const CPURegister& dst6, const CPURegister& dst7) { | 
|  | // It is not valid to pop into the same register more than once in one | 
|  | // instruction, not even into the zero register. | 
|  | DCHECK(!AreAliased(dst0, dst1, dst2, dst3, dst4, dst5, dst6, dst7)); | 
|  | DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3, dst4, dst5, dst6, dst7)); | 
|  | DCHECK(dst0.is_valid()); | 
|  |  | 
|  | int count = 5 + dst5.is_valid() + dst6.is_valid() + dst7.is_valid(); | 
|  | int size = dst0.SizeInBytes(); | 
|  | DCHECK_EQ(0, (size * count) % 16); | 
|  |  | 
|  | PopHelper(4, size, dst0, dst1, dst2, dst3); | 
|  | PopHelper(count - 4, size, dst4, dst5, dst6, dst7); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::PushMultipleTimes(CPURegister src, Register count) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireSameSizeAs(count); | 
|  |  | 
|  | 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 TurboAssembler::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); | 
|  |  | 
|  | DCHECK(AreSameSizeAndType(src0, src1, src2, src3)); | 
|  | DCHECK(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: | 
|  | DCHECK(src1.IsNone() && src2.IsNone() && src3.IsNone()); | 
|  | str(src0, MemOperand(sp, -1 * size, PreIndex)); | 
|  | break; | 
|  | case 2: | 
|  | DCHECK(src2.IsNone() && src3.IsNone()); | 
|  | stp(src1, src0, MemOperand(sp, -2 * size, PreIndex)); | 
|  | break; | 
|  | case 3: | 
|  | DCHECK(src3.IsNone()); | 
|  | stp(src2, src1, MemOperand(sp, -3 * size, PreIndex)); | 
|  | str(src0, MemOperand(sp, 2 * size)); | 
|  | break; | 
|  | case 4: | 
|  | // Skip over 4 * size, then fill in the gap. This allows four W registers | 
|  | // to be pushed using sp, whilst maintaining 16-byte alignment for sp | 
|  | // at all times. | 
|  | stp(src3, src2, MemOperand(sp, -4 * size, PreIndex)); | 
|  | stp(src1, src0, MemOperand(sp, 2 * size)); | 
|  | break; | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::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); | 
|  |  | 
|  | DCHECK(AreSameSizeAndType(dst0, dst1, dst2, dst3)); | 
|  | DCHECK(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: | 
|  | DCHECK(dst1.IsNone() && dst2.IsNone() && dst3.IsNone()); | 
|  | ldr(dst0, MemOperand(sp, 1 * size, PostIndex)); | 
|  | break; | 
|  | case 2: | 
|  | DCHECK(dst2.IsNone() && dst3.IsNone()); | 
|  | ldp(dst0, dst1, MemOperand(sp, 2 * size, PostIndex)); | 
|  | break; | 
|  | case 3: | 
|  | DCHECK(dst3.IsNone()); | 
|  | ldr(dst2, MemOperand(sp, 2 * size)); | 
|  | ldp(dst0, dst1, MemOperand(sp, 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 sp, whilst maintaining 16-byte alignment | 
|  | // for sp at all times. | 
|  | ldp(dst2, dst3, MemOperand(sp, 2 * size)); | 
|  | ldp(dst0, dst1, MemOperand(sp, 4 * size, PostIndex)); | 
|  | break; | 
|  | default: | 
|  | UNREACHABLE(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::PokePair(const CPURegister& src1, const CPURegister& src2, | 
|  | int offset) { | 
|  | DCHECK(AreSameSizeAndType(src1, src2)); | 
|  | DCHECK((offset >= 0) && ((offset % src1.SizeInBytes()) == 0)); | 
|  | Stp(src1, src2, MemOperand(sp, offset)); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::PeekPair(const CPURegister& dst1, const CPURegister& dst2, | 
|  | int offset) { | 
|  | DCHECK(AreSameSizeAndType(dst1, dst2)); | 
|  | DCHECK((offset >= 0) && ((offset % dst1.SizeInBytes()) == 0)); | 
|  | Ldp(dst1, dst2, MemOperand(sp, offset)); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::PushCalleeSavedRegisters() { | 
|  | #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY | 
|  | Pacibsp(); | 
|  | #endif | 
|  |  | 
|  | { | 
|  | // Ensure that the macro-assembler doesn't use any scratch registers. | 
|  | InstructionAccurateScope scope(this); | 
|  |  | 
|  | MemOperand tos(sp, -2 * static_cast<int>(kXRegSize), PreIndex); | 
|  |  | 
|  | stp(d14, d15, tos); | 
|  | stp(d12, d13, tos); | 
|  | stp(d10, d11, tos); | 
|  | stp(d8, d9, tos); | 
|  |  | 
|  | STATIC_ASSERT( | 
|  | EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair == | 
|  | 8 * kSystemPointerSize); | 
|  | stp(x29, x30, tos);  // fp, lr | 
|  |  | 
|  | STATIC_ASSERT( | 
|  | EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair == | 
|  | 10 * kSystemPointerSize); | 
|  |  | 
|  | stp(x27, x28, tos); | 
|  | 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); | 
|  |  | 
|  | MemOperand tos(sp, 2 * kXRegSize, PostIndex); | 
|  |  | 
|  | ldp(x19, x20, tos); | 
|  | ldp(x21, x22, tos); | 
|  | ldp(x23, x24, tos); | 
|  | ldp(x25, x26, tos); | 
|  | ldp(x27, x28, tos); | 
|  | ldp(x29, x30, tos); | 
|  |  | 
|  | ldp(d8, d9, tos); | 
|  | ldp(d10, d11, tos); | 
|  | ldp(d12, d13, tos); | 
|  | ldp(d14, d15, tos); | 
|  | } | 
|  |  | 
|  | #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY | 
|  | Autibsp(); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void TurboAssembler::AssertSpAligned() { | 
|  | if (emit_debug_code()) { | 
|  | HardAbortScope hard_abort(this);  // Avoid calls to Abort. | 
|  | // Arm64 requires the stack pointer to be 16-byte aligned prior to address | 
|  | // calculation. | 
|  | UseScratchRegisterScope scope(this); | 
|  | Register temp = scope.AcquireX(); | 
|  | Mov(temp, sp); | 
|  | Tst(temp, 15); | 
|  | Check(eq, AbortReason::kUnexpectedStackPointer); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CopySlots(int dst, Register src, Register slot_count) { | 
|  | DCHECK(!src.IsZero()); | 
|  | UseScratchRegisterScope scope(this); | 
|  | Register dst_reg = scope.AcquireX(); | 
|  | SlotAddress(dst_reg, dst); | 
|  | SlotAddress(src, src); | 
|  | CopyDoubleWords(dst_reg, src, slot_count); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CopySlots(Register dst, Register src, | 
|  | Register slot_count) { | 
|  | DCHECK(!dst.IsZero() && !src.IsZero()); | 
|  | SlotAddress(dst, dst); | 
|  | SlotAddress(src, src); | 
|  | CopyDoubleWords(dst, src, slot_count); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CopyDoubleWords(Register dst, Register src, Register count, | 
|  | CopyDoubleWordsMode mode) { | 
|  | DCHECK(!AreAliased(dst, src, count)); | 
|  |  | 
|  | if (emit_debug_code()) { | 
|  | Register pointer1 = dst; | 
|  | Register pointer2 = src; | 
|  | if (mode == kSrcLessThanDst) { | 
|  | pointer1 = src; | 
|  | pointer2 = dst; | 
|  | } | 
|  | // Copy requires pointer1 < pointer2 || (pointer1 - pointer2) >= count. | 
|  | Label pointer1_below_pointer2; | 
|  | Subs(pointer1, pointer1, pointer2); | 
|  | B(lt, &pointer1_below_pointer2); | 
|  | Cmp(pointer1, count); | 
|  | Check(ge, AbortReason::kOffsetOutOfRange); | 
|  | Bind(&pointer1_below_pointer2); | 
|  | Add(pointer1, pointer1, pointer2); | 
|  | } | 
|  | static_assert(kSystemPointerSize == kDRegSize, | 
|  | "pointers must be the same size as doubles"); | 
|  |  | 
|  | if (mode == kDstLessThanSrcAndReverse) { | 
|  | Add(src, src, Operand(count, LSL, kSystemPointerSizeLog2)); | 
|  | Sub(src, src, kSystemPointerSize); | 
|  | } | 
|  |  | 
|  | int src_direction = (mode == kDstLessThanSrc) ? 1 : -1; | 
|  | int dst_direction = (mode == kSrcLessThanDst) ? -1 : 1; | 
|  |  | 
|  | UseScratchRegisterScope scope(this); | 
|  | VRegister temp0 = scope.AcquireD(); | 
|  | VRegister temp1 = scope.AcquireD(); | 
|  |  | 
|  | Label pairs, loop, done; | 
|  |  | 
|  | Tbz(count, 0, &pairs); | 
|  | Ldr(temp0, MemOperand(src, src_direction * kSystemPointerSize, PostIndex)); | 
|  | Sub(count, count, 1); | 
|  | Str(temp0, MemOperand(dst, dst_direction * kSystemPointerSize, PostIndex)); | 
|  |  | 
|  | Bind(&pairs); | 
|  | if (mode == kSrcLessThanDst) { | 
|  | // Adjust pointers for post-index ldp/stp with negative offset: | 
|  | Sub(dst, dst, kSystemPointerSize); | 
|  | Sub(src, src, kSystemPointerSize); | 
|  | } else if (mode == kDstLessThanSrcAndReverse) { | 
|  | Sub(src, src, kSystemPointerSize); | 
|  | } | 
|  | Bind(&loop); | 
|  | Cbz(count, &done); | 
|  | Ldp(temp0, temp1, | 
|  | MemOperand(src, 2 * src_direction * kSystemPointerSize, PostIndex)); | 
|  | Sub(count, count, 2); | 
|  | if (mode == kDstLessThanSrcAndReverse) { | 
|  | Stp(temp1, temp0, | 
|  | MemOperand(dst, 2 * dst_direction * kSystemPointerSize, PostIndex)); | 
|  | } else { | 
|  | Stp(temp0, temp1, | 
|  | MemOperand(dst, 2 * dst_direction * kSystemPointerSize, PostIndex)); | 
|  | } | 
|  | B(&loop); | 
|  |  | 
|  | // TODO(all): large copies may benefit from using temporary Q registers | 
|  | // to copy four double words per iteration. | 
|  |  | 
|  | Bind(&done); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::SlotAddress(Register dst, int slot_offset) { | 
|  | Add(dst, sp, slot_offset << kSystemPointerSizeLog2); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::SlotAddress(Register dst, Register slot_offset) { | 
|  | Add(dst, sp, Operand(slot_offset, LSL, kSystemPointerSizeLog2)); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::AssertFPCRState(Register fpcr) { | 
|  | if (emit_debug_code()) { | 
|  | Label unexpected_mode, done; | 
|  | UseScratchRegisterScope temps(this); | 
|  | if (fpcr.IsNone()) { | 
|  | fpcr = temps.AcquireX(); | 
|  | Mrs(fpcr, FPCR); | 
|  | } | 
|  |  | 
|  | // 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(AbortReason::kUnexpectedFPCRMode); | 
|  |  | 
|  | Bind(&done); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CanonicalizeNaN(const VRegister& dst, | 
|  | const VRegister& src) { | 
|  | AssertFPCRState(); | 
|  |  | 
|  | // Subtracting 0.0 preserves all inputs except for signalling NaNs, which | 
|  | // become quiet NaNs. We use fsub rather than fadd because fsub preserves -0.0 | 
|  | // inputs: -0.0 + 0.0 = 0.0, but -0.0 - 0.0 = -0.0. | 
|  | Fsub(dst, src, fp_zero); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadRoot(Register destination, RootIndex 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(kRootRegister, RootRegisterOffsetForRootIndex(index))); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Move(Register dst, Smi src) { Mov(dst, src); } | 
|  |  | 
|  | void TurboAssembler::MovePair(Register dst0, Register src0, Register dst1, | 
|  | Register src1) { | 
|  | DCHECK_NE(dst0, dst1); | 
|  | if (dst0 != src1) { | 
|  | Mov(dst0, src0); | 
|  | Mov(dst1, src1); | 
|  | } else if (dst1 != src0) { | 
|  | // Swap the order of the moves to resolve the overlap. | 
|  | Mov(dst1, src1); | 
|  | Mov(dst0, src0); | 
|  | } else { | 
|  | // Worse case scenario, this is a swap. | 
|  | Swap(dst0, src0); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Swap(Register lhs, Register rhs) { | 
|  | DCHECK(lhs.IsSameSizeAndType(rhs)); | 
|  | DCHECK_NE(lhs, rhs); | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | Mov(temp, rhs); | 
|  | Mov(rhs, lhs); | 
|  | Mov(lhs, temp); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Swap(VRegister lhs, VRegister rhs) { | 
|  | DCHECK(lhs.IsSameSizeAndType(rhs)); | 
|  | DCHECK_NE(lhs, rhs); | 
|  | UseScratchRegisterScope temps(this); | 
|  | VRegister temp = VRegister::no_reg(); | 
|  | if (lhs.IsS()) { | 
|  | temp = temps.AcquireS(); | 
|  | } else if (lhs.IsD()) { | 
|  | temp = temps.AcquireD(); | 
|  | } else { | 
|  | DCHECK(lhs.IsQ()); | 
|  | temp = temps.AcquireQ(); | 
|  | } | 
|  | Mov(temp, rhs); | 
|  | Mov(rhs, lhs); | 
|  | Mov(lhs, temp); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::AssertSmi(Register object, AbortReason reason) { | 
|  | if (emit_debug_code()) { | 
|  | STATIC_ASSERT(kSmiTag == 0); | 
|  | Tst(object, kSmiTagMask); | 
|  | Check(eq, reason); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MacroAssembler::AssertNotSmi(Register object, AbortReason reason) { | 
|  | if (emit_debug_code()) { | 
|  | STATIC_ASSERT(kSmiTag == 0); | 
|  | Tst(object, kSmiTagMask); | 
|  | Check(ne, reason); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MacroAssembler::AssertConstructor(Register object) { | 
|  | if (emit_debug_code()) { | 
|  | AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAConstructor); | 
|  |  | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  |  | 
|  | LoadMap(temp, object); | 
|  | Ldrb(temp, FieldMemOperand(temp, Map::kBitFieldOffset)); | 
|  | Tst(temp, Operand(Map::Bits1::IsConstructorBit::kMask)); | 
|  |  | 
|  | Check(ne, AbortReason::kOperandIsNotAConstructor); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MacroAssembler::AssertFunction(Register object) { | 
|  | if (emit_debug_code()) { | 
|  | AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAFunction); | 
|  |  | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  |  | 
|  | CompareObjectType(object, temp, temp, JS_FUNCTION_TYPE); | 
|  | Check(eq, AbortReason::kOperandIsNotAFunction); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MacroAssembler::AssertBoundFunction(Register object) { | 
|  | if (emit_debug_code()) { | 
|  | AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotABoundFunction); | 
|  |  | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  |  | 
|  | CompareObjectType(object, temp, temp, JS_BOUND_FUNCTION_TYPE); | 
|  | Check(eq, AbortReason::kOperandIsNotABoundFunction); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MacroAssembler::AssertGeneratorObject(Register object) { | 
|  | if (!emit_debug_code()) return; | 
|  | AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAGeneratorObject); | 
|  |  | 
|  | // Load map | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | LoadMap(temp, object); | 
|  |  | 
|  | Label do_check; | 
|  | // Load instance type and check if JSGeneratorObject | 
|  | CompareInstanceType(temp, temp, JS_GENERATOR_OBJECT_TYPE); | 
|  | B(eq, &do_check); | 
|  |  | 
|  | // Check if JSAsyncFunctionObject | 
|  | Cmp(temp, JS_ASYNC_FUNCTION_OBJECT_TYPE); | 
|  | B(eq, &do_check); | 
|  |  | 
|  | // Check if JSAsyncGeneratorObject | 
|  | Cmp(temp, JS_ASYNC_GENERATOR_OBJECT_TYPE); | 
|  |  | 
|  | bind(&do_check); | 
|  | // Restore generator object to register and perform assertion | 
|  | Check(eq, AbortReason::kOperandIsNotAGeneratorObject); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::AssertUndefinedOrAllocationSite(Register object) { | 
|  | if (emit_debug_code()) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  | Label done_checking; | 
|  | AssertNotSmi(object); | 
|  | JumpIfRoot(object, RootIndex::kUndefinedValue, &done_checking); | 
|  | LoadMap(scratch, object); | 
|  | CompareInstanceType(scratch, scratch, ALLOCATION_SITE_TYPE); | 
|  | Assert(eq, AbortReason::kExpectedUndefinedOrCell); | 
|  | Bind(&done_checking); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::AssertPositiveOrZero(Register value) { | 
|  | if (emit_debug_code()) { | 
|  | Label done; | 
|  | int sign_bit = value.Is64Bits() ? kXSignBit : kWSignBit; | 
|  | Tbz(value, sign_bit, &done); | 
|  | Abort(AbortReason::kUnexpectedNegativeValue); | 
|  | Bind(&done); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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::Create(f)); | 
|  |  | 
|  | Handle<Code> code = | 
|  | CodeFactory::CEntry(isolate(), f->result_size, save_doubles); | 
|  | Call(code, RelocInfo::CODE_TARGET); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::JumpToExternalReference(const ExternalReference& builtin, | 
|  | bool builtin_exit_frame) { | 
|  | Mov(x1, builtin); | 
|  | Handle<Code> code = CodeFactory::CEntry(isolate(), 1, kDontSaveFPRegs, | 
|  | kArgvOnStack, builtin_exit_frame); | 
|  | Jump(code, RelocInfo::CODE_TARGET); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::JumpToInstructionStream(Address entry) { | 
|  | Ldr(kOffHeapTrampolineRegister, Operand(entry, RelocInfo::OFF_HEAP_TARGET)); | 
|  | Br(kOffHeapTrampolineRegister); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::TailCallRuntime(Runtime::FunctionId fid) { | 
|  | const Runtime::Function* function = Runtime::FunctionForId(fid); | 
|  | DCHECK_EQ(1, function->result_size); | 
|  | if (function->nargs >= 0) { | 
|  | // 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, function->nargs); | 
|  | } | 
|  | JumpToExternalReference(ExternalReference::Create(fid)); | 
|  | } | 
|  |  | 
|  | int TurboAssembler::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 base::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 TurboAssembler::CallCFunction(ExternalReference function, | 
|  | int num_of_reg_args) { | 
|  | CallCFunction(function, num_of_reg_args, 0); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::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); | 
|  | } | 
|  |  | 
|  | static const int kRegisterPassedArguments = 8; | 
|  |  | 
|  | void TurboAssembler::CallCFunction(Register function, int num_of_reg_args, | 
|  | int num_of_double_args) { | 
|  | DCHECK_LE(num_of_reg_args + num_of_double_args, kMaxCParameters); | 
|  | DCHECK(has_frame()); | 
|  |  | 
|  | // 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) { | 
|  | DCHECK_LE(num_of_reg_args, 1); | 
|  | DCHECK_LE(num_of_double_args + num_of_reg_args, 2); | 
|  | } | 
|  |  | 
|  | // Save the frame pointer and PC so that the stack layout remains iterable, | 
|  | // even without an ExitFrame which normally exists between JS and C frames. | 
|  | Register pc_scratch = x4; | 
|  | Register addr_scratch = x5; | 
|  | Push(pc_scratch, addr_scratch); | 
|  |  | 
|  | Label get_pc; | 
|  | Bind(&get_pc); | 
|  | Adr(pc_scratch, &get_pc); | 
|  |  | 
|  | // See x64 code for reasoning about how to address the isolate data fields. | 
|  | if (root_array_available()) { | 
|  | Str(pc_scratch, | 
|  | MemOperand(kRootRegister, IsolateData::fast_c_call_caller_pc_offset())); | 
|  | Str(fp, | 
|  | MemOperand(kRootRegister, IsolateData::fast_c_call_caller_fp_offset())); | 
|  | } else { | 
|  | DCHECK_NOT_NULL(isolate()); | 
|  | Mov(addr_scratch, | 
|  | ExternalReference::fast_c_call_caller_pc_address(isolate())); | 
|  | Str(pc_scratch, MemOperand(addr_scratch)); | 
|  | Mov(addr_scratch, | 
|  | ExternalReference::fast_c_call_caller_fp_address(isolate())); | 
|  | Str(fp, MemOperand(addr_scratch)); | 
|  | } | 
|  |  | 
|  | Pop(addr_scratch, pc_scratch); | 
|  |  | 
|  | // Call directly. The function called cannot cause a GC, or allow preemption, | 
|  | // so the return address in the link register stays correct. | 
|  | Call(function); | 
|  |  | 
|  | // We don't unset the PC; the FP is the source of truth. | 
|  | if (root_array_available()) { | 
|  | Str(xzr, | 
|  | MemOperand(kRootRegister, IsolateData::fast_c_call_caller_fp_offset())); | 
|  | } else { | 
|  | DCHECK_NOT_NULL(isolate()); | 
|  | Push(addr_scratch, xzr); | 
|  | Mov(addr_scratch, | 
|  | ExternalReference::fast_c_call_caller_fp_address(isolate())); | 
|  | Str(xzr, MemOperand(addr_scratch)); | 
|  | Pop(xzr, addr_scratch); | 
|  | } | 
|  |  | 
|  | if (num_of_reg_args > kRegisterPassedArguments) { | 
|  | // Drop the register passed arguments. | 
|  | int claim_slots = RoundUp(num_of_reg_args - kRegisterPassedArguments, 2); | 
|  | Drop(claim_slots); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadFromConstantsTable(Register destination, | 
|  | int constant_index) { | 
|  | DCHECK(RootsTable::IsImmortalImmovable(RootIndex::kBuiltinsConstantsTable)); | 
|  | LoadRoot(destination, RootIndex::kBuiltinsConstantsTable); | 
|  | LoadTaggedPointerField( | 
|  | destination, FieldMemOperand(destination, FixedArray::OffsetOfElementAt( | 
|  | constant_index))); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadRootRelative(Register destination, int32_t offset) { | 
|  | Ldr(destination, MemOperand(kRootRegister, offset)); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadRootRegisterOffset(Register destination, | 
|  | intptr_t offset) { | 
|  | if (offset == 0) { | 
|  | Mov(destination, kRootRegister); | 
|  | } else { | 
|  | Add(destination, kRootRegister, offset); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Jump(Register target, Condition cond) { | 
|  | if (cond == nv) return; | 
|  | Label done; | 
|  | if (cond != al) B(NegateCondition(cond), &done); | 
|  | Br(target); | 
|  | Bind(&done); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::JumpHelper(int64_t offset, RelocInfo::Mode rmode, | 
|  | Condition cond) { | 
|  | if (cond == nv) return; | 
|  | Label done; | 
|  | if (cond != al) B(NegateCondition(cond), &done); | 
|  | if (CanUseNearCallOrJump(rmode)) { | 
|  | DCHECK(IsNearCallOffset(offset)); | 
|  | near_jump(static_cast<int>(offset), rmode); | 
|  | } else { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | uint64_t imm = reinterpret_cast<uint64_t>(pc_) + offset * kInstrSize; | 
|  | Mov(temp, Immediate(imm, rmode)); | 
|  | Br(temp); | 
|  | } | 
|  | Bind(&done); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // The calculated offset is either: | 
|  | // * the 'target' input unmodified if this is a Wasm call, or | 
|  | // * the offset of the target from the current PC, in instructions, for any | 
|  | //   other type of call. | 
|  | static int64_t CalculateTargetOffset(Address target, RelocInfo::Mode rmode, | 
|  | byte* pc) { | 
|  | int64_t offset = static_cast<int64_t>(target); | 
|  | // The target of WebAssembly calls is still an index instead of an actual | 
|  | // address at this point, and needs to be encoded as-is. | 
|  | if (rmode != RelocInfo::WASM_CALL && rmode != RelocInfo::WASM_STUB_CALL) { | 
|  | offset -= reinterpret_cast<int64_t>(pc); | 
|  | DCHECK_EQ(offset % kInstrSize, 0); | 
|  | offset = offset / static_cast<int>(kInstrSize); | 
|  | } | 
|  | return offset; | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | void TurboAssembler::Jump(Address target, RelocInfo::Mode rmode, | 
|  | Condition cond) { | 
|  | JumpHelper(CalculateTargetOffset(target, rmode, pc_), rmode, cond); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Jump(Handle<Code> code, RelocInfo::Mode rmode, | 
|  | Condition cond) { | 
|  | DCHECK(RelocInfo::IsCodeTarget(rmode)); | 
|  | DCHECK_IMPLIES(options().isolate_independent_code, | 
|  | Builtins::IsIsolateIndependentBuiltin(*code)); | 
|  |  | 
|  | if (options().inline_offheap_trampolines) { | 
|  | int builtin_index = Builtins::kNoBuiltinId; | 
|  | if (isolate()->builtins()->IsBuiltinHandle(code, &builtin_index)) { | 
|  | // Inline the trampoline. | 
|  | RecordCommentForOffHeapTrampoline(builtin_index); | 
|  | CHECK_NE(builtin_index, Builtins::kNoBuiltinId); | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  | EmbeddedData d = EmbeddedData::FromBlob(); | 
|  | Address entry = d.InstructionStartOfBuiltin(builtin_index); | 
|  | Ldr(scratch, Operand(entry, RelocInfo::OFF_HEAP_TARGET)); | 
|  | Jump(scratch, cond); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (CanUseNearCallOrJump(rmode)) { | 
|  | EmbeddedObjectIndex index = AddEmbeddedObject(code); | 
|  | DCHECK(is_int32(index)); | 
|  | JumpHelper(static_cast<int64_t>(index), rmode, cond); | 
|  | } else { | 
|  | Jump(code.address(), rmode, cond); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Jump(const ExternalReference& reference) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  | Mov(scratch, reference); | 
|  | Jump(scratch); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Call(Register target) { | 
|  | BlockPoolsScope scope(this); | 
|  | Blr(target); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Call(Address target, RelocInfo::Mode rmode) { | 
|  | BlockPoolsScope scope(this); | 
|  |  | 
|  | if (CanUseNearCallOrJump(rmode)) { | 
|  | int64_t offset = CalculateTargetOffset(target, rmode, pc_); | 
|  | DCHECK(IsNearCallOffset(offset)); | 
|  | near_call(static_cast<int>(offset), rmode); | 
|  | } else { | 
|  | IndirectCall(target, rmode); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Call(Handle<Code> code, RelocInfo::Mode rmode) { | 
|  | DCHECK_IMPLIES(options().isolate_independent_code, | 
|  | Builtins::IsIsolateIndependentBuiltin(*code)); | 
|  | BlockPoolsScope scope(this); | 
|  |  | 
|  | if (options().inline_offheap_trampolines) { | 
|  | int builtin_index = Builtins::kNoBuiltinId; | 
|  | if (isolate()->builtins()->IsBuiltinHandle(code, &builtin_index)) { | 
|  | // Inline the trampoline. | 
|  | CallBuiltin(builtin_index); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | DCHECK(code->IsExecutable()); | 
|  | if (CanUseNearCallOrJump(rmode)) { | 
|  | EmbeddedObjectIndex index = AddEmbeddedObject(code); | 
|  | DCHECK(is_int32(index)); | 
|  | near_call(static_cast<int32_t>(index), rmode); | 
|  | } else { | 
|  | IndirectCall(code.address(), rmode); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Call(ExternalReference target) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | Mov(temp, target); | 
|  | Call(temp); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadEntryFromBuiltinIndex(Register builtin_index) { | 
|  | // The builtin_index register contains the builtin index as a Smi. | 
|  | // Untagging is folded into the indexing operand below. | 
|  | if (SmiValuesAre32Bits()) { | 
|  | Asr(builtin_index, builtin_index, kSmiShift - kSystemPointerSizeLog2); | 
|  | Add(builtin_index, builtin_index, | 
|  | IsolateData::builtin_entry_table_offset()); | 
|  | Ldr(builtin_index, MemOperand(kRootRegister, builtin_index)); | 
|  | } else { | 
|  | DCHECK(SmiValuesAre31Bits()); | 
|  | if (COMPRESS_POINTERS_BOOL) { | 
|  | Add(builtin_index, kRootRegister, | 
|  | Operand(builtin_index.W(), SXTW, kSystemPointerSizeLog2 - kSmiShift)); | 
|  | } else { | 
|  | Add(builtin_index, kRootRegister, | 
|  | Operand(builtin_index, LSL, kSystemPointerSizeLog2 - kSmiShift)); | 
|  | } | 
|  | Ldr(builtin_index, | 
|  | MemOperand(builtin_index, IsolateData::builtin_entry_table_offset())); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadEntryFromBuiltinIndex(Builtins::Name builtin_index, | 
|  | Register destination) { | 
|  | Ldr(destination, | 
|  | MemOperand(kRootRegister, | 
|  | IsolateData::builtin_entry_slot_offset(builtin_index))); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallBuiltinByIndex(Register builtin_index) { | 
|  | LoadEntryFromBuiltinIndex(builtin_index); | 
|  | Call(builtin_index); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallBuiltin(int builtin_index) { | 
|  | DCHECK(Builtins::IsBuiltinId(builtin_index)); | 
|  | RecordCommentForOffHeapTrampoline(builtin_index); | 
|  | CHECK_NE(builtin_index, Builtins::kNoBuiltinId); | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  | EmbeddedData d = EmbeddedData::FromBlob(); | 
|  | Address entry = d.InstructionStartOfBuiltin(builtin_index); | 
|  | Ldr(scratch, Operand(entry, RelocInfo::OFF_HEAP_TARGET)); | 
|  | Call(scratch); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadCodeObjectEntry(Register destination, | 
|  | Register code_object) { | 
|  | // Code objects are called differently depending on whether we are generating | 
|  | // builtin code (which will later be embedded into the binary) or compiling | 
|  | // user JS code at runtime. | 
|  | // * Builtin code runs in --jitless mode and thus must not call into on-heap | 
|  | //   Code targets. Instead, we dispatch through the builtins entry table. | 
|  | // * Codegen at runtime does not have this restriction and we can use the | 
|  | //   shorter, branchless instruction sequence. The assumption here is that | 
|  | //   targets are usually generated code and not builtin Code objects. | 
|  |  | 
|  | if (options().isolate_independent_code) { | 
|  | DCHECK(root_array_available()); | 
|  | Label if_code_is_off_heap, out; | 
|  |  | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  |  | 
|  | DCHECK(!AreAliased(destination, scratch)); | 
|  | DCHECK(!AreAliased(code_object, scratch)); | 
|  |  | 
|  | // Check whether the Code object is an off-heap trampoline. If so, call its | 
|  | // (off-heap) entry point directly without going through the (on-heap) | 
|  | // trampoline.  Otherwise, just call the Code object as always. | 
|  |  | 
|  | Ldrsw(scratch, FieldMemOperand(code_object, Code::kFlagsOffset)); | 
|  | Tst(scratch, Operand(Code::IsOffHeapTrampoline::kMask)); | 
|  | B(ne, &if_code_is_off_heap); | 
|  |  | 
|  | // Not an off-heap trampoline object, the entry point is at | 
|  | // Code::raw_instruction_start(). | 
|  | Add(destination, code_object, Code::kHeaderSize - kHeapObjectTag); | 
|  | B(&out); | 
|  |  | 
|  | // An off-heap trampoline, the entry point is loaded from the builtin entry | 
|  | // table. | 
|  | bind(&if_code_is_off_heap); | 
|  | Ldrsw(scratch, FieldMemOperand(code_object, Code::kBuiltinIndexOffset)); | 
|  | Lsl(destination, scratch, kSystemPointerSizeLog2); | 
|  | Add(destination, destination, kRootRegister); | 
|  | Ldr(destination, | 
|  | MemOperand(destination, IsolateData::builtin_entry_table_offset())); | 
|  |  | 
|  | bind(&out); | 
|  | } else { | 
|  | Add(destination, code_object, Code::kHeaderSize - kHeapObjectTag); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallCodeObject(Register code_object) { | 
|  | LoadCodeObjectEntry(code_object, code_object); | 
|  | Call(code_object); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::JumpCodeObject(Register code_object) { | 
|  | LoadCodeObjectEntry(code_object, code_object); | 
|  |  | 
|  | UseScratchRegisterScope temps(this); | 
|  | if (code_object != x17) { | 
|  | temps.Exclude(x17); | 
|  | Mov(x17, code_object); | 
|  | } | 
|  | Jump(x17); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::StoreReturnAddressAndCall(Register target) { | 
|  | // This generates the final instruction sequence for calls to C functions | 
|  | // once an exit frame has been constructed. | 
|  | // | 
|  | // Note that this assumes the caller code (i.e. the Code object currently | 
|  | // being generated) is immovable or that the callee function cannot trigger | 
|  | // GC, since the callee function will return to it. | 
|  |  | 
|  | UseScratchRegisterScope temps(this); | 
|  | temps.Exclude(x16, x17); | 
|  |  | 
|  | Label return_location; | 
|  | Adr(x17, &return_location); | 
|  | #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY | 
|  | Add(x16, sp, kSystemPointerSize); | 
|  | Pacib1716(); | 
|  | #endif | 
|  | Poke(x17, 0); | 
|  |  | 
|  | if (emit_debug_code()) { | 
|  | // Verify that the slot below fp[kSPOffset]-8 points to the signed return | 
|  | // location. | 
|  | Ldr(x16, MemOperand(fp, ExitFrameConstants::kSPOffset)); | 
|  | Ldr(x16, MemOperand(x16, -static_cast<int64_t>(kXRegSize))); | 
|  | Cmp(x16, x17); | 
|  | Check(eq, AbortReason::kReturnAddressNotFoundInFrame); | 
|  | } | 
|  |  | 
|  | Blr(target); | 
|  | Bind(&return_location); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::IndirectCall(Address target, RelocInfo::Mode rmode) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | Mov(temp, Immediate(target, rmode)); | 
|  | Blr(temp); | 
|  | } | 
|  |  | 
|  | bool TurboAssembler::IsNearCallOffset(int64_t offset) { | 
|  | return is_int26(offset); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallForDeoptimization( | 
|  | Builtins::Name target, int deopt_id, Label* exit, DeoptimizeKind kind, | 
|  | Label* jump_deoptimization_entry_label) { | 
|  | BlockPoolsScope scope(this); | 
|  | bl(jump_deoptimization_entry_label); | 
|  | DCHECK_EQ(SizeOfCodeGeneratedSince(exit), | 
|  | (kind == DeoptimizeKind::kLazy) | 
|  | ? Deoptimizer::kLazyDeoptExitSize | 
|  | : Deoptimizer::kNonLazyDeoptExitSize); | 
|  | USE(exit, kind); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::PrepareForTailCall(Register callee_args_count, | 
|  | Register caller_args_count, | 
|  | Register scratch0, Register scratch1) { | 
|  | DCHECK(!AreAliased(callee_args_count, caller_args_count, scratch0, scratch1)); | 
|  |  | 
|  | // Calculate the end of destination area where we will put the arguments | 
|  | // after we drop current frame. We add kSystemPointerSize to count the | 
|  | // receiver argument which is not included into formal parameters count. | 
|  | Register dst_reg = scratch0; | 
|  | Add(dst_reg, fp, Operand(caller_args_count, LSL, kSystemPointerSizeLog2)); | 
|  | Add(dst_reg, dst_reg, | 
|  | StandardFrameConstants::kCallerSPOffset + kSystemPointerSize); | 
|  | // Round dst_reg up to a multiple of 16 bytes, so that we overwrite any | 
|  | // potential padding. | 
|  | Add(dst_reg, dst_reg, 15); | 
|  | Bic(dst_reg, dst_reg, 15); | 
|  |  | 
|  | Register src_reg = caller_args_count; | 
|  | // Calculate the end of source area. +kSystemPointerSize is for the receiver. | 
|  | Add(src_reg, sp, Operand(callee_args_count, LSL, kSystemPointerSizeLog2)); | 
|  | Add(src_reg, src_reg, kSystemPointerSize); | 
|  |  | 
|  | // Round src_reg up to a multiple of 16 bytes, so we include any potential | 
|  | // padding in the copy. | 
|  | Add(src_reg, src_reg, 15); | 
|  | Bic(src_reg, src_reg, 15); | 
|  |  | 
|  | if (FLAG_debug_code) { | 
|  | Cmp(src_reg, dst_reg); | 
|  | Check(lo, AbortReason::kStackAccessBelowStackPointer); | 
|  | } | 
|  |  | 
|  | // Restore caller's frame pointer and return address now as they will be | 
|  | // overwritten by the copying loop. | 
|  | RestoreFPAndLR(); | 
|  |  | 
|  | // Now copy callee arguments to the caller frame going backwards to avoid | 
|  | // callee arguments corruption (source and destination areas could overlap). | 
|  |  | 
|  | // Both src_reg and dst_reg are pointing to the word after the one to copy, | 
|  | // so they must be pre-decremented in the loop. | 
|  | Register tmp_reg = scratch1; | 
|  | Label loop, entry; | 
|  | B(&entry); | 
|  | bind(&loop); | 
|  | Ldr(tmp_reg, MemOperand(src_reg, -kSystemPointerSize, PreIndex)); | 
|  | Str(tmp_reg, MemOperand(dst_reg, -kSystemPointerSize, PreIndex)); | 
|  | bind(&entry); | 
|  | Cmp(sp, src_reg); | 
|  | B(ne, &loop); | 
|  |  | 
|  | // Leave current frame. | 
|  | Mov(sp, dst_reg); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::LoadStackLimit(Register destination, StackLimitKind kind) { | 
|  | DCHECK(root_array_available()); | 
|  | Isolate* isolate = this->isolate(); | 
|  | ExternalReference limit = | 
|  | kind == StackLimitKind::kRealStackLimit | 
|  | ? ExternalReference::address_of_real_jslimit(isolate) | 
|  | : ExternalReference::address_of_jslimit(isolate); | 
|  | DCHECK(TurboAssembler::IsAddressableThroughRootRegister(isolate, limit)); | 
|  |  | 
|  | intptr_t offset = | 
|  | TurboAssembler::RootRegisterOffsetForExternalReference(isolate, limit); | 
|  | Ldr(destination, MemOperand(kRootRegister, offset)); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::StackOverflowCheck(Register num_args, | 
|  | Label* stack_overflow) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  |  | 
|  | // 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. | 
|  |  | 
|  | LoadStackLimit(scratch, StackLimitKind::kRealStackLimit); | 
|  | // Make scratch the space we have left. The stack might already be overflowed | 
|  | // here which will cause scratch to become negative. | 
|  | Sub(scratch, sp, scratch); | 
|  | // Check if the arguments will overflow the stack. | 
|  | Cmp(scratch, Operand(num_args, LSL, kSystemPointerSizeLog2)); | 
|  | B(le, stack_overflow); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::InvokePrologue(Register formal_parameter_count, | 
|  | Register actual_argument_count, Label* done, | 
|  | InvokeFlag flag) { | 
|  | //  x0: actual arguments count. | 
|  | //  x1: function (passed through to callee). | 
|  | //  x2: expected arguments count. | 
|  | //  x3: new target | 
|  | Label regular_invoke; | 
|  | DCHECK_EQ(actual_argument_count, x0); | 
|  | DCHECK_EQ(formal_parameter_count, x2); | 
|  |  | 
|  | #ifdef V8_NO_ARGUMENTS_ADAPTOR | 
|  | // If the formal parameter count is equal to the adaptor sentinel, no need | 
|  | // to push undefined value as arguments. | 
|  | Cmp(formal_parameter_count, Operand(kDontAdaptArgumentsSentinel)); | 
|  | B(eq, ®ular_invoke); | 
|  |  | 
|  | // If overapplication or if the actual argument count is equal to the | 
|  | // formal parameter count, no need to push extra undefined values. | 
|  | Register extra_argument_count = x2; | 
|  | Subs(extra_argument_count, formal_parameter_count, actual_argument_count); | 
|  | B(le, ®ular_invoke); | 
|  |  | 
|  | // The stack pointer in arm64 needs to be 16-byte aligned. We might need to | 
|  | // (1) add an extra padding or (2) remove (re-use) the extra padding already | 
|  | // in the stack. Let {slots_to_copy} be the number of slots (arguments) to | 
|  | // move up in the stack and let {slots_to_claim} be the number of extra stack | 
|  | // slots to claim. | 
|  | Label even_extra_count, skip_move; | 
|  | Register slots_to_copy = x4; | 
|  | Register slots_to_claim = x5; | 
|  |  | 
|  | Add(slots_to_copy, actual_argument_count, 1);  // Copy with receiver. | 
|  | Mov(slots_to_claim, extra_argument_count); | 
|  | Tbz(extra_argument_count, 0, &even_extra_count); | 
|  |  | 
|  | // Calculate {slots_to_claim} when {extra_argument_count} is odd. | 
|  | // If {actual_argument_count} is even, we need one extra padding slot | 
|  | // {slots_to_claim = extra_argument_count + 1}. | 
|  | // If {actual_argument_count} is odd, we know that the | 
|  | // original arguments will have a padding slot that we can reuse | 
|  | // {slots_to_claim = extra_argument_count - 1}. | 
|  | { | 
|  | Register scratch = x11; | 
|  | Add(slots_to_claim, extra_argument_count, 1); | 
|  | And(scratch, actual_argument_count, 1); | 
|  | Eor(scratch, scratch, 1); | 
|  | Sub(slots_to_claim, slots_to_claim, Operand(scratch, LSL, 1)); | 
|  | } | 
|  |  | 
|  | Bind(&even_extra_count); | 
|  | Cbz(slots_to_claim, &skip_move); | 
|  |  | 
|  | Label stack_overflow; | 
|  | StackOverflowCheck(slots_to_claim, &stack_overflow); | 
|  | Claim(slots_to_claim); | 
|  |  | 
|  | // Move the arguments already in the stack including the receiver. | 
|  | { | 
|  | Register src = x6; | 
|  | Register dst = x7; | 
|  | SlotAddress(src, slots_to_claim); | 
|  | SlotAddress(dst, 0); | 
|  | CopyDoubleWords(dst, src, slots_to_copy); | 
|  | } | 
|  |  | 
|  | Bind(&skip_move); | 
|  | Register actual_argument_with_receiver = x4; | 
|  | Register pointer_next_value = x5; | 
|  | Add(actual_argument_with_receiver, actual_argument_count, | 
|  | 1);  // {slots_to_copy} was scratched. | 
|  |  | 
|  | // Copy extra arguments as undefined values. | 
|  | { | 
|  | Label loop; | 
|  | Register undefined_value = x6; | 
|  | Register count = x7; | 
|  | LoadRoot(undefined_value, RootIndex::kUndefinedValue); | 
|  | SlotAddress(pointer_next_value, actual_argument_with_receiver); | 
|  | Mov(count, extra_argument_count); | 
|  | Bind(&loop); | 
|  | Str(undefined_value, | 
|  | MemOperand(pointer_next_value, kSystemPointerSize, PostIndex)); | 
|  | Subs(count, count, 1); | 
|  | Cbnz(count, &loop); | 
|  | } | 
|  |  | 
|  | // Set padding if needed. | 
|  | { | 
|  | Label skip; | 
|  | Register total_args_slots = x4; | 
|  | Add(total_args_slots, actual_argument_with_receiver, extra_argument_count); | 
|  | Tbz(total_args_slots, 0, &skip); | 
|  | Str(padreg, MemOperand(pointer_next_value)); | 
|  | Bind(&skip); | 
|  | } | 
|  | B(®ular_invoke); | 
|  |  | 
|  | bind(&stack_overflow); | 
|  | { | 
|  | FrameScope frame(this, | 
|  | has_frame() ? StackFrame::NONE : StackFrame::INTERNAL); | 
|  | CallRuntime(Runtime::kThrowStackOverflow); | 
|  | Unreachable(); | 
|  | } | 
|  | #else | 
|  | // Check whether the expected and actual arguments count match. The registers | 
|  | // are set up according to contract with ArgumentsAdaptorTrampoline.ct. | 
|  | // If actual == expected perform a regular invocation. | 
|  | Cmp(formal_parameter_count, actual_argument_count); | 
|  | B(eq, ®ular_invoke); | 
|  |  | 
|  | // The argument counts mismatch, generate a call to the argument adaptor. | 
|  | Handle<Code> adaptor = BUILTIN_CODE(isolate(), ArgumentsAdaptorTrampoline); | 
|  | if (flag == CALL_FUNCTION) { | 
|  | Call(adaptor); | 
|  | // If the arg counts don't match, no extra code is emitted by | 
|  | // MAsm::InvokeFunctionCode and we can just fall through. | 
|  | B(done); | 
|  | } else { | 
|  | Jump(adaptor, RelocInfo::CODE_TARGET); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | Bind(®ular_invoke); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::CallDebugOnFunctionCall(Register fun, Register new_target, | 
|  | Register expected_parameter_count, | 
|  | Register actual_parameter_count) { | 
|  | // Load receiver to pass it later to DebugOnFunctionCall hook. | 
|  | Peek(x4, ReceiverOperand(actual_parameter_count)); | 
|  | FrameScope frame(this, has_frame() ? StackFrame::NONE : StackFrame::INTERNAL); | 
|  |  | 
|  | if (!new_target.is_valid()) new_target = padreg; | 
|  |  | 
|  | // Save values on stack. | 
|  | SmiTag(expected_parameter_count); | 
|  | SmiTag(actual_parameter_count); | 
|  | Push(expected_parameter_count, actual_parameter_count, new_target, fun); | 
|  | Push(fun, x4); | 
|  | CallRuntime(Runtime::kDebugOnFunctionCall); | 
|  |  | 
|  | // Restore values from stack. | 
|  | Pop(fun, new_target, actual_parameter_count, expected_parameter_count); | 
|  | SmiUntag(actual_parameter_count); | 
|  | SmiUntag(expected_parameter_count); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::InvokeFunctionCode(Register function, Register new_target, | 
|  | Register expected_parameter_count, | 
|  | Register actual_parameter_count, | 
|  | InvokeFlag flag) { | 
|  | // You can't call a function without a valid frame. | 
|  | DCHECK_IMPLIES(flag == CALL_FUNCTION, has_frame()); | 
|  | DCHECK_EQ(function, x1); | 
|  | DCHECK_IMPLIES(new_target.is_valid(), new_target == x3); | 
|  |  | 
|  | // On function call, call into the debugger if necessary. | 
|  | Label debug_hook, continue_after_hook; | 
|  | { | 
|  | Mov(x4, ExternalReference::debug_hook_on_function_call_address(isolate())); | 
|  | Ldrsb(x4, MemOperand(x4)); | 
|  | Cbnz(x4, &debug_hook); | 
|  | } | 
|  | bind(&continue_after_hook); | 
|  |  | 
|  | // Clear the new.target register if not given. | 
|  | if (!new_target.is_valid()) { | 
|  | LoadRoot(x3, RootIndex::kUndefinedValue); | 
|  | } | 
|  |  | 
|  | Label done; | 
|  | InvokePrologue(expected_parameter_count, actual_parameter_count, &done, flag); | 
|  |  | 
|  | // If actual != expected, InvokePrologue will have handled the call through | 
|  | // the argument adaptor mechanism. | 
|  | // The called function expects the call kind in x5. | 
|  | // We call indirectly through the code field in the function to | 
|  | // allow recompilation to take effect without changing any of the | 
|  | // call sites. | 
|  | Register code = kJavaScriptCallCodeStartRegister; | 
|  | LoadTaggedPointerField(code, | 
|  | FieldMemOperand(function, JSFunction::kCodeOffset)); | 
|  | if (flag == CALL_FUNCTION) { | 
|  | CallCodeObject(code); | 
|  | } else { | 
|  | DCHECK(flag == JUMP_FUNCTION); | 
|  | JumpCodeObject(code); | 
|  | } | 
|  | B(&done); | 
|  |  | 
|  | // Deferred debug hook. | 
|  | bind(&debug_hook); | 
|  | CallDebugOnFunctionCall(function, new_target, expected_parameter_count, | 
|  | actual_parameter_count); | 
|  | B(&continue_after_hook); | 
|  |  | 
|  | // Continue here if InvokePrologue does handle the invocation due to | 
|  | // mismatched parameter counts. | 
|  | Bind(&done); | 
|  | } | 
|  |  | 
|  | Operand MacroAssembler::ReceiverOperand(Register arg_count) { | 
|  | return Operand(0); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::InvokeFunctionWithNewTarget( | 
|  | Register function, Register new_target, Register actual_parameter_count, | 
|  | InvokeFlag flag) { | 
|  | // You can't call a function without a valid frame. | 
|  | DCHECK(flag == JUMP_FUNCTION || has_frame()); | 
|  |  | 
|  | // Contract with called JS functions requires that function is passed in x1. | 
|  | // (See FullCodeGenerator::Generate().) | 
|  | DCHECK_EQ(function, x1); | 
|  |  | 
|  | Register expected_parameter_count = x2; | 
|  |  | 
|  | LoadTaggedPointerField(cp, | 
|  | FieldMemOperand(function, JSFunction::kContextOffset)); | 
|  | // The number of arguments is stored as an int32_t, and -1 is a marker | 
|  | // (kDontAdaptArgumentsSentinel), so we need sign | 
|  | // extension to correctly handle it. | 
|  | LoadTaggedPointerField( | 
|  | expected_parameter_count, | 
|  | FieldMemOperand(function, JSFunction::kSharedFunctionInfoOffset)); | 
|  | Ldrh(expected_parameter_count, | 
|  | FieldMemOperand(expected_parameter_count, | 
|  | SharedFunctionInfo::kFormalParameterCountOffset)); | 
|  |  | 
|  | InvokeFunctionCode(function, new_target, expected_parameter_count, | 
|  | actual_parameter_count, flag); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::InvokeFunction(Register function, | 
|  | Register expected_parameter_count, | 
|  | Register actual_parameter_count, | 
|  | InvokeFlag flag) { | 
|  | // You can't call a function without a valid frame. | 
|  | DCHECK(flag == JUMP_FUNCTION || has_frame()); | 
|  |  | 
|  | // Contract with called JS functions requires that function is passed in x1. | 
|  | // (See FullCodeGenerator::Generate().) | 
|  | DCHECK_EQ(function, x1); | 
|  |  | 
|  | // Set up the context. | 
|  | LoadTaggedPointerField(cp, | 
|  | FieldMemOperand(function, JSFunction::kContextOffset)); | 
|  |  | 
|  | InvokeFunctionCode(function, no_reg, expected_parameter_count, | 
|  | actual_parameter_count, flag); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::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 occurred, 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 TurboAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone, | 
|  | Register result, | 
|  | DoubleRegister double_input, | 
|  | StubCallMode stub_mode, | 
|  | LinkRegisterStatus lr_status) { | 
|  | if (CpuFeatures::IsSupported(JSCVT)) { | 
|  | Fjcvtzs(result.W(), double_input); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Label done; | 
|  |  | 
|  | // 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. | 
|  | if (lr_status == kLRHasNotBeenSaved) { | 
|  | Push<TurboAssembler::kSignLR>(lr, double_input); | 
|  | } else { | 
|  | Push<TurboAssembler::kDontStoreLR>(xzr, double_input); | 
|  | } | 
|  |  | 
|  | // DoubleToI preserves any registers it needs to clobber. | 
|  | if (stub_mode == StubCallMode::kCallWasmRuntimeStub) { | 
|  | Call(wasm::WasmCode::kDoubleToI, RelocInfo::WASM_STUB_CALL); | 
|  | } else if (options().inline_offheap_trampolines) { | 
|  | CallBuiltin(Builtins::kDoubleToI); | 
|  | } else { | 
|  | Call(BUILTIN_CODE(isolate, DoubleToI), RelocInfo::CODE_TARGET); | 
|  | } | 
|  | Ldr(result, MemOperand(sp, 0)); | 
|  |  | 
|  | DCHECK_EQ(xzr.SizeInBytes(), double_input.SizeInBytes()); | 
|  |  | 
|  | if (lr_status == kLRHasNotBeenSaved) { | 
|  | // Pop into xzr here to drop the double input on the stack: | 
|  | Pop<TurboAssembler::kAuthLR>(xzr, lr); | 
|  | } else { | 
|  | Drop(2); | 
|  | } | 
|  |  | 
|  | Bind(&done); | 
|  | // Keep our invariant that the upper 32 bits are zero. | 
|  | Uxtw(result.W(), result.W()); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Prologue() { | 
|  | Push<TurboAssembler::kSignLR>(lr, fp); | 
|  | mov(fp, sp); | 
|  | STATIC_ASSERT(kExtraSlotClaimedByPrologue == 1); | 
|  | Push(cp, kJSFunctionRegister, kJavaScriptCallArgCountRegister, padreg); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::EnterFrame(StackFrame::Type type) { | 
|  | UseScratchRegisterScope temps(this); | 
|  |  | 
|  | if (type == StackFrame::INTERNAL || type == StackFrame::WASM_DEBUG_BREAK) { | 
|  | Register type_reg = temps.AcquireX(); | 
|  | Mov(type_reg, StackFrame::TypeToMarker(type)); | 
|  | Push<TurboAssembler::kSignLR>(lr, fp, type_reg, padreg); | 
|  | const int kFrameSize = | 
|  | TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize; | 
|  | Add(fp, sp, kFrameSize); | 
|  | // sp[3] : lr | 
|  | // sp[2] : fp | 
|  | // sp[1] : type | 
|  | // sp[0] : for alignment | 
|  | } else if (type == StackFrame::WASM || | 
|  | type == StackFrame::WASM_COMPILE_LAZY || | 
|  | type == StackFrame::WASM_EXIT) { | 
|  | Register type_reg = temps.AcquireX(); | 
|  | Mov(type_reg, StackFrame::TypeToMarker(type)); | 
|  | Push<TurboAssembler::kSignLR>(lr, fp); | 
|  | Mov(fp, sp); | 
|  | Push(type_reg, padreg); | 
|  | // sp[3] : lr | 
|  | // sp[2] : fp | 
|  | // sp[1] : type | 
|  | // sp[0] : for alignment | 
|  | } else { | 
|  | DCHECK_EQ(type, StackFrame::CONSTRUCT); | 
|  | Register type_reg = temps.AcquireX(); | 
|  | Mov(type_reg, StackFrame::TypeToMarker(type)); | 
|  |  | 
|  | // Users of this frame type push a context pointer after the type field, | 
|  | // so do it here to keep the stack pointer aligned. | 
|  | Push<TurboAssembler::kSignLR>(lr, fp, type_reg, cp); | 
|  |  | 
|  | // The context pointer isn't part of the fixed frame, so add an extra slot | 
|  | // to account for it. | 
|  | Add(fp, sp, | 
|  | TypedFrameConstants::kFixedFrameSizeFromFp + kSystemPointerSize); | 
|  | // sp[3] : lr | 
|  | // sp[2] : fp | 
|  | // sp[1] : type | 
|  | // sp[0] : cp | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LeaveFrame(StackFrame::Type type) { | 
|  | // Drop the execution stack down to the frame pointer and restore | 
|  | // the caller frame pointer and return address. | 
|  | Mov(sp, fp); | 
|  | Pop<TurboAssembler::kAuthLR>(fp, lr); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::ExitFramePreserveFPRegs() { | 
|  | DCHECK_EQ(kCallerSavedV.Count() % 2, 0); | 
|  | PushCPURegList(kCallerSavedV); | 
|  | } | 
|  |  | 
|  | 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 = kCallerSavedV; | 
|  | DCHECK_EQ(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, | 
|  | StackFrame::Type frame_type) { | 
|  | DCHECK(frame_type == StackFrame::EXIT || | 
|  | frame_type == StackFrame::BUILTIN_EXIT); | 
|  |  | 
|  | // Set up the new stack frame. | 
|  | Push<TurboAssembler::kSignLR>(lr, fp); | 
|  | Mov(fp, sp); | 
|  | Mov(scratch, StackFrame::TypeToMarker(frame_type)); | 
|  | Push(scratch, xzr); | 
|  | //          fp[8]: CallerPC (lr) | 
|  | //    fp -> fp[0]: CallerFP (old fp) | 
|  | //          fp[-8]: STUB marker | 
|  | //    sp -> fp[-16]: Space reserved for SPOffset. | 
|  | STATIC_ASSERT((2 * kSystemPointerSize) == | 
|  | ExitFrameConstants::kCallerSPOffset); | 
|  | STATIC_ASSERT((1 * kSystemPointerSize) == | 
|  | ExitFrameConstants::kCallerPCOffset); | 
|  | STATIC_ASSERT((0 * kSystemPointerSize) == | 
|  | ExitFrameConstants::kCallerFPOffset); | 
|  | STATIC_ASSERT((-2 * kSystemPointerSize) == ExitFrameConstants::kSPOffset); | 
|  |  | 
|  | // Save the frame pointer and context pointer in the top frame. | 
|  | Mov(scratch, | 
|  | ExternalReference::Create(IsolateAddressId::kCEntryFPAddress, isolate())); | 
|  | Str(fp, MemOperand(scratch)); | 
|  | Mov(scratch, | 
|  | ExternalReference::Create(IsolateAddressId::kContextAddress, isolate())); | 
|  | Str(cp, MemOperand(scratch)); | 
|  |  | 
|  | STATIC_ASSERT((-2 * kSystemPointerSize) == | 
|  | ExitFrameConstants::kLastExitFrameField); | 
|  | if (save_doubles) { | 
|  | ExitFramePreserveFPRegs(); | 
|  | } | 
|  |  | 
|  | // Round the number of space we need to claim to a multiple of two. | 
|  | int slots_to_claim = RoundUp(extra_space + 1, 2); | 
|  |  | 
|  | // 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(slots_to_claim, kXRegSize); | 
|  | //         fp[8]: CallerPC (lr) | 
|  | //   fp -> fp[0]: CallerFP (old fp) | 
|  | //         fp[-8]: STUB marker | 
|  | //         fp[-16]: Space reserved for SPOffset. | 
|  | //         fp[-16 - fp_size]: Saved doubles (if save_doubles is true). | 
|  | //         sp[8]: Extra space reserved for caller (if extra_space != 0). | 
|  | //   sp -> sp[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, sp, kXRegSize); | 
|  | Str(scratch, MemOperand(fp, ExitFrameConstants::kSPOffset)); | 
|  | } | 
|  |  | 
|  | // Leave the current exit frame. | 
|  | void MacroAssembler::LeaveExitFrame(bool restore_doubles, | 
|  | const Register& scratch, | 
|  | const Register& scratch2) { | 
|  | if (restore_doubles) { | 
|  | ExitFrameRestoreFPRegs(); | 
|  | } | 
|  |  | 
|  | // Restore the context pointer from the top frame. | 
|  | Mov(scratch, | 
|  | ExternalReference::Create(IsolateAddressId::kContextAddress, isolate())); | 
|  | Ldr(cp, MemOperand(scratch)); | 
|  |  | 
|  | if (emit_debug_code()) { | 
|  | // Also emit debug code to clear the cp in the top frame. | 
|  | Mov(scratch2, Operand(Context::kInvalidContext)); | 
|  | Mov(scratch, ExternalReference::Create(IsolateAddressId::kContextAddress, | 
|  | isolate())); | 
|  | Str(scratch2, MemOperand(scratch)); | 
|  | } | 
|  | // Clear the frame pointer from the top frame. | 
|  | Mov(scratch, | 
|  | ExternalReference::Create(IsolateAddressId::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(sp, fp); | 
|  | Pop<TurboAssembler::kAuthLR>(fp, lr); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::LoadGlobalProxy(Register dst) { | 
|  | LoadNativeContextSlot(Context::GLOBAL_PROXY_INDEX, dst); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::LoadWeakValue(Register out, Register in, | 
|  | Label* target_if_cleared) { | 
|  | CompareAndBranch(in.W(), Operand(kClearedWeakHeapObjectLower32), eq, | 
|  | target_if_cleared); | 
|  |  | 
|  | and_(out, in, Operand(~kWeakHeapObjectMask)); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::IncrementCounter(StatsCounter* counter, int value, | 
|  | Register scratch1, Register scratch2) { | 
|  | DCHECK_NE(value, 0); | 
|  | if (FLAG_native_code_counters && counter->Enabled()) { | 
|  | // This operation has to be exactly 32-bit wide in case the external | 
|  | // reference table redirects the counter to a uint32_t dummy_stats_counter_ | 
|  | // field. | 
|  | Mov(scratch2, ExternalReference::Create(counter)); | 
|  | Ldr(scratch1.W(), MemOperand(scratch2)); | 
|  | Add(scratch1.W(), scratch1.W(), value); | 
|  | Str(scratch1.W(), MemOperand(scratch2)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MacroAssembler::DecrementCounter(StatsCounter* counter, int value, | 
|  | Register scratch1, Register scratch2) { | 
|  | IncrementCounter(counter, -value, scratch1, scratch2); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::MaybeDropFrames() { | 
|  | // Check whether we need to drop frames to restart a function on the stack. | 
|  | Mov(x1, ExternalReference::debug_restart_fp_address(isolate())); | 
|  | Ldr(x1, MemOperand(x1)); | 
|  | Tst(x1, x1); | 
|  | Jump(BUILTIN_CODE(isolate(), FrameDropperTrampoline), RelocInfo::CODE_TARGET, | 
|  | ne); | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | // Sets condition flags based on comparison, and returns type in type_reg. | 
|  | void MacroAssembler::CompareObjectType(Register object, Register map, | 
|  | Register type_reg, InstanceType type) { | 
|  | LoadMap(map, object); | 
|  | CompareInstanceType(map, type_reg, type); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::LoadMap(Register dst, Register object) { | 
|  | LoadTaggedPointerField(dst, FieldMemOperand(object, HeapObject::kMapOffset)); | 
|  | } | 
|  |  | 
|  | // Sets condition flags based on comparison, and returns type in type_reg. | 
|  | void MacroAssembler::CompareInstanceType(Register map, Register type_reg, | 
|  | InstanceType type) { | 
|  | Ldrh(type_reg, FieldMemOperand(map, Map::kInstanceTypeOffset)); | 
|  | Cmp(type_reg, type); | 
|  | } | 
|  |  | 
|  | 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::Bits2::ElementsKindBits>(result); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::CompareRoot(const Register& obj, RootIndex index) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  | DCHECK(!AreAliased(obj, temp)); | 
|  | LoadRoot(temp, index); | 
|  | CmpTagged(obj, temp); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::JumpIfRoot(const Register& obj, RootIndex index, | 
|  | Label* if_equal) { | 
|  | CompareRoot(obj, index); | 
|  | B(eq, if_equal); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::JumpIfNotRoot(const Register& obj, RootIndex index, | 
|  | Label* if_not_equal) { | 
|  | CompareRoot(obj, index); | 
|  | B(ne, if_not_equal); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::JumpIfIsInRange(const Register& value, | 
|  | unsigned lower_limit, | 
|  | unsigned higher_limit, | 
|  | Label* on_in_range) { | 
|  | if (lower_limit != 0) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireW(); | 
|  | Sub(scratch, value, Operand(lower_limit)); | 
|  | CompareAndBranch(scratch, Operand(higher_limit - lower_limit), ls, | 
|  | on_in_range); | 
|  | } else { | 
|  | CompareAndBranch(value, Operand(higher_limit - lower_limit), ls, | 
|  | on_in_range); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadTaggedPointerField(const Register& destination, | 
|  | const MemOperand& field_operand) { | 
|  | if (COMPRESS_POINTERS_BOOL) { | 
|  | DecompressTaggedPointer(destination, field_operand); | 
|  | } else { | 
|  | Ldr(destination, field_operand); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::LoadAnyTaggedField(const Register& destination, | 
|  | const MemOperand& field_operand) { | 
|  | if (COMPRESS_POINTERS_BOOL) { | 
|  | DecompressAnyTagged(destination, field_operand); | 
|  | } else { | 
|  | Ldr(destination, field_operand); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::SmiUntagField(Register dst, const MemOperand& src) { | 
|  | SmiUntag(dst, src); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::StoreTaggedField(const Register& value, | 
|  | const MemOperand& dst_field_operand) { | 
|  | if (COMPRESS_POINTERS_BOOL) { | 
|  | Str(value.W(), dst_field_operand); | 
|  | } else { | 
|  | Str(value, dst_field_operand); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::DecompressTaggedSigned(const Register& destination, | 
|  | const MemOperand& field_operand) { | 
|  | RecordComment("[ DecompressTaggedSigned"); | 
|  | Ldr(destination.W(), field_operand); | 
|  | if (FLAG_debug_code) { | 
|  | // Corrupt the top 32 bits. Made up of 16 fixed bits and 16 pc offset bits. | 
|  | Add(destination, destination, | 
|  | ((kDebugZapValue << 16) | (pc_offset() & 0xffff)) << 32); | 
|  | } | 
|  | RecordComment("]"); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::DecompressTaggedPointer(const Register& destination, | 
|  | const MemOperand& field_operand) { | 
|  | RecordComment("[ DecompressTaggedPointer"); | 
|  | Ldr(destination.W(), field_operand); | 
|  | Add(destination, kRootRegister, destination); | 
|  | RecordComment("]"); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::DecompressTaggedPointer(const Register& destination, | 
|  | const Register& source) { | 
|  | RecordComment("[ DecompressTaggedPointer"); | 
|  | Add(destination, kRootRegister, Operand(source, UXTW)); | 
|  | RecordComment("]"); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::DecompressAnyTagged(const Register& destination, | 
|  | const MemOperand& field_operand) { | 
|  | RecordComment("[ DecompressAnyTagged"); | 
|  | Ldr(destination.W(), field_operand); | 
|  | Add(destination, kRootRegister, destination); | 
|  | RecordComment("]"); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CheckPageFlag(const Register& object, int mask, | 
|  | Condition cc, Label* condition_met) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  | And(scratch, object, ~kPageAlignmentMask); | 
|  | Ldr(scratch, MemOperand(scratch, BasicMemoryChunk::kFlagsOffset)); | 
|  | if (cc == eq) { | 
|  | TestAndBranchIfAnySet(scratch, mask, condition_met); | 
|  | } else { | 
|  | DCHECK_EQ(cc, ne); | 
|  | TestAndBranchIfAllClear(scratch, mask, condition_met); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MacroAssembler::RecordWriteField(Register object, int offset, | 
|  | Register value, | 
|  | LinkRegisterStatus lr_status, | 
|  | SaveFPRegsMode save_fp, | 
|  | RememberedSetAction remembered_set_action, | 
|  | SmiCheck smi_check) { | 
|  | // 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 kTaggedSize. | 
|  | DCHECK(IsAligned(offset, kTaggedSize)); | 
|  |  | 
|  | if (emit_debug_code()) { | 
|  | Label ok; | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register scratch = temps.AcquireX(); | 
|  | Add(scratch, object, offset - kHeapObjectTag); | 
|  | Tst(scratch, kTaggedSize - 1); | 
|  | B(eq, &ok); | 
|  | Abort(AbortReason::kUnalignedCellInWriteBarrier); | 
|  | Bind(&ok); | 
|  | } | 
|  |  | 
|  | RecordWrite(object, Operand(offset - kHeapObjectTag), value, lr_status, | 
|  | save_fp, remembered_set_action, OMIT_SMI_CHECK); | 
|  |  | 
|  | Bind(&done); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::SaveRegisters(RegList registers) { | 
|  | DCHECK_GT(NumRegs(registers), 0); | 
|  | CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers); | 
|  | // If we were saving LR, we might need to sign it. | 
|  | DCHECK(!regs.IncludesAliasOf(lr)); | 
|  | regs.Align(); | 
|  | PushCPURegList(regs); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::RestoreRegisters(RegList registers) { | 
|  | DCHECK_GT(NumRegs(registers), 0); | 
|  | CPURegList regs(CPURegister::kRegister, kXRegSizeInBits, registers); | 
|  | // If we were saving LR, we might need to sign it. | 
|  | DCHECK(!regs.IncludesAliasOf(lr)); | 
|  | regs.Align(); | 
|  | PopCPURegList(regs); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallEphemeronKeyBarrier(Register object, Operand offset, | 
|  | SaveFPRegsMode fp_mode) { | 
|  | EphemeronKeyBarrierDescriptor descriptor; | 
|  | RegList registers = descriptor.allocatable_registers(); | 
|  |  | 
|  | SaveRegisters(registers); | 
|  |  | 
|  | Register object_parameter( | 
|  | descriptor.GetRegisterParameter(EphemeronKeyBarrierDescriptor::kObject)); | 
|  | Register slot_parameter(descriptor.GetRegisterParameter( | 
|  | EphemeronKeyBarrierDescriptor::kSlotAddress)); | 
|  | Register fp_mode_parameter( | 
|  | descriptor.GetRegisterParameter(EphemeronKeyBarrierDescriptor::kFPMode)); | 
|  |  | 
|  | MoveObjectAndSlot(object_parameter, slot_parameter, object, offset); | 
|  |  | 
|  | Mov(fp_mode_parameter, Smi::FromEnum(fp_mode)); | 
|  | Call(isolate()->builtins()->builtin_handle(Builtins::kEphemeronKeyBarrier), | 
|  | RelocInfo::CODE_TARGET); | 
|  | RestoreRegisters(registers); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallRecordWriteStub( | 
|  | Register object, Operand offset, RememberedSetAction remembered_set_action, | 
|  | SaveFPRegsMode fp_mode) { | 
|  | CallRecordWriteStub( | 
|  | object, offset, remembered_set_action, fp_mode, | 
|  | isolate()->builtins()->builtin_handle(Builtins::kRecordWrite), | 
|  | kNullAddress); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallRecordWriteStub( | 
|  | Register object, Operand offset, RememberedSetAction remembered_set_action, | 
|  | SaveFPRegsMode fp_mode, Address wasm_target) { | 
|  | CallRecordWriteStub(object, offset, remembered_set_action, fp_mode, | 
|  | Handle<Code>::null(), wasm_target); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::CallRecordWriteStub( | 
|  | Register object, Operand offset, RememberedSetAction remembered_set_action, | 
|  | SaveFPRegsMode fp_mode, Handle<Code> code_target, Address wasm_target) { | 
|  | DCHECK_NE(code_target.is_null(), wasm_target == kNullAddress); | 
|  | // TODO(albertnetymk): For now we ignore remembered_set_action and fp_mode, | 
|  | // i.e. always emit remember set and save FP registers in RecordWriteStub. If | 
|  | // large performance regression is observed, we should use these values to | 
|  | // avoid unnecessary work. | 
|  |  | 
|  | RecordWriteDescriptor descriptor; | 
|  | RegList registers = descriptor.allocatable_registers(); | 
|  |  | 
|  | SaveRegisters(registers); | 
|  |  | 
|  | Register object_parameter( | 
|  | descriptor.GetRegisterParameter(RecordWriteDescriptor::kObject)); | 
|  | Register slot_parameter( | 
|  | descriptor.GetRegisterParameter(RecordWriteDescriptor::kSlot)); | 
|  | Register remembered_set_parameter( | 
|  | descriptor.GetRegisterParameter(RecordWriteDescriptor::kRememberedSet)); | 
|  | Register fp_mode_parameter( | 
|  | descriptor.GetRegisterParameter(RecordWriteDescriptor::kFPMode)); | 
|  |  | 
|  | MoveObjectAndSlot(object_parameter, slot_parameter, object, offset); | 
|  |  | 
|  | Mov(remembered_set_parameter, Smi::FromEnum(remembered_set_action)); | 
|  | Mov(fp_mode_parameter, Smi::FromEnum(fp_mode)); | 
|  | if (code_target.is_null()) { | 
|  | Call(wasm_target, RelocInfo::WASM_STUB_CALL); | 
|  | } else { | 
|  | Call(code_target, RelocInfo::CODE_TARGET); | 
|  | } | 
|  |  | 
|  | RestoreRegisters(registers); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::MoveObjectAndSlot(Register dst_object, Register dst_slot, | 
|  | Register object, Operand offset) { | 
|  | DCHECK_NE(dst_object, dst_slot); | 
|  | // If `offset` is a register, it cannot overlap with `object`. | 
|  | DCHECK_IMPLIES(!offset.IsImmediate(), offset.reg() != object); | 
|  |  | 
|  | // If the slot register does not overlap with the object register, we can | 
|  | // overwrite it. | 
|  | if (dst_slot != object) { | 
|  | Add(dst_slot, object, offset); | 
|  | Mov(dst_object, object); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(dst_slot, object); | 
|  |  | 
|  | // If the destination object register does not overlap with the offset | 
|  | // register, we can overwrite it. | 
|  | if (offset.IsImmediate() || (offset.reg() != dst_object)) { | 
|  | Mov(dst_object, dst_slot); | 
|  | Add(dst_slot, dst_slot, offset); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(dst_object, offset.reg()); | 
|  |  | 
|  | // We only have `dst_slot` and `dst_object` left as distinct registers so we | 
|  | // have to swap them. We write this as a add+sub sequence to avoid using a | 
|  | // scratch register. | 
|  | Add(dst_slot, dst_slot, dst_object); | 
|  | Sub(dst_object, dst_slot, dst_object); | 
|  | } | 
|  |  | 
|  | // If lr_status is kLRHasBeenSaved, lr will be clobbered. | 
|  | // | 
|  | // The register 'object' contains a heap object pointer. The heap object tag is | 
|  | // shifted away. | 
|  | void MacroAssembler::RecordWrite(Register object, Operand offset, | 
|  | Register value, LinkRegisterStatus lr_status, | 
|  | SaveFPRegsMode fp_mode, | 
|  | RememberedSetAction remembered_set_action, | 
|  | SmiCheck smi_check) { | 
|  | ASM_LOCATION_IN_ASSEMBLER("MacroAssembler::RecordWrite"); | 
|  | DCHECK(!AreAliased(object, value)); | 
|  |  | 
|  | if (emit_debug_code()) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | Register temp = temps.AcquireX(); | 
|  |  | 
|  | Add(temp, object, offset); | 
|  | LoadTaggedPointerField(temp, MemOperand(temp)); | 
|  | Cmp(temp, value); | 
|  | Check(eq, AbortReason::kWrongAddressOrValuePassedToRecordWrite); | 
|  | } | 
|  |  | 
|  | if ((remembered_set_action == OMIT_REMEMBERED_SET && | 
|  | !FLAG_incremental_marking) || | 
|  | FLAG_disable_write_barriers) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // 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) { | 
|  | DCHECK_EQ(0, kSmiTag); | 
|  | JumpIfSmi(value, &done); | 
|  | } | 
|  | CheckPageFlag(value, MemoryChunk::kPointersToHereAreInterestingMask, ne, | 
|  | &done); | 
|  |  | 
|  | CheckPageFlag(object, MemoryChunk::kPointersFromHereAreInterestingMask, ne, | 
|  | &done); | 
|  |  | 
|  | // Record the actual write. | 
|  | if (lr_status == kLRHasNotBeenSaved) { | 
|  | Push<TurboAssembler::kSignLR>(padreg, lr); | 
|  | } | 
|  | CallRecordWriteStub(object, offset, remembered_set_action, fp_mode); | 
|  | if (lr_status == kLRHasNotBeenSaved) { | 
|  | Pop<TurboAssembler::kAuthLR>(lr, padreg); | 
|  | } | 
|  |  | 
|  | Bind(&done); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Assert(Condition cond, AbortReason reason) { | 
|  | if (emit_debug_code()) { | 
|  | Check(cond, reason); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TurboAssembler::AssertUnreachable(AbortReason reason) { | 
|  | if (emit_debug_code()) Abort(reason); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Check(Condition cond, AbortReason reason) { | 
|  | Label ok; | 
|  | B(cond, &ok); | 
|  | Abort(reason); | 
|  | // Will not return here. | 
|  | Bind(&ok); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Trap() { Brk(0); } | 
|  | void TurboAssembler::DebugBreak() { Debug("DebugBreak", 0, BREAK); } | 
|  |  | 
|  | void TurboAssembler::Abort(AbortReason reason) { | 
|  | #ifdef DEBUG | 
|  | RecordComment("Abort message: "); | 
|  | RecordComment(GetAbortReason(reason)); | 
|  | #endif | 
|  |  | 
|  | // Avoid emitting call to builtin if requested. | 
|  | if (trap_on_abort()) { | 
|  | Brk(0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // 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 (should_abort_hard()) { | 
|  | // We don't care if we constructed a frame. Just pretend we did. | 
|  | FrameScope assume_frame(this, StackFrame::NONE); | 
|  | Mov(w0, static_cast<int>(reason)); | 
|  | Call(ExternalReference::abort_with_reason()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Avoid infinite recursion; Push contains some assertions that use Abort. | 
|  | HardAbortScope hard_aborts(this); | 
|  |  | 
|  | Mov(x1, Smi::FromInt(static_cast<int>(reason))); | 
|  |  | 
|  | 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); | 
|  | Call(BUILTIN_CODE(isolate(), Abort), RelocInfo::CODE_TARGET); | 
|  | } else { | 
|  | Call(BUILTIN_CODE(isolate(), Abort), RelocInfo::CODE_TARGET); | 
|  | } | 
|  |  | 
|  | TmpList()->set_list(old_tmp_list); | 
|  | } | 
|  |  | 
|  | void MacroAssembler::LoadNativeContextSlot(int index, Register dst) { | 
|  | LoadMap(dst, cp); | 
|  | LoadTaggedPointerField( | 
|  | dst, FieldMemOperand( | 
|  | dst, Map::kConstructorOrBackPointerOrNativeContextOffset)); | 
|  | LoadTaggedPointerField(dst, MemOperand(dst, Context::SlotOffset(index))); | 
|  | } | 
|  |  | 
|  | // This is the main Printf implementation. All other Printf variants call | 
|  | // PrintfNoPreserve after setting up one or more PreserveRegisterScopes. | 
|  | void TurboAssembler::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. | 
|  | DCHECK(!kCallerSaved.IncludesAliasOf(sp)); | 
|  |  | 
|  | // 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::kVRegister, 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 = kCallerSavedV; | 
|  | fp_tmp_list.Remove(kPCSVarargsFP); | 
|  | fp_tmp_list.Remove(arg0, arg1, arg2, arg3); | 
|  |  | 
|  | // Override the TurboAssembler'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; | 
|  | #ifndef V8_OS_WIN | 
|  | CPURegList pcs_varargs_fp = kPCSVarargsFP; | 
|  | #endif | 
|  |  | 
|  | // 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].IsVRegister()) { | 
|  | // In C, floats are always cast to doubles for varargs calls. | 
|  | #ifdef V8_OS_WIN | 
|  | // In case of variadic functions SIMD and Floating-point registers | 
|  | // aren't used. The general x0-x7 should be used instead. | 
|  | // https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions | 
|  | pcs[i] = pcs_varargs.PopLowestIndex().X(); | 
|  | #else | 
|  | pcs[i] = pcs_varargs_fp.PopLowestIndex().D(); | 
|  | #endif | 
|  | } else { | 
|  | DCHECK(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 = args[i].Reg(); | 
|  | Register new_arg = temps.AcquireSameSizeAs(old_arg); | 
|  | Mov(new_arg, old_arg); | 
|  | args[i] = new_arg; | 
|  | } else { | 
|  | VRegister old_arg = args[i].VReg(); | 
|  | VRegister 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++) { | 
|  | #ifdef V8_OS_WIN | 
|  | if (args[i].IsVRegister()) { | 
|  | if (pcs[i].SizeInBytes() != args[i].SizeInBytes()) { | 
|  | // If the argument is half- or single-precision | 
|  | // converts to double-precision before that is | 
|  | // moved into the one of X scratch register. | 
|  | VRegister temp0 = temps.AcquireD(); | 
|  | Fcvt(temp0.VReg(), args[i].VReg()); | 
|  | Fmov(pcs[i].Reg(), temp0); | 
|  | } else { | 
|  | Fmov(pcs[i].Reg(), args[i].VReg()); | 
|  | } | 
|  | } else { | 
|  | Mov(pcs[i].Reg(), args[i].Reg(), kDiscardForSameWReg); | 
|  | } | 
|  | #else | 
|  | DCHECK(pcs[i].type() == args[i].type()); | 
|  | if (pcs[i].IsRegister()) { | 
|  | Mov(pcs[i].Reg(), args[i].Reg(), kDiscardForSameWReg); | 
|  | } else { | 
|  | DCHECK(pcs[i].IsVRegister()); | 
|  | if (pcs[i].SizeInBytes() == args[i].SizeInBytes()) { | 
|  | Fmov(pcs[i].VReg(), args[i].VReg()); | 
|  | } else { | 
|  | Fcvt(pcs[i].VReg(), args[i].VReg()); | 
|  | } | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | // 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); | 
|  | } | 
|  |  | 
|  | CallPrintf(arg_count, pcs); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::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. | 
|  | if (options().enable_simulator_code) { | 
|  | InstructionAccurateScope scope(this, kPrintfLength / kInstrSize); | 
|  | 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 { | 
|  | DCHECK(args[i].Is64Bits()); | 
|  | arg_pattern = kPrintfArgD; | 
|  | } | 
|  | DCHECK(arg_pattern < (1 << kPrintfArgPatternBits)); | 
|  | arg_pattern_list |= (arg_pattern << (kPrintfArgPatternBits * i)); | 
|  | } | 
|  | dc32(arg_pattern_list);  // kPrintfArgPatternListOffset | 
|  | return; | 
|  | } | 
|  |  | 
|  | Call(ExternalReference::printf_function()); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::Printf(const char* format, CPURegister arg0, | 
|  | CPURegister arg1, CPURegister arg2, | 
|  | CPURegister 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); | 
|  |  | 
|  | CPURegList saved_registers = kCallerSaved; | 
|  | saved_registers.Align(); | 
|  |  | 
|  | // Preserve all caller-saved registers as well as NZCV. | 
|  | // PushCPURegList asserts that the size of each list is a multiple of 16 | 
|  | // bytes. | 
|  | PushCPURegList<kDontStoreLR>(saved_registers); | 
|  | PushCPURegList(kCallerSavedV); | 
|  |  | 
|  | // We can use caller-saved registers as scratch values (except for argN). | 
|  | CPURegList tmp_list = saved_registers; | 
|  | CPURegList fp_tmp_list = kCallerSavedV; | 
|  | 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 = arg0.is_valid() && sp.Aliases(arg0); | 
|  | bool arg1_sp = arg1.is_valid() && sp.Aliases(arg1); | 
|  | bool arg2_sp = arg2.is_valid() && sp.Aliases(arg2); | 
|  | bool arg3_sp = arg3.is_valid() && sp.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, sp, | 
|  | saved_registers.TotalSizeInBytes() + | 
|  | kCallerSavedV.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(kCallerSavedV); | 
|  | PopCPURegList<kDontLoadLR>(saved_registers); | 
|  |  | 
|  | TmpList()->set_list(old_tmp_list); | 
|  | FPTmpList()->set_list(old_fp_tmp_list); | 
|  | } | 
|  |  | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | VRegister UseScratchRegisterScope::AcquireSameSizeAs(const VRegister& reg) { | 
|  | int code = AcquireNextAvailable(availablefp_).code(); | 
|  | return VRegister::Create(code, reg.SizeInBits()); | 
|  | } | 
|  |  | 
|  | CPURegister UseScratchRegisterScope::AcquireNextAvailable( | 
|  | CPURegList* available) { | 
|  | CHECK(!available->IsEmpty()); | 
|  | CPURegister result = available->PopLowestIndex(); | 
|  | DCHECK(!AreAliased(result, xzr, sp)); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void TurboAssembler::ComputeCodeStartAddress(const Register& rd) { | 
|  | // We can use adr to load a pc relative location. | 
|  | adr(rd, -pc_offset()); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::ResetSpeculationPoisonRegister() { | 
|  | Mov(kSpeculationPoisonRegister, -1); | 
|  | } | 
|  |  | 
|  | void TurboAssembler::RestoreFPAndLR() { | 
|  | static_assert(StandardFrameConstants::kCallerFPOffset + kSystemPointerSize == | 
|  | StandardFrameConstants::kCallerPCOffset, | 
|  | "Offsets must be consecutive for ldp!"); | 
|  | #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY | 
|  | // Make sure we can use x16 and x17. | 
|  | UseScratchRegisterScope temps(this); | 
|  | temps.Exclude(x16, x17); | 
|  | // We can load the return address directly into x17. | 
|  | Add(x16, fp, StandardFrameConstants::kCallerSPOffset); | 
|  | Ldp(fp, x17, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); | 
|  | Autib1716(); | 
|  | Mov(lr, x17); | 
|  | #else | 
|  | Ldp(fp, lr, MemOperand(fp, StandardFrameConstants::kCallerFPOffset)); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void TurboAssembler::StoreReturnAddressInWasmExitFrame(Label* return_location) { | 
|  | UseScratchRegisterScope temps(this); | 
|  | temps.Exclude(x16, x17); | 
|  | Adr(x17, return_location); | 
|  | #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY | 
|  | Add(x16, fp, WasmExitFrameConstants::kCallingPCOffset + kSystemPointerSize); | 
|  | Pacib1716(); | 
|  | #endif | 
|  | Str(x17, MemOperand(fp, WasmExitFrameConstants::kCallingPCOffset)); | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace v8 | 
|  |  | 
|  | #endif  // V8_TARGET_ARCH_ARM64 |