blob: c7060b57a287f9080219372adb13286bb3e78522 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/arm64/MacroAssembler-arm64.h"
#include "jit/arm64/MoveEmitter-arm64.h"
#include "jit/arm64/SharedICRegisters-arm64.h"
#include "jit/Bailouts.h"
#include "jit/BaselineFrame.h"
#include "jit/MacroAssembler.h"
#include "jit/MacroAssembler-inl.h"
namespace js {
namespace jit {
void
MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output)
{
ARMRegister dest(output, 32);
Fcvtns(dest, ARMFPRegister(input, 64));
{
vixl::UseScratchRegisterScope temps(this);
const ARMRegister scratch32 = temps.AcquireW();
Mov(scratch32, Operand(0xff));
Cmp(dest, scratch32);
Csel(dest, dest, scratch32, LessThan);
}
Cmp(dest, Operand(0));
Csel(dest, dest, wzr, GreaterThan);
}
void
MacroAssembler::alignFrameForICArguments(MacroAssembler::AfterICSaveLive& aic)
{
// Exists for MIPS compatibility.
}
void
MacroAssembler::restoreFrameAlignmentForICArguments(MacroAssembler::AfterICSaveLive& aic)
{
// Exists for MIPS compatibility.
}
js::jit::MacroAssembler&
MacroAssemblerCompat::asMasm()
{
return *static_cast<js::jit::MacroAssembler*>(this);
}
const js::jit::MacroAssembler&
MacroAssemblerCompat::asMasm() const
{
return *static_cast<const js::jit::MacroAssembler*>(this);
}
vixl::MacroAssembler&
MacroAssemblerCompat::asVIXL()
{
return *static_cast<vixl::MacroAssembler*>(this);
}
const vixl::MacroAssembler&
MacroAssemblerCompat::asVIXL() const
{
return *static_cast<const vixl::MacroAssembler*>(this);
}
BufferOffset
MacroAssemblerCompat::movePatchablePtr(ImmPtr ptr, Register dest)
{
const size_t numInst = 1; // Inserting one load instruction.
const unsigned numPoolEntries = 2; // Every pool entry is 4 bytes.
uint8_t* literalAddr = (uint8_t*)(&ptr.value); // TODO: Should be const.
// Scratch space for generating the load instruction.
//
// allocEntry() will use InsertIndexIntoTag() to store a temporary
// index to the corresponding PoolEntry in the instruction itself.
//
// That index will be fixed up later when finishPool()
// walks over all marked loads and calls PatchConstantPoolLoad().
uint32_t instructionScratch = 0;
// Emit the instruction mask in the scratch space.
// The offset doesn't matter: it will be fixed up later.
vixl::Assembler::ldr((Instruction*)&instructionScratch, ARMRegister(dest, 64), 0);
// Add the entry to the pool, fix up the LDR imm19 offset,
// and add the completed instruction to the buffer.
return allocEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
literalAddr);
}
BufferOffset
MacroAssemblerCompat::movePatchablePtr(ImmWord ptr, Register dest)
{
const size_t numInst = 1; // Inserting one load instruction.
const unsigned numPoolEntries = 2; // Every pool entry is 4 bytes.
uint8_t* literalAddr = (uint8_t*)(&ptr.value);
// Scratch space for generating the load instruction.
//
// allocEntry() will use InsertIndexIntoTag() to store a temporary
// index to the corresponding PoolEntry in the instruction itself.
//
// That index will be fixed up later when finishPool()
// walks over all marked loads and calls PatchConstantPoolLoad().
uint32_t instructionScratch = 0;
// Emit the instruction mask in the scratch space.
// The offset doesn't matter: it will be fixed up later.
vixl::Assembler::ldr((Instruction*)&instructionScratch, ARMRegister(dest, 64), 0);
// Add the entry to the pool, fix up the LDR imm19 offset,
// and add the completed instruction to the buffer.
return allocEntry(numInst, numPoolEntries, (uint8_t*)&instructionScratch,
literalAddr);
}
void
MacroAssemblerCompat::loadPrivate(const Address& src, Register dest)
{
loadPtr(src, dest);
asMasm().lshiftPtr(Imm32(1), dest);
}
void
MacroAssemblerCompat::handleFailureWithHandlerTail(void* handler)
{
// Reserve space for exception information.
int64_t size = (sizeof(ResumeFromException) + 7) & ~7;
Sub(GetStackPointer64(), GetStackPointer64(), Operand(size));
if (!GetStackPointer64().Is(sp))
Mov(sp, GetStackPointer64());
Mov(x0, GetStackPointer64());
// Call the handler.
asMasm().setupUnalignedABICall(r1);
asMasm().passABIArg(r0);
asMasm().callWithABI(handler);
Label entryFrame;
Label catch_;
Label finally;
Label return_;
Label bailout;
MOZ_ASSERT(GetStackPointer64().Is(x28)); // Lets the code below be a little cleaner.
loadPtr(Address(r28, offsetof(ResumeFromException, kind)), r0);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_CATCH), &catch_);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout);
breakpoint(); // Invalid kind.
// No exception handler. Load the error value, load the new stack pointer,
// and return from the entry frame.
bind(&entryFrame);
moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
loadPtr(Address(r28, offsetof(ResumeFromException, stackPointer)), r28);
retn(Imm32(1 * sizeof(void*))); // Pop from stack and return.
// If we found a catch handler, this must be a baseline frame. Restore state
// and jump to the catch block.
bind(&catch_);
loadPtr(Address(r28, offsetof(ResumeFromException, target)), r0);
loadPtr(Address(r28, offsetof(ResumeFromException, framePointer)), BaselineFrameReg);
loadPtr(Address(r28, offsetof(ResumeFromException, stackPointer)), r28);
syncStackPtr();
Br(x0);
// If we found a finally block, this must be a baseline frame.
// Push two values expected by JSOP_RETSUB: BooleanValue(true)
// and the exception.
bind(&finally);
ARMRegister exception = x1;
Ldr(exception, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, exception)));
Ldr(x0, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, target)));
Ldr(ARMRegister(BaselineFrameReg, 64),
MemOperand(GetStackPointer64(), offsetof(ResumeFromException, framePointer)));
Ldr(GetStackPointer64(), MemOperand(GetStackPointer64(), offsetof(ResumeFromException, stackPointer)));
syncStackPtr();
pushValue(BooleanValue(true));
push(exception);
Br(x0);
// Only used in debug mode. Return BaselineFrame->returnValue() to the caller.
bind(&return_);
loadPtr(Address(r28, offsetof(ResumeFromException, framePointer)), BaselineFrameReg);
loadPtr(Address(r28, offsetof(ResumeFromException, stackPointer)), r28);
loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfReturnValue()),
JSReturnOperand);
movePtr(BaselineFrameReg, r28);
vixl::MacroAssembler::Pop(ARMRegister(BaselineFrameReg, 64), vixl::lr);
syncStackPtr();
vixl::MacroAssembler::Ret(vixl::lr);
// If we are bailing out to baseline to handle an exception,
// jump to the bailout tail stub.
bind(&bailout);
Ldr(x2, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, bailoutInfo)));
Ldr(x1, MemOperand(GetStackPointer64(), offsetof(ResumeFromException, target)));
Mov(x0, BAILOUT_RETURN_OK);
Br(x1);
}
void
MacroAssemblerCompat::branchPtrInNurseryRange(Condition cond, Register ptr, Register temp,
Label* label)
{
MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
MOZ_ASSERT(ptr != temp);
MOZ_ASSERT(ptr != ScratchReg && ptr != ScratchReg2); // Both may be used internally.
MOZ_ASSERT(temp != ScratchReg && temp != ScratchReg2);
const Nursery& nursery = GetJitContext()->runtime->gcNursery();
movePtr(ImmWord(-ptrdiff_t(nursery.start())), temp);
addPtr(ptr, temp);
branchPtr(cond == Assembler::Equal ? Assembler::Below : Assembler::AboveOrEqual,
temp, ImmWord(nursery.nurserySize()), label);
}
void
MacroAssemblerCompat::branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp,
Label* label)
{
MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
MOZ_ASSERT(temp != ScratchReg && temp != ScratchReg2); // Both may be used internally.
const Nursery& nursery = GetJitContext()->runtime->gcNursery();
// Avoid creating a bogus ObjectValue below.
if (!nursery.exists())
return;
// 'Value' representing the start of the nursery tagged as a JSObject
Value start = ObjectValue(*reinterpret_cast<JSObject*>(nursery.start()));
movePtr(ImmWord(-ptrdiff_t(start.asRawBits())), temp);
addPtr(value.valueReg(), temp);
branchPtr(cond == Assembler::Equal ? Assembler::Below : Assembler::AboveOrEqual,
temp, ImmWord(nursery.nurserySize()), label);
}
void
MacroAssemblerCompat::breakpoint()
{
static int code = 0xA77;
Brk((code++) & 0xffff);
}
template<typename T>
void
MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
Register oldval, Register newval,
Register temp, AnyRegister output)
{
switch (arrayType) {
case Scalar::Int8:
compareExchange8SignExtend(mem, oldval, newval, output.gpr());
break;
case Scalar::Uint8:
compareExchange8ZeroExtend(mem, oldval, newval, output.gpr());
break;
case Scalar::Int16:
compareExchange16SignExtend(mem, oldval, newval, output.gpr());
break;
case Scalar::Uint16:
compareExchange16ZeroExtend(mem, oldval, newval, output.gpr());
break;
case Scalar::Int32:
compareExchange32(mem, oldval, newval, output.gpr());
break;
case Scalar::Uint32:
// At the moment, the code in MCallOptimize.cpp requires the output
// type to be double for uint32 arrays. See bug 1077305.
MOZ_ASSERT(output.isFloat());
compareExchange32(mem, oldval, newval, temp);
convertUInt32ToDouble(temp, output.fpu());
break;
default:
MOZ_CRASH("Invalid typed array type");
}
}
template void
MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
Register oldval, Register newval, Register temp,
AnyRegister output);
template void
MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
Register oldval, Register newval, Register temp,
AnyRegister output);
template<typename T>
void
MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem,
Register value, Register temp, AnyRegister output)
{
switch (arrayType) {
case Scalar::Int8:
atomicExchange8SignExtend(mem, value, output.gpr());
break;
case Scalar::Uint8:
atomicExchange8ZeroExtend(mem, value, output.gpr());
break;
case Scalar::Int16:
atomicExchange16SignExtend(mem, value, output.gpr());
break;
case Scalar::Uint16:
atomicExchange16ZeroExtend(mem, value, output.gpr());
break;
case Scalar::Int32:
atomicExchange32(mem, value, output.gpr());
break;
case Scalar::Uint32:
// At the moment, the code in MCallOptimize.cpp requires the output
// type to be double for uint32 arrays. See bug 1077305.
MOZ_ASSERT(output.isFloat());
atomicExchange32(mem, value, temp);
convertUInt32ToDouble(temp, output.fpu());
break;
default:
MOZ_CRASH("Invalid typed array type");
}
}
template void
MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem,
Register value, Register temp, AnyRegister output);
template void
MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem,
Register value, Register temp, AnyRegister output);
//{{{ check_macroassembler_style
// ===============================================================
// Stack manipulation functions.
void
MacroAssembler::PushRegsInMask(LiveRegisterSet set)
{
for (GeneralRegisterBackwardIterator iter(set.gprs()); iter.more(); ) {
vixl::CPURegister src[4] = { vixl::NoCPUReg, vixl::NoCPUReg, vixl::NoCPUReg, vixl::NoCPUReg };
for (size_t i = 0; i < 4 && iter.more(); i++) {
src[i] = ARMRegister(*iter, 64);
++iter;
adjustFrame(8);
}
vixl::MacroAssembler::Push(src[0], src[1], src[2], src[3]);
}
for (FloatRegisterBackwardIterator iter(set.fpus().reduceSetForPush()); iter.more(); ) {
vixl::CPURegister src[4] = { vixl::NoCPUReg, vixl::NoCPUReg, vixl::NoCPUReg, vixl::NoCPUReg };
for (size_t i = 0; i < 4 && iter.more(); i++) {
src[i] = ARMFPRegister(*iter, 64);
++iter;
adjustFrame(8);
}
vixl::MacroAssembler::Push(src[0], src[1], src[2], src[3]);
}
}
void
MacroAssembler::PopRegsInMaskIgnore(LiveRegisterSet set, LiveRegisterSet ignore)
{
// The offset of the data from the stack pointer.
uint32_t offset = 0;
for (FloatRegisterIterator iter(set.fpus().reduceSetForPush()); iter.more(); ) {
vixl::CPURegister dest[2] = { vixl::NoCPUReg, vixl::NoCPUReg };
uint32_t nextOffset = offset;
for (size_t i = 0; i < 2 && iter.more(); i++) {
if (!ignore.has(*iter))
dest[i] = ARMFPRegister(*iter, 64);
++iter;
nextOffset += sizeof(double);
}
if (!dest[0].IsNone() && !dest[1].IsNone())
Ldp(dest[0], dest[1], MemOperand(GetStackPointer64(), offset));
else if (!dest[0].IsNone())
Ldr(dest[0], MemOperand(GetStackPointer64(), offset));
else if (!dest[1].IsNone())
Ldr(dest[1], MemOperand(GetStackPointer64(), offset + sizeof(double)));
offset = nextOffset;
}
MOZ_ASSERT(offset == set.fpus().getPushSizeInBytes());
for (GeneralRegisterIterator iter(set.gprs()); iter.more(); ) {
vixl::CPURegister dest[2] = { vixl::NoCPUReg, vixl::NoCPUReg };
uint32_t nextOffset = offset;
for (size_t i = 0; i < 2 && iter.more(); i++) {
if (!ignore.has(*iter))
dest[i] = ARMRegister(*iter, 64);
++iter;
nextOffset += sizeof(uint64_t);
}
if (!dest[0].IsNone() && !dest[1].IsNone())
Ldp(dest[0], dest[1], MemOperand(GetStackPointer64(), offset));
else if (!dest[0].IsNone())
Ldr(dest[0], MemOperand(GetStackPointer64(), offset));
else if (!dest[1].IsNone())
Ldr(dest[1], MemOperand(GetStackPointer64(), offset + sizeof(uint64_t)));
offset = nextOffset;
}
size_t bytesPushed = set.gprs().size() * sizeof(uint64_t) + set.fpus().getPushSizeInBytes();
MOZ_ASSERT(offset == bytesPushed);
freeStack(bytesPushed);
}
void
MacroAssembler::Push(Register reg)
{
push(reg);
adjustFrame(sizeof(intptr_t));
}
void
MacroAssembler::Push(Register reg1, Register reg2, Register reg3, Register reg4)
{
push(reg1, reg2, reg3, reg4);
adjustFrame(4 * sizeof(intptr_t));
}
void
MacroAssembler::Push(const Imm32 imm)
{
push(imm);
adjustFrame(sizeof(intptr_t));
}
void
MacroAssembler::Push(const ImmWord imm)
{
push(imm);
adjustFrame(sizeof(intptr_t));
}
void
MacroAssembler::Push(const ImmPtr imm)
{
push(imm);
adjustFrame(sizeof(intptr_t));
}
void
MacroAssembler::Push(const ImmGCPtr ptr)
{
push(ptr);
adjustFrame(sizeof(intptr_t));
}
void
MacroAssembler::Push(FloatRegister f)
{
push(f);
adjustFrame(sizeof(double));
}
void
MacroAssembler::Pop(Register reg)
{
pop(reg);
adjustFrame(-1 * int64_t(sizeof(int64_t)));
}
void
MacroAssembler::Pop(const ValueOperand& val)
{
pop(val);
adjustFrame(-1 * int64_t(sizeof(int64_t)));
}
void
MacroAssembler::reserveStack(uint32_t amount)
{
// TODO: This bumps |sp| every time we reserve using a second register.
// It would save some instructions if we had a fixed frame size.
vixl::MacroAssembler::Claim(Operand(amount));
adjustFrame(amount);
}
// ===============================================================
// Simple call functions.
CodeOffset
MacroAssembler::call(Register reg)
{
syncStackPtr();
Blr(ARMRegister(reg, 64));
return CodeOffset(currentOffset());
}
CodeOffset
MacroAssembler::call(Label* label)
{
syncStackPtr();
Bl(label);
return CodeOffset(currentOffset());
}
void
MacroAssembler::call(ImmWord imm)
{
call(ImmPtr((void*)imm.value));
}
void
MacroAssembler::call(ImmPtr imm)
{
syncStackPtr();
movePtr(imm, ip0);
Blr(vixl::ip0);
}
void
MacroAssembler::call(wasm::SymbolicAddress imm)
{
vixl::UseScratchRegisterScope temps(this);
const Register scratch = temps.AcquireX().asUnsized();
syncStackPtr();
movePtr(imm, scratch);
call(scratch);
}
void
MacroAssembler::call(JitCode* c)
{
vixl::UseScratchRegisterScope temps(this);
const ARMRegister scratch64 = temps.AcquireX();
syncStackPtr();
BufferOffset off = immPool64(scratch64, uint64_t(c->raw()));
addPendingJump(off, ImmPtr(c->raw()), Relocation::JITCODE);
blr(scratch64);
}
CodeOffset
MacroAssembler::callWithPatch()
{
MOZ_CRASH("NYI");
return CodeOffset();
}
void
MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::pushReturnAddress()
{
push(lr);
}
// ===============================================================
// ABI function calls.
void
MacroAssembler::setupUnalignedABICall(Register scratch)
{
setupABICall();
dynamicAlignment_ = true;
int64_t alignment = ~(int64_t(ABIStackAlignment) - 1);
ARMRegister scratch64(scratch, 64);
// Always save LR -- Baseline ICs assume that LR isn't modified.
push(lr);
// Unhandled for sp -- needs slightly different logic.
MOZ_ASSERT(!GetStackPointer64().Is(sp));
// Remember the stack address on entry.
Mov(scratch64, GetStackPointer64());
// Make alignment, including the effective push of the previous sp.
Sub(GetStackPointer64(), GetStackPointer64(), Operand(8));
And(GetStackPointer64(), GetStackPointer64(), Operand(alignment));
// If the PseudoStackPointer is used, sp must be <= psp before a write is valid.
syncStackPtr();
// Store previous sp to the top of the stack, aligned.
Str(scratch64, MemOperand(GetStackPointer64(), 0));
}
void
MacroAssembler::callWithABIPre(uint32_t* stackAdjust, bool callFromAsmJS)
{
MOZ_ASSERT(inCall_);
uint32_t stackForCall = abiArgs_.stackBytesConsumedSoFar();
// ARM64 /really/ wants the stack to always be aligned. Since we're already tracking it
// getting it aligned for an abi call is pretty easy.
MOZ_ASSERT(dynamicAlignment_);
stackForCall += ComputeByteAlignment(stackForCall, StackAlignment);
*stackAdjust = stackForCall;
reserveStack(*stackAdjust);
{
moveResolver_.resolve();
MoveEmitter emitter(*this);
emitter.emit(moveResolver_);
emitter.finish();
}
// Call boundaries communicate stack via sp.
syncStackPtr();
}
void
MacroAssembler::callWithABIPost(uint32_t stackAdjust, MoveOp::Type result)
{
// Call boundaries communicate stack via sp.
if (!GetStackPointer64().Is(sp))
Mov(GetStackPointer64(), sp);
freeStack(stackAdjust);
// Restore the stack pointer from entry.
if (dynamicAlignment_)
Ldr(GetStackPointer64(), MemOperand(GetStackPointer64(), 0));
// Restore LR.
pop(lr);
// TODO: This one shouldn't be necessary -- check that callers
// aren't enforcing the ABI themselves!
syncStackPtr();
// If the ABI's return regs are where ION is expecting them, then
// no other work needs to be done.
#ifdef DEBUG
MOZ_ASSERT(inCall_);
inCall_ = false;
#endif
}
void
MacroAssembler::callWithABINoProfiler(Register fun, MoveOp::Type result)
{
vixl::UseScratchRegisterScope temps(this);
const Register scratch = temps.AcquireX().asUnsized();
movePtr(fun, scratch);
uint32_t stackAdjust;
callWithABIPre(&stackAdjust);
call(scratch);
callWithABIPost(stackAdjust, result);
}
void
MacroAssembler::callWithABINoProfiler(const Address& fun, MoveOp::Type result)
{
vixl::UseScratchRegisterScope temps(this);
const Register scratch = temps.AcquireX().asUnsized();
loadPtr(fun, scratch);
uint32_t stackAdjust;
callWithABIPre(&stackAdjust);
call(scratch);
callWithABIPost(stackAdjust, result);
}
// ===============================================================
// Jit Frames.
uint32_t
MacroAssembler::pushFakeReturnAddress(Register scratch)
{
enterNoPool(3);
Label fakeCallsite;
Adr(ARMRegister(scratch, 64), &fakeCallsite);
Push(scratch);
bind(&fakeCallsite);
uint32_t pseudoReturnOffset = currentOffset();
leaveNoPool();
return pseudoReturnOffset;
}
//}}} check_macroassembler_style
} // namespace jit
} // namespace js