| // 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 <optional> |
| |
| #include "src/base/bits.h" |
| #include "src/base/division-by-constant.h" |
| #include "src/builtins/builtins-inl.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/interface-descriptors-inl.h" |
| #include "src/codegen/macro-assembler-inl.h" |
| #include "src/codegen/register-configuration.h" |
| #include "src/codegen/reloc-info.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/mutable-page.h" |
| #include "src/init/bootstrapper.h" |
| #include "src/logging/counters.h" |
| #include "src/runtime/runtime.h" |
| #include "src/snapshot/snapshot.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 |
| |
| #define __ ACCESS_MASM(masm) |
| |
| namespace v8 { |
| namespace internal { |
| |
| CPURegList MacroAssembler::DefaultTmpList() { return CPURegList(ip0, ip1); } |
| |
| CPURegList MacroAssembler::DefaultFPTmpList() { |
| return CPURegList(fp_scratch1, fp_scratch2); |
| } |
| |
| namespace { |
| |
| // For WebAssembly we care about the full floating point register. If we are not |
| // running Wasm, we can get away with saving half of those registers. |
| #if V8_ENABLE_WEBASSEMBLY |
| constexpr bool kSaveFullFPRegistersOnStack = true; |
| #else |
| constexpr bool kSaveFullFPRegistersOnStack = false; |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| } // namespace |
| |
| void MacroAssembler::PushCPURegList(CPURegList registers) { |
| // If LR was stored here, we would need to sign it if |
| // V8_ENABLE_CONTROL_FLOW_INTEGRITY is on. |
| DCHECK(!registers.IncludesAliasOf(lr)); |
| |
| int size = registers.RegisterSizeInBytes(); |
| DCHECK_EQ(0, (size * registers.Count()) % 16); |
| |
| // Push up to four registers at a time. |
| while (!registers.IsEmpty()) { |
| int count_before = registers.Count(); |
| const CPURegister& src0 = registers.PopHighestIndex(); |
| const CPURegister& src1 = registers.PopHighestIndex(); |
| const CPURegister& src2 = registers.PopHighestIndex(); |
| const CPURegister& src3 = registers.PopHighestIndex(); |
| int count = count_before - registers.Count(); |
| PushHelper(count, size, src0, src1, src2, src3); |
| } |
| } |
| |
| void MacroAssembler::PopCPURegList(CPURegList registers) { |
| int size = registers.RegisterSizeInBytes(); |
| DCHECK_EQ(0, (size * registers.Count()) % 16); |
| |
| // If LR was loaded here, we would need to authenticate it if |
| // V8_ENABLE_CONTROL_FLOW_INTEGRITY is on. |
| DCHECK(!registers.IncludesAliasOf(lr)); |
| |
| // Pop up to four registers at a time. |
| while (!registers.IsEmpty()) { |
| int count_before = registers.Count(); |
| const CPURegister& dst0 = registers.PopLowestIndex(); |
| const CPURegister& dst1 = registers.PopLowestIndex(); |
| const CPURegister& dst2 = registers.PopLowestIndex(); |
| const CPURegister& dst3 = registers.PopLowestIndex(); |
| int count = count_before - registers.Count(); |
| PopHelper(count, size, dst0, dst1, dst2, dst3); |
| } |
| } |
| |
| void MacroAssembler::PushAll(RegList reglist) { |
| if (reglist.Count() % 2 != 0) { |
| DCHECK(!reglist.has(xzr)); |
| reglist.set(xzr); |
| } |
| |
| CPURegList registers(kXRegSizeInBits, reglist); |
| int size = registers.RegisterSizeInBytes(); |
| DCHECK_EQ(0, (size * registers.Count()) % 16); |
| |
| // If LR was stored here, we would need to sign it if |
| // V8_ENABLE_CONTROL_FLOW_INTEGRITY is on. |
| DCHECK(!registers.IncludesAliasOf(lr)); |
| |
| while (!registers.IsEmpty()) { |
| const CPURegister& src0 = registers.PopLowestIndex(); |
| const CPURegister& src1 = registers.PopLowestIndex(); |
| stp(src1, src0, MemOperand(sp, -2 * size, PreIndex)); |
| } |
| } |
| |
| void MacroAssembler::PopAll(RegList reglist) { |
| if (reglist.Count() % 2 != 0) { |
| DCHECK(!reglist.has(xzr)); |
| reglist.set(xzr); |
| } |
| |
| CPURegList registers(kXRegSizeInBits, reglist); |
| int size = registers.RegisterSizeInBytes(); |
| DCHECK_EQ(0, (size * registers.Count()) % 16); |
| |
| // If LR was loaded here, we would need to authenticate it if |
| // V8_ENABLE_CONTROL_FLOW_INTEGRITY is on. |
| DCHECK(!registers.IncludesAliasOf(lr)); |
| |
| while (!registers.IsEmpty()) { |
| const CPURegister& dst0 = registers.PopHighestIndex(); |
| const CPURegister& dst1 = registers.PopHighestIndex(); |
| ldp(dst0, dst1, MemOperand(sp, 2 * size, PostIndex)); |
| } |
| } |
| |
| int MacroAssembler::RequiredStackSizeForCallerSaved(SaveFPRegsMode fp_mode, |
| Register exclusion) const { |
| auto list = kCallerSaved; |
| list.Remove(exclusion); |
| list.Align(); |
| |
| int bytes = list.TotalSizeInBytes(); |
| |
| if (fp_mode == SaveFPRegsMode::kSave) { |
| // TODO(all): consider splitting SaveFPRegsMode::kSave into kSaveFPOnly |
| // and kSaveFPAndSIMD. The former is useful for those functions that don't |
| // use SIMD registers. |
| auto fp_list = kSaveFullFPRegistersOnStack ? CPURegList::GetCallerSavedV() |
| : CPURegList::GetCallerSavedD(); |
| DCHECK_EQ(fp_list.Count() % 2, 0); |
| bytes += fp_list.TotalSizeInBytes(); |
| } |
| return bytes; |
| } |
| |
| int MacroAssembler::PushCallerSaved(SaveFPRegsMode fp_mode, |
| Register exclusion) { |
| ASM_CODE_COMMENT(this); |
| auto list = kCallerSaved; |
| list.Remove(exclusion); |
| list.Align(); |
| |
| PushCPURegList(list); |
| |
| int bytes = list.TotalSizeInBytes(); |
| |
| if (fp_mode == SaveFPRegsMode::kSave) { |
| // TODO(all): consider splitting SaveFPRegsMode::kSave into kSaveFPOnly |
| // and kSaveFPAndSIMD. The former is useful for those functions that don't |
| // use SIMD registers. |
| auto fp_list = kSaveFullFPRegistersOnStack ? CPURegList::GetCallerSavedV() |
| : CPURegList::GetCallerSavedD(); |
| DCHECK_EQ(fp_list.Count() % 2, 0); |
| PushCPURegList(fp_list); |
| bytes += fp_list.TotalSizeInBytes(); |
| } |
| return bytes; |
| } |
| |
| int MacroAssembler::PopCallerSaved(SaveFPRegsMode fp_mode, Register exclusion) { |
| ASM_CODE_COMMENT(this); |
| int bytes = 0; |
| if (fp_mode == SaveFPRegsMode::kSave) { |
| // TODO(all): consider splitting SaveFPRegsMode::kSave into kSaveFPOnly |
| // and kSaveFPAndSIMD. The former is useful for those functions that don't |
| // use SIMD registers. |
| auto fp_list = kSaveFullFPRegistersOnStack ? CPURegList::GetCallerSavedV() |
| : CPURegList::GetCallerSavedD(); |
| DCHECK_EQ(fp_list.Count() % 2, 0); |
| PopCPURegList(fp_list); |
| bytes += fp_list.TotalSizeInBytes(); |
| } |
| |
| auto list = kCallerSaved; |
| list.Remove(exclusion); |
| list.Align(); |
| |
| PopCPURegList(list); |
| bytes += list.TotalSizeInBytes(); |
| |
| return bytes; |
| } |
| |
| void MacroAssembler::LogicalMacro(const Register& rd, const Register& rn, |
| const Operand& operand, LogicalOp op) { |
| ASM_CODE_COMMENT(this); |
| 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()) { |
| 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 MacroAssembler::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 (CountSetHalfWords(imm, reg_size) > CountSetHalfWords(~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 MacroAssembler::Mov(const Register& rd, ExternalReference reference) { |
| if (root_array_available_) { |
| if (reference.IsIsolateFieldId()) { |
| Add(rd, kRootRegister, Operand(reference.offset_from_root_register())); |
| return; |
| } |
| } |
| // External references should not get created with IDs if |
| // `!root_array_available()`. |
| CHECK(!reference.IsIsolateFieldId()); |
| Mov(rd, Operand(reference)); |
| } |
| |
| void MacroAssembler::LoadIsolateField(const Register& rd, IsolateFieldId id) { |
| Mov(rd, ExternalReference::Create(id)); |
| } |
| |
| void MacroAssembler::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 = base::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 MacroAssembler::Mov(const Register& rd, Tagged<Smi> smi) { |
| return Mov(rd, Operand(smi)); |
| } |
| |
| void MacroAssembler::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 MacroAssembler::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 MacroAssembler::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()) { |
| fmov(vd.D(), temp); |
| } else { |
| dup(vd.V2D(), temp); |
| } |
| } |
| } |
| |
| void MacroAssembler::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 MacroAssembler::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()); |
| if (hi == lo) { |
| Movi(vd.V2D(), lo); |
| return; |
| } |
| |
| Movi(vd.V1D(), lo); |
| |
| if (hi != 0) { |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| Mov(temp, hi); |
| Ins(vd.V2D(), 1, temp); |
| } |
| } |
| |
| void MacroAssembler::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 MacroAssembler::CountSetHalfWords(uint64_t imm, unsigned reg_size) { |
| DCHECK_EQ(reg_size % 16, 0); |
| |
| #define HALFWORD(idx) (((imm >> ((idx)*16)) & 0xFFFF) ? 1u : 0u) |
| switch (reg_size / 16) { |
| case 1: |
| return HALFWORD(0); |
| case 2: |
| return HALFWORD(0) + HALFWORD(1); |
| case 4: |
| return HALFWORD(0) + HALFWORD(1) + HALFWORD(2) + HALFWORD(3); |
| } |
| #undef HALFWORD |
| UNREACHABLE(); |
| } |
| |
| // The movz instruction can generate immediates containing an arbitrary 16-bit |
| // half-word, with remaining bits clear, eg. 0x00001234, 0x0000123400000000. |
| bool MacroAssembler::IsImmMovz(uint64_t imm, unsigned reg_size) { |
| DCHECK((reg_size == kXRegSizeInBits) || (reg_size == kWRegSizeInBits)); |
| return CountSetHalfWords(imm, reg_size) <= 1; |
| } |
| |
| // The movn instruction can generate immediates containing an arbitrary 16-bit |
| // half-word, with remaining bits set, eg. 0xFFFF1234, 0xFFFF1234FFFFFFFF. |
| bool MacroAssembler::IsImmMovn(uint64_t imm, unsigned reg_size) { |
| return IsImmMovz(~imm, reg_size); |
| } |
| |
| void MacroAssembler::ConditionalCompareMacro(const Register& rn, |
| const Operand& operand, |
| StatusFlags nzcv, Condition cond, |
| ConditionalCompareOp op) { |
| 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 MacroAssembler::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 MacroAssembler::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 MacroAssembler::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 MacroAssembler::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.AcquireSameSizeAs(rn); |
| DCHECK_IMPLIES(temp.IsW(), RelocInfo::IsCompressedEmbeddedObject( |
| operand.ImmediateRMode())); |
| 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 MacroAssembler::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 MacroAssembler::LoadStoreMacro(const CPURegister& rt, |
| const MemOperand& addr, LoadStoreOp op) { |
| // Call the most common addressing modes used by Liftoff directly for improved |
| // compilation performance: X register + immediate, X register + W register. |
| Instr memop = op | Rt(rt) | RnSP(addr.base()); |
| if (addr.IsImmediateOffset()) { |
| int64_t offset = addr.offset(); |
| unsigned size_log2 = CalcLSDataSizeLog2(op); |
| if (IsImmLSScaled(offset, size_log2)) { |
| LoadStoreScaledImmOffset(memop, static_cast<int>(offset), size_log2); |
| return; |
| } else if (IsImmLSUnscaled(offset)) { |
| LoadStoreUnscaledImmOffset(memop, static_cast<int>(offset)); |
| return; |
| } |
| } else if (addr.IsRegisterOffset() && (addr.extend() == UXTW) && |
| (addr.shift_amount() == 0)) { |
| LoadStoreWRegOffset(memop, addr.regoffset()); |
| return; |
| } |
| |
| // Remaining complex cases handled in sub-function. |
| LoadStoreMacroComplex(rt, addr, op); |
| } |
| |
| void MacroAssembler::LoadStoreMacroComplex(const CPURegister& rt, |
| const MemOperand& addr, |
| LoadStoreOp op) { |
| int64_t offset = addr.offset(); |
| bool is_imm_unscaled = IsImmLSUnscaled(offset); |
| if (addr.IsRegisterOffset() || |
| (is_imm_unscaled && (addr.IsPostIndex() || addr.IsPreIndex()))) { |
| // Load/store encodable in one instruction. |
| LoadStore(rt, addr, op); |
| } else if (addr.IsImmediateOffset()) { |
| // Load/stores with immediate offset addressing should have been handled by |
| // the caller. |
| DCHECK(!IsImmLSScaled(offset, CalcLSDataSizeLog2(op)) && !is_imm_unscaled); |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireSameSizeAs(addr.base()); |
| Mov(temp, offset); |
| LoadStore(rt, MemOperand(addr.base(), temp), op); |
| } else if (addr.IsPostIndex()) { |
| // Post-index beyond unscaled addressing range. |
| DCHECK(!is_imm_unscaled); |
| LoadStore(rt, MemOperand(addr.base()), op); |
| add(addr.base(), addr.base(), offset); |
| } else { |
| // Pre-index beyond unscaled addressing range. |
| DCHECK(!is_imm_unscaled && addr.IsPreIndex()); |
| add(addr.base(), addr.base(), offset); |
| LoadStore(rt, MemOperand(addr.base()), op); |
| } |
| } |
| |
| void MacroAssembler::LoadStorePairMacro(const CPURegister& rt, |
| const CPURegister& rt2, |
| const MemOperand& addr, |
| LoadStorePairOp op) { |
| if (addr.IsRegisterOffset()) { |
| UseScratchRegisterScope temps(this); |
| Register base = addr.base(); |
| Register temp = temps.AcquireSameSizeAs(base); |
| Add(temp, base, addr.regoffset()); |
| LoadStorePair(rt, rt2, MemOperand(temp), op); |
| return; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| void MacroAssembler::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 MacroAssembler::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 MacroAssembler::B(Label* label, Condition cond) { |
| DCHECK(allow_macro_instructions()); |
| DCHECK((cond != al) && (cond != nv)); |
| |
| bool need_extra_instructions = |
| NeedExtraInstructionsOrRegisterBranch<CondBranchType>(label); |
| |
| if (V8_UNLIKELY(need_extra_instructions)) { |
| Label done; |
| b(&done, NegateCondition(cond)); |
| B(label); |
| bind(&done); |
| } else { |
| b(label, cond); |
| } |
| } |
| |
| void MacroAssembler::Bc(Condition cond, Label* label) { |
| DCHECK(allow_macro_instructions()); |
| DCHECK((cond != al) && (cond != nv)); |
| |
| bool need_extra_instructions = |
| NeedExtraInstructionsOrRegisterBranch<CondBranchType>(label); |
| |
| if (V8_UNLIKELY(need_extra_instructions)) { |
| Label done; |
| bc(&done, NegateCondition(cond)); |
| B(label); |
| bind(&done); |
| } else { |
| bc(label, cond); |
| } |
| } |
| |
| void MacroAssembler::Tbnz(const Register& rt, unsigned bit_pos, Label* label) { |
| DCHECK(allow_macro_instructions()); |
| |
| bool need_extra_instructions = |
| NeedExtraInstructionsOrRegisterBranch<TestBranchType>(label); |
| |
| if (V8_UNLIKELY(need_extra_instructions)) { |
| Label done; |
| tbz(rt, bit_pos, &done); |
| B(label); |
| bind(&done); |
| } else { |
| tbnz(rt, bit_pos, label); |
| } |
| } |
| |
| void MacroAssembler::Tbz(const Register& rt, unsigned bit_pos, Label* label) { |
| DCHECK(allow_macro_instructions()); |
| |
| bool need_extra_instructions = |
| NeedExtraInstructionsOrRegisterBranch<TestBranchType>(label); |
| |
| if (V8_UNLIKELY(need_extra_instructions)) { |
| Label done; |
| tbnz(rt, bit_pos, &done); |
| B(label); |
| bind(&done); |
| } else { |
| tbz(rt, bit_pos, label); |
| } |
| } |
| |
| void MacroAssembler::Cbnz(const Register& rt, Label* label) { |
| DCHECK(allow_macro_instructions()); |
| |
| bool need_extra_instructions = |
| NeedExtraInstructionsOrRegisterBranch<CompareBranchType>(label); |
| |
| if (V8_UNLIKELY(need_extra_instructions)) { |
| Label done; |
| cbz(rt, &done); |
| B(label); |
| bind(&done); |
| } else { |
| cbnz(rt, label); |
| } |
| } |
| |
| void MacroAssembler::Cbz(const Register& rt, Label* label) { |
| DCHECK(allow_macro_instructions()); |
| |
| bool need_extra_instructions = |
| NeedExtraInstructionsOrRegisterBranch<CompareBranchType>(label); |
| |
| if (V8_UNLIKELY(need_extra_instructions)) { |
| Label done; |
| cbnz(rt, &done); |
| B(label); |
| bind(&done); |
| } else { |
| cbz(rt, label); |
| } |
| } |
| |
| // Pseudo-instructions. |
| |
| void MacroAssembler::AbsWithOverflow(const Register& rd, const Register& rm, |
| Label* is_not_representable, |
| Label* is_representable) { |
| DCHECK(allow_macro_instructions()); |
| DCHECK(AreSameSizeAndType(rd, rm)); |
| |
| Cmp(rm, 1); |
| |
| if (CpuFeatures::IsSupported(CSSC)) { |
| CpuFeatureScope scope(this, CSSC); |
| |
| Abs(rd, rm); |
| } else { |
| 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); |
| } |
| } |
| |
| void MacroAssembler::Switch(Register scratch, Register value, |
| int case_value_base, Label** labels, |
| int num_labels) { |
| Register table = scratch; |
| Label fallthrough, jump_table; |
| if (case_value_base != 0) { |
| Sub(value, value, case_value_base); |
| } |
| Cmp(value, Immediate(num_labels)); |
| B(&fallthrough, hs); |
| Adr(table, &jump_table); |
| Ldr(table, MemOperand(table, value, LSL, kSystemPointerSizeLog2)); |
| Br(table); |
| // Emit the jump table inline, under the assumption that it's not too big. |
| // Make sure there are no veneer pool entries in the middle of the table. |
| const int jump_table_size = num_labels * kSystemPointerSize; |
| CheckVeneerPool(false, false, jump_table_size); |
| BlockPoolsScope no_pool_inbetween(this, jump_table_size); |
| Align(kSystemPointerSize); |
| bind(&jump_table); |
| for (int i = 0; i < num_labels; ++i) { |
| dcptr(labels[i]); |
| } |
| bind(&fallthrough); |
| } |
| |
| // Abstracted stack operations. |
| |
| void MacroAssembler::Push(const CPURegister& src0, const CPURegister& src1, |
| const CPURegister& src2, const CPURegister& src3, |
| const CPURegister& src4, const CPURegister& src5, |
| const CPURegister& src6, const CPURegister& src7) { |
| 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 MacroAssembler::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 MacroAssembler::PushHelper(int count, int size, const CPURegister& src0, |
| const CPURegister& src1, |
| const CPURegister& src2, |
| const CPURegister& src3) { |
| // Ensure that we don't unintentially modify scratch or debug registers. |
| InstructionAccurateScope scope(this, count <= 2 ? 1 : 2); |
| |
| 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 MacroAssembler::PopHelper(int count, int size, const CPURegister& dst0, |
| const CPURegister& dst1, const CPURegister& dst2, |
| const CPURegister& dst3) { |
| // Ensure that we don't unintentially modify scratch or debug registers. |
| InstructionAccurateScope scope(this, count <= 2 ? 1 : 2); |
| |
| 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 MacroAssembler::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() { |
| ASM_CODE_COMMENT(this); |
| #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY |
| constexpr int kInstrCount = 11; |
| #else |
| constexpr int kInstrCount = 10; |
| #endif |
| |
| // Ensure that the macro-assembler doesn't use any scratch registers. |
| InstructionAccurateScope scope(this, kInstrCount); |
| |
| 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); |
| |
| stp(x27, x28, tos); |
| stp(x25, x26, tos); |
| stp(x23, x24, tos); |
| stp(x21, x22, tos); |
| stp(x19, x20, tos); |
| |
| static_assert( |
| EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair == |
| 18 * kSystemPointerSize); |
| |
| #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY |
| // Use the stack pointer's value immediately before pushing the LR as the |
| // context for signing it. This is what the StackFrameIterator expects. |
| pacibsp(); |
| #endif |
| |
| stp(x29, x30, tos); // fp, lr |
| |
| static_assert( |
| EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair == 0); |
| } |
| |
| void MacroAssembler::PopCalleeSavedRegisters() { |
| ASM_CODE_COMMENT(this); |
| #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY |
| constexpr int kInstrCount = 11; |
| #else |
| constexpr int kInstrCount = 10; |
| #endif |
| |
| // Ensure that the macro-assembler doesn't use any scratch registers. |
| InstructionAccurateScope scope(this, kInstrCount); |
| |
| MemOperand tos(sp, 2 * kXRegSize, PostIndex); |
| |
| ldp(x29, x30, tos); // fp, lr |
| |
| #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY |
| // The context (stack pointer value) for authenticating the LR here must |
| // match the one used for signing it (see `PushCalleeSavedRegisters`). |
| autibsp(); |
| #endif |
| |
| ldp(x19, x20, tos); |
| ldp(x21, x22, tos); |
| ldp(x23, x24, tos); |
| ldp(x25, x26, tos); |
| ldp(x27, x28, tos); |
| |
| ldp(d8, d9, tos); |
| ldp(d10, d11, tos); |
| ldp(d12, d13, tos); |
| ldp(d14, d15, tos); |
| } |
| |
| #ifdef V8_ENABLE_DEBUG_CODE |
| void MacroAssembler::AssertFeedbackCell(Register object, Register scratch) { |
| if (v8_flags.debug_code) { |
| IsObjectType(object, scratch, scratch, FEEDBACK_CELL_TYPE); |
| Assert(eq, AbortReason::kExpectedFeedbackCell); |
| } |
| } |
| void MacroAssembler::AssertFeedbackVector(Register object, Register scratch) { |
| if (v8_flags.debug_code) { |
| IsObjectType(object, scratch, scratch, FEEDBACK_VECTOR_TYPE); |
| Assert(eq, AbortReason::kExpectedFeedbackVector); |
| } |
| } |
| #endif // V8_ENABLE_DEBUG_CODE |
| |
| void MacroAssembler::GenerateTailCallToReturnedCode( |
| Runtime::FunctionId function_id) { |
| ASM_CODE_COMMENT(this); |
| // ----------- S t a t e ------------- |
| // -- x0 : actual argument count (preserved for callee) |
| // -- x1 : target function (preserved for callee) |
| // -- x3 : new target (preserved for callee) |
| // -- x4 : dispatch handle (preserved for callee) |
| // ----------------------------------- |
| { |
| FrameScope scope(this, StackFrame::INTERNAL); |
| // Push a copy of the target function, the new target, the actual |
| // argument count, and the dispatch handle. |
| SmiTag(kJavaScriptCallArgCountRegister); |
| Push(kJavaScriptCallTargetRegister, kJavaScriptCallNewTargetRegister); |
| PushDispatchHandle(kJavaScriptCallDispatchHandleRegister, |
| kJavaScriptCallArgCountRegister, x5, x6); |
| // Push another copy as a parameter to the runtime call. |
| PushArgument(kJavaScriptCallTargetRegister); |
| |
| CallRuntime(function_id, 1); |
| |
| // Restore target function, new target, actual argument count and dispatch |
| // handle. |
| PopDispatchHandle(kJavaScriptCallDispatchHandleRegister, |
| kJavaScriptCallArgCountRegister, x5, x6); |
| Pop(kJavaScriptCallNewTargetRegister, kJavaScriptCallTargetRegister); |
| SmiUntag(kJavaScriptCallArgCountRegister); |
| } |
| |
| static_assert(kJavaScriptCallCodeStartRegister == x2, "ABI mismatch"); |
| #ifndef V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE |
| Move(kJavaScriptCallDispatchHandleRegister.W(), |
| FieldMemOperand(kJavaScriptCallTargetRegister, |
| offsetof(JSFunction, dispatch_handle_))); |
| #endif |
| // We jump through x17 here because for Branch Identification (BTI) we use |
| // "Call" (`bti c`) rather than "Jump" (`bti j`) landing pads for tail-called |
| // code. See TailCallBuiltin for more information. |
| LoadEntrypointFromJSDispatchTable(x2, kJavaScriptCallDispatchHandleRegister, |
| x5); |
| Move(x17, x2); |
| Jump(x17); |
| } |
| |
| Condition MacroAssembler::CheckSmi(Register object) { |
| static_assert(kSmiTag == 0); |
| Tst(object, kSmiTagMask); |
| return eq; |
| } |
| |
| #ifdef V8_ENABLE_DEBUG_CODE |
| void MacroAssembler::AssertSpAligned() { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| 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 MacroAssembler::AssertFPCRState(Register fpcr) { |
| // TODO(olivf, 382005099) This check is currently behind `slow_debug_code` as |
| // a temporary hack to not have it enabled on dcheck enabled canaries. The |
| // reason is that this check is violated by callbacks from webaudio. |
| if (!v8_flags.slow_debug_code) return; |
| ASM_CODE_COMMENT(this); |
| 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. |
| // TODO(leszeks): Reenable check based on isolate flag. |
| // 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 MacroAssembler::AssertSmi(Register object, AbortReason reason) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| static_assert(kSmiTag == 0); |
| Tst(object, kSmiTagMask); |
| Check(eq, reason); |
| } |
| |
| void MacroAssembler::AssertNotSmi(Register object, AbortReason reason) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| static_assert(kSmiTag == 0); |
| Tst(object, kSmiTagMask); |
| Check(ne, reason); |
| } |
| |
| void MacroAssembler::AssertZeroExtended(Register int32_register) { |
| if (!v8_flags.slow_debug_code) return; |
| ASM_CODE_COMMENT(this); |
| Tst(int32_register.X(), kMaxUInt32); |
| Check(ls, AbortReason::k32BitValueInRegisterIsNotZeroExtended); |
| } |
| |
| void MacroAssembler::AssertMap(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| AssertNotSmi(object, AbortReason::kOperandIsNotAMap); |
| |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| |
| IsObjectType(object, temp, temp, MAP_TYPE); |
| Check(eq, AbortReason::kOperandIsNotAMap); |
| } |
| |
| void MacroAssembler::AssertCode(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| AssertNotSmi(object, AbortReason::kOperandIsNotACode); |
| |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| |
| IsObjectType(object, temp, temp, CODE_TYPE); |
| Check(eq, AbortReason::kOperandIsNotACode); |
| } |
| |
| void MacroAssembler::AssertConstructor(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAConstructor); |
| |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| |
| LoadMap(temp, object); |
| Ldrb(temp, FieldMemOperand(temp, offsetof(Map, bit_field_))); |
| Tst(temp, Operand(Map::Bits1::IsConstructorBit::kMask)); |
| |
| Check(ne, AbortReason::kOperandIsNotAConstructor); |
| } |
| |
| void MacroAssembler::AssertFunction(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAFunction); |
| |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| LoadMap(temp, object); |
| CompareInstanceTypeRange(temp, temp, FIRST_JS_FUNCTION_TYPE, |
| LAST_JS_FUNCTION_TYPE); |
| Check(ls, AbortReason::kOperandIsNotAFunction); |
| } |
| |
| void MacroAssembler::AssertCallableFunction(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAFunction); |
| |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| LoadMap(temp, object); |
| CompareInstanceTypeRange(temp, temp, FIRST_CALLABLE_JS_FUNCTION_TYPE, |
| LAST_CALLABLE_JS_FUNCTION_TYPE); |
| Check(ls, AbortReason::kOperandIsNotACallableFunction); |
| } |
| |
| void MacroAssembler::AssertBoundFunction(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotABoundFunction); |
| |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| |
| IsObjectType(object, temp, temp, JS_BOUND_FUNCTION_TYPE); |
| Check(eq, AbortReason::kOperandIsNotABoundFunction); |
| } |
| |
| void MacroAssembler::AssertSmiOrHeapObjectInMainCompressionCage( |
| Register object) { |
| if (!PointerCompressionIsEnabled()) return; |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| // We may not have any scratch registers so we preserve our input register. |
| Push(object, xzr); |
| Label ok; |
| B(&ok, CheckSmi(object)); |
| Mov(object, Operand(object, LSR, 32)); |
| // Either the value is now equal to the right-shifted pointer compression |
| // cage base or it's zero if we got a compressed pointer register as input. |
| Cmp(object, 0); |
| B(kEqual, &ok); |
| Cmp(object, Operand(kPtrComprCageBaseRegister, LSR, 32)); |
| Check(kEqual, AbortReason::kObjectNotTagged); |
| bind(&ok); |
| Pop(xzr, object); |
| } |
| |
| void MacroAssembler::AssertGeneratorObject(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| AssertNotSmi(object, AbortReason::kOperandIsASmiAndNotAGeneratorObject); |
| |
| // Load map |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| LoadMap(temp, object); |
| |
| // Load instance type and check if JSGeneratorObject |
| CompareInstanceTypeRange(temp, temp, FIRST_JS_GENERATOR_OBJECT_TYPE, |
| LAST_JS_GENERATOR_OBJECT_TYPE); |
| // Restore generator object to register and perform assertion |
| Check(ls, AbortReason::kOperandIsNotAGeneratorObject); |
| } |
| |
| void MacroAssembler::AssertUndefinedOrAllocationSite(Register object) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| 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 MacroAssembler::AssertPositiveOrZero(Register value) { |
| if (!v8_flags.debug_code) return; |
| ASM_CODE_COMMENT(this); |
| Label done; |
| int sign_bit = value.Is64Bits() ? kXSignBit : kWSignBit; |
| Tbz(value, sign_bit, &done); |
| Abort(AbortReason::kUnexpectedNegativeValue); |
| Bind(&done); |
| } |
| |
| void MacroAssembler::AssertJSAny(Register object, Register map_tmp, |
| Register tmp, AbortReason abort_reason) { |
| if (!v8_flags.debug_code) return; |
| |
| ASM_CODE_COMMENT(this); |
| DCHECK(!AreAliased(object, map_tmp, tmp)); |
| Label ok; |
| |
| JumpIfSmi(object, &ok); |
| |
| LoadMap(map_tmp, object); |
| CompareInstanceType(map_tmp, tmp, LAST_NAME_TYPE); |
| B(kUnsignedLessThanEqual, &ok); |
| |
| CompareInstanceType(map_tmp, tmp, FIRST_JS_RECEIVER_TYPE); |
| B(kUnsignedGreaterThanEqual, &ok); |
| |
| CompareRoot(map_tmp, RootIndex::kHeapNumberMap); |
| B(kEqual, &ok); |
| |
| CompareRoot(map_tmp, RootIndex::kBigIntMap); |
| B(kEqual, &ok); |
| |
| CompareRoot(object, RootIndex::kUndefinedValue); |
| B(kEqual, &ok); |
| |
| CompareRoot(object, RootIndex::kTrueValue); |
| B(kEqual, &ok); |
| |
| CompareRoot(object, RootIndex::kFalseValue); |
| B(kEqual, &ok); |
| |
| CompareRoot(object, RootIndex::kNullValue); |
| B(kEqual, &ok); |
| |
| Abort(abort_reason); |
| |
| bind(&ok); |
| } |
| |
| void MacroAssembler::Assert(Condition cond, AbortReason reason) { |
| if (v8_flags.debug_code) { |
| Check(cond, reason); |
| } |
| } |
| |
| void MacroAssembler::AssertUnreachable(AbortReason reason) { |
| if (v8_flags.debug_code) Abort(reason); |
| } |
| #endif // V8_ENABLE_DEBUG_CODE |
| |
| void MacroAssembler::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 MacroAssembler::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 MacroAssembler::CopyDoubleWords(Register dst, Register src, Register count, |
| CopyDoubleWordsMode mode) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(!AreAliased(dst, src, count)); |
| |
| if (v8_flags.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 MacroAssembler::SlotAddress(Register dst, int slot_offset) { |
| Add(dst, sp, slot_offset << kSystemPointerSizeLog2); |
| } |
| |
| void MacroAssembler::SlotAddress(Register dst, Register slot_offset) { |
| Add(dst, sp, Operand(slot_offset, LSL, kSystemPointerSizeLog2)); |
| } |
| |
| void MacroAssembler::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 MacroAssembler::LoadTaggedRoot(Register destination, RootIndex index) { |
| ASM_CODE_COMMENT(this); |
| if (CanBeImmediate(index)) { |
| Mov(destination, |
| Immediate(ReadOnlyRootPtr(index), RelocInfo::Mode::NO_INFO)); |
| return; |
| } |
| LoadRoot(destination, index); |
| } |
| |
| void MacroAssembler::LoadRoot(Register destination, RootIndex index) { |
| ASM_CODE_COMMENT(this); |
| if (V8_STATIC_ROOTS_BOOL && RootsTable::IsReadOnly(index) && |
| IsImmAddSub(ReadOnlyRootPtr(index))) { |
| DecompressTagged(destination, ReadOnlyRootPtr(index)); |
| return; |
| } |
| // Many roots have addresses that are too large to fit into addition immediate |
| // operands. Evidence suggests that the extra instruction for decompression |
| // costs us more than the load. |
| Ldr(destination, |
| MemOperand(kRootRegister, RootRegisterOffsetForRootIndex(index))); |
| } |
| |
| void MacroAssembler::PushRoot(RootIndex index) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register tmp = temps.AcquireX(); |
| LoadRoot(tmp, index); |
| Push(tmp); |
| } |
| |
| void MacroAssembler::Move(Register dst, Tagged<Smi> src) { Mov(dst, src); } |
| void MacroAssembler::Move(Register dst, MemOperand src) { Ldr(dst, src); } |
| void MacroAssembler::Move(Register dst, Register src) { |
| if (dst == src) return; |
| Mov(dst, src); |
| } |
| |
| void MacroAssembler::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 MacroAssembler::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 MacroAssembler::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 MacroAssembler::CallRuntime(const Runtime::Function* f, |
| int num_arguments) { |
| ASM_CODE_COMMENT(this); |
| // 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)); |
| |
| bool switch_to_central = options().is_wasm; |
| CallBuiltin(Builtins::RuntimeCEntry(f->result_size, switch_to_central)); |
| } |
| |
| void MacroAssembler::JumpToExternalReference(const ExternalReference& builtin, |
| bool builtin_exit_frame) { |
| ASM_CODE_COMMENT(this); |
| Mov(x1, builtin); |
| TailCallBuiltin(Builtins::CEntry(1, ArgvMode::kStack, builtin_exit_frame)); |
| } |
| |
| void MacroAssembler::TailCallRuntime(Runtime::FunctionId fid) { |
| ASM_CODE_COMMENT(this); |
| 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 MacroAssembler::ActivationFrameAlignment() { |
| #if V8_HOST_ARCH_ARM64 |
| // Running on the real platform. Use the alignment as mandated by the local |
| // environment. |
| // Note: This will break if we ever start generating snapshots on one ARM |
| // platform for another ARM platform with a different alignment. |
| return 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 v8_flags.sim_stack_alignment; |
| #endif // V8_HOST_ARCH_ARM64 |
| } |
| |
| int MacroAssembler::CallCFunction(ExternalReference function, |
| int num_of_reg_args, |
| SetIsolateDataSlots set_isolate_data_slots, |
| Label* return_location) { |
| return CallCFunction(function, num_of_reg_args, 0, set_isolate_data_slots, |
| return_location); |
| } |
| |
| int MacroAssembler::CallCFunction(ExternalReference function, |
| int num_of_reg_args, int num_of_double_args, |
| SetIsolateDataSlots set_isolate_data_slots, |
| Label* return_location) { |
| // Note: The "CallCFunction" code comment will be generated by the other |
| // CallCFunction method called below. |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| Mov(temp, function); |
| return CallCFunction(temp, num_of_reg_args, num_of_double_args, |
| set_isolate_data_slots, return_location); |
| } |
| |
| int MacroAssembler::CallCFunction(Register function, int num_of_reg_args, |
| int num_of_double_args, |
| SetIsolateDataSlots set_isolate_data_slots, |
| Label* return_location) { |
| ASM_CODE_COMMENT(this); |
| DCHECK_LE(num_of_reg_args + num_of_double_args, kMaxCParameters); |
| DCHECK(has_frame()); |
| |
| Label get_pc; |
| UseScratchRegisterScope temps(this); |
| // We're doing a C call, which means non-parameter caller-saved registers |
| // (x8-x17) will be clobbered and so are available to use as scratches. |
| // In the worst-case scenario, we'll need 2 scratch registers. We pick 3 |
| // registers minus the `function` register, in case `function` aliases with |
| // any of the registers. |
| temps.Include(CPURegList(64, {x8, x9, x10, function})); |
| temps.Exclude(function); |
| |
| if (set_isolate_data_slots == SetIsolateDataSlots::kYes) { |
| // 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. |
| UseScratchRegisterScope temps(this); |
| Register pc_scratch = temps.AcquireX(); |
| |
| Adr(pc_scratch, &get_pc); |
| |
| CHECK(root_array_available()); |
| // Note that the field for PC is just before the FP. This ensures that in |
| // simulator builds the `Stp` below stores the PC (the lower address) first |
| // and only then the FP. This is necessary because during profiling we |
| // assume that once the FP field is set, the PC is also set already. |
| static_assert(IsolateData::GetOffset(IsolateFieldId::kFastCCallCallerFP) == |
| IsolateData::GetOffset(IsolateFieldId::kFastCCallCallerPC) + |
| 8); |
| Stp(pc_scratch, fp, |
| ExternalReferenceAsOperand(IsolateFieldId::kFastCCallCallerPC)); |
| } |
| |
| int call_pc_offset; |
| { |
| BlockPoolsScope block_const_pool_scope(this); |
| Call(function); |
| call_pc_offset = pc_offset(); |
| bind(&get_pc); |
| if (return_location) bind(return_location); |
| |
| int before_offset = pc_offset(); |
| int claim_slots = 0; |
| if (num_of_reg_args > kRegisterPassedArguments) { |
| claim_slots += RoundUp(num_of_reg_args - kRegisterPassedArguments, 2); |
| } |
| |
| if (num_of_double_args > kFPRegisterPassedArguments) { |
| claim_slots += |
| RoundUp(num_of_double_args - kFPRegisterPassedArguments, 2); |
| } |
| Drop(claim_slots); |
| |
| if (kMaxSizeOfMoveAfterFastCall > pc_offset() - before_offset) { |
| Nop(); |
| } |
| // We assume that with the nop padding, the move instruction uses |
| // kMaxSizeOfMoveAfterFastCall bytes. When we patch in the deopt trampoline, |
| // we patch it in after the move instruction, so that the stack has been |
| // restored correctly. |
| CHECK_EQ(kMaxSizeOfMoveAfterFastCall, pc_offset() - before_offset); |
| } |
| |
| if (set_isolate_data_slots == SetIsolateDataSlots::kYes) { |
| // We don't unset the PC; the FP is the source of truth. |
| Str(xzr, ExternalReferenceAsOperand(IsolateFieldId::kFastCCallCallerFP)); |
| } |
| |
| return call_pc_offset; |
| } |
| |
| void MacroAssembler::LoadFromConstantsTable(Register destination, |
| int constant_index) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(RootsTable::IsImmortalImmovable(RootIndex::kBuiltinsConstantsTable)); |
| LoadRoot(destination, RootIndex::kBuiltinsConstantsTable); |
| LoadTaggedField(destination, |
| FieldMemOperand(destination, FixedArray::OffsetOfElementAt( |
| constant_index))); |
| } |
| |
| void MacroAssembler::LoadRootRelative(Register destination, int32_t offset) { |
| Ldr(destination, MemOperand(kRootRegister, offset)); |
| } |
| |
| void MacroAssembler::StoreRootRelative(int32_t offset, Register value) { |
| Str(value, MemOperand(kRootRegister, offset)); |
| } |
| |
| void MacroAssembler::LoadRootRegisterOffset(Register destination, |
| intptr_t offset) { |
| if (offset == 0) { |
| Mov(destination, kRootRegister); |
| } else { |
| Add(destination, kRootRegister, offset); |
| } |
| } |
| |
| MemOperand MacroAssembler::ExternalReferenceAsOperand( |
| ExternalReference reference, Register scratch) { |
| if (root_array_available()) { |
| if (reference.IsIsolateFieldId()) { |
| return MemOperand(kRootRegister, reference.offset_from_root_register()); |
| } |
| if (options().enable_root_relative_access) { |
| intptr_t offset = |
| RootRegisterOffsetForExternalReference(isolate(), reference); |
| if (is_int32(offset)) { |
| return MemOperand(kRootRegister, static_cast<int32_t>(offset)); |
| } |
| } |
| if (options().isolate_independent_code) { |
| if (IsAddressableThroughRootRegister(isolate(), reference)) { |
| // Some external references can be efficiently loaded as an offset from |
| // kRootRegister. |
| intptr_t offset = |
| RootRegisterOffsetForExternalReference(isolate(), reference); |
| CHECK(is_int32(offset)); |
| return MemOperand(kRootRegister, static_cast<int32_t>(offset)); |
| } else { |
| // Otherwise, do a memory load from the external reference table. |
| Ldr(scratch, |
| MemOperand(kRootRegister, |
| RootRegisterOffsetForExternalReferenceTableEntry( |
| isolate(), reference))); |
| return MemOperand(scratch, 0); |
| } |
| } |
| } |
| Mov(scratch, reference); |
| return MemOperand(scratch, 0); |
| } |
| |
| void MacroAssembler::Jump(Register target, Condition cond) { |
| if (cond == nv) return; |
| Label done; |
| if (cond != al) B(NegateCondition(cond), &done); |
| Br(target); |
| Bind(&done); |
| } |
| |
| void MacroAssembler::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); |
| } |
| |
| // 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 MacroAssembler::CalculateTargetOffset(Address target, |
| RelocInfo::Mode rmode, |
| uint8_t* pc) { |
| int64_t offset = static_cast<int64_t>(target); |
| if (rmode == RelocInfo::WASM_CALL || rmode == RelocInfo::WASM_STUB_CALL) { |
| // The target of WebAssembly calls is still an index instead of an actual |
| // address at this point, and needs to be encoded as-is. |
| return offset; |
| } |
| offset -= reinterpret_cast<int64_t>(pc); |
| DCHECK_EQ(offset % kInstrSize, 0); |
| offset = offset / static_cast<int>(kInstrSize); |
| return offset; |
| } |
| |
| void MacroAssembler::Jump(Address target, RelocInfo::Mode rmode, |
| Condition cond) { |
| int64_t offset = CalculateTargetOffset(target, rmode, pc_); |
| JumpHelper(offset, rmode, cond); |
| } |
| |
| void MacroAssembler::Jump(Handle<Code> code, RelocInfo::Mode rmode, |
| Condition cond) { |
| DCHECK(RelocInfo::IsCodeTarget(rmode)); |
| DCHECK_IMPLIES(options().isolate_independent_code, |
| Builtins::IsIsolateIndependentBuiltin(*code)); |
| |
| Builtin builtin = Builtin::kNoBuiltinId; |
| if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) { |
| TailCallBuiltin(builtin, cond); |
| return; |
| } |
| DCHECK(RelocInfo::IsCodeTarget(rmode)); |
| 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 MacroAssembler::Jump(const ExternalReference& reference) { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Mov(scratch, reference); |
| Jump(scratch); |
| } |
| |
| void MacroAssembler::Call(Address target, RelocInfo::Mode rmode) { |
| 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 MacroAssembler::Call(Handle<Code> code, RelocInfo::Mode rmode) { |
| DCHECK_IMPLIES(options().isolate_independent_code, |
| Builtins::IsIsolateIndependentBuiltin(*code)); |
| |
| Builtin builtin = Builtin::kNoBuiltinId; |
| if (isolate()->builtins()->IsBuiltinHandle(code, &builtin)) { |
| CallBuiltin(builtin); |
| return; |
| } |
| |
| DCHECK(RelocInfo::IsCodeTarget(rmode)); |
| |
| 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 MacroAssembler::Call(ExternalReference target) { |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| Mov(temp, target); |
| Call(temp); |
| } |
| |
| void MacroAssembler::LoadEntryFromBuiltinIndex(Register builtin_index, |
| Register target) { |
| ASM_CODE_COMMENT(this); |
| // The builtin_index register contains the builtin index as a Smi. |
| if (SmiValuesAre32Bits()) { |
| Asr(target, builtin_index, kSmiShift - kSystemPointerSizeLog2); |
| Add(target, target, IsolateData::builtin_entry_table_offset()); |
| Ldr(target, MemOperand(kRootRegister, target)); |
| } else { |
| DCHECK(SmiValuesAre31Bits()); |
| if (COMPRESS_POINTERS_BOOL) { |
| Add(target, kRootRegister, |
| Operand(builtin_index.W(), SXTW, kSystemPointerSizeLog2 - kSmiShift)); |
| } else { |
| Add(target, kRootRegister, |
| Operand(builtin_index, LSL, kSystemPointerSizeLog2 - kSmiShift)); |
| } |
| Ldr(target, MemOperand(target, IsolateData::builtin_entry_table_offset())); |
| } |
| } |
| |
| void MacroAssembler::LoadEntryFromBuiltin(Builtin builtin, |
| Register destination) { |
| Ldr(destination, EntryFromBuiltinAsOperand(builtin)); |
| } |
| |
| MemOperand MacroAssembler::EntryFromBuiltinAsOperand(Builtin builtin) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(root_array_available()); |
| return MemOperand(kRootRegister, |
| IsolateData::BuiltinEntrySlotOffset(builtin)); |
| } |
| |
| void MacroAssembler::CallBuiltinByIndex(Register builtin_index, |
| Register target) { |
| ASM_CODE_COMMENT(this); |
| LoadEntryFromBuiltinIndex(builtin_index, target); |
| Call(target); |
| } |
| |
| void MacroAssembler::CallBuiltin(Builtin builtin) { |
| ASM_CODE_COMMENT_STRING(this, CommentForOffHeapTrampoline("call", builtin)); |
| switch (options().builtin_call_jump_mode) { |
| case BuiltinCallJumpMode::kAbsolute: { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Ldr(scratch, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET)); |
| Call(scratch); |
| break; |
| } |
| case BuiltinCallJumpMode::kPCRelative: |
| near_call(static_cast<int>(builtin), RelocInfo::NEAR_BUILTIN_ENTRY); |
| break; |
| case BuiltinCallJumpMode::kIndirect: { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| LoadEntryFromBuiltin(builtin, scratch); |
| Call(scratch); |
| break; |
| } |
| case BuiltinCallJumpMode::kForMksnapshot: { |
| if (options().use_pc_relative_calls_and_jumps_for_mksnapshot) { |
| Handle<Code> code = isolate()->builtins()->code_handle(builtin); |
| EmbeddedObjectIndex index = AddEmbeddedObject(code); |
| DCHECK(is_int32(index)); |
| near_call(static_cast<int32_t>(index), RelocInfo::CODE_TARGET); |
| } else { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| LoadEntryFromBuiltin(builtin, scratch); |
| Call(scratch); |
| } |
| break; |
| } |
| } |
| } |
| |
| // TODO(ishell): remove cond parameter from here to simplify things. |
| void MacroAssembler::TailCallBuiltin(Builtin builtin, Condition cond) { |
| ASM_CODE_COMMENT_STRING(this, |
| CommentForOffHeapTrampoline("tail call", builtin)); |
| |
| // The control flow integrity (CFI) feature allows us to "sign" code entry |
| // points as a target for calls, jumps or both. Arm64 has special |
| // instructions for this purpose, so-called "landing pads" (see |
| // MacroAssembler::CallTarget(), MacroAssembler::JumpTarget() and |
| // MacroAssembler::JumpOrCallTarget()). Currently, we generate "Call" |
| // landing pads for CPP builtins. In order to allow tail calling to those |
| // builtins we have to use a workaround. |
| // x17 is used to allow using "Call" (i.e. `bti c`) rather than "Jump" |
| // (i.e. `bti j`) landing pads for the tail-called code. |
| Register temp = x17; |
| |
| switch (options().builtin_call_jump_mode) { |
| case BuiltinCallJumpMode::kAbsolute: { |
| Ldr(temp, Operand(BuiltinEntry(builtin), RelocInfo::OFF_HEAP_TARGET)); |
| Jump(temp, cond); |
| break; |
| } |
| case BuiltinCallJumpMode::kPCRelative: { |
| if (cond != nv) { |
| Label done; |
| if (cond != al) B(NegateCondition(cond), &done); |
| near_jump(static_cast<int>(builtin), RelocInfo::NEAR_BUILTIN_ENTRY); |
| Bind(&done); |
| } |
| break; |
| } |
| case BuiltinCallJumpMode::kIndirect: { |
| LoadEntryFromBuiltin(builtin, temp); |
| Jump(temp, cond); |
| break; |
| } |
| case BuiltinCallJumpMode::kForMksnapshot: { |
| if (options().use_pc_relative_calls_and_jumps_for_mksnapshot) { |
| Handle<Code> code = isolate()->builtins()->code_handle(builtin); |
| EmbeddedObjectIndex index = AddEmbeddedObject(code); |
| DCHECK(is_int32(index)); |
| JumpHelper(static_cast<int64_t>(index), RelocInfo::CODE_TARGET, cond); |
| } else { |
| LoadEntryFromBuiltin(builtin, temp); |
| Jump(temp, cond); |
| } |
| break; |
| } |
| } |
| } |
| |
| void MacroAssembler::LoadCodeInstructionStart(Register destination, |
| Register code_object, |
| CodeEntrypointTag tag) { |
| ASM_CODE_COMMENT(this); |
| Ldr(destination, FieldMemOperand(code_object, Code::kInstructionStartOffset)); |
| #ifdef V8_ENABLE_SANDBOX |
| if (tag != 0) { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Mov(scratch, Immediate(tag)); |
| Eor(destination, destination, scratch); |
| } |
| #endif |
| } |
| |
| void MacroAssembler::CallCodeObject(Register code_object, |
| CodeEntrypointTag tag) { |
| ASM_CODE_COMMENT(this); |
| LoadCodeInstructionStart(code_object, code_object, tag); |
| Call(code_object); |
| } |
| |
| void MacroAssembler::JumpCodeObject(Register code_object, CodeEntrypointTag tag, |
| JumpMode jump_mode) { |
| // TODO(saelo): can we avoid using this for JavaScript functions |
| // (kJSEntrypointTag) and instead use a variant that ensures that the caller |
| // and callee agree on the signature (i.e. parameter count)? |
| ASM_CODE_COMMENT(this); |
| DCHECK_EQ(JumpMode::kJump, jump_mode); |
| LoadCodeInstructionStart(code_object, code_object, tag); |
| // We jump through x17 here because for Branch Identification (BTI) we use |
| // "Call" (`bti c`) rather than "Jump" (`bti j`) landing pads for tail-called |
| // code. See TailCallBuiltin for more information. |
| if (code_object != x17) { |
| Mov(x17, code_object); |
| } |
| Jump(x17); |
| } |
| |
| void MacroAssembler::CallJSFunction(Register function_object, |
| uint16_t argument_count) { |
| Register code = kJavaScriptCallCodeStartRegister; |
| Register dispatch_handle = kJavaScriptCallDispatchHandleRegister; |
| Register parameter_count = x20; |
| Register scratch = x21; |
| |
| Ldr(dispatch_handle.W(), |
| FieldMemOperand(function_object, offsetof(JSFunction, dispatch_handle_))); |
| LoadEntrypointAndParameterCountFromJSDispatchTable(code, parameter_count, |
| dispatch_handle, scratch); |
| // Force a safe crash if the parameter count doesn't match. |
| // TODO(412398354): to avoid this runtime check, we should switch all |
| // remaining users to call the function via its dispatch handle instead. See |
| // CallJSDispatchEntry below and crbug.com/412398354 for more details. |
| Cmp(parameter_count, Immediate(argument_count)); |
| SbxCheck(le, AbortReason::kJSSignatureMismatch); |
| Call(code); |
| } |
| |
| void MacroAssembler::CallJSDispatchEntry(JSDispatchHandle dispatch_handle, |
| uint16_t argument_count) { |
| Register code = kJavaScriptCallCodeStartRegister; |
| Register scratch = x21; |
| Mov(kJavaScriptCallDispatchHandleRegister.W(), |
| Immediate(dispatch_handle.value(), RelocInfo::JS_DISPATCH_HANDLE)); |
| LoadEntrypointFromJSDispatchTable(code, kJavaScriptCallDispatchHandleRegister, |
| scratch); |
| CHECK_EQ(argument_count, |
| isolate()->js_dispatch_table().GetParameterCount(dispatch_handle)); |
| Call(code); |
| } |
| |
| void MacroAssembler::JumpJSFunction(Register function_object, |
| JumpMode jump_mode) { |
| CHECK(!V8_ENABLE_SANDBOX_BOOL); |
| // This implementation is not currently used because callers usually need |
| // to load both entry point and parameter count and then do something with |
| // the latter before the actual call. |
| UNREACHABLE(); |
| } |
| |
| #ifdef V8_ENABLE_WEBASSEMBLY |
| |
| void MacroAssembler::ResolveWasmCodePointer(Register target, |
| uint64_t signature_hash) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Mov(scratch, ExternalReference::wasm_code_pointer_table()); |
| #ifdef V8_ENABLE_SANDBOX |
| static constexpr int kNumRelevantBits = |
| base::bits::WhichPowerOfTwo(WasmCodePointer::kIndexSpaceSize); |
| static constexpr int kLeftShift = |
| base::bits::WhichPowerOfTwo(sizeof(wasm::WasmCodePointerTableEntry)); |
| |
| // Keep `kNumRelevantBits` bits, shifted by `kLeftShift`. |
| Ubfiz(target.W(), target.W(), kLeftShift, kNumRelevantBits); |
| |
| Add(target, scratch, target); |
| |
| Ldr(scratch, |
| MemOperand(target, wasm::WasmCodePointerTable::kOffsetOfSignatureHash)); |
| bool has_second_tmp = temps.CanAcquire(); |
| Register signature_hash_register = has_second_tmp ? temps.AcquireX() : target; |
| if (!has_second_tmp) { |
| Push(signature_hash_register, padreg); |
| } |
| Mov(signature_hash_register, signature_hash); |
| Cmp(scratch, signature_hash_register); |
| SbxCheck(Condition::kEqual, AbortReason::kWasmSignatureMismatch); |
| if (!has_second_tmp) { |
| Pop(padreg, signature_hash_register); |
| } |
| #else |
| static_assert(sizeof(wasm::WasmCodePointerTableEntry) == 8); |
| Add(target, scratch, Operand(target, LSL, 3)); |
| #endif |
| |
| Ldr(target, MemOperand(target)); |
| } |
| |
| void MacroAssembler::CallWasmCodePointer(Register target, |
| uint64_t signature_hash, |
| CallJumpMode call_jump_mode) { |
| ResolveWasmCodePointer(target, signature_hash); |
| if (call_jump_mode == CallJumpMode::kTailCall) { |
| Jump(target); |
| } else { |
| Call(target); |
| } |
| } |
| |
| void MacroAssembler::CallWasmCodePointerNoSignatureCheck(Register target) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Mov(scratch, ExternalReference::wasm_code_pointer_table()); |
| |
| static constexpr int kNumRelevantBits = |
| base::bits::WhichPowerOfTwo(WasmCodePointer::kIndexSpaceSize); |
| static constexpr int kLeftShift = |
| base::bits::WhichPowerOfTwo(sizeof(wasm::WasmCodePointerTableEntry)); |
| |
| // Keep `kNumRelevantBits` bits, shifted by `kLeftShift`. |
| Ubfiz(target.W(), target.W(), kLeftShift, kNumRelevantBits); |
| |
| Ldr(target, MemOperand(scratch, target)); |
| |
| Call(target); |
| } |
| |
| void MacroAssembler::LoadWasmCodePointer(Register dst, MemOperand src) { |
| static_assert(sizeof(WasmCodePointer) == 4); |
| Ldr(dst.W(), src); |
| } |
| |
| #endif |
| |
| void MacroAssembler::StoreReturnAddressAndCall(Register target) { |
| ASM_CODE_COMMENT(this); |
| // 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 InstructionStream 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); |
| DCHECK(!AreAliased(x16, x17, target)); |
| |
| Label return_location; |
| Adr(x17, &return_location); |
| #ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY |
| Add(x16, sp, kSystemPointerSize); |
| Pacib1716(); |
| #endif |
| Str(x17, MemOperand(sp)); |
| |
| if (v8_flags.debug_code) { |
| ASM_CODE_COMMENT_STRING(this, "Verify fp[kSPOffset]-8"); |
| // 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); |
| } |
| |
| Call(target); |
| Bind(&return_location); |
| } |
| |
| void MacroAssembler::IndirectCall(Address target, RelocInfo::Mode rmode) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| Mov(temp, Immediate(target, rmode)); |
| Call(temp); |
| } |
| |
| bool MacroAssembler::IsNearCallOffset(int64_t offset) { |
| return is_int26(offset); |
| } |
| |
| void MacroAssembler::AssertNotDeoptimized() { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| int offset = InstructionStream::kCodeOffset - InstructionStream::kHeaderSize; |
| LoadProtectedPointerField( |
| scratch, MemOperand(kJavaScriptCallCodeStartRegister, offset)); |
| Ldr(scratch.W(), FieldMemOperand(scratch, Code::kFlagsOffset)); |
| Label not_deoptimized; |
| Tbz(scratch.W(), Code::kMarkedForDeoptimizationBit, ¬_deoptimized); |
| Abort(AbortReason::kInvalidDeoptimizedCode); |
| Bind(¬_deoptimized); |
| } |
| |
| void MacroAssembler::CallForDeoptimization( |
| Builtin target, int deopt_id, Label* exit, DeoptimizeKind kind, Label* ret, |
| Label* jump_deoptimization_entry_label) { |
| ASM_CODE_COMMENT(this); |
| Call(jump_deoptimization_entry_label); |
| DCHECK_EQ(SizeOfCodeGeneratedSince(exit), |
| (kind == DeoptimizeKind::kLazy || |
| kind == DeoptimizeKind::kLazyAfterFastCall) |
| ? Deoptimizer::kLazyDeoptExitSize |
| : Deoptimizer::kEagerDeoptExitSize); |
| } |
| |
| void MacroAssembler::LoadStackLimit(Register destination, StackLimitKind kind) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(root_array_available()); |
| intptr_t offset = kind == StackLimitKind::kRealStackLimit |
| ? IsolateData::real_jslimit_offset() |
| : IsolateData::jslimit_offset(); |
| |
| Ldr(destination, MemOperand(kRootRegister, offset)); |
| } |
| |
| void MacroAssembler::StackOverflowCheck(Register num_args, |
| Label* stack_overflow) { |
| ASM_CODE_COMMENT(this); |
| 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, |
| InvokeType type) { |
| ASM_CODE_COMMENT(this); |
| // 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); |
| |
| // 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 (reuse) 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 = x5; |
| Register slots_to_claim = x6; |
| |
| Mov(slots_to_copy, actual_argument_count); |
| 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); |
| 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 = x7; |
| Register dst = x8; |
| SlotAddress(src, slots_to_claim); |
| SlotAddress(dst, 0); |
| CopyDoubleWords(dst, src, slots_to_copy); |
| } |
| |
| Bind(&skip_move); |
| Register pointer_next_value = x6; |
| |
| // Copy extra arguments as undefined values. |
| { |
| Label loop; |
| Register undefined_value = x7; |
| Register count = x8; |
| LoadRoot(undefined_value, RootIndex::kUndefinedValue); |
| SlotAddress(pointer_next_value, actual_argument_count); |
| 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 = x5; |
| Add(total_args_slots, actual_argument_count, 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::NO_FRAME_TYPE : StackFrame::INTERNAL); |
| CallRuntime(Runtime::kThrowStackOverflow); |
| Unreachable(); |
| } |
| |
| Bind(®ular_invoke); |
| } |
| |
| void MacroAssembler::CallDebugOnFunctionCall(Register fun, Register new_target, |
| Register dispatch_handle, |
| Register actual_parameter_count) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(!AreAliased(x5, fun, new_target, dispatch_handle, |
| actual_parameter_count)); |
| // Load receiver to pass it later to DebugOnFunctionCall hook. |
| Peek(x5, ReceiverOperand()); |
| FrameScope frame( |
| this, has_frame() ? StackFrame::NO_FRAME_TYPE : StackFrame::INTERNAL); |
| |
| if (!new_target.is_valid()) new_target = padreg; |
| |
| // Save values on stack. |
| // We must not Smi-tag the dispatch handle, because its top bits are |
| // meaningful; and we also don't need to, because its low bits are zero. |
| static_assert(kJSDispatchHandleShift >= 1); |
| SmiTag(actual_parameter_count); |
| Push(dispatch_handle, actual_parameter_count, new_target, fun); |
| Push(fun, x5); |
| CallRuntime(Runtime::kDebugOnFunctionCall); |
| |
| // Restore values from stack. |
| Pop(fun, new_target, actual_parameter_count, dispatch_handle); |
| SmiUntag(actual_parameter_count); |
| } |
| |
| void MacroAssembler::InvokeFunction( |
| Register function, Register actual_parameter_count, InvokeType type, |
| ArgumentAdaptionMode argument_adaption_mode) { |
| ASM_CODE_COMMENT(this); |
| // You can't call a function without a valid frame. |
| DCHECK(type == InvokeType::kJump || 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. |
| LoadTaggedField(cp, |
| FieldMemOperand(function, offsetof(JSFunction, context_))); |
| |
| InvokeFunctionCode(function, no_reg, actual_parameter_count, type, |
| argument_adaption_mode); |
| } |
| |
| void MacroAssembler::InvokeFunctionWithNewTarget( |
| Register function, Register new_target, Register actual_parameter_count, |
| InvokeType type) { |
| ASM_CODE_COMMENT(this); |
| // You can't call a function without a valid frame. |
| DCHECK(type == InvokeType::kJump || has_frame()); |
| |
| // Contract with called JS functions requires that function is passed in x1. |
| // (See FullCodeGenerator::Generate().) |
| DCHECK_EQ(function, x1); |
| |
| LoadTaggedField(cp, |
| FieldMemOperand(function, offsetof(JSFunction, context_))); |
| |
| InvokeFunctionCode(function, new_target, actual_parameter_count, type); |
| } |
| |
| void MacroAssembler::InvokeFunctionCode( |
| Register function, Register new_target, Register actual_parameter_count, |
| InvokeType type, ArgumentAdaptionMode argument_adaption_mode) { |
| ASM_CODE_COMMENT(this); |
| // You can't call a function without a valid frame. |
| DCHECK_IMPLIES(type == InvokeType::kCall, has_frame()); |
| DCHECK_EQ(function, x1); |
| DCHECK_IMPLIES(new_target.is_valid(), new_target == x3); |
| |
| Register dispatch_handle = kJavaScriptCallDispatchHandleRegister; |
| Ldr(dispatch_handle.W(), |
| FieldMemOperand(function, offsetof(JSFunction, dispatch_handle_))); |
| |
| // On function call, call into the debugger if necessary. |
| Label debug_hook, continue_after_hook; |
| { |
| Mov(x5, ExternalReference::debug_hook_on_function_call_address(isolate())); |
| Ldrsb(x5, MemOperand(x5)); |
| Cbnz(x5, &debug_hook); |
| } |
| bind(&continue_after_hook); |
| |
| // Clear the new.target register if not given. |
| if (!new_target.is_valid()) { |
| LoadRoot(x3, RootIndex::kUndefinedValue); |
| } |
| |
| Register scratch = x20; |
| if (argument_adaption_mode == ArgumentAdaptionMode::kAdapt) { |
| Register expected_parameter_count = x2; |
| LoadParameterCountFromJSDispatchTable(expected_parameter_count, |
| dispatch_handle, scratch); |
| InvokePrologue(expected_parameter_count, actual_parameter_count, type); |
| } |
| |
| // We call indirectly through the code field in the function to |
| // allow recompilation to take effect without changing any of the |
| // call sites. |
| LoadEntrypointFromJSDispatchTable(kJavaScriptCallCodeStartRegister, |
| dispatch_handle, scratch); |
| switch (type) { |
| case InvokeType::kCall: |
| Call(kJavaScriptCallCodeStartRegister); |
| break; |
| case InvokeType::kJump: |
| // We jump through x17 here because for Branch Identification (BTI) we use |
| // "Call" (`bti c`) rather than "Jump" (`bti j`) landing pads for |
| // tail-called code. See TailCallBuiltin for more information. |
| Mov(x17, kJavaScriptCallCodeStartRegister); |
| Jump(x17); |
| break; |
| } |
| Label done; |
| B(&done); |
| |
| // Deferred debug hook. |
| bind(&debug_hook); |
| CallDebugOnFunctionCall(function, new_target, dispatch_handle, |
| actual_parameter_count); |
| B(&continue_after_hook); |
| |
| bind(&done); |
| } |
| |
| void MacroAssembler::JumpIfCodeIsMarkedForDeoptimization( |
| Register code, Register scratch, Label* if_marked_for_deoptimization) { |
| Ldr(scratch.W(), FieldMemOperand(code, Code::kFlagsOffset)); |
| Tbnz(scratch.W(), Code::kMarkedForDeoptimizationBit, |
| if_marked_for_deoptimization); |
| } |
| |
| void MacroAssembler::JumpIfCodeIsTurbofanned(Register code, Register scratch, |
| Label* if_turbofanned) { |
| Ldr(scratch.W(), FieldMemOperand(code, Code::kFlagsOffset)); |
| Tbnz(scratch.W(), Code::kIsTurbofannedBit, if_turbofanned); |
| } |
| |
| Operand MacroAssembler::ClearedValue() const { |
| return Operand(static_cast<int32_t>(i::kClearedWeakValue.ptr())); |
| } |
| |
| Operand MacroAssembler::ReceiverOperand() { return Operand(0); } |
| |
| void MacroAssembler::TryConvertDoubleToInt64(Register result, |
| DoubleRegister double_input, |
| Label* done) { |
| ASM_CODE_COMMENT(this); |
| // 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 MacroAssembler::TruncateDoubleToI(Isolate* isolate, Zone* zone, |
| Register result, |
| DoubleRegister double_input, |
| StubCallMode stub_mode, |
| LinkRegisterStatus lr_status) { |
| ASM_CODE_COMMENT(this); |
| 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<MacroAssembler::kSignLR>(lr, double_input); |
| } else { |
| Push<MacroAssembler::kDontStoreLR>(xzr, double_input); |
| } |
| |
| // DoubleToI preserves any registers it needs to clobber. |
| #if V8_ENABLE_WEBASSEMBLY |
| if (stub_mode == StubCallMode::kCallWasmRuntimeStub) { |
| Call(static_cast<Address>(Builtin::kDoubleToI), RelocInfo::WASM_STUB_CALL); |
| #else |
| // For balance. |
| if (false) { |
| #endif // V8_ENABLE_WEBASSEMBLY |
| } else { |
| CallBuiltin(Builtin::kDoubleToI); |
| } |
| 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<MacroAssembler::kAuthLR>(xzr, lr); |
| } else { |
| Drop(2); |
| } |
| |
| Bind(&done); |
| // Keep our invariant that the upper 32 bits are zero. |
| Uxtw(result.W(), result.W()); |
| } |
| |
| void MacroAssembler::Float64Mod(VRegister out, VRegister left, |
| VRegister right) { |
| ASM_CODE_COMMENT(this); |
| DCHECK_EQ(left, d0); |
| DCHECK_EQ(right, d1); |
| DCHECK_EQ(out, d0); |
| |
| Label slow, done_mod; |
| { |
| UseScratchRegisterScope temps(this); |
| VRegister d_temp = temps.AcquireD(); |
| Register x_scratch = temps.AcquireX(); |
| |
| // Check if left is a positive integer. |
| Fcvtzu(x_scratch, left); |
| Scvtf(d_temp, x_scratch); |
| Fcmp(left, d_temp); |
| B(ne, &slow); |
| Fcmp(left, 0.0); |
| B(le, &slow); |
| |
| // Check if right is a positive integer. |
| Fcvtzu(x_scratch, right); |
| Scvtf(d_temp, x_scratch); |
| Fcmp(right, d_temp); |
| B(ne, &slow); |
| Fcmp(right, 0.0); |
| B(le, &slow); |
| |
| // Both are positive integers. The fast path is only valid if the computed |
| // remainder stays within the range [0, right). |
| Fdiv(d_temp, left, right); |
| Frintz(d_temp, d_temp); |
| Fmsub(d_temp, d_temp, right, left); |
| |
| // If quotient rounding changed the integer truncation result, the computed |
| // remainder escapes [0, right) and we must fall back to the precise slow |
| // path. |
| Fcmp(d_temp, 0.0); |
| B(lt, &slow); |
| Fcmp(d_temp, right); |
| B(ge, &slow); |
| |
| Fmov(out, d_temp); |
| B(&done_mod); |
| } |
| |
| Bind(&slow); |
| { |
| FrameScope assume_frame(this, StackFrame::NO_FRAME_TYPE); |
| CallCFunction(ExternalReference::mod_two_doubles_operation(), 0, 2); |
| } |
| |
| Bind(&done_mod); |
| } |
| |
| void MacroAssembler::Prologue() { |
| ASM_CODE_COMMENT(this); |
| Push<MacroAssembler::kSignLR>(lr, fp); |
| mov(fp, sp); |
| static_assert(kExtraSlotClaimedByPrologue == 1); |
| Push(cp, kJSFunctionRegister, kJavaScriptCallArgCountRegister, padreg); |
| } |
| |
| void MacroAssembler::EnterFrame(StackFrame::Type type) { |
| UseScratchRegisterScope temps(this); |
| |
| if (StackFrame::IsJavaScript(type)) { |
| // Just push a minimal "machine frame", saving the frame pointer and return |
| // address, without any markers. |
| Push<MacroAssembler::kSignLR>(lr, fp); |
| Mov(fp, sp); |
| // sp[1] : lr |
| // sp[0] : fp |
| } else { |
| Register type_reg = temps.AcquireX(); |
| Mov(type_reg, StackFrame::TypeToMarker(type)); |
| Register fourth_reg = padreg; |
| if (type == StackFrame::CONSTRUCT || type == StackFrame::FAST_CONSTRUCT) { |
| fourth_reg = cp; |
| } |
| #if V8_ENABLE_WEBASSEMBLY |
| if (type == StackFrame::WASM || type == StackFrame::WASM_LIFTOFF_SETUP || |
| type == StackFrame::WASM_EXIT) { |
| fourth_reg = kWasmImplicitArgRegister; |
| } |
| #endif // V8_ENABLE_WEBASSEMBLY |
| Push<MacroAssembler::kSignLR>(lr, fp, type_reg, fourth_reg); |
| static constexpr int kSPToFPDelta = 2 * kSystemPointerSize; |
| Add(fp, sp, kSPToFPDelta); |
| // sp[3] : lr |
| // sp[2] : fp |
| // sp[1] : type |
| // sp[0] : cp | wasm instance | for alignment |
| } |
| } |
| |
| void MacroAssembler::LeaveFrame(StackFrame::Type type) { |
| ASM_CODE_COMMENT(this); |
| // Drop the execution stack down to the frame pointer and restore |
| // the caller frame pointer and return address. |
| Mov(sp, fp); |
| Pop<MacroAssembler::kAuthLR>(fp, lr); |
| } |
| |
| void MacroAssembler::EnterExitFrame(const Register& scratch, int extra_space, |
| StackFrame::Type frame_type) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(frame_type == StackFrame::EXIT || |
| frame_type == StackFrame::BUILTIN_EXIT || |
| frame_type == StackFrame::API_NAMED_ACCESSOR_EXIT || |
| frame_type == StackFrame::API_CALLBACK_EXIT); |
| |
| // Set up the new stack frame. |
| Push<MacroAssembler::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. |
| Str(fp, AsMemOperand(IsolateFieldId::kCEntryFP)); |
| Str(cp, AsMemOperand(IsolateFieldId::kContext)); |
| |
| static_assert((-2 * kSystemPointerSize) == |
| ExitFrameConstants::kLastExitFrameField); |
| |
| // 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. |
| // 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(const Register& scratch, |
| const Register& scratch2) { |
| ASM_CODE_COMMENT(this); |
| |
| // Restore the context pointer from the top frame. |
| Ldr(cp, AsMemOperand(IsolateFieldId::kContext)); |
| |
| if (v8_flags.debug_code) { |
| // Also emit debug code to clear the cp in the top frame. |
| static_assert(Context::kNoContext == 0); |
| Str(xzr, AsMemOperand(IsolateFieldId::kContext)); |
| } |
| // Clear the frame pointer from the top frame. |
| Str(xzr, AsMemOperand(IsolateFieldId::kCEntryFP)); |
| |
| // Pop the exit frame. |
| // fp[8]: CallerPC (lr) |
| // fp -> fp[0]: CallerFP (old fp) |
| // fp[...]: The rest of the frame. |
| Mov(sp, fp); |
| Pop<MacroAssembler::kAuthLR>(fp, lr); |
| } |
| |
| void MacroAssembler::LoadGlobalProxy(Register dst) { |
| ASM_CODE_COMMENT(this); |
| LoadNativeContextSlot(dst, Context::GLOBAL_PROXY_INDEX); |
| } |
| |
| void MacroAssembler::LoadWeakValue(Register out, Register in, |
| Label* target_if_cleared) { |
| ASM_CODE_COMMENT(this); |
| CompareAndBranch(in.W(), Operand(kClearedWeakHeapObjectLower32), eq, |
| target_if_cleared); |
| |
| and_(out, in, Operand(~kWeakHeapObjectMask)); |
| } |
| |
| void MacroAssembler::EmitIncrementCounter(StatsCounter* counter, int value, |
| Register scratch1, |
| Register scratch2) { |
| ASM_CODE_COMMENT(this); |
| DCHECK_NE(value, 0); |
| if (v8_flags.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::JumpIfObjectType(Register object, Register map, |
| Register type_reg, InstanceType type, |
| Label* if_cond_pass, Condition cond) { |
| ASM_CODE_COMMENT(this); |
| CompareObjectType(object, map, type_reg, type); |
| B(cond, if_cond_pass); |
| } |
| |
| void MacroAssembler::JumpIfJSAnyIsNotPrimitive(Register heap_object, |
| Register scratch, Label* target, |
| Label::Distance distance, |
| Condition cc) { |
| CHECK(cc == Condition::kUnsignedLessThan || |
| cc == Condition::kUnsignedGreaterThanEqual); |
| if (V8_STATIC_ROOTS_BOOL) { |
| #ifdef DEBUG |
| Label ok; |
| LoadMap(scratch, heap_object); |
| CompareInstanceTypeRange(scratch, scratch, FIRST_JS_RECEIVER_TYPE, |
| LAST_JS_RECEIVER_TYPE); |
| B(Condition::kUnsignedLessThanEqual, &ok); |
| LoadMap(scratch, heap_object); |
| CompareInstanceTypeRange(scratch, scratch, FIRST_PRIMITIVE_HEAP_OBJECT_TYPE, |
| LAST_PRIMITIVE_HEAP_OBJECT_TYPE); |
| B(Condition::kUnsignedLessThanEqual, &ok); |
| Abort(AbortReason::kInvalidReceiver); |
| bind(&ok); |
| #endif // DEBUG |
| |
| // All primitive object's maps are allocated at the start of the read only |
| // heap. Thus JS_RECEIVER's must have maps with larger (compressed) |
| // addresses. |
| LoadCompressedMap(scratch, heap_object); |
| CmpTagged(scratch, Immediate(InstanceTypeChecker::kNonJsReceiverMapLimit)); |
| } else { |
| static_assert(LAST_JS_RECEIVER_TYPE == LAST_TYPE); |
| CompareObjectType(heap_object, scratch, scratch, FIRST_JS_RECEIVER_TYPE); |
| } |
| B(cc, target); |
| } |
| |
| #if V8_STATIC_ROOTS_BOOL |
| void MacroAssembler::CompareInstanceTypeWithUniqueCompressedMap( |
| Register map, Register scratch, InstanceType type) { |
| std::optional<RootIndex> expected = |
| InstanceTypeChecker::UniqueMapOfInstanceType(type); |
| CHECK(expected); |
| Tagged_t expected_ptr = ReadOnlyRootPtr(*expected); |
| DCHECK_NE(map, scratch); |
| UseScratchRegisterScope temps(this); |
| CHECK(IsImmAddSub(expected_ptr) || scratch != Register::no_reg() || |
| temps.CanAcquire()); |
| if (!IsImmAddSub(expected_ptr)) { |
| if (scratch == Register::no_reg()) { |
| scratch = temps.AcquireX(); |
| DCHECK_NE(map, scratch); |
| } |
| Operand imm_operand = |
| MoveImmediateForShiftedOp(scratch, expected_ptr, kAnyShift); |
| CmpTagged(map, imm_operand); |
| } else { |
| CmpTagged(map, Immediate(expected_ptr)); |
| } |
| } |
| |
| void MacroAssembler::IsObjectTypeFast(Register object, |
| Register compressed_map_scratch, |
| InstanceType type) { |
| ASM_CODE_COMMENT(this); |
| CHECK(InstanceTypeChecker::UniqueMapOfInstanceType(type)); |
| LoadCompressedMap(compressed_map_scratch, object); |
| CompareInstanceTypeWithUniqueCompressedMap(compressed_map_scratch, |
| Register::no_reg(), type); |
| } |
| #endif // V8_STATIC_ROOTS_BOOL |
| |
| // Sets equality condition flags. |
| void MacroAssembler::IsObjectType(Register object, Register scratch1, |
| Register scratch2, InstanceType type) { |
| ASM_CODE_COMMENT(this); |
| |
| #if V8_STATIC_ROOTS_BOOL |
| if (InstanceTypeChecker::UniqueMapOfInstanceType(type)) { |
| LoadCompressedMap(scratch1, object); |
| CompareInstanceTypeWithUniqueCompressedMap( |
| scratch1, scratch1 != scratch2 ? scratch2 : Register::no_reg(), type); |
| return; |
| } |
| #endif // V8_STATIC_ROOTS_BOOL |
| |
| CompareObjectType(object, scratch1, scratch2, type); |
| } |
| |
| // Sets equality condition flags. |
| void MacroAssembler::IsObjectTypeInRange(Register heap_object, Register scratch, |
| InstanceType lower_limit, |
| InstanceType higher_limit) { |
| DCHECK_LT(lower_limit, higher_limit); |
| #if V8_STATIC_ROOTS_BOOL |
| if (auto range = InstanceTypeChecker::UniqueMapRangeOfInstanceTypeRange( |
| lower_limit, higher_limit)) { |
| LoadCompressedMap(scratch.W(), heap_object); |
| CompareRange(scratch.W(), scratch.W(), range->first, range->second); |
| return; |
| } |
| #endif // V8_STATIC_ROOTS_BOOL |
| LoadMap(scratch, heap_object); |
| CompareInstanceTypeRange(scratch, scratch, lower_limit, higher_limit); |
| } |
| |
| // Sets condition flags based on comparison, and returns type in type_reg. |
| void MacroAssembler::CompareObjectType(Register object, Register map, |
| Register type_reg, InstanceType type) { |
| ASM_CODE_COMMENT(this); |
| LoadMap(map, object); |
| CompareInstanceType(map, type_reg, type); |
| } |
| |
| void MacroAssembler::CompareRange(Register value, Register scratch, |
| unsigned lower_limit, unsigned higher_limit) { |
| ASM_CODE_COMMENT(this); |
| DCHECK_LT(lower_limit, higher_limit); |
| if (lower_limit != 0) { |
| Sub(scratch.W(), value.W(), Operand(lower_limit)); |
| Cmp(scratch.W(), Operand(higher_limit - lower_limit)); |
| } else { |
| Cmp(value.W(), Immediate(higher_limit)); |
| } |
| } |
| |
| void MacroAssembler::JumpIfIsInRange(Register value, Register scratch, |
| unsigned lower_limit, |
| unsigned higher_limit, |
| Label* on_in_range) { |
| CompareRange(value, scratch, lower_limit, higher_limit); |
| B(ls, on_in_range); |
| } |
| |
| void MacroAssembler::LoadCompressedMap(Register dst, Register object) { |
| ASM_CODE_COMMENT(this); |
| Ldr(dst.W(), FieldMemOperand(object, offsetof(HeapObject, map_))); |
| } |
| |
| void MacroAssembler::LoadMap(Register dst, Register object) { |
| ASM_CODE_COMMENT(this); |
| LoadTaggedField(dst, FieldMemOperand(object, offsetof(HeapObject, map_))); |
| } |
| |
| void MacroAssembler::LoadFeedbackCell(Register dst, Register closure) { |
| LoadTaggedField( |
| dst, FieldMemOperand(closure, offsetof(JSFunction, feedback_cell_))); |
| } |
| |
| void MacroAssembler::LoadFeedbackVectorFromCell(Register dst, |
| Register feedback_cell, |
| Register scratch, |
| Label* fbv_undef) { |
| Label done; |
| LoadTaggedField( |
| dst, FieldMemOperand(feedback_cell, offsetof(FeedbackCell, value_))); |
| |
| // Check if feedback vector is valid. |
| LoadTaggedField(scratch, FieldMemOperand(dst, offsetof(HeapObject, map_))); |
| Ldrh(scratch, FieldMemOperand(scratch, offsetof(Map, instance_type_))); |
| Cmp(scratch, FEEDBACK_VECTOR_TYPE); |
| B(eq, &done); |
| |
| // Not valid, load undefined. |
| LoadRoot(dst, RootIndex::kUndefinedValue); |
| B(fbv_undef); |
| |
| Bind(&done); |
| } |
| |
| void MacroAssembler::LoadFeedbackVector(Register dst, Register closure, |
| Register scratch, Label* fbv_undef) { |
| LoadFeedbackCell(dst, closure); |
| LoadFeedbackVectorFromCell(dst, dst, scratch, fbv_undef); |
| } |
| |
| void MacroAssembler::LoadInterpreterDataBytecodeArray( |
| Register destination, Register interpreter_data) { |
| LoadProtectedPointerField( |
| destination, FieldMemOperand(interpreter_data, |
| offsetof(InterpreterData, bytecode_array_))); |
| } |
| |
| void MacroAssembler::LoadInterpreterDataInterpreterTrampoline( |
| Register destination, Register interpreter_data) { |
| LoadProtectedPointerField( |
| destination, |
| FieldMemOperand(interpreter_data, |
| offsetof(InterpreterData, interpreter_trampoline_))); |
| } |
| |
| // Sets condition flags based on comparison, and returns type in type_reg. |
| void MacroAssembler::CompareInstanceType(Register map, Register type_reg, |
| InstanceType type) { |
| ASM_CODE_COMMENT(this); |
| Ldrh(type_reg, FieldMemOperand(map, offsetof(Map, instance_type_))); |
| Cmp(type_reg, type); |
| } |
| |
| // Sets condition flags based on comparison, and returns type in type_reg. |
| void MacroAssembler::CompareInstanceTypeRange(Register map, Register type_reg, |
| InstanceType lower_limit, |
| InstanceType higher_limit) { |
| ASM_CODE_COMMENT(this); |
| DCHECK_LT(lower_limit, higher_limit); |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Ldrh(type_reg, FieldMemOperand(map, offsetof(Map, instance_type_))); |
| CompareRange(type_reg, scratch, lower_limit, higher_limit); |
| } |
| |
| void MacroAssembler::LoadElementsKindFromMap(Register result, Register map) { |
| ASM_CODE_COMMENT(this); |
| // Load the map's "bit field 2". |
| Ldrb(result, FieldMemOperand(map, offsetof(Map, bit_field2_))); |
| // Retrieve elements_kind from bit field 2. |
| DecodeField<Map::Bits2::ElementsKindBits>(result); |
| } |
| |
| void MacroAssembler::CompareTaggedRoot(const Register& obj, RootIndex index) { |
| ASM_CODE_COMMENT(this); |
| AssertSmiOrHeapObjectInMainCompressionCage(obj); |
| UseScratchRegisterScope temps(this); |
| if (V8_STATIC_ROOTS_BOOL && RootsTable::IsReadOnly(index)) { |
| CmpTagged(obj, Immediate(ReadOnlyRootPtr(index))); |
| return; |
| } |
| // Some smi roots contain system pointer size values like stack limits. |
| DCHECK(base::IsInRange(index, RootIndex::kFirstStrongOrReadOnlyRoot, |
| RootIndex::kLastStrongOrReadOnlyRoot)); |
| Register temp = temps.AcquireX(); |
| DCHECK(!AreAliased(obj, temp)); |
| LoadRoot(temp, index); |
| CmpTagged(obj, temp); |
| } |
| |
| void MacroAssembler::CompareRoot(const Register& obj, RootIndex index, |
| ComparisonMode mode) { |
| ASM_CODE_COMMENT(this); |
| if (mode == ComparisonMode::kFullPointer || |
| !base::IsInRange(index, RootIndex::kFirstStrongOrReadOnlyRoot, |
| RootIndex::kLastStrongOrReadOnlyRoot)) { |
| // Some smi roots contain system pointer size values like stack limits. |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| DCHECK(!AreAliased(obj, temp)); |
| LoadRoot(temp, index); |
| Cmp(obj, temp); |
| return; |
| } |
| CompareTaggedRoot(obj, index); |
| } |
| |
| 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) { |
| ASM_CODE_COMMENT(this); |
| 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 MacroAssembler::LoadTaggedField(const Register& destination, |
| const MemOperand& field_operand) { |
| if (COMPRESS_POINTERS_BOOL) { |
| DecompressTagged(destination, field_operand); |
| } else { |
| Ldr(destination, field_operand); |
| } |
| } |
| |
| void MacroAssembler::LoadTaggedFieldWithoutDecompressing( |
| const Register& destination, const MemOperand& field_operand) { |
| if (COMPRESS_POINTERS_BOOL) { |
| Ldr(destination.W(), field_operand); |
| } else { |
| Ldr(destination, field_operand); |
| } |
| } |
| |
| void MacroAssembler::LoadTaggedSignedField(const Register& destination, |
| const MemOperand& field_operand) { |
| if (COMPRESS_POINTERS_BOOL) { |
| DecompressTaggedSigned(destination, field_operand); |
| } else { |
| Ldr(destination, field_operand); |
| } |
| } |
| |
| void MacroAssembler::SmiUntagField(Register dst, const MemOperand& src) { |
| SmiUntag(dst, src); |
| } |
| |
| void MacroAssembler::SmiUntagFieldUnsigned(Register dst, |
| const MemOperand& src) { |
| SmiUntagUnsigned(dst, src); |
| } |
| |
| void MacroAssembler::StoreTwoTaggedFields(const Register& value, |
| const MemOperand& dst_field_operand) { |
| if (COMPRESS_POINTERS_BOOL) { |
| Stp(value.W(), value.W(), dst_field_operand); |
| } else { |
| Stp(value, value, dst_field_operand); |
| } |
| } |
| |
| void MacroAssembler::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 MacroAssembler::AtomicStoreTaggedField(const Register& value, |
| const Register& dst_base, |
| const Register& dst_index, |
| const Register& temp) { |
| Add(temp, dst_base, dst_index); |
| if (COMPRESS_POINTERS_BOOL) { |
| Stlr(value.W(), temp); |
| } else { |
| Stlr(value, temp); |
| } |
| } |
| |
| void MacroAssembler::DecompressTaggedSigned(const Register& destination, |
| const MemOperand& field_operand) { |
| ASM_CODE_COMMENT(this); |
| Ldr(destination.W(), field_operand); |
| if (v8_flags.slow_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); |
| } |
| } |
| |
| void MacroAssembler::DecompressTagged(const Register& destination, |
| const MemOperand& field_operand) { |
| ASM_CODE_COMMENT(this); |
| Ldr(destination.W(), field_operand); |
| Orr(destination, kPtrComprCageBaseRegister, destination); |
| } |
| |
| void MacroAssembler::DecompressTagged(const Register& destination, |
| const Register& source) { |
| ASM_CODE_COMMENT(this); |
| // Runtime values decompress with the cage base or'd in so that accidental |
| // double-decompression is idempotent, but Orr has no extended-register |
| // form, so or-ing a W source takes a scratch register and two instructions |
| // (ubfx + orr). The extended-register Add is a single instruction and |
| // computes the same function: Uxtw strips the high word -- an |
| // already-present cage base included, which is what keeps the idempotence |
| // -- and the 4GB-aligned cage base has no low bits to carry into, so |
| // adding equals or-ing for every source value. |
| Add(destination, kPtrComprCageBaseRegister, Operand(source, UXTW)); |
| } |
| |
| void MacroAssembler::DecompressTagged(const Register& destination, |
| Tagged_t immediate) { |
| ASM_CODE_COMMENT(this); |
| if (IsImmAddSub(immediate)) { |
| // Runtime values decompress with Orr so that accidental |
| // double-decompression is idempotent, but a constant has no input that |
| // could already be decompressed, and the 4GB-aligned cage base makes Add |
| // equal to Orr for any 32-bit offset. Only Add can encode the offset |
| // directly, though -- Orr would need a (rarely matching) logical |
| // immediate and otherwise materializes through a scratch register. |
| Add(destination, kPtrComprCageBaseRegister, |
| Immediate(immediate, RelocInfo::Mode::NO_INFO)); |
| } else { |
| // Immediate is larger than 12 bit and therefore can't be encoded directly. |
| // Use destination as a temporary to not acquire a scratch register. |
| DCHECK_NE(destination, sp); |
| Operand imm_operand = |
| MoveImmediateForShiftedOp(destination, immediate, kAnyShift); |
| Orr(destination, kPtrComprCageBaseRegister, imm_operand); |
| } |
| } |
| |
| void MacroAssembler::DecompressProtected(const Register& destination, |
| const MemOperand& field_operand) { |
| #if V8_ENABLE_SANDBOX |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Ldr(destination.W(), field_operand); |
| Ldr(scratch, |
| MemOperand(kRootRegister, IsolateData::trusted_cage_base_offset())); |
| Orr(destination, destination, scratch); |
| #else |
| UNREACHABLE(); |
| #endif // V8_ENABLE_SANDBOX |
| } |
| |
| void MacroAssembler::AtomicDecompressTaggedSigned(const Register& destination, |
| const Register& base, |
| const Register& index, |
| const Register& temp) { |
| ASM_CODE_COMMENT(this); |
| Add(temp, base, index); |
| Ldar(destination.W(), temp); |
| if (v8_flags.slow_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); |
| } |
| } |
| |
| int MacroAssembler::AtomicDecompressTagged(const Register& destination, |
| const Register& base, |
| const Register& index, |
| const Register& temp) { |
| ASM_CODE_COMMENT(this); |
| Add(temp, base, index); |
| int pc_offset_of_load = pc_offset(); |
| Ldar(destination.W(), temp); |
| Orr(destination, kPtrComprCageBaseRegister, destination); |
| return pc_offset_of_load; |
| } |
| |
| void MacroAssembler::CheckPageFlag(const Register& object, int mask, |
| Condition cc, Label* condition_met) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| And(scratch, object, ~MemoryChunk::GetAlignmentMaskForAssembler()); |
| Ldr(scratch, MemOperand(scratch, MemoryChunk::FlagsOffset())); |
| if (cc == ne) { |
| TestAndBranchIfAnySet(scratch, mask, condition_met); |
| } else { |
| DCHECK_EQ(cc, eq); |
| TestAndBranchIfAllClear(scratch, mask, condition_met); |
| } |
| } |
| |
| void MacroAssembler::JumpIfMarking(Label* is_marking, |
| Label::Distance condition_met_distance) { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Ldrb(scratch, |
| MemOperand(kRootRegister, IsolateData::is_marking_flag_offset())); |
| Cbnz(scratch, is_marking); |
| } |
| |
| void MacroAssembler::JumpIfNotMarking(Label* not_marking, |
| Label::Distance condition_met_distance) { |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Ldrb(scratch, |
| MemOperand(kRootRegister, IsolateData::is_marking_flag_offset())); |
| Cbz(scratch, not_marking); |
| } |
| |
| void MacroAssembler::PreCheckSkippedWriteBarrier(Register object, |
| Register value, |
| Register scratch, Label* ok) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(!AreAliased(object, scratch)); |
| DCHECK(!AreAliased(value, scratch)); |
| |
| // The most common case: Static write barrier elimination is allowed on the |
| // last young allocation. |
| { |
| UseScratchRegisterScope temps(this); |
| Register scratch1 = temps.AcquireX(); |
| sub(scratch, object, kHeapObjectTag); |
| Ldr(scratch1, |
| MemOperand(kRootRegister, IsolateData::last_young_allocation_offset())); |
| cmp(scratch, scratch1); |
| B(Condition::kEqual, ok); |
| } |
| |
| #if CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL |
| JumpIfUnsignedLessThan(value, kContiguousReadOnlyReservationSize, ok); |
| #else // !CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL |
| // Write barier can also be removed if value is in read-only space. |
| CheckPageFlag(value, scratch, MemoryChunk::kIsInReadOnlyHeapMask, ne, ok); |
| #endif // !CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL |
| |
| Label not_ok; |
| |
| // Handle allocation folding, allow WB removal if: |
| // LAB start <= last_young_allocation_ < (object address+1) < LAB top |
| // Note that object has tag bit set, so object == object address+1. |
| |
| { |
| UseScratchRegisterScope temps(this); |
| Register scratch1 = temps.AcquireX(); |
| |
| // Check LAB start <= last_young_allocation_. |
| ldr(scratch, MemOperand(kRootRegister, |
| IsolateData::new_allocation_info_start_offset())); |
| ldr(scratch1, |
| MemOperand(kRootRegister, IsolateData::last_young_allocation_offset())); |
| cmp(scratch, scratch1); |
| B(Condition::kUnsignedGreaterThan, ¬_ok); |
| |
| // Check last_young_allocation_ < (object address+1). |
| cmp(scratch1, object); |
| B(Condition::kUnsignedGreaterThanEqual, ¬_ok); |
| |
| // Check (object address+1) < LAB top. |
| ldr(scratch, MemOperand(kRootRegister, |
| IsolateData::new_allocation_info_top_offset())); |
| cmp(object, scratch); |
| B(Condition::kUnsignedLessThan, ok); |
| } |
| |
| // Slow path: Potentially check more cases in C++. |
| bind(¬_ok); |
| } |
| |
| void MacroAssembler::MaybeJumpIfReadOnlyOrSmallSmi(Register value, |
| Label* dest) { |
| #if V8_STATIC_ROOTS_BOOL && CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL |
| // Quick check for Read-only and small Smi values. |
| // This optimization requires contiguous compressed RO space to ensure RO |
| // space is at the beginning of the cage; otherwise, objects from other spaces |
| // could alias with low addresses. |
| constexpr int kLastStaticRootPage = |
| RoundUp<kRegularPageSize>(StaticReadOnlyRoot::kLastAllocatedRoot); |
| static_assert(kLastStaticRootPage <= kContiguousReadOnlyReservationSize); |
| JumpIfUnsignedLessThan(value, kContiguousReadOnlyReservationSize, dest); |
| #endif // V8_STATIC_ROOTS_BOOL && CONTIGUOUS_COMPRESSED_READ_ONLY_SPACE_BOOL |
| } |
| |
| void MacroAssembler::RecordWriteField( |
| Register object, int offset, Register value, LinkRegisterStatus lr_status, |
| SaveFPRegsMode save_fp, SmiCheck smi_check, ReadOnlyCheck ro_check, |
| SlotDescriptor slot) { |
| ASM_CODE_COMMENT(this); |
| DCHECK(!AreAliased(object, value)); |
| // First, check if a write barrier is even needed. The tests below |
| // catch stores of Smis and read-only objects. |
| Label done; |
| |
| if (ro_check == ReadOnlyCheck::kInline) { |
| MaybeJumpIfReadOnlyOrSmallSmi(value, &done); |
| } |
| |
| // Skip the barrier if writing a smi. |
| if (smi_check == SmiCheck::kInline) { |
| 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 (v8_flags.slow_debug_code) { |
| ASM_CODE_COMMENT_STRING(this, "Verify slot_address"); |
| Label ok; |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| DCHECK(!AreAliased(object, value, scratch)); |
| 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, SmiCheck::kOmit, ReadOnlyCheck::kOmit, slot); |
| |
| Bind(&done); |
| } |
| |
| void MacroAssembler::LoadSandboxedPointerField(Register destination, |
| MemOperand field_operand) { |
| #ifdef V8_ENABLE_SANDBOX |
| ASM_CODE_COMMENT(this); |
| Ldr(destination, field_operand); |
| Add(destination, kPtrComprCageBaseRegister, |
| Operand(destination, LSR, kSandboxedPointerShift)); |
| #else |
| UNREACHABLE(); |
| #endif |
| } |
| |
| void MacroAssembler::StoreSandboxedPointerField(Register value, |
| MemOperand dst_field_operand) { |
| #ifdef V8_ENABLE_SANDBOX |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Sub(scratch, value, kPtrComprCageBaseRegister); |
| Mov(scratch, Operand(scratch, LSL, kSandboxedPointerShift)); |
| Str(scratch, dst_field_operand); |
| #else |
| UNREACHABLE(); |
| #endif |
| } |
| |
| void MacroAssembler::LoadExternalPointerField(Register destination, |
| MemOperand field_operand, |
| ExternalPointerTagRange tag_range, |
| Register isolate_root) { |
| DCHECK(!AreAliased(destination, isolate_root)); |
| ASM_CODE_COMMENT(this); |
| #ifdef V8_ENABLE_SANDBOX |
| DCHECK(!tag_range.IsEmpty()); |
| DCHECK(!IsSharedExternalPointerType(tag_range)); |
| UseScratchRegisterScope temps(this); |
| Register external_table = temps.AcquireX(); |
| if (isolate_root == no_reg) { |
| DCHECK(root_array_available_); |
| isolate_root = kRootRegister; |
| } |
| Ldr(external_table, |
| MemOperand(isolate_root, |
| IsolateData::external_pointer_table_offset() + |
| Internals::kExternalEntityTableBasePointerOffset)); |
| Ldr(destination.W(), field_operand); |
| Mov(destination, Operand(destination, LSR, kExternalPointerIndexShift)); |
| Ldr(destination, MemOperand(external_table, destination, LSL, |
| kExternalPointerTableEntrySizeLog2)); |
| |
| // We don't expect to see empty fields here. If this is ever needed, consider |
| // using an dedicated empty value entry for those tags instead (i.e. an entry |
| // with the right tag and nullptr payload). |
| // Although interceptor callbacks can be empty in general, once we decide |
| // to generate a code loading a callback value it's guaranteed that the |
| // external pointer handle is not empty. |
| DCHECK(!ExternalPointerCanBeEmpty(tag_range) || |
| kAnyInterceptorInfoExternalPointerTagRange.Contains(tag_range)); |
| |
| // We need another scratch register for the 64-bit tag constant. Instead of |
| // forcing the `And` to allocate a new temp register (which we may not have), |
| // reuse the temp register that we used for the external pointer table base. |
| Register scratch = external_table; |
| if (tag_range.Size() == 1) { |
| // The common and simple case: we expect exactly one tag. |
| static_assert(kExternalPointerShiftedTagMask == 0x7f); |
| Ubfx(scratch, destination, kExternalPointerTagShift, 7); |
| Cmp(scratch, Immediate(tag_range.first)); |
| SbxCheck(eq, AbortReason::kExternalPointerTagMismatch); |
| And(destination, destination, Immediate(kExternalPointerPayloadMask)); |
| } else { |
| // Not currently supported. Implement once needed. |
| DCHECK_NE(tag_range, kAnyExternalPointerTagRange); |
| UNREACHABLE(); |
| } |
| #else |
| Ldr(destination, field_operand); |
| #endif // V8_ENABLE_SANDBOX |
| } |
| |
| void MacroAssembler::LoadTrustedPointerField(Register destination, |
| MemOperand field_operand, |
| IndirectPointerTag tag) { |
| #ifdef V8_ENABLE_SANDBOX |
| LoadIndirectPointerField(destination, field_operand, tag); |
| #else |
| LoadTaggedField(destination, field_operand); |
| #endif |
| } |
| |
| void MacroAssembler::LoadTrustedUnknownPointerField( |
| Register destination, MemOperand field_operand, Register scratch, |
| const std::initializer_list<std::tuple<InstanceType, Label*>>& cases, |
| Label* is_unavailable) { |
| DCHECK(!AreAliased(destination, scratch)); |
| Label zero_and_fallthrough, done; |
| |
| // The label is_unavailable will be used if the field is null (with enabled |
| // sandbox) or a Smi (with disabled sandbox). In these two cases, if the |
| // label is a nullptr, then we zero the destination register and fall through. |
| if (!is_unavailable) is_unavailable = &zero_and_fallthrough; |
| |
| #ifdef V8_ENABLE_SANDBOX |
| { |
| Register handle = scratch; |
| Ldr(handle.W(), field_operand); |
| |
| static_assert(kNullIndirectPointerHandle == 0); |
| Cbz(handle, is_unavailable); |
| |
| ResolveIndirectPointerHandle(destination, handle, kAllIndirectPointerTags); |
| } |
| #else |
| LoadTaggedField(destination, field_operand); |
| JumpIfSmi(destination, is_unavailable); |
| #endif // V8_ENABLE_SANDBOX |
| |
| #if V8_STATIC_ROOTS_BOOL |
| LoadCompressedMap(scratch, destination); |
| for (auto& [type, label] : cases) { |
| CompareInstanceTypeWithUniqueCompressedMap(scratch, Register::no_reg(), |
| type); |
| B(eq, label); |
| } |
| #else |
| LoadMap(scratch, destination); |
| Ldrh(scratch, FieldMemOperand(scratch, offsetof(Map, instance_type_))); |
| for (auto& [type, label] : cases) { |
| Cmp(scratch, type); |
| B(eq, label); |
| } |
| #endif // V8_STATIC_ROOTS_BOOL |
| |
| B(&done); |
| |
| bind(&zero_and_fallthrough); |
| Mov(destination, xzr); |
| bind(&done); |
| } |
| |
| void MacroAssembler::StoreTrustedPointerField(Register value, |
| MemOperand dst_field_operand) { |
| #ifdef V8_ENABLE_SANDBOX |
| StoreIndirectPointerField(value, dst_field_operand); |
| #else |
| StoreTaggedField(value, dst_field_operand); |
| #endif |
| } |
| |
| void MacroAssembler::LoadIndirectPointerField( |
| Register destination, MemOperand field_operand, |
| IndirectPointerTagRange tag_range) { |
| #ifdef V8_ENABLE_SANDBOX |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| |
| Register handle = temps.AcquireX(); |
| Ldr(handle.W(), field_operand); |
| ResolveIndirectPointerHandle(destination, handle, tag_range); |
| #else |
| UNREACHABLE(); |
| #endif // V8_ENABLE_SANDBOX |
| } |
| |
| void MacroAssembler::StoreIndirectPointerField(Register value, |
| MemOperand dst_field_operand) { |
| #ifdef V8_ENABLE_SANDBOX |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| Ldr(scratch.W(), FieldMemOperand(value, offsetof(ExposedTrustedObject, |
| self_indirect_pointer_))); |
| Str(scratch.W(), dst_field_operand); |
| #else |
| UNREACHABLE(); |
| #endif // V8_ENABLE_SANDBOX |
| } |
| |
| #ifdef V8_ENABLE_SANDBOX |
| void MacroAssembler::ResolveIndirectPointerHandle( |
| Register destination, Register handle, IndirectPointerTagRange tag_range) { |
| DCHECK(!AreAliased(handle, destination)); |
| |
| Register table = destination; |
| DCHECK(root_array_available_); |
| Ldr(table, |
| MemOperand{kRootRegister, IsolateData::trusted_pointer_table_offset()}); |
| Mov(handle, Operand(handle, LSR, kTrustedPointerHandleShift)); |
| Ldr(destination, |
| MemOperand(table, handle, LSL, kTrustedPointerTableEntrySizeLog2)); |
| |
| if (IsFastIndirectPointerTagRange(tag_range)) { |
| uint64_t mask = ComputeUntaggingMaskForFastIndirectPointerTag(tag_range); |
| And(destination, destination, Immediate(mask)); |
| } else { |
| Register tag_reg = handle; |
| Lsr(tag_reg, destination, kTrustedPointerTableTagShift); |
| |
| if (tag_range.Size() == 1) { |
| Cmp(tag_reg, Immediate(tag_range.first)); |
| Csel(destination, destination, xzr, eq); |
| } else { |
| Sub(tag_reg, tag_reg, Immediate(tag_range.first)); |
| Cmp(tag_reg, Immediate(tag_range.last - tag_range.first)); |
| Csel(destination, destination, xzr, ls); |
| } |
| |
| And(destination, destination, Immediate(kTrustedPointerTablePayloadMask)); |
| } |
| } |
| |
| #endif // V8_ENABLE_SANDBOX |
| |
| void MacroAssembler::LoadEntrypointFromJSDispatchTable(Register destination, |
| Register dispatch_handle, |
| Register scratch) { |
| DCHECK(!AreAliased(destination, dispatch_handle, scratch)); |
| ASM_CODE_COMMENT(this); |
| |
| Register index = destination; |
| CHECK(root_array_available()); |
| Ldr(scratch, ExternalReferenceAsOperand(IsolateFieldId::kJSDispatchTable)); |
| Mov(index, Operand(dispatch_handle, LSR, kJSDispatchHandleShift)); |
| Add(scratch, scratch, Operand(index, LSL, kJSDispatchTableEntrySizeLog2)); |
| Ldr(destination, MemOperand(scratch, JSDispatchEntry::kEntrypointOffset)); |
| } |
| |
| void MacroAssembler::LoadParameterCountFromJSDispatchTable( |
| Register destination, Register dispatch_handle, Register scratch) { |
| DCHECK(!AreAliased(destination, dispatch_handle, scratch)); |
| ASM_CODE_COMMENT(this); |
| |
| Register index = destination; |
| CHECK(root_array_available()); |
| Ldr(scratch, ExternalReferenceAsOperand(IsolateFieldId::kJSDispatchTable)); |
| Mov(index, Operand(dispatch_handle, LSR, kJSDispatchHandleShift)); |
| Add(scratch, scratch, Operand(index, LSL, kJSDispatchTableEntrySizeLog2)); |
| static_assert(JSDispatchEntry::kParameterCountMask == 0xffff); |
| Ldrh(destination, MemOperand(scratch, JSDispatchEntry::kCodeObjectOffset)); |
| } |
| |
| void MacroAssembler::LoadEntrypointAndParameterCountFromJSDispatchTable( |
| Register entrypoint, Register parameter_count, Register dispatch_handle, |
| Register scratch) { |
| DCHECK(!AreAliased(entrypoint, parameter_count, dispatch_handle, scratch)); |
| ASM_CODE_COMMENT(this); |
| |
| Register index = parameter_count; |
| CHECK(root_array_available()); |
| Ldr(scratch, ExternalReferenceAsOperand(IsolateFieldId::kJSDispatchTable)); |
| Mov(index, Operand(dispatch_handle, LSR, kJSDispatchHandleShift)); |
| Add(scratch, scratch, Operand(index, LSL, kJSDispatchTableEntrySizeLog2)); |
| Ldr(entrypoint, MemOperand(scratch, JSDispatchEntry::kEntrypointOffset)); |
| static_assert(JSDispatchEntry::kParameterCountMask == 0xffff); |
| Ldrh(parameter_count, |
| MemOperand(scratch, JSDispatchEntry::kCodeObjectOffset)); |
| } |
| |
| void MacroAssembler::PushDispatchHandle(Register dispatch_handle, |
| Register other, Register scratch1, |
| Register scratch2) { |
| Register maybe_dispatch_handle = |
| V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE_BOOL ? dispatch_handle : padreg; |
| #ifdef V8_ENABLE_SANDBOX |
| DCHECK(!AreAliased(dispatch_handle, other, scratch1, scratch2)); |
| AssertZeroExtended(dispatch_handle); |
| LoadParameterCountFromJSDispatchTable(scratch1, dispatch_handle, scratch2); |
| Bfi(dispatch_handle, scratch1, 32, 32); |
| #endif |
| Push(maybe_dispatch_handle, other); |
| // No need to SmiTag as dispatch handles always look like Smis. |
| static_assert(kJSDispatchHandleShift > 0); |
| AssertSmi(maybe_dispatch_handle); |
| } |
| |
| void MacroAssembler::PopDispatchHandle(Register dispatch_handle, Register other, |
| Register scratch1, Register scratch2) { |
| Register maybe_dispatch_handle = |
| V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE_BOOL ? dispatch_handle : padreg; |
| Pop(other, maybe_dispatch_handle); |
| #ifdef V8_ENABLE_SANDBOX |
| DCHECK(!AreAliased(dispatch_handle, other, scratch1, scratch2)); |
| UseScratchRegisterScope temps(this); |
| Lsr(scratch1, dispatch_handle, 32); |
| Uxtw(dispatch_handle, dispatch_handle); |
| LoadParameterCountFromJSDispatchTable(scratch2, dispatch_handle, |
| temps.AcquireX()); |
| Cmp(scratch1, scratch2); |
| SbxCheck(eq, AbortReason::kJSSignatureMismatch); |
| #endif |
| } |
| |
| void MacroAssembler::LoadProtectedPointerField(Register destination, |
| MemOperand field_operand) { |
| DCHECK(root_array_available()); |
| #ifdef V8_ENABLE_SANDBOX |
| DecompressProtected(destination, field_operand); |
| #else |
| LoadTaggedField(destination, field_operand); |
| #endif |
| } |
| |
| void MacroAssembler::MaybeSaveRegisters(RegList registers) { |
| if (registers.is_empty()) return; |
| ASM_CODE_COMMENT(this); |
| CPURegList regs(kXRegSizeInBits, registers); |
| // If we were saving LR, we might need to sign it. |
| DCHECK(!regs.IncludesAliasOf(lr)); |
| regs.Align(); |
| PushCPURegList(regs); |
| } |
| |
| void MacroAssembler::MaybeRestoreRegisters(RegList registers) { |
| if (registers.is_empty()) return; |
| ASM_CODE_COMMENT(this); |
| CPURegList regs(kXRegSizeInBits, registers); |
| // If we were saving LR, we might need to sign it. |
| DCHECK(!regs.IncludesAliasOf(lr)); |
| regs.Align(); |
| PopCPURegList(regs); |
| } |
| |
| void MacroAssembler::CallEphemeronKeyBarrier(Register object, Operand offset, |
| SaveFPRegsMode fp_mode) { |
| ASM_CODE_COMMENT(this); |
| RegList registers = WriteBarrierDescriptor::ComputeSavedRegisters(object); |
| MaybeSaveRegisters(registers); |
| |
| MoveObjectAndSlot(WriteBarrierDescriptor::ObjectRegister(), |
| WriteBarrierDescriptor::SlotAddressRegister(), object, |
| offset); |
| |
| CallBuiltin(Builtins::EphemeronKeyBarrier(fp_mode)); |
| MaybeRestoreRegisters(registers); |
| } |
| |
| void MacroAssembler::CallIndirectPointerBarrier(Register object, Operand offset, |
| SaveFPRegsMode fp_mode, |
| IndirectPointerTag tag) { |
| ASM_CODE_COMMENT(this); |
| RegList registers = |
| IndirectPointerWriteBarrierDescriptor::ComputeSavedRegisters(object); |
| MaybeSaveRegisters(registers); |
| |
| MoveObjectAndSlot( |
| IndirectPointerWriteBarrierDescriptor::ObjectRegister(), |
| IndirectPointerWriteBarrierDescriptor::SlotAddressRegister(), object, |
| offset); |
| Mov(IndirectPointerWriteBarrierDescriptor::IndirectPointerTagRegister(), |
| Operand(tag)); |
| |
| CallBuiltin(Builtins::IndirectPointerBarrier(fp_mode)); |
| MaybeRestoreRegisters(registers); |
| } |
| |
| void MacroAssembler::CallRecordWriteStubSaveRegisters(Register object, |
| Operand offset, |
| SaveFPRegsMode fp_mode, |
| StubCallMode mode) { |
| ASM_CODE_COMMENT(this); |
| RegList registers = WriteBarrierDescriptor::ComputeSavedRegisters(object); |
| MaybeSaveRegisters(registers); |
| |
| Register object_parameter = WriteBarrierDescriptor::ObjectRegister(); |
| Register slot_address_parameter = |
| WriteBarrierDescriptor::SlotAddressRegister(); |
| MoveObjectAndSlot(object_parameter, slot_address_parameter, object, offset); |
| |
| CallRecordWriteStub(object_parameter, slot_address_parameter, fp_mode, mode); |
| |
| MaybeRestoreRegisters(registers); |
| } |
| |
| void MacroAssembler::CallRecordWriteStub(Register object, Register slot_address, |
| SaveFPRegsMode fp_mode, |
| StubCallMode mode) { |
| ASM_CODE_COMMENT(this); |
| DCHECK_EQ(WriteBarrierDescriptor::ObjectRegister(), object); |
| DCHECK_EQ(WriteBarrierDescriptor::SlotAddressRegister(), slot_address); |
| #if V8_ENABLE_WEBASSEMBLY |
| if (mode == StubCallMode::kCallWasmRuntimeStub) { |
| auto wasm_target = |
| static_cast<Address>(wasm::WasmCode::GetRecordWriteBuiltin(fp_mode)); |
| Call(wasm_target, RelocInfo::WASM_STUB_CALL); |
| #else |
| if (false) { |
| #endif |
| } else { |
| CallBuiltin(Builtins::RecordWrite(fp_mode)); |
| } |
| } |
| |
| void MacroAssembler::CallVerifySkippedWriteBarrierStubSaveRegisters( |
| Register object, Register value, SaveFPRegsMode fp_mode) { |
| ASM_CODE_COMMENT(this); |
| PushCallerSaved(fp_mode); |
| CallVerifySkippedWriteBarrierStub(object, value); |
| PopCallerSaved(fp_mode); |
| } |
| |
| void MacroAssembler::CallVerifySkippedWriteBarrierStub(Register object, |
| Register value) { |
| ASM_CODE_COMMENT(this); |
| MovePair(kCArgRegs[0], object, kCArgRegs[1], value); |
| CallCFunction(ExternalReference::verify_skipped_write_barrier(), 2, |
| SetIsolateDataSlots::kNo); |
| } |
| |
| void MacroAssembler::CallVerifySkippedIndirectWriteBarrierStubSaveRegisters( |
| Register object, Register value, SaveFPRegsMode fp_mode) { |
| ASM_CODE_COMMENT(this); |
| PushCallerSaved(fp_mode); |
| CallVerifySkippedIndirectWriteBarrierStub(object, value); |
| PopCallerSaved(fp_mode); |
| } |
| |
| void MacroAssembler::CallVerifySkippedIndirectWriteBarrierStub(Register object, |
| Register value) { |
| ASM_CODE_COMMENT(this); |
| MovePair(kCArgRegs[0], object, kCArgRegs[1], value); |
| CallCFunction(ExternalReference::verify_skipped_indirect_write_barrier(), 2, |
| SetIsolateDataSlots::kNo); |
| } |
| |
| void MacroAssembler::MoveObjectAndSlot(Register dst_object, Register dst_slot, |
| Register object, Operand offset) { |
| ASM_CODE_COMMENT(this); |
| DCHECK_NE(dst_object, dst_slot); |
| // If `offset` is a register, it cannot overlap with `object`. |
| DCHECK_IMPLIES(!offset.IsImmediate(), !offset.reg().Aliases(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().Aliases(dst_object)) { |
| Mov(dst_object, dst_slot); |
| Add(dst_slot, dst_slot, offset); |
| return; |
| } |
| |
| DCHECK(dst_object.Aliases(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, offset); |
| Sub(dst_object, dst_slot, offset); |
| } |
| |
| // 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, SmiCheck smi_check, |
| ReadOnlyCheck ro_check, SlotDescriptor slot) { |
| ASM_CODE_COMMENT(this); |
| ASM_LOCATION_IN_ASSEMBLER("MacroAssembler::RecordWrite"); |
| DCHECK(!AreAliased(object, value)); |
| |
| if (v8_flags.slow_debug_code) { |
| ASM_CODE_COMMENT_STRING(this, "Verify slot_address"); |
| UseScratchRegisterScope temps(this); |
| Register temp = temps.AcquireX(); |
| DCHECK(!AreAliased(object, value, temp)); |
| Add(temp, object, offset); |
| if (slot.contains_indirect_pointer()) { |
| LoadIndirectPointerField(temp, MemOperand(temp), |
| slot.indirect_pointer_tag()); |
| } else { |
| DCHECK(slot.contains_direct_pointer()); |
| LoadTaggedField(temp, MemOperand(temp)); |
| } |
| Cmp(temp, value); |
| Check(eq, AbortReason::kWrongAddressOrValuePassedToRecordWrite); |
| } |
| |
| if (v8_flags.disable_write_barriers) { |
| return; |
| } |
| |
| // First, check if a write barrier is even needed. The tests below |
| // catch stores of smisand read-only objects, as well as stores into the |
| // young generation. |
| Label done; |
| |
| if (ro_check == ReadOnlyCheck::kInline) { |
| MaybeJumpIfReadOnlyOrSmallSmi(value, &done); |
| } |
| |
| if (smi_check == SmiCheck::kInline) { |
| DCHECK_EQ(0, kSmiTag); |
| JumpIfSmi(value, &done); |
| } |
| |
| if (slot.contains_indirect_pointer()) { |
| // The indirect pointer write barrier is only enabled during marking. |
| JumpIfNotMarking(&done); |
| } else { |
| CheckPageFlag(value, MemoryChunk::kPointersToHereAreInterestingMask, eq, |
| &done); |
| |
| CheckPageFlag(object, MemoryChunk::kPointersFromHereAreInterestingMask, eq, |
| &done); |
| } |
| |
| // Record the actual write. |
| if (lr_status == kLRHasNotBeenSaved) { |
| Push<MacroAssembler::kSignLR>(padreg, lr); |
| } |
| Register slot_address = WriteBarrierDescriptor::SlotAddressRegister(); |
| DCHECK(!AreAliased(object, slot_address, value)); |
| if (slot.contains_direct_pointer()) { |
| // TODO(cbruni): Turn offset into int. |
| DCHECK(offset.IsImmediate()); |
| Add(slot_address, object, offset); |
| CallRecordWriteStub(object, slot_address, fp_mode, |
| StubCallMode::kCallBuiltinPointer); |
| } else { |
| DCHECK(slot.contains_indirect_pointer()); |
| CallIndirectPointerBarrier(object, offset, fp_mode, |
| slot.indirect_pointer_tag()); |
| } |
| if (lr_status == kLRHasNotBeenSaved) { |
| Pop<MacroAssembler::kAuthLR>(lr, padreg); |
| } |
| if (v8_flags.slow_debug_code) Mov(slot_address, Operand(kZapValue)); |
| |
| Bind(&done); |
| } |
| |
| void MacroAssembler::Check(Condition cond, AbortReason reason) { |
| Label ok; |
| B(cond, &ok); |
| Abort(reason); |
| // Will not return here. |
| Bind(&ok); |
| } |
| |
| void MacroAssembler::SbxCheck(Condition cc, AbortReason reason) { |
| Check(cc, reason); |
| } |
| |
| void MacroAssembler::Trap() { Brk(0); } |
| void MacroAssembler::DebugBreak() { Debug("DebugBreak", 0, BREAK); } |
| |
| void MacroAssembler::Abort(AbortReason reason) { |
| ASM_CODE_COMMENT(this); |
| if (v8_flags.code_comments) { |
| RecordComment("Abort message:", SourceLocation{}); |
| RecordComment(GetAbortReason(reason), SourceLocation{}); |
| } |
| |
| // Without debug code, save the code size and just trap. |
| if (!v8_flags.debug_code || v8_flags.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. |
| uint64_t old_tmp_list = TmpList()->bits(); |
| 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::NO_FRAME_TYPE); |
| 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))); |
| |
| { |
| // 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::NO_FRAME_TYPE); |
| if (root_array_available()) { |
| // Generate an indirect call via builtins entry table here in order to |
| // ensure that the interpreter_entry_return_pc_offset is the same for |
| // InterpreterEntryTrampoline and InterpreterEntryTrampolineForProfiling |
| // when v8_flags.debug_code is enabled. |
| UseScratchRegisterScope temps(this); |
| Register scratch = temps.AcquireX(); |
| LoadEntryFromBuiltin(Builtin::kAbort, scratch); |
| Call(scratch); |
| } else { |
| CallBuiltin(Builtin::kAbort); |
| } |
| } |
| |
| TmpList()->set_bits(old_tmp_list); |
| } |
| |
| void MacroAssembler::LoadNativeContextSlot(Register dst, int index) { |
| LoadMap(dst, cp); |
| LoadTaggedField( |
| dst, |
| FieldMemOperand( |
| dst, offsetof(Map, constructor_or_back_pointer_or_native_context_))); |
| LoadTaggedField(dst, MemOperand(dst, Context::SlotOffset(index))); |
| } |
| |
| void MacroAssembler::TryLoadOptimizedOsrCode(Register scratch_and_result, |
| CodeKind min_opt_level, |
| Register feedback_vector, |
| FeedbackSlot slot, |
| Label* on_result, |
| Label::Distance) { |
| Label fallthrough, clear_slot; |
| LoadTaggedField( |
| scratch_and_result, |
| FieldMemOperand(feedback_vector, |
| FeedbackVector::OffsetOfElementAt(slot.ToInt()))); |
| LoadWeakValue(scratch_and_result, scratch_and_result, &fallthrough); |
| |
| // Is it marked_for_deoptimization? If yes, clear the slot. |
| { |
| UseScratchRegisterScope temps(this); |
| |
| // The entry references a CodeWrapper object. Unwrap it now. |
| LoadCodePointerField( |
| scratch_and_result, |
| FieldMemOperand(scratch_and_result, offsetof(CodeWrapper, code_))); |
| |
| Register temp = temps.AcquireX(); |
| JumpIfCodeIsMarkedForDeoptimization(scratch_and_result, temp, &clear_slot); |
| if (min_opt_level == CodeKind::TURBOFAN_JS) { |
| JumpIfCodeIsTurbofanned(scratch_and_result, temp, on_result); |
| B(&fallthrough); |
| } else { |
| B(on_result); |
| } |
| } |
| |
| bind(&clear_slot); |
| Mov(scratch_and_result, ClearedValue()); |
| StoreTaggedField( |
| scratch_and_result, |
| FieldMemOperand(feedback_vector, |
| FeedbackVector::OffsetOfElementAt(slot.ToInt()))); |
| |
| bind(&fallthrough); |
| Mov(scratch_and_result, 0); |
| } |
| |
| void MacroAssembler::ComputeCodeStartAddress(const Register& rd) { |
| // We can use adr to load a pc relative location. |
| adr(rd, -pc_offset()); |
| } |
| |
| void MacroAssembler::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 |
| } |
| |
| #if V8_ENABLE_WEBASSEMBLY |
| void MacroAssembler::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)); |
| } |
| #endif // V8_ENABLE_WEBASSEMBLY |
| |
| void MacroAssembler::PopcntHelper(Register dst, Register src) { |
| if (CpuFeatures::IsSupported(CSSC)) { |
| CpuFeatureScope scope(this, CSSC); |
| |
| Cnt(dst, src); |
| } else { |
| UseScratchRegisterScope temps(this); |
| VRegister scratch = temps.AcquireV(kFormat8B); |
| VRegister tmp = src.Is32Bits() ? scratch.S() : scratch.D(); |
| Fmov(tmp, src); |
| Cnt(scratch, scratch); |
| Addv(scratch.B(), scratch); |
| Fmov(dst, tmp); |
| } |
| } |
| |
| void MacroAssembler::I8x16BitMask(Register dst, VRegister src, VRegister temp) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| VRegister mask = temps.AcquireQ(); |
| VRegister msb_vector = temps.AcquireQ(); |
| |
| if (CpuFeatures::IsSupported(SVEBITPERM)) { |
| CpuFeatureScope scope(this, SVEBITPERM); |
| |
| // Mask that selects the most significant bits from each 8-bit vector |
| // element. |
| Movi(mask.V16B(), 0b1000'0000); |
| // Collect the input bits into a byte of the output - once for each half of |
| // the input. Note that the actual SVE register size does not matter because |
| // the BEXT instruction does not perform cross-lane operations, while the |
| // rest of the implementation ignores all register bits except for the least |
| // significant 128. |
| Bext(mask.Z().VnD(), src.Z().VnD(), mask.Z().VnD()); |
| // Combine the bits from both input halves. |
| Ins(mask.V16B(), 1, mask.V16B(), 8); |
| Fmov(dst.W(), mask.S()); |
| } else if (CpuFeatures::IsSupported(PMULL1Q) && temp.is_valid()) { |
| CpuFeatureScope scope(this, PMULL1Q); |
| |
| Movi(mask.V2D(), 0x0102'0408'1020'4080); |
| // Normalize the input - at most 1 bit per vector element should be set. |
| Ushr(msb_vector.V16B(), src.V16B(), 7); |
| // Collect the input bits into a byte of the output - once for each |
| // half of the input. |
| Pmull2(temp.V1Q(), mask.V2D(), msb_vector.V2D()); |
| Pmull(msb_vector.V1Q(), mask.V1D(), msb_vector.V1D()); |
| // Combine the bits from both input halves. |
| Trn2(msb_vector.V8B(), msb_vector.V8B(), temp.V8B()); |
| Mov(dst.W(), msb_vector.V8H(), 3); |
| } else { |
| // Set i-th bit of each lane i. When AND with msb_vector, the lanes that |
| // are signed will have i-th bit set, unsigned will be 0. |
| Sshr(msb_vector.V16B(), src.V16B(), 7); |
| Movi(mask.V2D(), 0x8040'2010'0804'0201); |
| And(msb_vector.V16B(), mask.V16B(), msb_vector.V16B()); |
| Ext(mask.V16B(), msb_vector.V16B(), msb_vector.V16B(), 8); |
| Zip1(msb_vector.V16B(), msb_vector.V16B(), mask.V16B()); |
| Addv(msb_vector.H(), msb_vector.V8H()); |
| Mov(dst.W(), msb_vector.V8H(), 0); |
| } |
| } |
| |
| void MacroAssembler::I16x8BitMask(Register dst, VRegister src) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| VRegister tmp = temps.AcquireQ(); |
| VRegister mask = temps.AcquireQ(); |
| |
| if (CpuFeatures::IsSupported(SVEBITPERM)) { |
| CpuFeatureScope scope(this, SVEBITPERM); |
| Register tmp_gpr = temps.AcquireX(); |
| |
| // Mask that selects the most significant bits from each 16-bit vector |
| // element. |
| Movi(mask.V8H(), 0b1000'0000'0000'0000); |
| // Collect the input bits into 4 bits of the output - once for each half of |
| // the input. Note that the actual SVE register size does not matter because |
| // the BEXT instruction does not perform cross-lane operations, while the |
| // rest of the implementation ignores all register bits except for the least |
| // significant 128. |
| Bext(mask.Z().VnD(), src.Z().VnD(), mask.Z().VnD()); |
| Mov(tmp_gpr, mask.D(), 1); |
| Fmov(dst.W(), mask.S()); |
| // Combine the bits from both input halves. |
| Orr(dst.W(), dst.W(), Operand(tmp_gpr.W(), LSL, 4)); |
| } else if (CpuFeatures::IsSupported(PMULL1Q)) { |
| CpuFeatureScope scope(this, PMULL1Q); |
| |
| // Normalize the input - at most 1 bit per vector element should be set. |
| Ushr(tmp.V8H(), src.V8H(), 15); |
| Movi(mask.V1D(), 0x0102'0408'1020'4080); |
| // Trim some of the redundant 0 bits, so that we can operate on |
| // only 64 bits. |
| Xtn(tmp.V8B(), tmp.V8H()); |
| // Collect the input bits into a byte of the output. |
| Pmull(tmp.V1Q(), tmp.V1D(), mask.V1D()); |
| Mov(dst.W(), tmp.V16B(), 7); |
| } else { |
| Sshr(tmp.V8H(), src.V8H(), 15); |
| // Set i-th bit of each lane i. When AND with tmp, the lanes that |
| // are signed will have i-th bit set, unsigned will be 0. |
| Movi(mask.V2D(), 0x0080'0040'0020'0010, 0x0008'0004'0002'0001); |
| And(tmp.V16B(), mask.V16B(), tmp.V16B()); |
| Addv(tmp.H(), tmp.V8H()); |
| Mov(dst.W(), tmp.V8H(), 0); |
| } |
| } |
| |
| void MacroAssembler::I32x4BitMask(Register dst, VRegister src) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope temps(this); |
| Register tmp = temps.AcquireX(); |
| |
| if (CpuFeatures::IsSupported(SVEBITPERM)) { |
| CpuFeatureScope scope(this, SVEBITPERM); |
| VRegister mask = temps.AcquireQ(); |
| |
| // Mask that selects the most significant bits from each 32-bit vector |
| // element. |
| Movi(mask.V4S(), 0x8000'0000); |
| // Collect the input bits into 2 bits of the output - once for each half of |
| // the input. Note that the actual SVE register size does not matter because |
| // the BEXT instruction does not perform cross-lane operations, while the |
| // rest of the implementation ignores all register bits except for the least |
| // significant 128. |
| Bext(mask.Z().VnD(), src.Z().VnD(), mask.Z().VnD()); |
| Mov(tmp, mask.D(), 1); |
| Fmov(dst.W(), mask.S()); |
| // Combine the bits from both input halves. |
| Orr(dst.W(), dst.W(), Operand(tmp.W(), LSL, 2)); |
| } else { |
| Mov(dst.X(), src.D(), 1); |
| Fmov(tmp, src.D()); |
| And(dst.X(), dst.X(), 0x80000000'80000000); |
| And(tmp, tmp, 0x80000000'80000000); |
| Orr(dst.X(), dst.X(), Operand(dst.X(), LSL, 31)); |
| Orr(tmp, tmp, Operand(tmp, LSL, 31)); |
| Lsr(dst.X(), dst.X(), 60); |
| Bfxil(dst.X(), tmp, 62, 2); |
| } |
| } |
| |
| void MacroAssembler::I64x2BitMask(Register dst, VRegister src) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope scope(this); |
| Register tmp = scope.AcquireX(); |
| Mov(dst.X(), src.D(), 1); |
| Fmov(tmp.X(), src.D()); |
| Lsr(dst.X(), dst.X(), 62); |
| Bfxil(dst.X(), tmp.X(), 63, 1); |
| } |
| |
| void MacroAssembler::I64x2AllTrue(Register dst, VRegister src) { |
| ASM_CODE_COMMENT(this); |
| UseScratchRegisterScope scope(this); |
| VRegister tmp = scope.AcquireV(kFormat2D); |
| Cmeq(tmp.V2D(), src.V2D(), 0); |
| Addp(tmp.D(), tmp); |
| Fcmp(tmp.D(), tmp.D()); |
| Cset(dst, eq); |
| } |
| |
| // Calls an API function. Allocates HandleScope, extracts returned value |
| // from handle and propagates exceptions. Clobbers C argument registers |
| // and C caller-saved registers. Restores context. On return removes |
| // (*argc_operand + slots_to_drop_on_return) * kSystemPointerSize |
| // (GCed, includes the call JS arguments space and the additional space |
| // allocated for the fast call). |
| void CallApiFunctionAndReturn(MacroAssembler* masm, bool with_profiling, |
| Register function_address, |
| ExternalReference thunk_ref, Register thunk_arg, |
| int slots_to_drop_on_return, |
| MemOperand* argc_operand, |
| MemOperand return_value_operand, |
| bool handle_interceptor_result) { |
| ASM_CODE_COMMENT(masm); |
| ASM_LOCATION("CallApiFunctionAndReturn"); |
| |
| using ER = ExternalReference; |
| |
| MemOperand next_mem_op = __ AsMemOperand(IsolateFieldId::kHandleScopeNext); |
| MemOperand limit_mem_op = __ AsMemOperand(IsolateFieldId::kHandleScopeLimit); |
| MemOperand level_mem_op = __ AsMemOperand(IsolateFieldId::kHandleScopeLevel); |
| |
| Register return_value = x0; |
| Register scratch = x5; |
| Register scratch2 = x6; |
| |
| // Allocate HandleScope in callee-saved registers. |
| // We will need to restore the HandleScope after the call to the API function, |
| // by allocating it in callee-saved registers it'll be preserved by C code. |
| Register prev_next_address_reg = x19; |
| Register prev_limit_reg = x20; |
| Register prev_level_reg = w21; |
| |
| // C arguments (kCArgRegs[0/1/2]) are expected to be initialized outside, so |
| // this function must not corrupt them (return_value overlaps with |
| // kCArgRegs[0] but that's ok because we start using it only after the C |
| // call). |
| DCHECK(!AreAliased(kCArgRegs[0], kCArgRegs[1], kCArgRegs[2], // C args |
| scratch, scratch2, prev_next_address_reg, prev_limit_reg)); |
| // function_address and thunk_arg might overlap but this function must not |
| // corrupt them until the call is made (i.e. overlap with return_value is |
| // fine). |
| DCHECK(!AreAliased(function_address, // incoming parameters |
| scratch, scratch2, prev_next_address_reg, prev_limit_reg)); |
| DCHECK(!AreAliased(thunk_arg, // incoming parameters |
| scratch, scratch2, prev_next_address_reg, prev_limit_reg)); |
| |
| // Explicitly include x16/x17 to let StoreReturnAddressAndCall() use them. |
| UseScratchRegisterScope fix_temps(masm); |
| fix_temps.Include(x16, x17); |
| |
| { |
| ASM_CODE_COMMENT_STRING(masm, |
| "Allocate HandleScope in callee-save registers."); |
| __ Ldr(prev_next_address_reg, next_mem_op); |
| __ Ldr(prev_limit_reg, limit_mem_op); |
| __ Ldr(prev_level_reg, level_mem_op); |
| __ Add(scratch.W(), prev_level_reg, 1); |
| __ Str(scratch.W(), level_mem_op); |
| } |
| |
| Label profiler_or_side_effects_check_enabled, done_api_call, |
| done_reading_result; |
| if (with_profiling) { |
| __ RecordComment("Check if profiler or side effects check is enabled"); |
| __ Ldrb(scratch.W(), __ AsMemOperand(IsolateFieldId::kExecutionMode)); |
| __ Cbnz(scratch.W(), &profiler_or_side_effects_check_enabled); |
| #ifdef V8_RUNTIME_CALL_STATS |
| __ RecordComment("Check if RCS is enabled"); |
| __ Mov(scratch, ER::address_of_runtime_stats_flag()); |
| __ Ldrsw(scratch.W(), MemOperand(scratch)); |
| __ Cbnz(scratch.W(), &profiler_or_side_effects_check_enabled); |
| #endif // V8_RUNTIME_CALL_STATS |
| } |
| |
| __ RecordComment("Call the api function directly."); |
| __ StoreReturnAddressAndCall(function_address); |
| __ Bind(&done_api_call); |
| |
| if (handle_interceptor_result) { |
| // Skip reading return value if the callback returned kInterceptedNo, |
| // this would make the builtin return kNotInterceptedSentinel value. |
| // Size is important here, otherwise the C++ function could have returned |
| // one- or two-byte value with junk in the upper part. |
| static_assert(kInterceptedNo == 1 && kInterceptedSize == 4); |
| static_assert(kInterceptedNo == kNotInterceptedSentinel); |
| static_assert(kInterceptedYes == 0); |
| __ Cbnz(return_value, &done_reading_result); |
| } |
| |
| Label propagate_exception; |
| Label delete_allocated_handles; |
| Label leave_exit_frame; |
| |
| __ RecordComment("Load the value from ReturnValue"); |
| __ Ldr(return_value, return_value_operand); |
| __ bind(&done_reading_result); |
| |
| { |
| ASM_CODE_COMMENT_STRING( |
| masm, |
| "No more valid handles (the result handle was the last one)." |
| "Restore previous handle scope."); |
| __ Str(prev_next_address_reg, next_mem_op); |
| if (v8_flags.debug_code) { |
| __ Ldr(scratch.W(), level_mem_op); |
| __ Sub(scratch.W(), scratch.W(), 1); |
| __ Cmp(scratch.W(), prev_level_reg); |
| __ Check(eq, AbortReason::kUnexpectedLevelAfterReturnFromApiCall); |
| } |
| __ Str(prev_level_reg, level_mem_op); |
| |
| __ Ldr(scratch, limit_mem_op); |
| __ Cmp(prev_limit_reg, scratch); |
| __ B(ne, &delete_allocated_handles); |
| } |
| |
| __ RecordComment("Leave the API exit frame."); |
| __ Bind(&leave_exit_frame); |
| |
| Register argc_reg = prev_limit_reg; |
| if (argc_operand != nullptr) { |
| // Load the number of stack slots to drop before LeaveExitFrame modifies sp. |
| __ Ldr(argc_reg, *argc_operand); |
| } |
| |
| __ LeaveExitFrame(scratch, scratch2); |
| |
| { |
| ASM_CODE_COMMENT_STRING(masm, |
| "Check if the function scheduled an exception."); |
| __ Ldr(scratch, __ AsMemOperand(IsolateFieldId::kException)); |
| __ JumpIfNotRoot(scratch, RootIndex::kTheHoleValue, &propagate_exception); |
| } |
| |
| #ifndef V8_ENABLE_MEMORY_CORRUPTION_API |
| // This check doesn't make sense for sandbox testing since |
| // Sandbox.getObjectAt(..) might legitimately return non-JSAny values |
| // and this check just hinders debugging. |
| if (v8_flags.debug_code) { |
| Label ok; |
| if (handle_interceptor_result) { |
| __ Cmp(return_value, kNotInterceptedSentinel); |
| __ B(eq, &ok); |
| } |
| __ AssertJSAny(return_value, scratch, scratch2, |
| AbortReason::kAPICallReturnedInvalidObject); |
| __ bind(&ok); |
| } |
| #endif // V8_ENABLE_MEMORY_CORRUPTION_API |
| |
| if (argc_operand == nullptr) { |
| DCHECK_NE(slots_to_drop_on_return, 0); |
| __ DropSlots(slots_to_drop_on_return); |
| } else { |
| // {argc_operand} was loaded into {argc_reg} above. |
| __ DropArguments(argc_reg, slots_to_drop_on_return); |
| } |
| __ Ret(); |
| |
| if (with_profiling) { |
| ASM_CODE_COMMENT_STRING(masm, "Call the api function via thunk wrapper."); |
| __ Bind(&profiler_or_side_effects_check_enabled); |
| // Additional parameter if provided. |
| if (thunk_arg.is_valid()) { |
| __ Str(thunk_arg, |
| __ AsMemOperand(IsolateFieldId::kApiCallbackThunkArgument)); |
| } |
| __ Mov(scratch, thunk_ref); |
| __ StoreReturnAddressAndCall(scratch); |
| __ B(&done_api_call); |
| } |
| |
| __ RecordComment("An exception was thrown. Propagate it."); |
| __ Bind(&propagate_exception); |
| __ TailCallRuntime(Runtime::kPropagateException); |
| |
| { |
| ASM_CODE_COMMENT_STRING( |
| masm, "HandleScope limit has changed. Delete allocated extensions."); |
| __ Bind(&delete_allocated_handles); |
| __ Str(prev_limit_reg, limit_mem_op); |
| // Save the return value in a callee-save register. |
| Register saved_result = prev_limit_reg; |
| __ Mov(saved_result, x0); |
| __ Mov(kCArgRegs[0], ER::isolate_address()); |
| __ CallCFunction(ER::delete_handle_scope_extensions(), 1); |
| __ Mov(kCArgRegs[0], saved_result); |
| __ B(&leave_exit_frame); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace v8 |
| |
| #undef __ |
| |
| #endif // V8_TARGET_ARCH_ARM64 |