blob: de8b4e2e779ccbc54cdb9ec5877d0595b91d4987 [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/. */
#ifndef jit_x86_Assembler_x86_h
#define jit_x86_Assembler_x86_h
#include "jit/shared/Assembler-shared.h"
#include "assembler/assembler/X86Assembler.h"
#include "jit/CompactBuffer.h"
#include "jit/IonCode.h"
#include "mozilla/Util.h"
namespace js {
namespace jit {
static const MOZ_CONSTEXPR Register eax = { JSC::X86Registers::eax };
static const MOZ_CONSTEXPR Register ecx = { JSC::X86Registers::ecx };
static const MOZ_CONSTEXPR Register edx = { JSC::X86Registers::edx };
static const MOZ_CONSTEXPR Register ebx = { JSC::X86Registers::ebx };
static const MOZ_CONSTEXPR Register esp = { JSC::X86Registers::esp };
static const MOZ_CONSTEXPR Register ebp = { JSC::X86Registers::ebp };
static const MOZ_CONSTEXPR Register esi = { JSC::X86Registers::esi };
static const MOZ_CONSTEXPR Register edi = { JSC::X86Registers::edi };
static const MOZ_CONSTEXPR FloatRegister xmm0 = { JSC::X86Registers::xmm0 };
static const MOZ_CONSTEXPR FloatRegister xmm1 = { JSC::X86Registers::xmm1 };
static const MOZ_CONSTEXPR FloatRegister xmm2 = { JSC::X86Registers::xmm2 };
static const MOZ_CONSTEXPR FloatRegister xmm3 = { JSC::X86Registers::xmm3 };
static const MOZ_CONSTEXPR FloatRegister xmm4 = { JSC::X86Registers::xmm4 };
static const MOZ_CONSTEXPR FloatRegister xmm5 = { JSC::X86Registers::xmm5 };
static const MOZ_CONSTEXPR FloatRegister xmm6 = { JSC::X86Registers::xmm6 };
static const MOZ_CONSTEXPR FloatRegister xmm7 = { JSC::X86Registers::xmm7 };
static const MOZ_CONSTEXPR Register InvalidReg = { JSC::X86Registers::invalid_reg };
static const MOZ_CONSTEXPR FloatRegister InvalidFloatReg = { JSC::X86Registers::invalid_xmm };
static const MOZ_CONSTEXPR Register JSReturnReg_Type = ecx;
static const MOZ_CONSTEXPR Register JSReturnReg_Data = edx;
static const MOZ_CONSTEXPR Register StackPointer = esp;
static const MOZ_CONSTEXPR Register FramePointer = ebp;
static const MOZ_CONSTEXPR Register ReturnReg = eax;
static const MOZ_CONSTEXPR FloatRegister ReturnFloatReg = xmm0;
static const MOZ_CONSTEXPR FloatRegister ScratchFloatReg = xmm7;
static const MOZ_CONSTEXPR Register ArgumentsRectifierReg = esi;
static const MOZ_CONSTEXPR Register CallTempReg0 = edi;
static const MOZ_CONSTEXPR Register CallTempReg1 = eax;
static const MOZ_CONSTEXPR Register CallTempReg2 = ebx;
static const MOZ_CONSTEXPR Register CallTempReg3 = ecx;
static const MOZ_CONSTEXPR Register CallTempReg4 = esi;
static const MOZ_CONSTEXPR Register CallTempReg5 = edx;
static const MOZ_CONSTEXPR Register CallTempReg6 = ebp;
// We have no arg regs, so our NonArgRegs are just our CallTempReg*
static const MOZ_CONSTEXPR Register CallTempNonArgRegs[] = { edi, eax, ebx, ecx, esi, edx };
static const uint32_t NumCallTempNonArgRegs =
mozilla::ArrayLength(CallTempNonArgRegs);
class ABIArgGenerator
{
uint32_t stackOffset_;
ABIArg current_;
public:
ABIArgGenerator();
ABIArg next(MIRType argType);
ABIArg &current() { return current_; }
uint32_t stackBytesConsumedSoFar() const { return stackOffset_; }
// Note: these registers are all guaranteed to be different
static const Register NonArgReturnVolatileReg0;
static const Register NonArgReturnVolatileReg1;
static const Register NonVolatileReg;
};
static const MOZ_CONSTEXPR Register OsrFrameReg = edx;
static const MOZ_CONSTEXPR Register PreBarrierReg = edx;
// GCC stack is aligned on 16 bytes, but we don't maintain the invariant in
// jitted code.
#if defined(__GNUC__)
static const uint32_t StackAlignment = 16;
#else
static const uint32_t StackAlignment = 4;
#endif
static const bool StackKeptAligned = false;
static const uint32_t CodeAlignment = 8;
static const uint32_t NativeFrameSize = sizeof(void*);
static const uint32_t AlignmentAtPrologue = sizeof(void*);
static const uint32_t AlignmentMidPrologue = AlignmentAtPrologue;
struct ImmTag : public Imm32
{
ImmTag(JSValueTag mask)
: Imm32(int32_t(mask))
{ }
};
struct ImmType : public ImmTag
{
ImmType(JSValueType type)
: ImmTag(JSVAL_TYPE_TO_TAG(type))
{ }
};
static const Scale ScalePointer = TimesFour;
class Operand
{
public:
enum Kind {
REG,
REG_DISP,
FPREG,
SCALE,
ADDRESS
};
Kind kind_ : 4;
int32_t index_ : 5;
Scale scale_ : 3;
int32_t base_;
int32_t disp_;
public:
explicit Operand(Register reg)
: kind_(REG),
base_(reg.code())
{ }
explicit Operand(FloatRegister reg)
: kind_(FPREG),
base_(reg.code())
{ }
explicit Operand(const Address &address)
: kind_(REG_DISP),
base_(address.base.code()),
disp_(address.offset)
{ }
explicit Operand(const BaseIndex &address)
: kind_(SCALE),
index_(address.index.code()),
scale_(address.scale),
base_(address.base.code()),
disp_(address.offset)
{ }
Operand(Register base, Register index, Scale scale, int32_t disp = 0)
: kind_(SCALE),
index_(index.code()),
scale_(scale),
base_(base.code()),
disp_(disp)
{ }
Operand(Register reg, int32_t disp)
: kind_(REG_DISP),
base_(reg.code()),
disp_(disp)
{ }
explicit Operand(const AbsoluteAddress &address)
: kind_(ADDRESS),
base_(reinterpret_cast<int32_t>(address.addr))
{ }
explicit Operand(const void *address)
: kind_(ADDRESS),
base_(reinterpret_cast<int32_t>(address))
{ }
Address toAddress() {
JS_ASSERT(kind() == REG_DISP);
return Address(Register::FromCode(base()), disp());
}
BaseIndex toBaseIndex() {
JS_ASSERT(kind() == SCALE);
return BaseIndex(Register::FromCode(base()), Register::FromCode(index()), scale(), disp());
}
Kind kind() const {
return kind_;
}
Registers::Code reg() const {
JS_ASSERT(kind() == REG);
return (Registers::Code)base_;
}
Registers::Code base() const {
JS_ASSERT(kind() == REG_DISP || kind() == SCALE);
return (Registers::Code)base_;
}
Registers::Code index() const {
JS_ASSERT(kind() == SCALE);
return (Registers::Code)index_;
}
Scale scale() const {
JS_ASSERT(kind() == SCALE);
return scale_;
}
FloatRegisters::Code fpu() const {
JS_ASSERT(kind() == FPREG);
return (FloatRegisters::Code)base_;
}
int32_t disp() const {
JS_ASSERT(kind() == REG_DISP || kind() == SCALE);
return disp_;
}
void *address() const {
JS_ASSERT(kind() == ADDRESS);
return reinterpret_cast<void *>(base_);
}
};
} // namespace jit
} // namespace js
#include "jit/shared/Assembler-x86-shared.h"
namespace js {
namespace jit {
static inline void
PatchJump(CodeLocationJump jump, CodeLocationLabel label)
{
#ifdef DEBUG
// Assert that we're overwriting a jump instruction, either:
// 0F 80+cc <imm32>, or
// E9 <imm32>
unsigned char *x = (unsigned char *)jump.raw() - 5;
JS_ASSERT(((*x >= 0x80 && *x <= 0x8F) && *(x - 1) == 0x0F) ||
(*x == 0xE9));
#endif
JSC::X86Assembler::setRel32(jump.raw(), label.raw());
}
// Return operand from a JS -> JS call.
static const ValueOperand JSReturnOperand = ValueOperand(JSReturnReg_Type, JSReturnReg_Data);
class Assembler : public AssemblerX86Shared
{
void writeRelocation(JmpSrc src) {
jumpRelocations_.writeUnsigned(src.offset());
}
void addPendingJump(JmpSrc src, void *target, Relocation::Kind kind) {
enoughMemory_ &= jumps_.append(RelativePatch(src.offset(), target, kind));
if (kind == Relocation::IONCODE)
writeRelocation(src);
}
public:
using AssemblerX86Shared::movl;
using AssemblerX86Shared::j;
using AssemblerX86Shared::jmp;
using AssemblerX86Shared::movsd;
using AssemblerX86Shared::retarget;
using AssemblerX86Shared::cmpl;
using AssemblerX86Shared::call;
using AssemblerX86Shared::push;
static void TraceJumpRelocations(JSTracer *trc, IonCode *code, CompactBufferReader &reader);
// Copy the assembly code to the given buffer, and perform any pending
// relocations relying on the target address.
void executableCopy(uint8_t *buffer);
// Actual assembly emitting functions.
void push(const ImmGCPtr &ptr) {
push(Imm32(ptr.value));
writeDataRelocation(ptr);
}
void push(const ImmWord imm) {
push(Imm32(imm.value));
}
void push(const FloatRegister &src) {
subl(Imm32(sizeof(double)), StackPointer);
movsd(src, Operand(StackPointer, 0));
}
CodeOffsetLabel pushWithPatch(const ImmWord &word) {
push(Imm32(word.value));
return masm.currentOffset();
}
CodeOffsetLabel movWithPatch(const ImmWord &word, const Register &dest) {
movl(Imm32(word.value), dest);
return masm.currentOffset();
}
void movl(const ImmGCPtr &ptr, const Register &dest) {
masm.movl_i32r(ptr.value, dest.code());
writeDataRelocation(ptr);
}
void movl(const ImmGCPtr &ptr, const Operand &dest) {
switch (dest.kind()) {
case Operand::REG:
masm.movl_i32r(ptr.value, dest.reg());
writeDataRelocation(ptr);
break;
case Operand::REG_DISP:
masm.movl_i32m(ptr.value, dest.disp(), dest.base());
writeDataRelocation(ptr);
break;
case Operand::SCALE:
masm.movl_i32m(ptr.value, dest.disp(), dest.base(), dest.index(), dest.scale());
writeDataRelocation(ptr);
break;
default:
JS_NOT_REACHED("unexpected operand kind");
}
}
void movl(ImmWord imm, Register dest) {
masm.movl_i32r(imm.value, dest.code());
}
void mov(ImmWord imm, Register dest) {
movl(imm, dest);
}
void mov(Imm32 imm, Register dest) {
movl(imm, dest);
}
void mov(const Operand &src, const Register &dest) {
movl(src, dest);
}
void mov(const Register &src, const Operand &dest) {
movl(src, dest);
}
void mov(Imm32 imm, const Operand &dest) {
movl(imm, dest);
}
void mov(AbsoluteLabel *label, const Register &dest) {
JS_ASSERT(!label->bound());
// Thread the patch list through the unpatched address word in the
// instruction stream.
masm.movl_i32r(label->prev(), dest.code());
label->setPrev(masm.size());
}
void mov(const Register &src, const Register &dest) {
movl(src, dest);
}
void lea(const Operand &src, const Register &dest) {
return leal(src, dest);
}
void cmpl(const Register src, ImmWord ptr) {
masm.cmpl_ir(ptr.value, src.code());
}
void cmpl(const Register src, ImmGCPtr ptr) {
masm.cmpl_ir(ptr.value, src.code());
writeDataRelocation(ptr);
}
void cmpl(const Register &lhs, const Register &rhs) {
masm.cmpl_rr(rhs.code(), lhs.code());
}
void cmpl(const Operand &op, ImmGCPtr imm) {
switch (op.kind()) {
case Operand::REG:
masm.cmpl_ir_force32(imm.value, op.reg());
writeDataRelocation(imm);
break;
case Operand::REG_DISP:
masm.cmpl_im_force32(imm.value, op.disp(), op.base());
writeDataRelocation(imm);
break;
case Operand::ADDRESS:
masm.cmpl_im(imm.value, op.address());
writeDataRelocation(imm);
break;
default:
JS_NOT_REACHED("unexpected operand kind");
}
}
CodeOffsetLabel cmplWithPatch(const Register &lhs, Imm32 rhs) {
masm.cmpl_ir_force32(rhs.value, lhs.code());
return masm.currentOffset();
}
void jmp(void *target, Relocation::Kind reloc = Relocation::HARDCODED) {
JmpSrc src = masm.jmp();
addPendingJump(src, target, reloc);
}
void j(Condition cond, void *target,
Relocation::Kind reloc = Relocation::HARDCODED) {
JmpSrc src = masm.jCC(static_cast<JSC::X86Assembler::Condition>(cond));
addPendingJump(src, target, reloc);
}
void jmp(IonCode *target) {
jmp(target->raw(), Relocation::IONCODE);
}
void j(Condition cond, IonCode *target) {
j(cond, target->raw(), Relocation::IONCODE);
}
void call(IonCode *target) {
JmpSrc src = masm.call();
addPendingJump(src, target->raw(), Relocation::IONCODE);
}
void call(ImmWord target) {
JmpSrc src = masm.call();
addPendingJump(src, target.asPointer(), Relocation::HARDCODED);
}
// Emit a CALL or CMP (nop) instruction. ToggleCall can be used to patch
// this instruction.
CodeOffsetLabel toggledCall(IonCode *target, bool enabled) {
CodeOffsetLabel offset(size());
JmpSrc src = enabled ? masm.call() : masm.cmp_eax();
addPendingJump(src, target->raw(), Relocation::IONCODE);
JS_ASSERT(size() - offset.offset() == ToggledCallSize());
return offset;
}
static size_t ToggledCallSize() {
// Size of a call instruction.
return 5;
}
// Re-routes pending jumps to an external target, flushing the label in the
// process.
void retarget(Label *label, void *target, Relocation::Kind reloc) {
JSC::MacroAssembler::Label jsclabel;
if (label->used()) {
bool more;
JSC::X86Assembler::JmpSrc jmp(label->offset());
do {
JSC::X86Assembler::JmpSrc next;
more = masm.nextJump(jmp, &next);
addPendingJump(jmp, target, reloc);
jmp = next;
} while (more);
}
label->reset();
}
void movsd(const double *dp, const FloatRegister &dest) {
JS_ASSERT(HasSSE2());
masm.movsd_mr((const void *)dp, dest.code());
}
// Move a 32-bit immediate into a register where the immediate can be
// patched.
CodeOffsetLabel movlWithPatch(Imm32 imm, Register dest) {
masm.movl_i32r(imm.value, dest.code());
return masm.currentOffset();
}
// Load from *addr where addr can be patched.
CodeOffsetLabel movlWithPatch(void *addr, Register dest) {
masm.movl_mr(addr, dest.code());
return masm.currentOffset();
}
CodeOffsetLabel movsdWithPatch(void *addr, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.movsd_mr(addr, dest.code());
return masm.currentOffset();
}
// Store to *addr where addr can be patched
CodeOffsetLabel movlWithPatch(Register src, void *addr) {
masm.movl_rm(src.code(), addr);
return masm.currentOffset();
}
CodeOffsetLabel movsdWithPatch(FloatRegister dest, void *addr) {
JS_ASSERT(HasSSE2());
masm.movsd_rm(dest.code(), addr);
return masm.currentOffset();
}
// Load from *(base + disp32) where disp32 can be patched.
CodeOffsetLabel movxblWithPatch(Address src, Register dest) {
masm.movxbl_mr_disp32(src.offset, src.base.code(), dest.code());
return masm.currentOffset();
}
CodeOffsetLabel movzblWithPatch(Address src, Register dest) {
masm.movzbl_mr_disp32(src.offset, src.base.code(), dest.code());
return masm.currentOffset();
}
CodeOffsetLabel movxwlWithPatch(Address src, Register dest) {
masm.movxwl_mr_disp32(src.offset, src.base.code(), dest.code());
return masm.currentOffset();
}
CodeOffsetLabel movzwlWithPatch(Address src, Register dest) {
masm.movzwl_mr_disp32(src.offset, src.base.code(), dest.code());
return masm.currentOffset();
}
CodeOffsetLabel movlWithPatch(Address src, Register dest) {
masm.movl_mr_disp32(src.offset, src.base.code(), dest.code());
return masm.currentOffset();
}
CodeOffsetLabel movssWithPatch(Address src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.movss_mr_disp32(src.offset, src.base.code(), dest.code());
return masm.currentOffset();
}
CodeOffsetLabel movsdWithPatch(Address src, FloatRegister dest) {
JS_ASSERT(HasSSE2());
masm.movsd_mr_disp32(src.offset, src.base.code(), dest.code());
return masm.currentOffset();
}
// Store to *(base + disp32) where disp32 can be patched.
CodeOffsetLabel movbWithPatch(Register src, Address dest) {
masm.movb_rm_disp32(src.code(), dest.offset, dest.base.code());
return masm.currentOffset();
}
CodeOffsetLabel movwWithPatch(Register src, Address dest) {
masm.movw_rm_disp32(src.code(), dest.offset, dest.base.code());
return masm.currentOffset();
}
CodeOffsetLabel movlWithPatch(Register src, Address dest) {
masm.movl_rm_disp32(src.code(), dest.offset, dest.base.code());
return masm.currentOffset();
}
CodeOffsetLabel movssWithPatch(FloatRegister src, Address dest) {
JS_ASSERT(HasSSE2());
masm.movss_rm_disp32(src.code(), dest.offset, dest.base.code());
return masm.currentOffset();
}
CodeOffsetLabel movsdWithPatch(FloatRegister src, Address dest) {
JS_ASSERT(HasSSE2());
masm.movsd_rm_disp32(src.code(), dest.offset, dest.base.code());
return masm.currentOffset();
}
// Load from *(addr + index*scale) where addr can be patched.
CodeOffsetLabel movlWithPatch(void *addr, Register index, Scale scale, Register dest) {
masm.movl_mr(addr, index.code(), scale, dest.code());
return masm.currentOffset();
}
};
// Get a register in which we plan to put a quantity that will be used as an
// integer argument. This differs from GetIntArgReg in that if we have no more
// actual argument registers to use we will fall back on using whatever
// CallTempReg* don't overlap the argument registers, and only fail once those
// run out too.
static inline bool
GetTempRegForIntArg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register *out)
{
if (usedIntArgs >= NumCallTempNonArgRegs)
return false;
*out = CallTempNonArgRegs[usedIntArgs];
return true;
}
} // namespace jit
} // namespace js
#endif /* jit_x86_Assembler_x86_h */