/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/x86-shared/CodeGenerator-x86-shared.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/MathAlgorithms.h"
#include "jit/CodeGenerator.h"
#include "jit/InlineScriptTree.h"
#include "jit/JitRuntime.h"
#include "jit/RangeAnalysis.h"
#include "jit/ReciprocalMulConstants.h"
#include "js/ScalarType.h" // js::Scalar::Type
#include "util/DifferentialTesting.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/shared/CodeGenerator-shared-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::Abs;
using mozilla::DebugOnly;
using mozilla::FloorLog2;
using mozilla::NegativeInfinity;
using JS::GenericNaN;
namespace js {
namespace jit {
CodeGeneratorX86Shared::CodeGeneratorX86Shared(MIRGenerator* gen,
LIRGraph* graph,
MacroAssembler* masm)
: CodeGeneratorShared(gen, graph, masm) {}
#ifdef JS_PUNBOX64
Operand CodeGeneratorX86Shared::ToOperandOrRegister64(
const LInt64Allocation& input) {
return ToOperand(input.value());
}
#else
Register64 CodeGeneratorX86Shared::ToOperandOrRegister64(
const LInt64Allocation& input) {
return ToRegister64(input);
}
#endif
void OutOfLineBailout::accept(CodeGeneratorX86Shared* codegen) {
codegen->visitOutOfLineBailout(
this);
}
void CodeGeneratorX86Shared::emitBranch(Assembler::Condition cond,
MBasicBlock* mirTrue,
MBasicBlock* mirFalse) {
if (isNextBlock(mirFalse->lir())) {
jumpToBlock(mirTrue, cond);
}
else {
jumpToBlock(mirFalse, Assembler::InvertCondition(cond));
jumpToBlock(mirTrue);
}
}
void CodeGeneratorX86Shared::emitBranch(Assembler::DoubleCondition cond,
MBasicBlock* ifTrue,
MBasicBlock* ifFalse,
Assembler::NaNCond ifNaN) {
if (ifNaN == Assembler::NaN_IsFalse) {
jumpToBlock(ifFalse, Assembler::Parity);
}
else if (ifNaN == Assembler::NaN_IsTrue) {
jumpToBlock(ifTrue, Assembler::Parity);
}
emitBranch(Assembler::ConditionFromDoubleCondition(cond), ifTrue, ifFalse);
}
void CodeGenerator::visitTestDAndBranch(LTestDAndBranch* test) {
const LAllocation* opd = test->input();
// vucomisd 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.
ScratchDoubleScope scratch(masm);
masm.zeroDouble(scratch);
masm.vucomisd(scratch, ToFloatRegister(opd));
emitBranch(Assembler::NotEqual, test->ifTrue(), test->ifFalse());
}
void CodeGenerator::visitTestFAndBranch(LTestFAndBranch* test) {
const LAllocation* opd = test->input();
// vucomiss flags are the same as doubles; see comment above
{
ScratchFloat32Scope scratch(masm);
masm.zeroFloat32(scratch);
masm.vucomiss(scratch, ToFloatRegister(opd));
}
emitBranch(Assembler::NotEqual, test->ifTrue(), test->ifFalse());
}
static Assembler::DoubleCondition ToDoubleCondition(FloatRegister lhs,
FloatRegister rhs,
JSOp op) {
if (lhs == rhs) {
switch (op) {
case JSOp::Eq:
case JSOp::StrictEq:
case JSOp::Le:
case JSOp::Ge:
return Assembler::DoubleOrdered;
case JSOp::Ne:
case JSOp::StrictNe:
return Assembler::DoubleUnordered;
default:
break;
}
}
return JSOpToDoubleCondition(op);
}
void CodeGenerator::visitCompareD(LCompareD* comp) {
FloatRegister lhs = ToFloatRegister(comp->left());
FloatRegister rhs = ToFloatRegister(comp->right());
Register output = ToRegister(comp->output());
Assembler::DoubleCondition cond =
ToDoubleCondition(lhs, rhs, comp->mir()->jsop());
Assembler::NaNCond nanCond = Assembler::NaNCondFromDoubleCondition(cond);
if (comp->mir()->operandsAreNeverNaN()) {
nanCond = Assembler::NaN_HandledByCond;
}
bool destIsZero = masm.maybeEmitSetZeroByteRegister(output);
masm.compareDouble(cond, lhs, rhs);
masm.emitSet(Assembler::ConditionFromDoubleCondition(cond), output,
destIsZero, nanCond);
}
void CodeGenerator::visitCompareF(LCompareF* comp) {
FloatRegister lhs = ToFloatRegister(comp->left());
FloatRegister rhs = ToFloatRegister(comp->right());
Register output = ToRegister(comp->output());
Assembler::DoubleCondition cond =
ToDoubleCondition(lhs, rhs, comp->mir()->jsop());
Assembler::NaNCond nanCond = Assembler::NaNCondFromDoubleCondition(cond);
if (comp->mir()->operandsAreNeverNaN()) {
nanCond = Assembler::NaN_HandledByCond;
}
bool destIsZero = masm.maybeEmitSetZeroByteRegister(output);
masm.compareFloat(cond, lhs, rhs);
masm.emitSet(Assembler::ConditionFromDoubleCondition(cond), output,
destIsZero, nanCond);
}
void CodeGenerator::visitNotD(LNotD* ins) {
FloatRegister opd = ToFloatRegister(ins->input());
Register output = ToRegister(ins->output());
// Not returns true if the input is a NaN. We don't have to worry about
// it if we know the input is never NaN though.
Assembler::NaNCond nanCond = Assembler::NaN_IsTrue;
if (ins->mir()->operandIsNeverNaN()) {
nanCond = Assembler::NaN_HandledByCond;
}
bool destIsZero = masm.maybeEmitSetZeroByteRegister(output);
ScratchDoubleScope scratch(masm);
masm.zeroDouble(scratch);
masm.compareDouble(Assembler::DoubleEqualOrUnordered, opd, scratch);
masm.emitSet(Assembler::Equal, output, destIsZero, nanCond);
}
void CodeGenerator::visitNotF(LNotF* ins) {
FloatRegister opd = ToFloatRegister(ins->input());
Register output = ToRegister(ins->output());
// Not returns true if the input is a NaN. We don't have to worry about
// it if we know the input is never NaN though.
Assembler::NaNCond nanCond = Assembler::NaN_IsTrue;
if (ins->mir()->operandIsNeverNaN()) {
nanCond = Assembler::NaN_HandledByCond;
}
bool destIsZero = masm.maybeEmitSetZeroByteRegister(output);
ScratchFloat32Scope scratch(masm);
masm.zeroFloat32(scratch);
masm.compareFloat(Assembler::DoubleEqualOrUnordered, opd, scratch);
masm.emitSet(Assembler::Equal, output, destIsZero, nanCond);
}
void CodeGenerator::visitCompareDAndBranch(LCompareDAndBranch* comp) {
FloatRegister lhs = ToFloatRegister(comp->left());
FloatRegister rhs = ToFloatRegister(comp->right());
Assembler::DoubleCondition cond =
ToDoubleCondition(lhs, rhs, comp->cmpMir()->jsop());
Assembler::NaNCond nanCond = Assembler::NaNCondFromDoubleCondition(cond);
if (comp->cmpMir()->operandsAreNeverNaN()) {
nanCond = Assembler::NaN_HandledByCond;
}
masm.compareDouble(cond, lhs, rhs);
emitBranch(cond, comp->ifTrue(), comp->ifFalse(), nanCond);
}
void CodeGenerator::visitCompareFAndBranch(LCompareFAndBranch* comp) {
FloatRegister lhs = ToFloatRegister(comp->left());
FloatRegister rhs = ToFloatRegister(comp->right());
Assembler::DoubleCondition cond =
ToDoubleCondition(lhs, rhs, comp->cmpMir()->jsop());
Assembler::NaNCond nanCond = Assembler::NaNCondFromDoubleCondition(cond);
if (comp->cmpMir()->operandsAreNeverNaN()) {
nanCond = Assembler::NaN_HandledByCond;
}
masm.compareFloat(cond, lhs, rhs);
emitBranch(cond, comp->ifTrue(), comp->ifFalse(), nanCond);
}
void CodeGenerator::visitWasmStackArg(LWasmStackArg* ins) {
const MWasmStackArg* mir = ins->mir();
Address dst(StackPointer, mir->spOffset());
if (ins->arg()->isConstant()) {
masm.storePtr(ImmWord(ToInt32(ins->arg())), dst);
}
else if (ins->arg()->isGeneralReg()) {
masm.storePtr(ToRegister(ins->arg()), dst);
}
else {
switch (mir->input()->type()) {
case MIRType::
Double:
masm.storeDouble(ToFloatRegister(ins->arg()), dst);
return;
case MIRType::Float32:
masm.storeFloat32(ToFloatRegister(ins->arg()), dst);
return;
#ifdef ENABLE_WASM_SIMD
case MIRType::Simd128:
masm.storeUnalignedSimd128(ToFloatRegister(ins->arg()), dst);
return;
#endif
default:
break;
}
MOZ_CRASH(
"unexpected mir type in WasmStackArg");
}
}
void CodeGenerator::visitWasmStackArgI64(LWasmStackArgI64* ins) {
const MWasmStackArg* mir = ins->mir();
Address dst(StackPointer, mir->spOffset());
if (IsConstant(ins->arg())) {
masm.store64(Imm64(ToInt64(ins->arg())), dst);
}
else {
masm.store64(ToRegister64(ins->arg()), dst);
}
}
void CodeGenerator::visitWasmSelect(LWasmSelect* ins) {
MIRType mirType = ins->mir()->type();
Register cond = ToRegister(ins->condExpr());
Operand falseExpr = ToOperand(ins->falseExpr());
masm.test32(cond, cond);
if (mirType == MIRType::Int32 || mirType == MIRType::WasmAnyRef) {
Register out = ToRegister(ins->output());
MOZ_ASSERT(ToRegister(ins->trueExpr()) == out,
"true expr input is reused for output");
if (mirType == MIRType::Int32) {
masm.cmovz32(falseExpr, out);
}
else {
masm.cmovzPtr(falseExpr, out);
}
return;
}
FloatRegister out = ToFloatRegister(ins->output());
MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out,
"true expr input is reused for output");
Label done;
masm.j(Assembler::NonZero, &done);
if (mirType == MIRType::Float32) {
if (falseExpr.kind() == Operand::FPREG) {
masm.moveFloat32(ToFloatRegister(ins->falseExpr()), out);
}
else {
masm.loadFloat32(falseExpr, out);
}
}
else if (mirType == MIRType::
Double) {
if (falseExpr.kind() == Operand::FPREG) {
masm.moveDouble(ToFloatRegister(ins->falseExpr()), out);
}
else {
masm.loadDouble(falseExpr, out);
}
}
else if (mirType == MIRType::Simd128) {
if (falseExpr.kind() == Operand::FPREG) {
masm.moveSimd128(ToFloatRegister(ins->falseExpr()), out);
}
else {
masm.loadUnalignedSimd128(falseExpr, out);
}
}
else {
MOZ_CRASH(
"unhandled type in visitWasmSelect!");
}
masm.bind(&done);
}
void CodeGenerator::visitAsmJSLoadHeap(LAsmJSLoadHeap* ins) {
const MAsmJSLoadHeap* mir = ins->mir();
MOZ_ASSERT(mir->access().offset64() == 0);
const LAllocation* ptr = ins->ptr();
const LAllocation* boundsCheckLimit = ins->boundsCheckLimit();
AnyRegister out = ToAnyRegister(ins->output());
Scalar::Type accessType = mir->accessType();
OutOfLineAsmJSLoadHeapOutOfBounds* ool = nullptr;
if (mir->needsBoundsCheck()) {
ool =
new (alloc()) OutOfLineAsmJSLoadHeapOutOfBounds(out, accessType);
addOutOfLineCode(ool, mir);
masm.wasmBoundsCheck32(Assembler::AboveOrEqual, ToRegister(ptr),
ToRegister(boundsCheckLimit), ool->entry());
}
Operand srcAddr = toMemoryAccessOperand(ins, 0);
masm.wasmLoad(mir->access(), srcAddr, out);
if (ool) {
masm.bind(ool->rejoin());
}
}
void CodeGeneratorX86Shared::visitOutOfLineAsmJSLoadHeapOutOfBounds(
OutOfLineAsmJSLoadHeapOutOfBounds* ool) {
switch (ool->viewType()) {
case Scalar::Int64:
case Scalar::BigInt64:
case Scalar::BigUint64:
case Scalar::Simd128:
case Scalar::Float16:
case Scalar::MaxTypedArrayViewType:
MOZ_CRASH(
"unexpected array type");
case Scalar::Float32:
masm.loadConstantFloat32(
float(GenericNaN()), ool->dest().fpu());
break;
case Scalar::Float64:
masm.loadConstantDouble(GenericNaN(), ool->dest().fpu());
break;
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
case Scalar::Uint8Clamped:
Register destReg = ool->dest().gpr();
masm.mov(ImmWord(0), destReg);
break;
}
masm.jmp(ool->rejoin());
}
void CodeGenerator::visitAsmJSStoreHeap(LAsmJSStoreHeap* ins) {
const MAsmJSStoreHeap* mir = ins->mir();
const LAllocation* ptr = ins->ptr();
const LAllocation* value = ins->value();
const LAllocation* boundsCheckLimit = ins->boundsCheckLimit();
Scalar::Type accessType = mir->accessType();
canonicalizeIfDeterministic(accessType, value);
Label rejoin;
if (mir->needsBoundsCheck()) {
masm.wasmBoundsCheck32(Assembler::AboveOrEqual, ToRegister(ptr),
ToRegister(boundsCheckLimit), &rejoin);
}
Operand dstAddr = toMemoryAccessOperand(ins, 0);
masm.wasmStore(mir->access(), ToAnyRegister(value), dstAddr);
if (rejoin.used()) {
masm.bind(&rejoin);
}
}
void CodeGenerator::visitWasmAddOffset(LWasmAddOffset* lir) {
MWasmAddOffset* mir = lir->mir();
Register base = ToRegister(lir->base());
Register out = ToRegister(lir->output());
if (base != out) {
masm.move32(base, out);
}
masm.add32(Imm32(mir->offset()), out);
OutOfLineAbortingWasmTrap* ool =
new (alloc())
OutOfLineAbortingWasmTrap(mir->trapSiteDesc(), wasm::Trap::OutOfBounds);
addOutOfLineCode(ool, mir);
masm.j(Assembler::CarrySet, ool->entry());
}
void CodeGenerator::visitWasmAddOffset64(LWasmAddOffset64* lir) {
MWasmAddOffset* mir = lir->mir();
Register64 base = ToRegister64(lir->base());
Register64 out = ToOutRegister64(lir);
if (base != out) {
masm.move64(base, out);
}
masm.add64(Imm64(mir->offset()), out);
OutOfLineAbortingWasmTrap* ool =
new (alloc())
OutOfLineAbortingWasmTrap(mir->trapSiteDesc(), wasm::Trap::OutOfBounds);
addOutOfLineCode(ool, mir);
masm.j(Assembler::CarrySet, ool->entry());
}
void CodeGenerator::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir) {
FloatRegister input = ToFloatRegister(lir->input());
Register output = ToRegister(lir->output());
MWasmTruncateToInt32* mir = lir->mir();
MIRType inputType = mir->input()->type();
MOZ_ASSERT(inputType == MIRType::
Double || inputType == MIRType::Float32);
auto* ool =
new (alloc()) OutOfLineWasmTruncateCheck(mir, input, output);
addOutOfLineCode(ool, mir);
Label* oolEntry = ool->entry();
if (mir->isUnsigned()) {
if (inputType == MIRType::
Double) {
masm.wasmTruncateDoubleToUInt32(input, output, mir->isSaturating(),
oolEntry);
}
else if (inputType == MIRType::Float32) {
masm.wasmTruncateFloat32ToUInt32(input, output, mir->isSaturating(),
oolEntry);
}
else {
MOZ_CRASH(
"unexpected type");
}
if (mir->isSaturating()) {
masm.bind(ool->rejoin());
}
return;
}
if (inputType == MIRType::
Double) {
masm.wasmTruncateDoubleToInt32(input, output, mir->isSaturating(),
oolEntry);
}
else if (inputType == MIRType::Float32) {
masm.wasmTruncateFloat32ToInt32(input, output, mir->isSaturating(),
oolEntry);
}
else {
MOZ_CRASH(
"unexpected type");
}
masm.bind(ool->rejoin());
}
bool CodeGeneratorX86Shared::generateOutOfLineCode() {
if (!CodeGeneratorShared::generateOutOfLineCode()) {
return false;
}
if (deoptLabel_.used()) {
// 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()));
TrampolinePtr handler = gen->jitRuntime()->getGenericBailoutHandler();
masm.jump(handler);
}
return !masm.oom();
}
class BailoutJump {
Assembler::Condition cond_;
public:
explicit BailoutJump(Assembler::Condition cond) : cond_(cond) {}
#ifdef JS_CODEGEN_X86
void operator()(MacroAssembler& masm, uint8_t* code)
const {
masm.j(cond_, ImmPtr(code), RelocationKind::HARDCODED);
}
#endif
void operator()(MacroAssembler& masm, Label* label)
const {
masm.j(cond_, label);
}
};
class BailoutLabel {
Label* label_;
public:
explicit BailoutLabel(Label* label) : label_(label) {}
#ifdef JS_CODEGEN_X86
void operator()(MacroAssembler& masm, uint8_t* code)
const {
masm.retarget(label_, ImmPtr(code), RelocationKind::HARDCODED);
}
#endif
void operator()(MacroAssembler& masm, Label* label)
const {
masm.retarget(label_, label);
}
};
template <
typename T>
void CodeGeneratorX86Shared::bailout(
const T& binder, LSnapshot* snapshot) {
encode(snapshot);
// All bailout code is associated with the bytecodeSite of the block we are
// bailing out from.
InlineScriptTree* tree = snapshot->mir()->block()->trackedTree();
OutOfLineBailout* ool =
new (alloc()) OutOfLineBailout(snapshot);
addOutOfLineCode(ool,
new (alloc()) BytecodeSite(tree, tree->script()->code()));
binder(masm, ool->entry());
}
void CodeGeneratorX86Shared::bailoutIf(Assembler::Condition condition,
LSnapshot* snapshot) {
bailout(BailoutJump(condition), snapshot);
}
void CodeGeneratorX86Shared::bailoutIf(Assembler::DoubleCondition condition,
LSnapshot* snapshot) {
MOZ_ASSERT(Assembler::NaNCondFromDoubleCondition(condition) ==
Assembler::NaN_HandledByCond);
bailoutIf(Assembler::ConditionFromDoubleCondition(condition), snapshot);
}
void CodeGeneratorX86Shared::bailoutFrom(Label* label, LSnapshot* snapshot) {
MOZ_ASSERT_IF(!masm.oom(), label->used() && !label->bound());
bailout(BailoutLabel(label), snapshot);
}
void CodeGeneratorX86Shared::bailout(LSnapshot* snapshot) {
Label label;
masm.jump(&label);
bailoutFrom(&label, snapshot);
}
void CodeGeneratorX86Shared::visitOutOfLineBailout(OutOfLineBailout* ool) {
masm.push(Imm32(ool->snapshot()->snapshotOffset()));
masm.jmp(&deoptLabel_);
}
void CodeGenerator::visitMinMaxD(LMinMaxD* ins) {
FloatRegister first = ToFloatRegister(ins->first());
FloatRegister second = ToFloatRegister(ins->second());
#ifdef DEBUG
FloatRegister output = ToFloatRegister(ins->output());
MOZ_ASSERT(first == output);
#endif
bool handleNaN = !ins->mir()->range() || ins->mir()->range()->canBeNaN();
if (ins->mir()->isMax()) {
masm.maxDouble(second, first, handleNaN);
}
else {
masm.minDouble(second, first, handleNaN);
}
}
void CodeGenerator::visitMinMaxF(LMinMaxF* ins) {
FloatRegister first = ToFloatRegister(ins->first());
FloatRegister second = ToFloatRegister(ins->second());
#ifdef DEBUG
FloatRegister output = ToFloatRegister(ins->output());
MOZ_ASSERT(first == output);
#endif
bool handleNaN = !ins->mir()->range() || ins->mir()->range()->canBeNaN();
if (ins->mir()->isMax()) {
masm.maxFloat32(second, first, handleNaN);
}
else {
masm.minFloat32(second, first, handleNaN);
}
}
void CodeGenerator::visitPowHalfD(LPowHalfD* ins) {
FloatRegister input = ToFloatRegister(ins->input());
FloatRegister output = ToFloatRegister(ins->output());
ScratchDoubleScope scratch(masm);
Label done, sqrt;
if (!ins->mir()->operandIsNeverNegativeInfinity()) {
// Branch if not -Infinity.
masm.loadConstantDouble(NegativeInfinity<
double>(), scratch);
Assembler::DoubleCondition cond = Assembler::DoubleNotEqualOrUnordered;
if (ins->mir()->operandIsNeverNaN()) {
cond = Assembler::DoubleNotEqual;
}
masm.branchDouble(cond, input, scratch, &sqrt);
// Math.pow(-Infinity, 0.5) == Infinity.
masm.zeroDouble(output);
masm.subDouble(scratch, output);
masm.jump(&done);
masm.bind(&sqrt);
}
if (!ins->mir()->operandIsNeverNegativeZero()) {
// Math.pow(-0, 0.5) == 0 == Math.pow(0, 0.5).
// Adding 0 converts any -0 to 0.
masm.zeroDouble(scratch);
masm.addDouble(input, scratch);
masm.vsqrtsd(scratch, output, output);
}
else {
masm.vsqrtsd(input, output, output);
}
masm.bind(&done);
}
class OutOfLineUndoALUOperation
:
public OutOfLineCodeBase<CodeGeneratorX86Shared> {
LInstruction* ins_;
public:
explicit OutOfLineUndoALUOperation(LInstruction* ins) : ins_(ins) {}
virtual void accept(CodeGeneratorX86Shared* codegen) override {
codegen->visitOutOfLineUndoALUOperation(
this);
}
LInstruction* ins()
const {
return ins_; }
};
void CodeGenerator::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 (alloc()) OutOfLineUndoALUOperation(ins);
addOutOfLineCode(ool, ins->mir());
masm.j(Assembler::Overflow, ool->entry());
}
else {
bailoutIf(Assembler::Overflow, ins->snapshot());
}
}
}
void CodeGenerator::visitAddI64(LAddI64* lir) {
LInt64Allocation lhs = lir->lhs();
LInt64Allocation rhs = lir->rhs();
MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs));
if (IsConstant(rhs)) {
masm.add64(Imm64(ToInt64(rhs)), ToRegister64(lhs));
return;
}
masm.add64(ToOperandOrRegister64(rhs), ToRegister64(lhs));
}
void CodeGenerator::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 (alloc()) OutOfLineUndoALUOperation(ins);
addOutOfLineCode(ool, ins->mir());
masm.j(Assembler::Overflow, ool->entry());
}
else {
bailoutIf(Assembler::Overflow, ins->snapshot());
}
}
}
void CodeGenerator::visitSubI64(LSubI64* lir) {
LInt64Allocation lhs = lir->lhs();
LInt64Allocation rhs = lir->rhs();
MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs));
if (IsConstant(rhs)) {
masm.sub64(Imm64(ToInt64(rhs)), ToRegister64(lhs));
return;
}
masm.sub64(ToOperandOrRegister64(rhs), ToRegister64(lhs));
}
void CodeGeneratorX86Shared::visitOutOfLineUndoALUOperation(
OutOfLineUndoALUOperation* ool) {
LInstruction* ins = ool->ins();
Register reg = ToRegister(ins->getDef(0));
DebugOnly<LAllocation*> lhs = ins->getOperand(0);
LAllocation* rhs = ins->getOperand(1);
MOZ_ASSERT(reg == ToRegister(lhs));
MOZ_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);
}
}
bailout(ool->ins()->snapshot());
}
class MulNegativeZeroCheck :
public OutOfLineCodeBase<CodeGeneratorX86Shared> {
LMulI* ins_;
public:
explicit MulNegativeZeroCheck(LMulI* ins) : ins_(ins) {}
virtual void accept(CodeGeneratorX86Shared* codegen) override {
codegen->visitMulNegativeZeroCheck(
this);
}
LMulI* ins()
const {
return ins_; }
};
void CodeGenerator::visitMulI(LMulI* ins) {
const LAllocation* lhs = ins->lhs();
const LAllocation* rhs = ins->rhs();
MMul* mul = ins->mir();
MOZ_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.test32(ToRegister(lhs), ToRegister(lhs));
bailoutIf(bailoutCond, ins->snapshot());
}
switch (constant) {
case -1:
masm.negl(ToOperand(lhs));
break;
case 0:
masm.xorl(ToOperand(lhs), ToRegister(lhs));
return;
// escape overflow check;
case 1:
// nop
return;
// 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 = FloorLog2(constant);
if ((1 << shift) == constant) {
masm.shll(Imm32(shift), ToRegister(lhs));
return;
}
}
masm.imull(Imm32(ToInt32(rhs)), ToRegister(lhs));
}
// Bailout on overflow
if (mul->canOverflow()) {
bailoutIf(Assembler::Overflow, ins->snapshot());
}
}
else {
masm.imull(ToOperand(rhs), ToRegister(lhs));
// Bailout on overflow
if (mul->canOverflow()) {
bailoutIf(Assembler::Overflow, ins->snapshot());
}
if (mul->canBeNegativeZero()) {
// Jump to an OOL path if the result is 0.
MulNegativeZeroCheck* ool =
new (alloc()) MulNegativeZeroCheck(ins);
addOutOfLineCode(ool, mul);
masm.test32(ToRegister(lhs), ToRegister(lhs));
masm.j(Assembler::Zero, ool->entry());
masm.bind(ool->rejoin());
}
}
}
void CodeGenerator::visitMulI64(LMulI64* lir) {
LInt64Allocation lhs = lir->lhs();
LInt64Allocation rhs = lir->rhs();
MOZ_ASSERT(ToRegister64(lhs) == ToOutRegister64(lir));
if (IsConstant(rhs)) {
int64_t constant = ToInt64(rhs);
switch (constant) {
case -1:
masm.neg64(ToRegister64(lhs));
return;
case 0:
masm.xor64(ToRegister64(lhs), ToRegister64(lhs));
return;
case 1:
// nop
return;
case 2:
masm.add64(ToRegister64(lhs), ToRegister64(lhs));
return;
default:
if (constant > 0) {
// Use shift if constant is power of 2.
int32_t shift = mozilla::FloorLog2(constant);
if (int64_t(1) << shift == constant) {
masm.lshift64(Imm32(shift), ToRegister64(lhs));
return;
}
}
Register temp = ToTempRegisterOrInvalid(lir->temp0());
masm.mul64(Imm64(constant), ToRegister64(lhs), temp);
}
}
else {
Register temp = ToTempRegisterOrInvalid(lir->temp0());
masm.mul64(ToOperandOrRegister64(rhs), ToRegister64(lhs), temp);
}
}
class ReturnZero :
public OutOfLineCodeBase<CodeGeneratorX86Shared> {
Register reg_;
public:
explicit ReturnZero(
Register reg) : reg_(reg) {}
virtual void accept(CodeGeneratorX86Shared* codegen) override {
codegen->visitReturnZero(
this);
}
Register reg()
const {
return reg_; }
};
void CodeGeneratorX86Shared::visitReturnZero(ReturnZero* ool) {
masm.mov(ImmWord(0), ool->reg());
masm.jmp(ool->rejoin());
}
void CodeGenerator::visitUDivOrMod(LUDivOrMod* ins) {
Register lhs = ToRegister(ins->lhs());
Register rhs = ToRegister(ins->rhs());
Register output = ToRegister(ins->output());
MOZ_ASSERT_IF(lhs != rhs, rhs != eax);
MOZ_ASSERT(rhs != edx);
MOZ_ASSERT_IF(output == eax, ToRegister(ins->remainder()) == edx);
ReturnZero* ool = nullptr;
// Put the lhs in eax.
if (lhs != eax) {
masm.mov(lhs, eax);
}
// Prevent divide by zero.
if (ins->canBeDivideByZero()) {
masm.test32(rhs, rhs);
if (ins->mir()->isTruncated()) {
if (ins->trapOnError()) {
Label nonZero;
masm.j(Assembler::NonZero, &nonZero);
masm.wasmTrap(wasm::Trap::IntegerDivideByZero, ins->trapSiteDesc());
masm.bind(&nonZero);
}
else {
ool =
new (alloc()) ReturnZero(output);
masm.j(Assembler::Zero, ool->entry());
}
}
else {
bailoutIf(Assembler::Zero, ins->snapshot());
}
}
// Zero extend the lhs into edx to make (edx:eax), since udiv is 64-bit.
masm.mov(ImmWord(0), edx);
masm.udiv(rhs);
// If the remainder is > 0, bailout since this must be a double.
if (ins->mir()->isDiv() && !ins->mir()->toDiv()->canTruncateRemainder()) {
Register remainder = ToRegister(ins->remainder());
masm.test32(remainder, remainder);
bailoutIf(Assembler::NonZero, ins->snapshot());
}
// Unsigned div or mod can return a value that's not a signed int32.
// If our users aren't expecting that, bail.
if (!ins->mir()->isTruncated()) {
masm.test32(output, output);
bailoutIf(Assembler::
Signed, ins->snapshot());
}
if (ool) {
addOutOfLineCode(ool, ins->mir());
masm.bind(ool->rejoin());
}
}
void CodeGenerator::visitUDivOrModConstant(LUDivOrModConstant* ins) {
Register lhs = ToRegister(ins->numerator());
Register output = ToRegister(ins->output());
uint32_t d = ins->denominator();
// This emits the division answer into edx or the modulus answer into eax.
MOZ_ASSERT(output == eax || output == edx);
MOZ_ASSERT(lhs != eax && lhs != edx);
bool isDiv = (output == edx);
if (d == 0) {
if (ins->mir()->isTruncated()) {
if (ins->trapOnError()) {
masm.wasmTrap(wasm::Trap::IntegerDivideByZero, ins->trapSiteDesc());
}
else {
masm.xorl(output, output);
}
}
else {
bailout(ins->snapshot());
}
return;
}
// The denominator isn't a power of 2 (see LDivPowTwoI and LModPowTwoI).
MOZ_ASSERT((d & (d - 1)) != 0);
auto rmc = ReciprocalMulConstants::computeUnsignedDivisionConstants(d);
// We first compute (M * n) >> 32, where M = rmc.multiplier.
masm.movl(Imm32(rmc.multiplier), eax);
masm.umull(lhs);
if (rmc.multiplier > UINT32_MAX) {
// M >= 2^32 and shift == 0 is impossible, as d >= 2 implies that
// ((M * n) >> (32 + shift)) >= n > floor(n/d) whenever n >= d,
// contradicting the proof of correctness in computeDivisionConstants.
MOZ_ASSERT(rmc.shiftAmount > 0);
MOZ_ASSERT(rmc.multiplier < (int64_t(1) << 33));
// We actually computed edx = ((uint32_t(M) * n) >> 32) instead. Since
// (M * n) >> (32 + shift) is the same as (edx + n) >> shift, we can
// correct for the overflow. This case is a bit trickier than the signed
// case, though, as the (edx + n) addition itself can overflow; however,
// note that (edx + n) >> shift == (((n - edx) >> 1) + edx) >> (shift - 1),
// which is overflow-free. See Hacker's Delight, section 10-8 for details.
// Compute (n - edx) >> 1 into eax.
masm.movl(lhs, eax);
masm.subl(edx, eax);
masm.shrl(Imm32(1), eax);
// Finish the computation.
masm.addl(eax, edx);
masm.shrl(Imm32(rmc.shiftAmount - 1), edx);
}
else {
masm.shrl(Imm32(rmc.shiftAmount), edx);
}
// We now have the truncated division value in edx. If we're
// computing a modulus or checking whether the division resulted
// in an integer, we need to multiply the obtained value by d and
// finish the computation/check.
if (!isDiv) {
masm.imull(Imm32(d), edx, edx);
masm.movl(lhs, eax);
masm.subl(edx, eax);
// The final result of the modulus op, just computed above by the
// sub instruction, can be a number in the range [2^31, 2^32). If
// this is the case and the modulus is not truncated, we must bail
// out.
if (!ins->mir()->isTruncated()) {
bailoutIf(Assembler::
Signed, ins->snapshot());
}
}
else if (!ins->mir()->isTruncated()) {
masm.imull(Imm32(d), edx, eax);
masm.cmpl(lhs, eax);
bailoutIf(Assembler::NotEqual, ins->snapshot());
}
}
void CodeGeneratorX86Shared::visitMulNegativeZeroCheck(
MulNegativeZeroCheck* ool) {
LMulI* ins = ool->ins();
Register result = ToRegister(ins->output());
Operand lhsCopy = ToOperand(ins->lhsCopy());
Operand rhs = ToOperand(ins->rhs());
MOZ_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);
bailoutIf(Assembler::
Signed, ins->snapshot());
masm.mov(ImmWord(0), result);
masm.jmp(ool->rejoin());
}
void CodeGenerator::visitDivPowTwoI(LDivPowTwoI* ins) {
Register lhs = ToRegister(ins->numerator());
DebugOnly<
Register> output = ToRegister(ins->output());
int32_t shift = ins->shift();
bool negativeDivisor = ins->negativeDivisor();
MDiv* mir = ins->mir();
// We use defineReuseInput so these should always be the same, which is
// convenient since all of our instructions here are two-address.
MOZ_ASSERT(lhs == output);
if (!mir->isTruncated() && negativeDivisor) {
// 0 divided by a negative number must return a double.
masm.test32(lhs, lhs);
bailoutIf(Assembler::Zero, ins->snapshot());
}
if (shift) {
if (!mir->isTruncated()) {
// If the remainder is != 0, bailout since this must be a double.
masm.test32(lhs, Imm32(UINT32_MAX >> (32 - shift)));
bailoutIf(Assembler::NonZero, ins->snapshot());
}
if (mir->isUnsigned()) {
masm.shrl(Imm32(shift), lhs);
}
else {
// 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.
if (mir->canBeNegativeDividend() && mir->isTruncated()) {
// Note: There is no need to execute this code, which handles how to
// round the signed integer division towards 0, if we previously bailed
// due to a non-zero remainder.
Register lhsCopy = ToRegister(ins->numeratorCopy());
MOZ_ASSERT(lhsCopy != lhs);
if (shift > 1) {
// Copy the sign bit of the numerator. (= (2^32 - 1) or 0)
masm.sarl(Imm32(31), lhs);
}
// Divide by 2^(32 - shift)
// i.e. (= (2^32 - 1) / 2^(32 - shift) or 0)
// i.e. (= (2^shift - 1) or 0)
masm.shrl(Imm32(32 - shift), lhs);
// If signed, make any 1 bit below the shifted bits to bubble up, such
// that once shifted the value would be rounded towards 0.
masm.addl(lhsCopy, lhs);
}
masm.sarl(Imm32(shift), lhs);
if (negativeDivisor) {
masm.negl(lhs);
}
}
return;
}
if (negativeDivisor) {
// INT32_MIN / -1 overflows.
masm.negl(lhs);
if (!mir->isTruncated()) {
bailoutIf(Assembler::Overflow, ins->snapshot());
}
else if (mir->trapOnError()) {
Label ok;
masm.j(Assembler::NoOverflow, &ok);
masm.wasmTrap(wasm::Trap::IntegerOverflow, mir->trapSiteDesc());
masm.bind(&ok);
}
}
else if (mir->isUnsigned() && !mir->isTruncated()) {
// Unsigned division by 1 can overflow if output is not
// truncated.
masm.test32(lhs, lhs);
bailoutIf(Assembler::
Signed, ins->snapshot());
}
}
void CodeGenerator::visitDivOrModConstantI(LDivOrModConstantI* ins) {
Register lhs = ToRegister(ins->numerator());
Register output = ToRegister(ins->output());
int32_t d = ins->denominator();
// This emits the division answer into edx or the modulus answer into eax.
MOZ_ASSERT(output == eax || output == edx);
MOZ_ASSERT(lhs != eax && lhs != edx);
bool isDiv = (output == edx);
// The absolute value of the denominator isn't a power of 2 (see LDivPowTwoI
// and LModPowTwoI).
MOZ_ASSERT((Abs(d) & (Abs(d) - 1)) != 0);
// We will first divide by Abs(d), and negate the answer if d is negative.
// If desired, this can be avoided by generalizing computeDivisionConstants.
auto rmc = ReciprocalMulConstants::computeSignedDivisionConstants(Abs(d));
// We first compute (M * n) >> 32, where M = rmc.multiplier.
masm.movl(Imm32(rmc.multiplier), eax);
masm.imull(lhs);
if (rmc.multiplier > INT32_MAX) {
MOZ_ASSERT(rmc.multiplier < (int64_t(1) << 32));
// We actually computed edx = ((int32_t(M) * n) >> 32) instead. Since
// (M * n) >> 32 is the same as (edx + n), we can correct for the overflow.
// (edx + n) can't overflow, as n and edx have opposite signs because
// int32_t(M) is negative.
masm.addl(lhs, edx);
}
// (M * n) >> (32 + shift) is the truncated division answer if n is
// non-negative, as proved in the comments of computeDivisionConstants. We
// must add 1 later if n is negative to get the right answer in all cases.
masm.sarl(Imm32(rmc.shiftAmount), edx);
// We'll subtract -1 instead of adding 1, because (n < 0 ? -1 : 0) can be
// computed with just a sign-extending shift of 31 bits.
if (ins->canBeNegativeDividend()) {
masm.movl(lhs, eax);
masm.sarl(Imm32(31), eax);
masm.subl(eax, edx);
}
// After this, edx contains the correct truncated division result.
if (d < 0) {
masm.negl(edx);
}
if (!isDiv) {
masm.imull(Imm32(-d), edx, eax);
masm.addl(lhs, eax);
}
if (!ins->mir()->isTruncated()) {
if (isDiv) {
// This is a division op. Multiply the obtained value by d to check if
// the correct answer is an integer. This cannot overflow, since |d| > 1.
masm.imull(Imm32(d), edx, eax);
masm.cmp32(lhs, eax);
bailoutIf(Assembler::NotEqual, ins->snapshot());
// If lhs is zero and the divisor is negative, the answer should have
// been -0.
if (d < 0) {
masm.test32(lhs, lhs);
bailoutIf(Assembler::Zero, ins->snapshot());
}
}
else if (ins->canBeNegativeDividend()) {
// This is a mod op. If the computed value is zero and lhs
// is negative, the answer should have been -0.
Label done;
masm.cmp32(lhs, Imm32(0));
masm.j(Assembler::GreaterThanOrEqual, &done);
masm.test32(eax, eax);
bailoutIf(Assembler::Zero, ins->snapshot());
masm.bind(&done);
}
}
}
void CodeGenerator::visitDivI(LDivI* ins) {
Register remainder = ToRegister(ins->temp0());
Register lhs = ToRegister(ins->lhs());
Register rhs = ToRegister(ins->rhs());
Register output = ToRegister(ins->output());
MDiv* mir = ins->mir();
MOZ_ASSERT_IF(lhs != rhs, rhs != eax);
MOZ_ASSERT(rhs != edx);
MOZ_ASSERT(remainder == edx);
MOZ_ASSERT(output == eax);
Label done;
ReturnZero* ool = nullptr;
// Put the lhs in eax, for either the negative overflow case or the regular
// divide case.
if (lhs != eax) {
masm.mov(lhs, eax);
}
// Handle divide by zero.
if (mir->canBeDivideByZero()) {
masm.test32(rhs, rhs);
if (mir->trapOnError()) {
Label nonZero;
masm.j(Assembler::NonZero, &nonZero);
masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc());
masm.bind(&nonZero);
}
else if (mir->canTruncateInfinities()) {
// Truncated division by zero is zero (Infinity|0 == 0)
if (!ool) {
ool =
new (alloc()) ReturnZero(output);
}
masm.j(Assembler::Zero, ool->entry());
}
else {
MOZ_ASSERT(mir->fallible());
bailoutIf(Assembler::Zero, ins->snapshot());
}
}
// Handle an integer overflow exception from -2147483648 / -1.
if (mir->canBeNegativeOverflow()) {
Label notOverflow;
masm.cmp32(lhs, Imm32(INT32_MIN));
masm.j(Assembler::NotEqual, ¬Overflow);
masm.cmp32(rhs, Imm32(-1));
if (mir->trapOnError()) {
masm.j(Assembler::NotEqual, ¬Overflow);
masm.wasmTrap(wasm::Trap::IntegerOverflow, mir->trapSiteDesc());
}
else if (mir->canTruncateOverflow()) {
// (-INT32_MIN)|0 == INT32_MIN and INT32_MIN is already in the
// output register (lhs == eax).
masm.j(Assembler::Equal, &done);
}
else {
MOZ_ASSERT(mir->fallible());
bailoutIf(Assembler::Equal, ins->snapshot());
}
masm.bind(¬Overflow);
}
// Handle negative 0.
if (!mir->canTruncateNegativeZero() && mir->canBeNegativeZero()) {
Label nonzero;
masm.test32(lhs, lhs);
masm.j(Assembler::NonZero, &nonzero);
masm.cmp32(rhs, Imm32(0));
bailoutIf(Assembler::LessThan, ins->snapshot());
masm.bind(&nonzero);
}
// Sign extend the lhs into edx to make (edx:eax), since idiv is 64-bit.
if (lhs != eax) {
masm.mov(lhs, eax);
}
masm.cdq();
masm.idiv(rhs);
if (!mir->canTruncateRemainder()) {
// If the remainder is > 0, bailout since this must be a double.
masm.test32(remainder, remainder);
bailoutIf(Assembler::NonZero, ins->snapshot());
}
masm.bind(&done);
if (ool) {
addOutOfLineCode(ool, mir);
masm.bind(ool->rejoin());
}
}
void CodeGenerator::visitModPowTwoI(LModPowTwoI* ins) {
Register lhs = ToRegister(ins->input());
int32_t shift = ins->shift();
Label negative;
if (!ins->mir()->isUnsigned() && ins->mir()->canBeNegativeDividend()) {
// Switch based on sign of the lhs.
// Positive numbers are just a bitmask
masm.branchTest32(Assembler::
Signed, lhs, lhs, &negative);
}
masm.andl(Imm32((uint32_t(1) << shift) - 1), lhs);
if (!ins->mir()->isUnsigned() && ins->mir()->canBeNegativeDividend()) {
Label done;
masm.jump(&done);
// Negative numbers need a negate, bitmask, negate
masm.bind(&negative);
// Unlike in the visitModI case, we are not computing the mod by means of a
// division. Therefore, the divisor = -1 case isn't problematic (the andl
// always returns 0, which is what we expect).
//
// The negl instruction overflows if lhs == INT32_MIN, but this is also not
// a problem: shift is at most 31, and so the andl also always returns 0.
masm.negl(lhs);
masm.andl(Imm32((uint32_t(1) << shift) - 1), lhs);
masm.negl(lhs);
// Since a%b has the same sign as b, and a is negative in this branch,
// an answer of 0 means the correct result is actually -0. Bail out.
if (!ins->mir()->isTruncated()) {
bailoutIf(Assembler::Zero, ins->snapshot());
}
masm.bind(&done);
}
}
class ModOverflowCheck :
public OutOfLineCodeBase<CodeGeneratorX86Shared> {
Label done_;
LModI* ins_;
Register rhs_;
public:
explicit ModOverflowCheck(LModI* ins,
Register rhs) : ins_(ins), rhs_(rhs) {}
virtual void accept(CodeGeneratorX86Shared* codegen) override {
codegen->visitModOverflowCheck(
this);
}
Label* done() {
return &done_; }
LModI* ins()
const {
return ins_; }
Register rhs()
const {
return rhs_; }
};
void CodeGeneratorX86Shared::visitModOverflowCheck(ModOverflowCheck* ool) {
masm.cmp32(ool->rhs(), Imm32(-1));
if (ool->ins()->mir()->isTruncated()) {
masm.j(Assembler::NotEqual, ool->rejoin());
masm.mov(ImmWord(0), edx);
masm.jmp(ool->done());
}
else {
bailoutIf(Assembler::Equal, ool->ins()->snapshot());
masm.jmp(ool->rejoin());
}
}
void CodeGenerator::visitModI(LModI* ins) {
Register remainder = ToRegister(ins->output());
Register lhs = ToRegister(ins->lhs());
Register rhs = ToRegister(ins->rhs());
// Required to use idiv.
MOZ_ASSERT_IF(lhs != rhs, rhs != eax);
MOZ_ASSERT(rhs != edx);
MOZ_ASSERT(remainder == edx);
MOZ_ASSERT(ToRegister(ins->temp0()) == eax);
Label done;
ReturnZero* ool = nullptr;
ModOverflowCheck* overflow = nullptr;
// Set up eax in preparation for doing a div.
if (lhs != eax) {
masm.mov(lhs, eax);
}
MMod* mir = ins->mir();
// Prevent divide by zero.
if (mir->canBeDivideByZero()) {
masm.test32(rhs, rhs);
if (mir->isTruncated()) {
if (mir->trapOnError()) {
Label nonZero;
masm.j(Assembler::NonZero, &nonZero);
masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc());
masm.bind(&nonZero);
}
else {
if (!ool) {
ool =
new (alloc()) ReturnZero(edx);
}
masm.j(Assembler::Zero, ool->entry());
}
}
else {
bailoutIf(Assembler::Zero, ins->snapshot());
}
}
Label negative;
// Switch based on sign of the lhs.
if (mir->canBeNegativeDividend()) {
masm.branchTest32(Assembler::
Signed, lhs, lhs, &negative);
}
// If lhs >= 0 then remainder = lhs % rhs. The remainder must be positive.
{
// Check if rhs is a power-of-two.
if (mir->canBePowerOfTwoDivisor()) {
MOZ_ASSERT(rhs != remainder);
// Rhs y is a power-of-two if (y & (y-1)) == 0. Note that if
// y is any negative number other than INT32_MIN, both y and
// y-1 will have the sign bit set so these are never optimized
// as powers-of-two. If y is INT32_MIN, y-1 will be INT32_MAX
// and because lhs >= 0 at this point, lhs & INT32_MAX returns
// the correct value.
Label notPowerOfTwo;
masm.mov(rhs, remainder);
masm.subl(Imm32(1), remainder);
masm.branchTest32(Assembler::NonZero, remainder, rhs, ¬PowerOfTwo);
{
masm.andl(lhs, remainder);
masm.jmp(&done);
}
masm.bind(¬PowerOfTwo);
}
// Since lhs >= 0, the sign-extension will be 0
masm.mov(ImmWord(0), edx);
masm.idiv(rhs);
}
// Otherwise, we have to beware of two special cases:
if (mir->canBeNegativeDividend()) {
masm.jump(&done);
masm.bind(&negative);
// Prevent an integer overflow exception from -2147483648 % -1
masm.cmp32(lhs, Imm32(INT32_MIN));
overflow =
new (alloc()) ModOverflowCheck(ins, rhs);
masm.j(Assembler::Equal, overflow->entry());
masm.bind(overflow->rejoin());
masm.cdq();
masm.idiv(rhs);
if (!mir->isTruncated()) {
// A remainder of 0 means that the rval must be -0, which is a double.
masm.test32(remainder, remainder);
bailoutIf(Assembler::Zero, ins->snapshot());
}
}
masm.bind(&done);
if (overflow) {
addOutOfLineCode(overflow, mir);
masm.bind(overflow->done());
}
if (ool) {
addOutOfLineCode(ool, mir);
masm.bind(ool->rejoin());
}
}
void CodeGenerator::visitBitNotI(LBitNotI* ins) {
const LAllocation* input = ins->input();
MOZ_ASSERT(!input->isConstant());
masm.notl(ToOperand(input));
}
void CodeGenerator::visitBitOpI(LBitOpI* ins) {
const LAllocation* lhs = ins->lhs();
const LAllocation* rhs = ins->rhs();
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:
MOZ_CRASH(
"unexpected binary opcode");
}
}
void CodeGenerator::visitBitOpI64(LBitOpI64* lir) {
LInt64Allocation lhs = lir->lhs();
LInt64Allocation rhs = lir->rhs();
MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs));
switch (lir->bitop()) {
case JSOp::
BitOr:
if (IsConstant(rhs)) {
masm.or64(Imm64(ToInt64(rhs)), ToRegister64(lhs));
}
else {
masm.or64(ToOperandOrRegister64(rhs), ToRegister64(lhs));
}
break;
case JSOp::BitXor:
if (IsConstant(rhs)) {
masm.xor64(Imm64(ToInt64(rhs)), ToRegister64(lhs));
}
else {
masm.xor64(ToOperandOrRegister64(rhs), ToRegister64(lhs));
}
break;
case JSOp::
BitAnd:
if (IsConstant(rhs)) {
masm.and64(Imm64(ToInt64(rhs)), ToRegister64(lhs));
}
else {
masm.and64(ToOperandOrRegister64(rhs), ToRegister64(lhs));
}
break;
default:
MOZ_CRASH(
"unexpected binary opcode");
}
}
void CodeGenerator::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.lshift32(Imm32(shift), lhs);
}
break;
case JSOp::Rsh:
if (shift) {
masm.rshift32Arithmetic(Imm32(shift), lhs);
}
break;
case JSOp::Ursh:
if (shift) {
masm.rshift32(Imm32(shift), lhs);
}
else if (ins->mir()->toUrsh()->fallible()) {
// x >>> 0 can overflow.
masm.test32(lhs, lhs);
bailoutIf(Assembler::
Signed, ins->snapshot());
}
break;
default:
MOZ_CRASH(
"Unexpected shift op");
}
}
else {
Register shift = ToRegister(rhs);
switch (ins->bitop()) {
case JSOp::Lsh:
masm.lshift32(shift, lhs);
break;
case JSOp::Rsh:
masm.rshift32Arithmetic(shift, lhs);
break;
case JSOp::Ursh:
masm.rshift32(shift, lhs);
if (ins->mir()->toUrsh()->fallible()) {
// x >>> 0 can overflow.
masm.test32(lhs, lhs);
bailoutIf(Assembler::
Signed, ins->snapshot());
}
break;
default:
MOZ_CRASH(
"Unexpected shift op");
}
}
}
void CodeGenerator::visitShiftI64(LShiftI64* lir) {
LInt64Allocation lhs = lir->lhs();
const LAllocation* rhs = lir->rhs();
MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs));
if (rhs->isConstant()) {
int32_t shift = int32_t(rhs->toConstant()->toInt64() & 0x3F);
switch (lir->bitop()) {
case JSOp::Lsh:
if (shift) {
masm.lshift64(Imm32(shift), ToRegister64(lhs));
}
break;
case JSOp::Rsh:
if (shift) {
masm.rshift64Arithmetic(Imm32(shift), ToRegister64(lhs));
}
break;
case JSOp::Ursh:
if (shift) {
masm.rshift64(Imm32(shift), ToRegister64(lhs));
}
break;
default:
MOZ_CRASH(
"Unexpected shift op");
}
return;
}
Register shift = ToRegister(rhs);
#ifdef JS_CODEGEN_X86
MOZ_ASSERT(shift == ecx);
#endif
switch (lir->bitop()) {
case JSOp::Lsh:
masm.lshift64(shift, ToRegister64(lhs));
break;
case JSOp::Rsh:
masm.rshift64Arithmetic(shift, ToRegister64(lhs));
break;
case JSOp::Ursh:
masm.rshift64(shift, ToRegister64(lhs));
break;
default:
MOZ_CRASH(
"Unexpected shift op");
}
}
void CodeGenerator::visitUrshD(LUrshD* ins) {
Register lhs = ToRegister(ins->lhs());
MOZ_ASSERT(ToRegister(ins->temp0()) == 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 {
Register shift = ToRegister(rhs);
masm.rshift32(shift, lhs);
}
masm.convertUInt32ToDouble(lhs, out);
}
Operand CodeGeneratorX86Shared::ToOperand(
const LAllocation& a) {
if (a.isGeneralReg()) {
return Operand(a.toGeneralReg()->reg());
}
if (a.isFloatReg()) {
return Operand(a.toFloatReg()->reg());
}
return Operand(ToAddress(a));
}
Operand CodeGeneratorX86Shared::ToOperand(
const LAllocation* a) {
return ToOperand(*a);
}
Operand CodeGeneratorX86Shared::ToOperand(
const LDefinition* def) {
return ToOperand(def->output());
}
MoveOperand CodeGeneratorX86Shared::toMoveOperand(LAllocation a)
const {
if (a.isGeneralReg()) {
return MoveOperand(ToRegister(a));
}
if (a.isFloatReg()) {
return MoveOperand(ToFloatRegister(a));
}
MoveOperand::Kind kind = a.isStackArea() ? MoveOperand::Kind::EffectiveAddress
: MoveOperand::Kind::Memory;
return MoveOperand(ToAddress(a), kind);
}
class OutOfLineTableSwitch :
public OutOfLineCodeBase<CodeGeneratorX86Shared> {
MTableSwitch* mir_;
CodeLabel jumpLabel_;
void accept(CodeGeneratorX86Shared* codegen) override {
codegen->visitOutOfLineTableSwitch(
this);
}
public:
explicit OutOfLineTableSwitch(MTableSwitch* mir) : mir_(mir) {}
MTableSwitch* mir()
const {
return mir_; }
CodeLabel* jumpLabel() {
return &jumpLabel_; }
};
void CodeGeneratorX86Shared::visitOutOfLineTableSwitch(
OutOfLineTableSwitch* ool) {
MTableSwitch* mir = ool->mir();
masm.haltingAlign(
sizeof(
void*));
masm.bind(ool->jumpLabel());
masm.addCodeLabel(*ool->jumpLabel());
for (size_t i = 0; i < mir->numCases(); i++) {
LBlock* caseblock = skipTrivialBlocks(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);
cl.target()->bind(caseoffset);
masm.addCodeLabel(cl);
}
}
void CodeGeneratorX86Shared::emitTableSwitchDispatch(MTableSwitch* mir,
Register index,
Register base) {
Label* defaultcase = skipTrivialBlocks(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.cmp32(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 (alloc()) OutOfLineTableSwitch(mir);
addOutOfLineCode(ool, mir);
// Compute the position where a pointer to the right case stands.
masm.mov(ool->jumpLabel(), base);
BaseIndex pointer(base, index, ScalePointer);
// Jump to the right case
masm.branchToComputedAddress(pointer);
}
void CodeGenerator::visitMathD(LMathD* math) {
FloatRegister lhs = ToFloatRegister(math->lhs());
Operand rhs = ToOperand(math->rhs());
FloatRegister output = ToFloatRegister(math->output());
switch (math->jsop()) {
case JSOp::Add:
masm.vaddsd(rhs, lhs, output);
break;
case JSOp::Sub:
masm.vsubsd(rhs, lhs, output);
break;
case JSOp::Mul:
masm.vmulsd(rhs, lhs, output);
break;
case JSOp::Div:
masm.vdivsd(rhs, lhs, output);
break;
default:
MOZ_CRASH(
"unexpected opcode");
}
}
void CodeGenerator::visitMathF(LMathF* math) {
FloatRegister lhs = ToFloatRegister(math->lhs());
Operand rhs = ToOperand(math->rhs());
FloatRegister output = ToFloatRegister(math->output());
switch (math->jsop()) {
case JSOp::Add:
masm.vaddss(rhs, lhs, output);
break;
case JSOp::Sub:
masm.vsubss(rhs, lhs, output);
break;
case JSOp::Mul:
masm.vmulss(rhs, lhs, output);
break;
case JSOp::Div:
masm.vdivss(rhs, lhs, output);
break;
default:
MOZ_CRASH(
"unexpected opcode");
}
}
void CodeGenerator::visitNearbyInt(LNearbyInt* lir) {
FloatRegister input = ToFloatRegister(lir->input());
FloatRegister output = ToFloatRegister(lir->output());
RoundingMode roundingMode = lir->mir()->roundingMode();
masm.nearbyIntDouble(roundingMode, input, output);
}
void CodeGenerator::visitNearbyIntF(LNearbyIntF* lir) {
FloatRegister input = ToFloatRegister(lir->input());
FloatRegister output = ToFloatRegister(lir->output());
RoundingMode roundingMode = lir->mir()->roundingMode();
masm.nearbyIntFloat32(roundingMode, input, output);
}
void CodeGenerator::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);
}
void 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)));
// Jump to the invalidator which will replace the current frame.
TrampolinePtr thunk = gen->jitRuntime()->getInvalidationThunk();
masm.jump(thunk);
}
void CodeGenerator::visitNegI(LNegI* ins) {
Register input = ToRegister(ins->input());
MOZ_ASSERT(input == ToRegister(ins->output()));
masm.neg32(input);
}
void CodeGenerator::visitNegI64(LNegI64* ins) {
Register64 input = ToRegister64(ins->input());
MOZ_ASSERT(input == ToOutRegister64(ins));
masm.neg64(input);
}
void CodeGenerator::visitNegD(LNegD* ins) {
FloatRegister input = ToFloatRegister(ins->input());
MOZ_ASSERT(input == ToFloatRegister(ins->output()));
masm.negateDouble(input);
}
void CodeGenerator::visitNegF(LNegF* ins) {
FloatRegister input = ToFloatRegister(ins->input());
MOZ_ASSERT(input == ToFloatRegister(ins->output()));
masm.negateFloat(input);
}
void CodeGenerator::visitCompareExchangeTypedArrayElement(
LCompareExchangeTypedArrayElement* lir) {
Register elements = ToRegister(lir->elements());
AnyRegister output = ToAnyRegister(lir->output());
Register temp = ToTempRegisterOrInvalid(lir->temp0());
Register oldval = ToRegister(lir->oldval());
Register newval = ToRegister(lir->newval());
Scalar::Type arrayType = lir->mir()->arrayType();
if (lir->index()->isConstant()) {
Address dest = ToAddress(elements, lir->index(), arrayType);
masm.compareExchangeJS(arrayType, Synchronization::Full(), dest, oldval,
newval, temp, output);
}
else {
BaseIndex dest(elements, ToRegister(lir->index()),
ScaleFromScalarType(arrayType));
masm.compareExchangeJS(arrayType, Synchronization::Full(), dest, oldval,
newval, temp, output);
}
}
void CodeGenerator::visitAtomicExchangeTypedArrayElement(
LAtomicExchangeTypedArrayElement* lir) {
Register elements = ToRegister(lir->elements());
AnyRegister output = ToAnyRegister(lir->output());
Register temp = ToTempRegisterOrInvalid(lir->temp0());
Register value = ToRegister(lir->value());
Scalar::Type arrayType = lir->mir()->arrayType();
if (lir->index()->isConstant()) {
Address dest = ToAddress(elements, lir->index(), arrayType);
masm.atomicExchangeJS(arrayType, Synchronization::Full(), dest, value, temp,
output);
}
else {
BaseIndex dest(elements, ToRegister(lir->index()),
ScaleFromScalarType(arrayType));
masm.atomicExchangeJS(arrayType, Synchronization::Full(), dest, value, temp,
output);
}
}
template <
typename T>
static inline void AtomicBinopToTypedArray(MacroAssembler& masm, AtomicOp op,
Scalar::Type arrayType,
const LAllocation* value,
const T& mem,
Register temp1,
Register temp2, AnyRegister output) {
if (value->isConstant()) {
masm.atomicFetchOpJS(arrayType, Synchronization::Full(), op,
Imm32(ToInt32(value)), mem, temp1, temp2, output);
}
else {
masm.atomicFetchOpJS(arrayType, Synchronization::Full(), op,
ToRegister(value), mem, temp1, temp2, output);
}
}
void CodeGenerator::visitAtomicTypedArrayElementBinop(
LAtomicTypedArrayElementBinop* lir) {
MOZ_ASSERT(!lir->mir()->isForEffect());
AnyRegister output = ToAnyRegister(lir->output());
Register elements = ToRegister(lir->elements());
Register temp1 = ToTempRegisterOrInvalid(lir->temp0());
Register temp2 = ToTempRegisterOrInvalid(lir->temp1());
const LAllocation* value = lir->value();
Scalar::Type arrayType = lir->mir()->arrayType();
if (lir->index()->isConstant()) {
Address mem = ToAddress(elements, lir->index(), arrayType);
AtomicBinopToTypedArray(masm, lir->mir()->operation(), arrayType, value,
mem, temp1, temp2, output);
}
else {
BaseIndex mem(elements, ToRegister(lir->index()),
ScaleFromScalarType(arrayType));
AtomicBinopToTypedArray(masm, lir->mir()->operation(), arrayType, value,
mem, temp1, temp2, output);
}
}
template <
typename T>
static inline void AtomicBinopToTypedArray(MacroAssembler& masm,
Scalar::Type arrayType, AtomicOp op,
const LAllocation* value,
const T& mem) {
if (value->isConstant()) {
masm.atomicEffectOpJS(arrayType, Synchronization::Full(), op,
Imm32(ToInt32(value)), mem, InvalidReg);
}
else {
masm.atomicEffectOpJS(arrayType, Synchronization::Full(), op,
ToRegister(value), mem, InvalidReg);
}
}
void CodeGenerator::visitAtomicTypedArrayElementBinopForEffect(
LAtomicTypedArrayElementBinopForEffect* lir) {
MOZ_ASSERT(lir->mir()->isForEffect());
Register elements = ToRegister(lir->elements());
const LAllocation* value = lir->value();
Scalar::Type arrayType = lir->mir()->arrayType();
if (lir->index()->isConstant()) {
Address mem = ToAddress(elements, lir->index(), arrayType);
AtomicBinopToTypedArray(masm, arrayType, lir->mir()->operation(), value,
mem);
}
else {
BaseIndex mem(elements, ToRegister(lir->index()),
ScaleFromScalarType(arrayType));
AtomicBinopToTypedArray(masm, arrayType, lir->mir()->operation(), value,
mem);
}
}
void CodeGeneratorX86Shared::visitOutOfLineWasmTruncateCheck(
OutOfLineWasmTruncateCheck* ool) {
FloatRegister input = ool->input();
Register output = ool->output();
Register64 output64 = ool->output64();
MIRType fromType = ool->fromType();
MIRType toType = ool->toType();
Label* oolRejoin = ool->rejoin();
TruncFlags flags = ool->flags();
const wasm::TrapSiteDesc& trapSiteDesc = ool->trapSiteDesc();
if (fromType == MIRType::Float32) {
if (toType == MIRType::Int32) {
masm.oolWasmTruncateCheckF32ToI32(input, output, flags, trapSiteDesc,
oolRejoin);
}
else if (toType == MIRType::Int64) {
masm.oolWasmTruncateCheckF32ToI64(input, output64, flags, trapSiteDesc,
oolRejoin);
}
else {
MOZ_CRASH(
"unexpected type");
}
}
else if (fromType == MIRType::
Double) {
if (toType == MIRType::Int32) {
masm.oolWasmTruncateCheckF64ToI32(input, output, flags, trapSiteDesc,
oolRejoin);
}
else if (toType == MIRType::Int64) {
masm.oolWasmTruncateCheckF64ToI64(input, output64, flags, trapSiteDesc,
oolRejoin);
}
else {
MOZ_CRASH(
"unexpected type");
}
}
else {
MOZ_CRASH(
"unexpected type");
}
}
void CodeGeneratorX86Shared::canonicalizeIfDeterministic(
Scalar::Type type,
const LAllocation* value) {
#ifdef DEBUG
if (!js::SupportDifferentialTesting()) {
return;
}
switch (type) {
case Scalar::Float16: {
// This method is only used for LAsmJSStoreHeap, so Float16 values are
// unexpected here.
MOZ_CRASH(
"asm.js doesn't support Float16");
}
case Scalar::Float32: {
FloatRegister in = ToFloatRegister(value);
masm.canonicalizeFloatIfDeterministic(in);
break;
}
case Scalar::Float64: {
FloatRegister in = ToFloatRegister(value);
masm.canonicalizeDoubleIfDeterministic(in);
break;
}
default: {
// Other types don't need canonicalization.
break;
}
}
#endif // DEBUG
}
template <
typename T>
Operand CodeGeneratorX86Shared::toMemoryAccessOperand(T* lir, int32_t disp) {
const LAllocation* ptr = lir->ptr();
const LAllocation* memoryBase = lir->memoryBase();
#ifdef JS_CODEGEN_X86
Operand destAddr = ptr->isBogus() ? Operand(ToRegister(memoryBase), disp)
: Operand(ToRegister(memoryBase),
ToRegister(ptr), TimesOne, disp);
#else
auto baseReg = memoryBase->isBogus() ? HeapReg : ToRegister(memoryBase);
Operand destAddr = ptr->isBogus()
? Operand(baseReg, disp)
: Operand(baseReg, ToRegister(ptr), TimesOne, disp);
#endif
return destAddr;
}
void CodeGenerator::visitCopySignF(LCopySignF* lir) {
FloatRegister lhs = ToFloatRegister(lir->lhs());
FloatRegister rhs = ToFloatRegister(lir->rhs());
FloatRegister out = ToFloatRegister(lir->output());
if (lhs == rhs) {
if (lhs != out) {
masm.moveFloat32(lhs, out);
}
return;
}
masm.copySignFloat32(lhs, rhs, out);
}
void CodeGenerator::visitCopySignD(LCopySignD* lir) {
FloatRegister lhs = ToFloatRegister(lir->lhs());
FloatRegister rhs = ToFloatRegister(lir->rhs());
FloatRegister out = ToFloatRegister(lir->output());
if (lhs == rhs) {
if (lhs != out) {
masm.moveDouble(lhs, out);
}
return;
}
masm.copySignDouble(lhs, rhs, out);
}
void CodeGenerator::visitSimd128(LSimd128* ins) {
--> --------------------
--> maximum size reached
--> --------------------