// Copyright 2015, ARM Limited // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of ARM Limited nor the names of its contributors may be // used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
int MacroAssembler::MoveImmediateHelper(MacroAssembler* masm, constRegister &rd,
uint64_t imm) { bool emit_code = (masm != NULL);
VIXL_ASSERT(IsUint32(imm) || IsInt32(imm) || rd.Is64Bits()); // The worst case for size is mov 64-bit immediate to sp: // * up to 4 instructions to materialise the constant // * 1 instruction to move to sp
MacroEmissionCheckScope guard(masm);
// 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 (OneInstrMoveImmediateHelper(masm, rd, imm)) { return 1;
} else { int instruction_count = 0; unsigned reg_size = rd.size();
// Generic immediate case. Imm will be represented by // [imm3, imm2, imm1, imm0], where each imm is 16 bits. // A move-zero or move-inverted is generated for the first non-zero or // non-0xffff immX, and a move-keep for subsequent non-zero immX.
uint64_t ignored_halfword = 0; bool invert_move = false; // If the number of 0xffff halfwords is greater than the number of 0x0000 // halfwords, it's more efficient to use move-inverted. if (CountClearHalfWords(~imm, reg_size) >
CountClearHalfWords(imm, reg_size)) {
ignored_halfword = 0xffff;
invert_move = true;
}
// Mov instructions can't move values into the stack pointer, so set up a // temporary register, if needed.
UseScratchRegisterScope temps; Register temp; if (emit_code) {
temps.Open(masm);
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.
VIXL_ASSERT((reg_size % 16) == 0); bool first_mov_done = false; for (unsigned i = 0; i < (temp.size() / 16); i++) {
uint64_t imm16 = (imm >> (16 * i)) & 0xffff; if (imm16 != ignored_halfword) { if (!first_mov_done) { if (invert_move) { if (emit_code) masm->movn(temp, ~imm16 & 0xffff, 16 * i);
instruction_count++;
} else { if (emit_code) masm->movz(temp, imm16, 16 * i);
instruction_count++;
}
first_mov_done = true;
} else { // Construct a wider constant. if (emit_code) masm->movk(temp, imm16, 16 * i);
instruction_count++;
}
}
}
VIXL_ASSERT(first_mov_done);
// Move the temporary if the original destination register was the stack // pointer. if (rd.IsSP()) { if (emit_code) masm->mov(rd, temp);
instruction_count++;
} return instruction_count;
}
}
if (IsImmMovz(imm, reg_size) && !dst.IsSP()) { // Immediate can be represented in a move zero instruction. Movz can't write // to the stack pointer. if (emit_code) {
masm->movz(dst, imm);
} returntrue;
} elseif (IsImmMovn(imm, reg_size) && !dst.IsSP()) { // Immediate can be represented in a move negative instruction. Movn can't // write to the stack pointer. if (emit_code) {
masm->movn(dst, dst.Is64Bits() ? ~imm : (~imm & kWRegMask));
} returntrue;
} elseif (IsImmLogical(imm, reg_size, &n, &imm_s, &imm_r)) { // Immediate can be represented in a logical orr instruction.
VIXL_ASSERT(!dst.IsZero()); if (emit_code) {
masm->LogicalImmediate(
dst, AppropriateZeroRegFor(dst), n, imm_s, imm_r, ORR);
} returntrue;
} returnfalse;
}
void MacroAssembler::B(Label* label, BranchType type, Register reg, int bit) {
VIXL_ASSERT((reg.Is(NoReg) || (type >= kBranchTypeFirstUsingReg)) &&
((bit == -1) || (type >= kBranchTypeFirstUsingBit))); if (kBranchTypeFirstCondition <= type && type <= kBranchTypeLastCondition) {
B(static_cast<Condition>(type), label);
} else { switch (type) { case always: B(label); break; case never: break; case reg_zero: Cbz(reg, label); break; case reg_not_zero: Cbnz(reg, label); break; case reg_bit_clear: Tbz(reg, bit, label); break; case reg_bit_set: Tbnz(reg, bit, label); break; default:
VIXL_UNREACHABLE();
}
}
}
void MacroAssembler::LogicalMacro(constRegister& rd, constRegister& rn, const Operand& operand,
LogicalOp op) { // The worst case for size is logical immediate to sp: // * up to 4 instructions to materialise the constant // * 1 instruction to do the operation // * 1 instruction to move to sp
MacroEmissionCheckScope guard(this);
UseScratchRegisterScope temps(this);
// If the operation is NOT, invert the operation and immediate. if ((op & NOT) == NOT) {
op = static_cast<LogicalOp>(op & ~NOT);
immediate = ~immediate;
}
// Ignore the top 32 bits of an immediate if we're moving to a W register. if (rd.Is32Bits()) { // Check that the top 32 bits are consistent.
VIXL_ASSERT(((immediate >> kWRegSize) == 0) ||
((immediate >> kWRegSize) == -1));
immediate &= kWRegMask;
}
// Special cases for all set or all clear immediates. if (immediate == 0) { switch (op) { caseAND:
Mov(rd, 0); return; case ORR:
VIXL_FALLTHROUGH(); case EOR:
Mov(rd, rn); return; case ANDS:
VIXL_FALLTHROUGH(); case BICS: break; default:
VIXL_UNREACHABLE();
}
} elseif ((rd.Is64Bits() && (immediate == -1)) ||
(rd.Is32Bits() && (immediate == 0xffffffff))) { switch (op) { caseAND:
Mov(rd, rn); return; case ORR:
Mov(rd, immediate); return; case EOR:
Mvn(rd, rn); return; case ANDS:
VIXL_FALLTHROUGH(); case BICS: break; default:
VIXL_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.IsSP() ? kNoShift : kAnyShift;
Operand imm_operand = MoveImmediateForShiftedOp(temp, immediate, mode);
// VIXL can acquire temp registers. Assert that the caller is aware.
VIXL_ASSERT(!temp.Is(rd) && !temp.Is(rn));
VIXL_ASSERT(!temp.Is(operand.maybeReg()));
if (rd.Is(sp)) { // 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);
}
}
} elseif (operand.IsExtendedRegister()) {
VIXL_ASSERT(operand.reg().size() <= rd.size()); // Add/sub extended supports shift <= 4. We want to support exactly the // same modes here.
VIXL_ASSERT(operand.shift_amount() <= 4);
VIXL_ASSERT(operand.reg().Is64Bits() ||
((operand.extend() != UXTX) && (operand.extend() != SXTX)));
// VIXL can acquire temp registers. Assert that the caller is aware.
VIXL_ASSERT(!temp.Is(rd) && !temp.Is(rn));
VIXL_ASSERT(!temp.Is(operand.maybeReg()));
EmitExtendShift(temp, operand.reg(), operand.extend(),
operand.shift_amount());
Logical(rd, rn, Operand(temp), op);
} else { // The operand can be encoded in the instruction.
VIXL_ASSERT(operand.IsShiftedRegister());
Logical(rd, rn, operand, op);
}
}
void MacroAssembler::Mov(constRegister& rd, const Operand& operand,
DiscardMoveMode discard_mode) { // The worst case for size is mov immediate with up to 4 instructions.
MacroEmissionCheckScope guard(this);
if (operand.IsImmediate()) { // Call the macro assembler for generic immediates.
Mov(rd, operand.immediate());
} elseif (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(rd, operand.reg(), operand.shift(), operand.shift_amount());
} elseif (operand.IsExtendedRegister()) { // Emit an extend instruction if moving an extended register. This handles // extend with post-shift operations, too.
EmitExtendShift(rd, 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 the sp is an operand, add #0 is emitted, otherwise, orr #0. if (!rd.Is(operand.reg()) || (rd.Is32Bits() &&
(discard_mode == kDontDiscardForSameWReg))) {
mov(rd, operand.reg());
}
}
}
// 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;
}
}
// 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;
}
// When hi == lo, the following generates good code. // // In situations where the constants are complex and hi != lo, the following // can turn into up to 10 instructions: 2*(mov + 3*movk + dup/insert). To do // any better, we could try to estimate whether splatting the high value and // updating the low value would generate fewer instructions than vice versa // (what we do now). // // (A PC-relative load from memory to the vector register (ADR + LD2) is going // to have fairly high latency but is fairly compact; not clear what the best // tradeoff is.)
void MacroAssembler::Mvn(constRegister& rd, const Operand& operand) { // The worst case for size is mvn immediate with up to 4 instructions.
MacroEmissionCheckScope guard(this);
if (operand.IsImmediate()) { // Call the macro assembler for generic immediates.
Mvn(rd, operand.immediate());
} elseif (operand.IsExtendedRegister()) {
UseScratchRegisterScope temps(this);
temps.Exclude(operand.reg());
// Emit two instructions for the extend case. This differs from Mov, as // the extend and invert can't be achieved in one instruction. Register temp = temps.AcquireSameSizeAs(rd);
// VIXL can acquire temp registers. Assert that the caller is aware.
VIXL_ASSERT(!temp.Is(rd) && !temp.Is(operand.maybeReg()));
EmitExtendShift(temp, operand.reg(), operand.extend(),
operand.shift_amount());
mvn(rd, Operand(temp));
} else { // Otherwise, register and shifted register cases can be handled by the // assembler directly, using orn.
mvn(rd, operand);
}
}
void MacroAssembler::ConditionalCompareMacro(constRegister& rn, const Operand& operand,
StatusFlags nzcv,
Condition cond,
ConditionalCompareOp op) {
VIXL_ASSERT((cond != al) && (cond != nv)); // The worst case for size is ccmp immediate: // * up to 4 instructions to materialise the constant // * 1 instruction for ccmp
MacroEmissionCheckScope guard(this);
if ((operand.IsShiftedRegister() && (operand.shift_amount() == 0)) ||
(operand.IsImmediate() && IsImmConditionalCompare(operand.immediate()))) { // 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 {
UseScratchRegisterScope temps(this); // The operand isn't directly supported by the instruction: perform the // operation on a temporary register. Register temp = temps.AcquireSameSizeAs(rn);
VIXL_ASSERT(!temp.Is(rn) && !temp.Is(operand.maybeReg()));
Mov(temp, operand);
ConditionalCompare(rn, temp, nzcv, cond, op);
}
}
void MacroAssembler::Csel(constRegister& rd, constRegister& rn, const Operand& operand,
Condition cond) {
VIXL_ASSERT(!rd.IsZero());
VIXL_ASSERT(!rn.IsZero());
VIXL_ASSERT((cond != al) && (cond != nv)); // The worst case for size is csel immediate: // * up to 4 instructions to materialise the constant // * 1 instruction for csel
MacroEmissionCheckScope guard(this);
void MacroAssembler::Fmov(VRegister vd, double imm) { // Floating point immediates are loaded through the literal pool.
MacroEmissionCheckScope guard(this);
VIXL_ASSERT(vd.Is1D() || vd.Is2D()); if (IsImmFP64(imm)) {
fmov(vd, imm);
} else {
uint64_t rawbits = DoubleToRawbits(imm); if (vd.IsScalar()) { if (rawbits == 0) {
fmov(vd, xzr);
} else {
Assembler::fImmPool64(vd, imm);
}
} else { // TODO: consider NEON support for load literal.
Movi(vd, rawbits);
}
}
}
void MacroAssembler::Fmov(VRegister vd, float imm) { // Floating point immediates are loaded through the literal pool.
MacroEmissionCheckScope guard(this);
if (vd.Is1D() || vd.Is2D()) {
Fmov(vd, static_cast<double>(imm)); return;
}
VIXL_ASSERT(vd.Is1S() || vd.Is2S() || vd.Is4S()); if (IsImmFP32(imm)) {
fmov(vd, imm);
} else {
uint32_t rawbits = FloatToRawbits(imm); if (vd.IsScalar()) { if (rawbits == 0) {
fmov(vd, wzr);
} else {
Assembler::fImmPool32(vd, imm);
}
} else { // TODO: consider NEON support for load literal.
Movi(vd, rawbits);
}
}
}
// 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 = CountTrailingZeros(imm, reg_size); 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, // inserting set bits in the least-significant bits. 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);
} elseif ((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 {
Mov(dst, imm);
}
} return Operand(dst);
}
void MacroAssembler::AddSubMacro(constRegister& rd, constRegister& rn, const Operand& operand,
FlagsUpdate S,
AddSubOp op) { // Worst case is add/sub immediate: // * up to 4 instructions to materialise the constant // * 1 instruction for add/sub
MacroEmissionCheckScope guard(this);
if (operand.IsZero() && rd.Is(rn) && rd.Is64Bits() && rn.Is64Bits() &&
(S == LeaveFlags)) { // The instruction would be a nop. Avoid generating useless code. return;
}
// 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.IsSP()) { // If the destination is SP and flags will be set, we can't pre-shift // the immediate at all.
mode = (S == SetFlags) ? kNoShift : kLimitShiftForSP;
} elseif (rn.IsSP()) {
mode = kLimitShiftForSP;
}
js::wasm::FaultingCodeOffset MacroAssembler::LoadStoreMacro( const CPURegister& rt, const MemOperand& addr,
LoadStoreOp op) { // Worst case is ldr/str pre/post index: // * 1 instruction for ldr/str // * up to 4 instructions to materialise the constant // * 1 instruction to update the base
MacroEmissionCheckScope guard(this);
void MacroAssembler::LoadStorePairMacro(const CPURegister& rt, const CPURegister& rt2, const MemOperand& addr,
LoadStorePairOp op) { // TODO(all): Should we support register offset for load-store-pair?
VIXL_ASSERT(!addr.IsRegisterOffset()); // Worst case is ldp/stp immediate: // * 1 instruction for ldp/stp // * up to 4 instructions to materialise the constant // * 1 instruction to update the base
MacroEmissionCheckScope guard(this);
// There are no pre- or post-index modes for prfm.
VIXL_ASSERT(addr.IsImmediateOffset() || addr.IsRegisterOffset());
// The access size is implicitly 8 bytes for all prefetch operations. unsigned size = kXRegSizeInBytesLog2;
// Check if an immediate offset fits in the immediate field of the // appropriate instruction. If not, emit two instructions to perform // the operation. if (addr.IsImmediateOffset() && !IsImmLSScaled(addr.offset(), size) &&
!IsImmLSUnscaled(addr.offset())) { // Immediate offset that can't be encoded using unsigned or unscaled // addressing modes.
UseScratchRegisterScope temps(this); Register temp = temps.AcquireSameSizeAs(addr.base());
Mov(temp, addr.offset());
Prefetch(op, MemOperand(addr.base(), temp));
} else { // Simple register-offsets are encodable in one instruction.
Prefetch(op, addr);
}
}
// Pushing a stack pointer leads to implementation-defined // behavior, which may be surprising. In particular, // str x28, [x28, #-8]! // pre-decrements the stack pointer, storing the decremented value. // Additionally, sp is read as xzr in this context, so it cannot be pushed. // So we must use a scratch register.
UseScratchRegisterScope temps(this); Register scratch = temps.AcquireX();
void MacroAssembler::Pop(const CPURegister& dst0, const CPURegister& dst1, const CPURegister& dst2, const CPURegister& dst3) { // It is not valid to pop into the same register more than once in one // instruction, not even into the zero register.
VIXL_ASSERT(!AreAliased(dst0, dst1, dst2, dst3));
VIXL_ASSERT(AreSameSizeAndType(dst0, dst1, dst2, dst3));
VIXL_ASSERT(dst0.IsValid());
int count = 1 + dst1.IsValid() + dst2.IsValid() + dst3.IsValid(); int size = dst0.SizeInBytes();
// Load the two registers at the bottom and drop the stack pointer. if (bottom_0.IsValid() && bottom_1.IsValid()) {
Ldp(bottom_0, bottom_1, MemOperand(GetStackPointer64(), size, PostIndex));
} elseif (bottom_0.IsValid()) {
Ldr(bottom_0, MemOperand(GetStackPointer64(), size, PostIndex));
}
}
void MacroAssembler::PushMultipleTimes(int count, Register src) { int size = src.SizeInBytes();
PrepareForPush(count, size); // Push up to four registers at a time if possible because if the current // stack pointer is sp and the register size is 32, registers must be pushed // in blocks of four in order to maintain the 16-byte alignment for sp. while (count >= 4) {
PushHelper(4, size, src, src, src, src);
count -= 4;
} if (count >= 2) {
PushHelper(2, size, src, src, NoReg, NoReg);
count -= 2;
} if (count == 1) {
PushHelper(1, size, src, NoReg, NoReg, NoReg);
count -= 1;
}
VIXL_ASSERT(count == 0);
}
void MacroAssembler::PushHelper(int count, int size, const CPURegister& src0, const CPURegister& src1, const CPURegister& src2, const CPURegister& src3) { // Ensure that we don't unintentionally modify scratch or debug registers. // Worst case for size is 2 stp.
InstructionAccurateScope scope(this, 2,
InstructionAccurateScope::kMaximumSize);
// Pushing the stack pointer has unexpected behavior. See PushStackPointer().
VIXL_ASSERT(!src0.Is(GetStackPointer64()) && !src0.Is(sp));
VIXL_ASSERT(!src1.Is(GetStackPointer64()) && !src1.Is(sp));
VIXL_ASSERT(!src2.Is(GetStackPointer64()) && !src2.Is(sp));
VIXL_ASSERT(!src3.Is(GetStackPointer64()) && !src3.Is(sp));
// The JS engine should never push 4 bytes.
VIXL_ASSERT(size >= 8);
// 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:
VIXL_ASSERT(src1.IsNone() && src2.IsNone() && src3.IsNone());
str(src0, MemOperand(GetStackPointer64(), -1 * size, PreIndex)); break; case 2:
VIXL_ASSERT(src2.IsNone() && src3.IsNone());
stp(src1, src0, MemOperand(GetStackPointer64(), -2 * size, PreIndex)); break; case 3:
VIXL_ASSERT(src3.IsNone());
stp(src2, src1, MemOperand(GetStackPointer64(), -3 * size, PreIndex));
str(src0, MemOperand(GetStackPointer64(), 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(GetStackPointer64(), -4 * size, PreIndex));
stp(src1, src0, MemOperand(GetStackPointer64(), 2 * size)); break; default:
VIXL_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 unintentionally modify scratch or debug registers. // Worst case for size is 2 ldp.
InstructionAccurateScope scope(this, 2,
InstructionAccurateScope::kMaximumSize);
// 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:
VIXL_ASSERT(dst1.IsNone() && dst2.IsNone() && dst3.IsNone());
ldr(dst0, MemOperand(GetStackPointer64(), 1 * size, PostIndex)); break; case 2:
VIXL_ASSERT(dst2.IsNone() && dst3.IsNone());
ldp(dst0, dst1, MemOperand(GetStackPointer64(), 2 * size, PostIndex)); break; case 3:
VIXL_ASSERT(dst3.IsNone());
ldr(dst2, MemOperand(GetStackPointer64(), 2 * size));
ldp(dst0, dst1, MemOperand(GetStackPointer64(), 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(GetStackPointer64(), 2 * size));
ldp(dst0, dst1, MemOperand(GetStackPointer64(), 4 * size, PostIndex)); break; default:
VIXL_UNREACHABLE();
}
}
void MacroAssembler::PrepareForPush(int count, int size) { if (sp.Is(GetStackPointer64())) { // If the current stack pointer is sp, then it must be aligned to 16 bytes // on entry and the total size of the specified registers must also be a // multiple of 16 bytes.
VIXL_ASSERT((count * size) % 16 == 0);
} else { // Even if the current stack pointer is not the system stack pointer (sp), // the system stack pointer will still be modified in order to comply with // ABI rules about accessing memory below the system stack pointer.
BumpSystemStackPointer(count * size);
}
}
void MacroAssembler::PrepareForPop(int count, int size) {
USE(count, size); if (sp.Is(GetStackPointer64())) { // If the current stack pointer is sp, then it must be aligned to 16 bytes // on entry and the total size of the specified registers must also be a // multiple of 16 bytes.
VIXL_ASSERT((count * size) % 16 == 0);
}
}
// Make sure the real stack pointer reflects the claimed stack space. // We can't use stack memory below the stack pointer, it could be clobbered by // interupts and signal handlers. if (!sp.Is(GetStackPointer64())) {
Mov(sp, GetStackPointer64());
}
}
void MacroAssembler::Drop(const Operand& size) {
if (size.IsZero()) { return;
}
if (size.IsImmediate()) {
VIXL_ASSERT(size.immediate() > 0); if (sp.Is(GetStackPointer64())) {
VIXL_ASSERT((size.immediate() % 16) == 0);
}
}
void MacroAssembler::PushCalleeSavedRegisters() { // Ensure that the macro-assembler doesn't use any scratch registers. // 10 stp will be emitted. // TODO(all): Should we use GetCalleeSaved and SavedFP.
InstructionAccurateScope scope(this, 10);
// This method must not be called unless the current stack pointer is sp.
VIXL_ASSERT(sp.Is(GetStackPointer64()));
void MacroAssembler::PopCalleeSavedRegisters() { // Ensure that the macro-assembler doesn't use any scratch registers. // 10 ldp will be emitted. // TODO(all): Should we use GetCalleeSaved and SavedFP.
InstructionAccurateScope scope(this, 10);
// This method must not be called unless the current stack pointer is sp.
VIXL_ASSERT(sp.Is(GetStackPointer64()));
void MacroAssembler::BumpSystemStackPointer(const Operand& space) {
VIXL_ASSERT(!sp.Is(GetStackPointer64())); // TODO: Several callers rely on this not using scratch registers, so we use // the assembler directly here. However, this means that large immediate // values of 'space' cannot be handled.
InstructionAccurateScope scope(this, 1);
sub(sp, GetStackPointer64(), space);
}
#ifdef JS_SIMULATOR_ARM64 // The arguments to the trace pseudo instruction need to be contiguous in // memory, so make sure we don't try to emit a literal pool.
InstructionAccurateScope scope(this, kTraceLength / kInstructionSize);
Label start;
bind(&start);
// Refer to simulator-a64.h for a description of the marker and its // arguments.
hlt(kTraceOpcode);
#ifdef JS_SIMULATOR_ARM64 // The arguments to the log pseudo instruction need to be contiguous in // memory, so make sure we don't try to emit a literal pool.
InstructionAccurateScope scope(this, kLogLength / kInstructionSize);
Label start;
bind(&start);
// Refer to simulator-a64.h for a description of the marker and its // arguments.
hlt(kLogOpcode);
// VIXL_ASSERT(SizeOfCodeGeneratedSince(&start) == kLogParamsOffset);
dc32(parameters); #else // Emit nothing on real hardware.
USE(parameters); #endif
}
// We allow only printable characters in the marker names. Unprintable // characters are reserved for controlling features of the instrumentation.
VIXL_ASSERT(isprint(marker_name[0]) && isprint(marker_name[1]));
void UseScratchRegisterScope::Include(const CPURegList& list) {
VIXL_ASSERT(initialised_); if (list.type() == CPURegister::kRegister) { // Make sure that neither sp nor xzr are included the list.
IncludeByRegList(available_, list.list() & ~(xzr.Bit() | sp.Bit()));
} else {
VIXL_ASSERT(list.type() == CPURegister::kVRegister);
IncludeByRegList(availablefp_, list.list());
}
}
void UseScratchRegisterScope::Include(constRegister& reg1, constRegister& reg2, constRegister& reg3, constRegister& reg4) {
VIXL_ASSERT(initialised_);
RegList include = reg1.Bit() | reg2.Bit() | reg3.Bit() | reg4.Bit(); // Make sure that neither sp nor xzr are included the list.
include &= ~(xzr.Bit() | sp.Bit());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.