blob: 363ce8a8ccd37278a0a6da2222bf2f4fbc3839d1 [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 "mozilla/DebugOnly.h"
#include "jscntxt.h"
#include "jscompartment.h"
#include "jsmath.h"
#include "CodeGenerator-x86-shared.h"
#include "CodeGenerator-shared-inl.h"
#include "jit/IonFrames.h"
#include "jit/MoveEmitter.h"
#include "jit/IonCompartment.h"
#include "jit/ParallelFunctions.h"
using namespace js;
using namespace js::jit;
namespace js {
namespace jit {
CodeGeneratorX86Shared::CodeGeneratorX86Shared(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm)
: CodeGeneratorShared(gen, graph, masm),
deoptLabel_(NULL)
{
}
double
test(double x, double y)
{
return x + y;
}
bool
CodeGeneratorX86Shared::generatePrologue()
{
// Note that this automatically sets MacroAssembler::framePushed().
masm.reserveStack(frameSize());
// Allocate returnLabel_ on the heap, so we don't run its destructor and
// assert-not-bound in debug mode on compilation failure.
returnLabel_ = new HeapLabel();
return true;
}
bool
CodeGeneratorX86Shared::generateEpilogue()
{
masm.bind(returnLabel_);
// Pop the stack we allocated at the start of the function.
masm.freeStack(frameSize());
JS_ASSERT(masm.framePushed() == 0);
masm.ret();
return true;
}
bool
OutOfLineBailout::accept(CodeGeneratorX86Shared *codegen)
{
return codegen->visitOutOfLineBailout(this);
}
void
CodeGeneratorX86Shared::emitBranch(Assembler::Condition cond, MBasicBlock *mirTrue,
MBasicBlock *mirFalse, Assembler::NaNCond ifNaN)
{
LBlock *ifTrue = mirTrue->lir();
LBlock *ifFalse = mirFalse->lir();
if (ifNaN == Assembler::NaN_IsFalse)
masm.j(Assembler::Parity, ifFalse->label());
else if (ifNaN == Assembler::NaN_IsTrue)
masm.j(Assembler::Parity, ifTrue->label());
if (isNextBlock(ifFalse)) {
masm.j(cond, ifTrue->label());
} else {
masm.j(Assembler::InvertCondition(cond), ifFalse->label());
if (!isNextBlock(ifTrue))
masm.jmp(ifTrue->label());
}
}
bool
CodeGeneratorX86Shared::visitDouble(LDouble *ins)
{
const LDefinition *out = ins->getDef(0);
masm.loadConstantDouble(ins->getDouble(), ToFloatRegister(out));
return true;
}
bool
CodeGeneratorX86Shared::visitTestIAndBranch(LTestIAndBranch *test)
{
const LAllocation *opd = test->input();
// Test the operand
masm.testl(ToRegister(opd), ToRegister(opd));
emitBranch(Assembler::NonZero, test->ifTrue(), test->ifFalse());
return true;
}
bool
CodeGeneratorX86Shared::visitTestDAndBranch(LTestDAndBranch *test)
{
const LAllocation *opd = test->input();
// ucomisd flags:
// Z P C
// ---------
// NaN 1 1 1
// > 0 0 0
// < 0 0 1
// = 1 0 0
//
// NaN is falsey, so comparing against 0 and then using the Z flag is
// enough to determine which branch to take.
masm.xorpd(ScratchFloatReg, ScratchFloatReg);
masm.ucomisd(ToFloatRegister(opd), ScratchFloatReg);
emitBranch(Assembler::NotEqual, test->ifTrue(), test->ifFalse());
return true;
}
void
CodeGeneratorX86Shared::emitCompare(MCompare::CompareType type, const LAllocation *left, const LAllocation *right)
{
#ifdef JS_CPU_X64
if (type == MCompare::Compare_Object) {
masm.cmpq(ToRegister(left), ToOperand(right));
return;
}
#endif
if (right->isConstant())
masm.cmpl(ToRegister(left), Imm32(ToInt32(right)));
else
masm.cmpl(ToRegister(left), ToOperand(right));
}
bool
CodeGeneratorX86Shared::visitCompare(LCompare *comp)
{
MCompare *mir = comp->mir();
emitCompare(mir->compareType(), comp->left(), comp->right());
masm.emitSet(JSOpToCondition(mir->compareType(), comp->jsop()), ToRegister(comp->output()));
return true;
}
bool
CodeGeneratorX86Shared::visitCompareAndBranch(LCompareAndBranch *comp)
{
MCompare *mir = comp->mir();
emitCompare(mir->compareType(), comp->left(), comp->right());
Assembler::Condition cond = JSOpToCondition(mir->compareType(), comp->jsop());
emitBranch(cond, comp->ifTrue(), comp->ifFalse());
return true;
}
bool
CodeGeneratorX86Shared::visitCompareD(LCompareD *comp)
{
FloatRegister lhs = ToFloatRegister(comp->left());
FloatRegister rhs = ToFloatRegister(comp->right());
Assembler::DoubleCondition cond = JSOpToDoubleCondition(comp->mir()->jsop());
masm.compareDouble(cond, lhs, rhs);
masm.emitSet(Assembler::ConditionFromDoubleCondition(cond), ToRegister(comp->output()),
Assembler::NaNCondFromDoubleCondition(cond));
return true;
}
bool
CodeGeneratorX86Shared::visitNotI(LNotI *ins)
{
masm.cmpl(ToRegister(ins->input()), Imm32(0));
masm.emitSet(Assembler::Equal, ToRegister(ins->output()));
return true;
}
bool
CodeGeneratorX86Shared::visitNotD(LNotD *ins)
{
FloatRegister opd = ToFloatRegister(ins->input());
masm.xorpd(ScratchFloatReg, ScratchFloatReg);
masm.compareDouble(Assembler::DoubleEqualOrUnordered, opd, ScratchFloatReg);
masm.emitSet(Assembler::Equal, ToRegister(ins->output()), Assembler::NaN_IsTrue);
return true;
}
bool
CodeGeneratorX86Shared::visitCompareDAndBranch(LCompareDAndBranch *comp)
{
FloatRegister lhs = ToFloatRegister(comp->left());
FloatRegister rhs = ToFloatRegister(comp->right());
Assembler::DoubleCondition cond = JSOpToDoubleCondition(comp->mir()->jsop());
masm.compareDouble(cond, lhs, rhs);
emitBranch(Assembler::ConditionFromDoubleCondition(cond), comp->ifTrue(), comp->ifFalse(),
Assembler::NaNCondFromDoubleCondition(cond));
return true;
}
bool
CodeGeneratorX86Shared::visitAsmJSPassStackArg(LAsmJSPassStackArg *ins)
{
const MAsmJSPassStackArg *mir = ins->mir();
Operand dst(StackPointer, mir->spOffset());
if (ins->arg()->isConstant()) {
masm.mov(Imm32(ToInt32(ins->arg())), dst);
} else {
if (ins->arg()->isGeneralReg())
masm.mov(ToRegister(ins->arg()), dst);
else
masm.movsd(ToFloatRegister(ins->arg()), dst);
}
return true;
}
bool
CodeGeneratorX86Shared::generateOutOfLineCode()
{
if (!CodeGeneratorShared::generateOutOfLineCode())
return false;
if (deoptLabel_) {
// All non-table-based bailouts will go here.
masm.bind(deoptLabel_);
// Push the frame size, so the handler can recover the IonScript.
masm.push(Imm32(frameSize()));
IonCompartment *ion = GetIonContext()->compartment->ionCompartment();
IonCode *handler = ion->getGenericBailoutHandler();
masm.jmp(handler->raw(), Relocation::IONCODE);
}
return true;
}
class BailoutJump {
Assembler::Condition cond_;
public:
BailoutJump(Assembler::Condition cond) : cond_(cond)
{ }
#ifdef JS_CPU_X86
void operator()(MacroAssembler &masm, uint8_t *code) const {
masm.j(cond_, code, Relocation::HARDCODED);
}
#endif
void operator()(MacroAssembler &masm, Label *label) const {
masm.j(cond_, label);
}
};
class BailoutLabel {
Label *label_;
public:
BailoutLabel(Label *label) : label_(label)
{ }
#ifdef JS_CPU_X86
void operator()(MacroAssembler &masm, uint8_t *code) const {
masm.retarget(label_, code, Relocation::HARDCODED);
}
#endif
void operator()(MacroAssembler &masm, Label *label) const {
masm.retarget(label_, label);
}
};
template <typename T> bool
CodeGeneratorX86Shared::bailout(const T &binder, LSnapshot *snapshot)
{
CompileInfo &info = snapshot->mir()->block()->info();
switch (info.executionMode()) {
case ParallelExecution: {
// in parallel mode, make no attempt to recover, just signal an error.
OutOfLineParallelAbort *ool = oolParallelAbort(ParallelBailoutUnsupported,
snapshot->mir()->block(),
snapshot->mir()->pc());
binder(masm, ool->entry());
return true;
}
case SequentialExecution:
break;
default:
JS_NOT_REACHED("No such execution mode");
}
if (!encode(snapshot))
return false;
// Though the assembler doesn't track all frame pushes, at least make sure
// the known value makes sense. We can't use bailout tables if the stack
// isn't properly aligned to the static frame size.
JS_ASSERT_IF(frameClass_ != FrameSizeClass::None() && deoptTable_,
frameClass_.frameSize() == masm.framePushed());
#ifdef JS_CPU_X86
// On x64, bailout tables are pointless, because 16 extra bytes are
// reserved per external jump, whereas it takes only 10 bytes to encode a
// a non-table based bailout.
if (assignBailoutId(snapshot)) {
binder(masm, deoptTable_->raw() + snapshot->bailoutId() * BAILOUT_TABLE_ENTRY_SIZE);
return true;
}
#endif
// We could not use a jump table, either because all bailout IDs were
// reserved, or a jump table is not optimal for this frame size or
// platform. Whatever, we will generate a lazy bailout.
OutOfLineBailout *ool = new OutOfLineBailout(snapshot);
if (!addOutOfLineCode(ool))
return false;
binder(masm, ool->entry());
return true;
}
bool
CodeGeneratorX86Shared::bailoutIf(Assembler::Condition condition, LSnapshot *snapshot)
{
return bailout(BailoutJump(condition), snapshot);
}
bool
CodeGeneratorX86Shared::bailoutFrom(Label *label, LSnapshot *snapshot)
{
JS_ASSERT(label->used() && !label->bound());
return bailout(BailoutLabel(label), snapshot);
}
bool
CodeGeneratorX86Shared::bailout(LSnapshot *snapshot)
{
Label label;
masm.jump(&label);
return bailoutFrom(&label, snapshot);
}
bool
CodeGeneratorX86Shared::visitOutOfLineBailout(OutOfLineBailout *ool)
{
if (!deoptLabel_)
deoptLabel_ = new HeapLabel();
masm.push(Imm32(ool->snapshot()->snapshotOffset()));
masm.jmp(deoptLabel_);
return true;
}
bool
CodeGeneratorX86Shared::visitMinMaxD(LMinMaxD *ins)
{
FloatRegister first = ToFloatRegister(ins->first());
FloatRegister second = ToFloatRegister(ins->second());
FloatRegister output = ToFloatRegister(ins->output());
JS_ASSERT(first == output);
Assembler::Condition cond = ins->mir()->isMax()
? Assembler::Above
: Assembler::Below;
Label nan, equal, returnSecond, done;
masm.ucomisd(second, first);
masm.j(Assembler::Parity, &nan); // first or second is NaN, result is NaN.
masm.j(Assembler::Equal, &equal); // make sure we handle -0 and 0 right.
masm.j(cond, &returnSecond);
masm.jmp(&done);
// Check for zero.
masm.bind(&equal);
masm.xorpd(ScratchFloatReg, ScratchFloatReg);
masm.ucomisd(first, ScratchFloatReg);
masm.j(Assembler::NotEqual, &done); // first wasn't 0 or -0, so just return it.
// So now both operands are either -0 or 0.
if (ins->mir()->isMax())
masm.addsd(second, first); // -0 + -0 = -0 and -0 + 0 = 0.
else
masm.orpd(second, first); // This just ors the sign bit.
masm.jmp(&done);
masm.bind(&nan);
masm.loadStaticDouble(&js_NaN, output);
masm.jmp(&done);
masm.bind(&returnSecond);
masm.movsd(second, output);
masm.bind(&done);
return true;
}
bool
CodeGeneratorX86Shared::visitAbsD(LAbsD *ins)
{
FloatRegister input = ToFloatRegister(ins->input());
JS_ASSERT(input == ToFloatRegister(ins->output()));
masm.xorpd(ScratchFloatReg, ScratchFloatReg);
masm.subsd(input, ScratchFloatReg); // negate the sign bit.
masm.andpd(ScratchFloatReg, input); // s & ~s
return true;
}
bool
CodeGeneratorX86Shared::visitSqrtD(LSqrtD *ins)
{
FloatRegister input = ToFloatRegister(ins->input());
JS_ASSERT(input == ToFloatRegister(ins->output()));
masm.sqrtsd(input, input);
return true;
}
bool
CodeGeneratorX86Shared::visitPowHalfD(LPowHalfD *ins)
{
FloatRegister input = ToFloatRegister(ins->input());
Register scratch = ToRegister(ins->temp());
JS_ASSERT(input == ToFloatRegister(ins->output()));
const uint32_t NegInfinityFloatBits = 0xFF800000;
Label done, sqrt;
// Branch if not -Infinity.
masm.move32(Imm32(NegInfinityFloatBits), scratch);
masm.loadFloatAsDouble(scratch, ScratchFloatReg);
masm.branchDouble(Assembler::DoubleNotEqualOrUnordered, input, ScratchFloatReg, &sqrt);
// Math.pow(-Infinity, 0.5) == Infinity.
masm.xorpd(input, input);
masm.subsd(ScratchFloatReg, input);
masm.jump(&done);
// Math.pow(-0, 0.5) == 0 == Math.pow(0, 0.5). Adding 0 converts any -0 to 0.
masm.bind(&sqrt);
masm.xorpd(ScratchFloatReg, ScratchFloatReg);
masm.addsd(ScratchFloatReg, input);
masm.sqrtsd(input, input);
masm.bind(&done);
return true;
}
class OutOfLineUndoALUOperation : public OutOfLineCodeBase<CodeGeneratorX86Shared>
{
LInstruction *ins_;
public:
OutOfLineUndoALUOperation(LInstruction *ins)
: ins_(ins)
{ }
virtual bool accept(CodeGeneratorX86Shared *codegen) {
return codegen->visitOutOfLineUndoALUOperation(this);
}
LInstruction *ins() const {
return ins_;
}
};
bool
CodeGeneratorX86Shared::visitAddI(LAddI *ins)
{
if (ins->rhs()->isConstant())
masm.addl(Imm32(ToInt32(ins->rhs())), ToOperand(ins->lhs()));
else
masm.addl(ToOperand(ins->rhs()), ToRegister(ins->lhs()));
if (ins->snapshot()) {
if (ins->recoversInput()) {
OutOfLineUndoALUOperation *ool = new OutOfLineUndoALUOperation(ins);
if (!addOutOfLineCode(ool))
return false;
masm.j(Assembler::Overflow, ool->entry());
} else {
if (!bailoutIf(Assembler::Overflow, ins->snapshot()))
return false;
}
}
return true;
}
bool
CodeGeneratorX86Shared::visitSubI(LSubI *ins)
{
if (ins->rhs()->isConstant())
masm.subl(Imm32(ToInt32(ins->rhs())), ToOperand(ins->lhs()));
else
masm.subl(ToOperand(ins->rhs()), ToRegister(ins->lhs()));
if (ins->snapshot()) {
if (ins->recoversInput()) {
OutOfLineUndoALUOperation *ool = new OutOfLineUndoALUOperation(ins);
if (!addOutOfLineCode(ool))
return false;
masm.j(Assembler::Overflow, ool->entry());
} else {
if (!bailoutIf(Assembler::Overflow, ins->snapshot()))
return false;
}
}
return true;
}
bool
CodeGeneratorX86Shared::visitOutOfLineUndoALUOperation(OutOfLineUndoALUOperation *ool)
{
LInstruction *ins = ool->ins();
Register reg = ToRegister(ins->getDef(0));
mozilla::DebugOnly<LAllocation *> lhs = ins->getOperand(0);
LAllocation *rhs = ins->getOperand(1);
JS_ASSERT(reg == ToRegister(lhs));
JS_ASSERT_IF(rhs->isGeneralReg(), reg != ToRegister(rhs));
// Undo the effect of the ALU operation, which was performed on the output
// register and overflowed. Writing to the output register clobbered an
// input reg, and the original value of the input needs to be recovered
// to satisfy the constraint imposed by any RECOVERED_INPUT operands to
// the bailout snapshot.
if (rhs->isConstant()) {
Imm32 constant(ToInt32(rhs));
if (ins->isAddI())
masm.subl(constant, reg);
else
masm.addl(constant, reg);
} else {
if (ins->isAddI())
masm.subl(ToOperand(rhs), reg);
else
masm.addl(ToOperand(rhs), reg);
}
return bailout(ool->ins()->snapshot());
}
class MulNegativeZeroCheck : public OutOfLineCodeBase<CodeGeneratorX86Shared>
{
LMulI *ins_;
public:
MulNegativeZeroCheck(LMulI *ins)
: ins_(ins)
{ }
virtual bool accept(CodeGeneratorX86Shared *codegen) {
return codegen->visitMulNegativeZeroCheck(this);
}
LMulI *ins() const {
return ins_;
}
};
bool
CodeGeneratorX86Shared::visitMulI(LMulI *ins)
{
const LAllocation *lhs = ins->lhs();
const LAllocation *rhs = ins->rhs();
MMul *mul = ins->mir();
JS_ASSERT_IF(mul->mode() == MMul::Integer, !mul->canBeNegativeZero() && !mul->canOverflow());
if (rhs->isConstant()) {
// Bailout on -0.0
int32_t constant = ToInt32(rhs);
if (mul->canBeNegativeZero() && constant <= 0) {
Assembler::Condition bailoutCond = (constant == 0) ? Assembler::Signed : Assembler::Equal;
masm.testl(ToRegister(lhs), ToRegister(lhs));
if (!bailoutIf(bailoutCond, ins->snapshot()))
return false;
}
switch (constant) {
case -1:
masm.negl(ToOperand(lhs));
break;
case 0:
masm.xorl(ToOperand(lhs), ToRegister(lhs));
return true; // escape overflow check;
case 1:
// nop
return true; // escape overflow check;
case 2:
masm.addl(ToOperand(lhs), ToRegister(lhs));
break;
default:
if (!mul->canOverflow() && constant > 0) {
// Use shift if cannot overflow and constant is power of 2
int32_t shift;
JS_FLOOR_LOG2(shift, constant);
if ((1 << shift) == constant) {
masm.shll(Imm32(shift), ToRegister(lhs));
return true;
}
}
masm.imull(Imm32(ToInt32(rhs)), ToRegister(lhs));
}
// Bailout on overflow
if (mul->canOverflow() && !bailoutIf(Assembler::Overflow, ins->snapshot()))
return false;
} else {
masm.imull(ToOperand(rhs), ToRegister(lhs));
// Bailout on overflow
if (mul->canOverflow() && !bailoutIf(Assembler::Overflow, ins->snapshot()))
return false;
if (mul->canBeNegativeZero()) {
// Jump to an OOL path if the result is 0.
MulNegativeZeroCheck *ool = new MulNegativeZeroCheck(ins);
if (!addOutOfLineCode(ool))
return false;
masm.testl(ToRegister(lhs), ToRegister(lhs));
masm.j(Assembler::Zero, ool->entry());
masm.bind(ool->rejoin());
}
}
return true;
}
bool
CodeGeneratorX86Shared::visitAsmJSDivOrMod(LAsmJSDivOrMod *ins)
{
JS_ASSERT(ToRegister(ins->lhs()) == eax);
Register rhs = ToRegister(ins->rhs());
Register output = ToRegister(ins->output());
JS_ASSERT_IF(output == eax, ToRegister(ins->remainder()) == edx);
Label afterDiv;
masm.testl(rhs, rhs);
Label notzero;
masm.j(Assembler::NonZero, &notzero);
masm.xorl(output, output);
masm.jmp(&afterDiv);
masm.bind(&notzero);
masm.xorl(edx, edx);
masm.udiv(rhs);
masm.bind(&afterDiv);
return true;
}
bool
CodeGeneratorX86Shared::visitMulNegativeZeroCheck(MulNegativeZeroCheck *ool)
{
LMulI *ins = ool->ins();
Register result = ToRegister(ins->output());
Operand lhsCopy = ToOperand(ins->lhsCopy());
Operand rhs = ToOperand(ins->rhs());
JS_ASSERT_IF(lhsCopy.kind() == Operand::REG, lhsCopy.reg() != result.code());
// Result is -0 if lhs or rhs is negative.
masm.movl(lhsCopy, result);
masm.orl(rhs, result);
if (!bailoutIf(Assembler::Signed, ins->snapshot()))
return false;
masm.xorl(result, result);
masm.jmp(ool->rejoin());
return true;
}
bool
CodeGeneratorX86Shared::visitDivPowTwoI(LDivPowTwoI *ins)
{
Register lhs = ToRegister(ins->numerator());
Register lhsCopy = ToRegister(ins->numeratorCopy());
mozilla::DebugOnly<Register> output = ToRegister(ins->output());
int32_t shift = ins->shift();
// We use defineReuseInput so these should always be the same, which is
// convenient since all of our instructions here are two-address.
JS_ASSERT(lhs == output);
if (shift != 0) {
if (!ins->mir()->isTruncated()) {
// If the remainder is != 0, bailout since this must be a double.
masm.testl(lhs, Imm32(UINT32_MAX >> (32 - shift)));
if (!bailoutIf(Assembler::NonZero, ins->snapshot()))
return false;
}
// Adjust the value so that shifting produces a correctly rounded result
// when the numerator is negative. See 10-1 "Signed Division by a Known
// Power of 2" in Henry S. Warren, Jr.'s Hacker's Delight.
// Note that we wouldn't need to do this adjustment if we could use
// Range Analysis to find cases when the value is never negative. We
// wouldn't even need the lhsCopy either in that case.
if (shift > 1)
masm.sarl(Imm32(31), lhs);
masm.shrl(Imm32(32 - shift), lhs);
masm.addl(lhsCopy, lhs);
// Do the shift.
masm.sarl(Imm32(shift), lhs);
}
return true;
}
bool
CodeGeneratorX86Shared::visitDivI(LDivI *ins)
{
Register remainder = ToRegister(ins->remainder());
Register lhs = ToRegister(ins->lhs());
Register rhs = ToRegister(ins->rhs());
Register output = ToRegister(ins->output());
MDiv *mir = ins->mir();
JS_ASSERT(remainder == edx);
JS_ASSERT(lhs == eax);
JS_ASSERT(output == eax);
Label done;
// Handle divide by zero.
if (mir->canBeDivideByZero()) {
masm.testl(rhs, rhs);
if (mir->isTruncated()) {
// Truncated division by zero is zero (Infinity|0 == 0)
Label notzero;
masm.j(Assembler::NonZero, &notzero);
masm.xorl(output, output);
masm.jmp(&done);
masm.bind(&notzero);
} else {
JS_ASSERT(mir->fallible());
if (!bailoutIf(Assembler::Zero, ins->snapshot()))
return false;
}
}
// Handle an integer overflow exception from -2147483648 / -1.
if (mir->canBeNegativeOverflow()) {
Label notmin;
masm.cmpl(lhs, Imm32(INT32_MIN));
masm.j(Assembler::NotEqual, &notmin);
masm.cmpl(rhs, Imm32(-1));
if (mir->isTruncated()) {
// (-INT32_MIN)|0 == INT32_MIN and INT32_MIN is already in the
// output register (lhs == eax).
masm.j(Assembler::Equal, &done);
} else {
JS_ASSERT(mir->fallible());
if (!bailoutIf(Assembler::Equal, ins->snapshot()))
return false;
}
masm.bind(&notmin);
}
// Handle negative 0.
if (!mir->isTruncated() && mir->canBeNegativeZero()) {
Label nonzero;
masm.testl(lhs, lhs);
masm.j(Assembler::NonZero, &nonzero);
masm.cmpl(rhs, Imm32(0));
if (!bailoutIf(Assembler::LessThan, ins->snapshot()))
return false;
masm.bind(&nonzero);
}
// Sign extend eax into edx to make (edx:eax), since idiv is 64-bit.
masm.cdq();
masm.idiv(rhs);
if (!mir->isTruncated()) {
// If the remainder is > 0, bailout since this must be a double.
masm.testl(remainder, remainder);
if (!bailoutIf(Assembler::NonZero, ins->snapshot()))
return false;
}
masm.bind(&done);
return true;
}
bool
CodeGeneratorX86Shared::visitModPowTwoI(LModPowTwoI *ins)
{
Register lhs = ToRegister(ins->getOperand(0));
int32_t shift = ins->shift();
Label negative, done;
// Switch based on sign of the lhs.
// Positive numbers are just a bitmask
masm.branchTest32(Assembler::Signed, lhs, lhs, &negative);
{
masm.andl(Imm32((1 << shift) - 1), lhs);
masm.jump(&done);
}
// Negative numbers need a negate, bitmask, negate
{
masm.bind(&negative);
// visitModI has an overflow check here to catch INT_MIN % -1, but
// here the rhs is a power of 2, and cannot be -1, so the check is not generated.
masm.negl(lhs);
masm.andl(Imm32((1 << shift) - 1), lhs);
masm.negl(lhs);
if (!ins->mir()->isTruncated() && !bailoutIf(Assembler::Zero, ins->snapshot()))
return false;
}
masm.bind(&done);
return true;
}
bool
CodeGeneratorX86Shared::visitModI(LModI *ins)
{
Register remainder = ToRegister(ins->remainder());
Register lhs = ToRegister(ins->lhs());
Register rhs = ToRegister(ins->rhs());
Register temp = ToRegister(ins->getTemp(0));
// Required to use idiv.
JS_ASSERT(remainder == edx);
JS_ASSERT(temp == eax);
if (lhs != temp) {
masm.mov(lhs, temp);
lhs = temp;
}
Label done;
// Prevent divide by zero
masm.testl(rhs, rhs);
if (ins->mir()->isTruncated()) {
Label notzero;
masm.j(Assembler::NonZero, &notzero);
masm.xorl(edx, edx);
masm.jmp(&done);
masm.bind(&notzero);
} else {
if (!bailoutIf(Assembler::Zero, ins->snapshot()))
return false;
}
Label negative;
// Switch based on sign of the lhs.
masm.branchTest32(Assembler::Signed, lhs, lhs, &negative);
// If lhs >= 0 then remainder = lhs % rhs. The remainder must be positive.
{
// Since lhs >= 0, the sign-extension will be 0
masm.xorl(edx, edx);
masm.idiv(rhs);
masm.jump(&done);
}
// Otherwise, we have to beware of two special cases:
{
masm.bind(&negative);
// Prevent an integer overflow exception from -2147483648 % -1
Label notmin;
masm.cmpl(lhs, Imm32(INT32_MIN));
masm.j(Assembler::NotEqual, &notmin);
masm.cmpl(rhs, Imm32(-1));
if (ins->mir()->isTruncated()) {
masm.j(Assembler::NotEqual, &notmin);
masm.xorl(edx, edx);
masm.jmp(&done);
} else {
if (!bailoutIf(Assembler::Equal, ins->snapshot()))
return false;
}
masm.bind(&notmin);
masm.cdq();
masm.idiv(rhs);
if (!ins->mir()->isTruncated()) {
// A remainder of 0 means that the rval must be -0, which is a double.
masm.testl(remainder, remainder);
if (!bailoutIf(Assembler::Zero, ins->snapshot()))
return false;
}
}
masm.bind(&done);
return true;
}
bool
CodeGeneratorX86Shared::visitBitNotI(LBitNotI *ins)
{
const LAllocation *input = ins->getOperand(0);
JS_ASSERT(!input->isConstant());
masm.notl(ToOperand(input));
return true;
}
bool
CodeGeneratorX86Shared::visitBitOpI(LBitOpI *ins)
{
const LAllocation *lhs = ins->getOperand(0);
const LAllocation *rhs = ins->getOperand(1);
switch (ins->bitop()) {
case JSOP_BITOR:
if (rhs->isConstant())
masm.orl(Imm32(ToInt32(rhs)), ToOperand(lhs));
else
masm.orl(ToOperand(rhs), ToRegister(lhs));
break;
case JSOP_BITXOR:
if (rhs->isConstant())
masm.xorl(Imm32(ToInt32(rhs)), ToOperand(lhs));
else
masm.xorl(ToOperand(rhs), ToRegister(lhs));
break;
case JSOP_BITAND:
if (rhs->isConstant())
masm.andl(Imm32(ToInt32(rhs)), ToOperand(lhs));
else
masm.andl(ToOperand(rhs), ToRegister(lhs));
break;
default:
JS_NOT_REACHED("unexpected binary opcode");
}
return true;
}
bool
CodeGeneratorX86Shared::visitShiftI(LShiftI *ins)
{
Register lhs = ToRegister(ins->lhs());
const LAllocation *rhs = ins->rhs();
if (rhs->isConstant()) {
int32_t shift = ToInt32(rhs) & 0x1F;
switch (ins->bitop()) {
case JSOP_LSH:
if (shift)
masm.shll(Imm32(shift), lhs);
break;
case JSOP_RSH:
if (shift)
masm.sarl(Imm32(shift), lhs);
break;
case JSOP_URSH:
if (shift) {
masm.shrl(Imm32(shift), lhs);
} else if (ins->mir()->toUrsh()->canOverflow()) {
// x >>> 0 can overflow.
masm.testl(lhs, lhs);
if (!bailoutIf(Assembler::Signed, ins->snapshot()))
return false;
}
break;
default:
JS_NOT_REACHED("Unexpected shift op");
}
} else {
JS_ASSERT(ToRegister(rhs) == ecx);
switch (ins->bitop()) {
case JSOP_LSH:
masm.shll_cl(lhs);
break;
case JSOP_RSH:
masm.sarl_cl(lhs);
break;
case JSOP_URSH:
masm.shrl_cl(lhs);
if (ins->mir()->toUrsh()->canOverflow()) {
// x >>> 0 can overflow.
masm.testl(lhs, lhs);
if (!bailoutIf(Assembler::Signed, ins->snapshot()))
return false;
}
break;
default:
JS_NOT_REACHED("Unexpected shift op");
}
}
return true;
}
bool
CodeGeneratorX86Shared::visitUrshD(LUrshD *ins)
{
Register lhs = ToRegister(ins->lhs());
JS_ASSERT(ToRegister(ins->temp()) == lhs);
const LAllocation *rhs = ins->rhs();
FloatRegister out = ToFloatRegister(ins->output());
if (rhs->isConstant()) {
int32_t shift = ToInt32(rhs) & 0x1F;
if (shift)
masm.shrl(Imm32(shift), lhs);
} else {
JS_ASSERT(ToRegister(rhs) == ecx);
masm.shrl_cl(lhs);
}
masm.convertUInt32ToDouble(lhs, out);
return true;
}
typedef MoveResolver::MoveOperand MoveOperand;
MoveOperand
CodeGeneratorX86Shared::toMoveOperand(const LAllocation *a) const
{
if (a->isGeneralReg())
return MoveOperand(ToRegister(a));
if (a->isFloatReg())
return MoveOperand(ToFloatRegister(a));
return MoveOperand(StackPointer, ToStackOffset(a));
}
bool
CodeGeneratorX86Shared::visitMoveGroup(LMoveGroup *group)
{
if (!group->numMoves())
return true;
MoveResolver &resolver = masm.moveResolver();
for (size_t i = 0; i < group->numMoves(); i++) {
const LMove &move = group->getMove(i);
const LAllocation *from = move.from();
const LAllocation *to = move.to();
// No bogus moves.
JS_ASSERT(*from != *to);
JS_ASSERT(!from->isConstant());
JS_ASSERT(from->isDouble() == to->isDouble());
MoveResolver::Move::Kind kind = from->isDouble()
? MoveResolver::Move::DOUBLE
: MoveResolver::Move::GENERAL;
if (!resolver.addMove(toMoveOperand(from), toMoveOperand(to), kind))
return false;
}
if (!resolver.resolve())
return false;
MoveEmitter emitter(masm);
emitter.emit(resolver);
emitter.finish();
return true;
}
class OutOfLineTableSwitch : public OutOfLineCodeBase<CodeGeneratorX86Shared>
{
MTableSwitch *mir_;
CodeLabel jumpLabel_;
bool accept(CodeGeneratorX86Shared *codegen) {
return codegen->visitOutOfLineTableSwitch(this);
}
public:
OutOfLineTableSwitch(MTableSwitch *mir)
: mir_(mir)
{}
MTableSwitch *mir() const {
return mir_;
}
CodeLabel *jumpLabel() {
return &jumpLabel_;
}
};
bool
CodeGeneratorX86Shared::visitOutOfLineTableSwitch(OutOfLineTableSwitch *ool)
{
MTableSwitch *mir = ool->mir();
masm.align(sizeof(void*));
masm.bind(ool->jumpLabel()->src());
if (!masm.addCodeLabel(*ool->jumpLabel()))
return false;
for (size_t i = 0; i < mir->numCases(); i++) {
LBlock *caseblock = mir->getCase(i)->lir();
Label *caseheader = caseblock->label();
uint32_t caseoffset = caseheader->offset();
// The entries of the jump table need to be absolute addresses and thus
// must be patched after codegen is finished.
CodeLabel cl;
masm.writeCodePointer(cl.dest());
cl.src()->bind(caseoffset);
if (!masm.addCodeLabel(cl))
return false;
}
return true;
}
bool
CodeGeneratorX86Shared::emitTableSwitchDispatch(MTableSwitch *mir, const Register &index,
const Register &base)
{
Label *defaultcase = mir->getDefault()->lir()->label();
// Lower value with low value
if (mir->low() != 0)
masm.subl(Imm32(mir->low()), index);
// Jump to default case if input is out of range
int32_t cases = mir->numCases();
masm.cmpl(index, Imm32(cases));
masm.j(AssemblerX86Shared::AboveOrEqual, defaultcase);
// To fill in the CodeLabels for the case entries, we need to first
// generate the case entries (we don't yet know their offsets in the
// instruction stream).
OutOfLineTableSwitch *ool = new OutOfLineTableSwitch(mir);
if (!addOutOfLineCode(ool))
return false;
// Compute the position where a pointer to the right case stands.
masm.mov(ool->jumpLabel()->dest(), base);
Operand pointer = Operand(base, index, ScalePointer);
// Jump to the right case
masm.jmp(pointer);
return true;
}
bool
CodeGeneratorX86Shared::visitMathD(LMathD *math)
{
FloatRegister lhs = ToFloatRegister(math->lhs());
Operand rhs = ToOperand(math->rhs());
JS_ASSERT(ToFloatRegister(math->output()) == lhs);
switch (math->jsop()) {
case JSOP_ADD:
masm.addsd(rhs, lhs);
break;
case JSOP_SUB:
masm.subsd(rhs, lhs);
break;
case JSOP_MUL:
masm.mulsd(rhs, lhs);
break;
case JSOP_DIV:
masm.divsd(rhs, lhs);
break;
default:
JS_NOT_REACHED("unexpected opcode");
return false;
}
return true;
}
bool
CodeGeneratorX86Shared::visitFloor(LFloor *lir)
{
FloatRegister input = ToFloatRegister(lir->input());
FloatRegister scratch = ScratchFloatReg;
Register output = ToRegister(lir->output());
if (AssemblerX86Shared::HasSSE41()) {
// Bail on negative-zero.
Assembler::Condition bailCond = masm.testNegativeZero(input, output);
if (!bailoutIf(bailCond, lir->snapshot()))
return false;
// Round toward -Infinity.
masm.roundsd(input, scratch, JSC::X86Assembler::RoundDown);
masm.cvttsd2si(scratch, output);
masm.cmp32(output, Imm32(INT_MIN));
if (!bailoutIf(Assembler::Equal, lir->snapshot()))
return false;
} else {
Label negative, end;
// Branch to a slow path for negative inputs. Doesn't catch NaN or -0.
masm.xorpd(scratch, scratch);
masm.branchDouble(Assembler::DoubleLessThan, input, scratch, &negative);
// Bail on negative-zero.
Assembler::Condition bailCond = masm.testNegativeZero(input, output);
if (!bailoutIf(bailCond, lir->snapshot()))
return false;
// Input is non-negative, so truncation correctly rounds.
masm.cvttsd2si(input, output);
masm.cmp32(output, Imm32(INT_MIN));
if (!bailoutIf(Assembler::Equal, lir->snapshot()))
return false;
masm.jump(&end);
// Input is negative, but isn't -0.
// Negative values go on a comparatively expensive path, since no
// native rounding mode matches JS semantics. Still better than callVM.
masm.bind(&negative);
{
// Truncate and round toward zero.
// This is off-by-one for everything but integer-valued inputs.
masm.cvttsd2si(input, output);
masm.cmp32(output, Imm32(INT_MIN));
if (!bailoutIf(Assembler::Equal, lir->snapshot()))
return false;
// Test whether the input double was integer-valued.
masm.cvtsi2sd(output, scratch);
masm.branchDouble(Assembler::DoubleEqualOrUnordered, input, scratch, &end);
// Input is not integer-valued, so we rounded off-by-one in the
// wrong direction. Correct by subtraction.
masm.subl(Imm32(1), output);
// Cannot overflow: output was already checked against INT_MIN.
}
masm.bind(&end);
}
return true;
}
bool
CodeGeneratorX86Shared::visitRound(LRound *lir)
{
FloatRegister input = ToFloatRegister(lir->input());
FloatRegister temp = ToFloatRegister(lir->temp());
FloatRegister scratch = ScratchFloatReg;
Register output = ToRegister(lir->output());
Label negative, end;
// Load 0.5 in the temp register.
static const double PointFive = 0.5;
masm.loadStaticDouble(&PointFive, temp);
// Branch to a slow path for negative inputs. Doesn't catch NaN or -0.
masm.xorpd(scratch, scratch);
masm.branchDouble(Assembler::DoubleLessThan, input, scratch, &negative);
// Bail on negative-zero.
Assembler::Condition bailCond = masm.testNegativeZero(input, output);
if (!bailoutIf(bailCond, lir->snapshot()))
return false;
// Input is non-negative. Add 0.5 and truncate, rounding down. Note that we
// have to add the input to the temp register (which contains 0.5) because
// we're not allowed to modify the input register.
masm.addsd(input, temp);
masm.cvttsd2si(temp, output);
masm.cmp32(output, Imm32(INT_MIN));
if (!bailoutIf(Assembler::Equal, lir->snapshot()))
return false;
masm.jump(&end);
// Input is negative, but isn't -0.
masm.bind(&negative);
if (AssemblerX86Shared::HasSSE41()) {
// Add 0.5 and round toward -Infinity. The result is stored in the temp
// register (currently contains 0.5).
masm.addsd(input, temp);
masm.roundsd(temp, scratch, JSC::X86Assembler::RoundDown);
// Truncate.
masm.cvttsd2si(scratch, output);
masm.cmp32(output, Imm32(INT_MIN));
if (!bailoutIf(Assembler::Equal, lir->snapshot()))
return false;
// If the result is positive zero, then the actual result is -0. Bail.
// Otherwise, the truncation will have produced the correct negative integer.
masm.testl(output, output);
if (!bailoutIf(Assembler::Zero, lir->snapshot()))
return false;
} else {
masm.addsd(input, temp);
// Round toward -Infinity without the benefit of ROUNDSD.
Label testZero;
{
// Truncate and round toward zero.
// This is off-by-one for everything but integer-valued inputs.
masm.cvttsd2si(temp, output);
masm.cmp32(output, Imm32(INT_MIN));
if (!bailoutIf(Assembler::Equal, lir->snapshot()))
return false;
// Test whether the truncated double was integer-valued.
masm.cvtsi2sd(output, scratch);
masm.branchDouble(Assembler::DoubleEqualOrUnordered, temp, scratch, &testZero);
// Input is not integer-valued, so we rounded off-by-one in the
// wrong direction. Correct by subtraction.
masm.subl(Imm32(1), output);
// Cannot overflow: output was already checked against INT_MIN.
// Fall through to testZero.
}
masm.bind(&testZero);
if (!bailoutIf(Assembler::Zero, lir->snapshot()))
return false;
}
masm.bind(&end);
return true;
}
bool
CodeGeneratorX86Shared::visitGuardShape(LGuardShape *guard)
{
Register obj = ToRegister(guard->input());
masm.cmpPtr(Operand(obj, JSObject::offsetOfShape()), ImmGCPtr(guard->mir()->shape()));
return bailoutIf(Assembler::NotEqual, guard->snapshot());
}
bool
CodeGeneratorX86Shared::visitGuardObjectType(LGuardObjectType *guard)
{
Register obj = ToRegister(guard->input());
masm.cmpPtr(Operand(obj, JSObject::offsetOfType()), ImmGCPtr(guard->mir()->typeObject()));
Assembler::Condition cond =
guard->mir()->bailOnEquality() ? Assembler::Equal : Assembler::NotEqual;
return bailoutIf(cond, guard->snapshot());
}
bool
CodeGeneratorX86Shared::visitGuardClass(LGuardClass *guard)
{
Register obj = ToRegister(guard->input());
Register tmp = ToRegister(guard->tempInt());
masm.loadPtr(Address(obj, JSObject::offsetOfType()), tmp);
masm.cmpPtr(Operand(tmp, offsetof(types::TypeObject, clasp)), ImmWord(guard->mir()->getClass()));
if (!bailoutIf(Assembler::NotEqual, guard->snapshot()))
return false;
return true;
}
bool
CodeGeneratorX86Shared::visitEffectiveAddress(LEffectiveAddress *ins)
{
const MEffectiveAddress *mir = ins->mir();
Register base = ToRegister(ins->base());
Register index = ToRegister(ins->index());
Register output = ToRegister(ins->output());
masm.leal(Operand(base, index, mir->scale(), mir->displacement()), output);
return true;
}
Operand
CodeGeneratorX86Shared::createArrayElementOperand(Register elements, const LAllocation *index)
{
if (index->isConstant())
return Operand(elements, ToInt32(index) * sizeof(js::Value));
return Operand(elements, ToRegister(index), TimesEight);
}
bool
CodeGeneratorX86Shared::generateInvalidateEpilogue()
{
// Ensure that there is enough space in the buffer for the OsiPoint
// patching to occur. Otherwise, we could overwrite the invalidation
// epilogue.
for (size_t i = 0; i < sizeof(void *); i+= Assembler::nopSize())
masm.nop();
masm.bind(&invalidate_);
// Push the Ion script onto the stack (when we determine what that pointer is).
invalidateEpilogueData_ = masm.pushWithPatch(ImmWord(uintptr_t(-1)));
IonCode *thunk = GetIonContext()->compartment->ionCompartment()->getInvalidationThunk();
masm.call(thunk);
// We should never reach this point in JIT code -- the invalidation thunk should
// pop the invalidated JS frame and return directly to its caller.
masm.breakpoint();
return true;
}
bool
CodeGeneratorX86Shared::visitNegI(LNegI *ins)
{
Register input = ToRegister(ins->input());
JS_ASSERT(input == ToRegister(ins->output()));
masm.neg32(input);
return true;
}
bool
CodeGeneratorX86Shared::visitNegD(LNegD *ins)
{
FloatRegister input = ToFloatRegister(ins->input());
JS_ASSERT(input == ToFloatRegister(ins->output()));
masm.negateDouble(input);
return true;
}
} // namespace jit
} // namespace js