/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=4 sw=4 et tw=99: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released * May 28, 2008. * * The Initial Developer of the Original Code is * Brendan Eich * * Contributor(s): * David Anderson * David Mandelin * Sean Stangl * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "jsbool.h" #include "jslibmath.h" #include "jsnum.h" #include "methodjit/MethodJIT.h" #include "methodjit/Compiler.h" #include "methodjit/StubCalls.h" #include "methodjit/FrameState-inl.h" using namespace js; using namespace js::mjit; using namespace JSC; typedef JSC::MacroAssembler::FPRegisterID FPRegisterID; bool mjit::Compiler::tryBinaryConstantFold(JSContext *cx, FrameState &frame, JSOp op, FrameEntry *lhs, FrameEntry *rhs) { if (!lhs->isConstant() || !rhs->isConstant()) return false; const Value &L = lhs->getValue(); const Value &R = rhs->getValue(); if (!L.isPrimitive() || !R.isPrimitive() || (op == JSOP_ADD && (L.isString() || R.isString()))) { return false; } bool needInt; switch (op) { case JSOP_ADD: case JSOP_SUB: case JSOP_MUL: case JSOP_DIV: needInt = false; break; case JSOP_MOD: needInt = (L.isInt32() && R.isInt32() && L.toInt32() >= 0 && R.toInt32() > 0); break; case JSOP_RSH: needInt = true; break; default: JS_NOT_REACHED("NYI"); needInt = false; /* Silence compiler warning. */ break; } double dL = 0, dR = 0; int32_t nL = 0, nR = 0; /* * We don't need to check for conversion failure, since primitive conversion * is infallible. */ if (needInt) { ValueToECMAInt32(cx, L, &nL); ValueToECMAInt32(cx, R, &nR); } else { ValueToNumber(cx, L, &dL); ValueToNumber(cx, R, &dR); } switch (op) { case JSOP_ADD: dL += dR; break; case JSOP_SUB: dL -= dR; break; case JSOP_MUL: dL *= dR; break; case JSOP_DIV: if (dR == 0) { #ifdef XP_WIN if (JSDOUBLE_IS_NaN(dR)) dL = js_NaN; else #endif if (dL == 0 || JSDOUBLE_IS_NaN(dL)) dL = js_NaN; else if (JSDOUBLE_IS_NEG(dL) != JSDOUBLE_IS_NEG(dR)) dL = cx->runtime->negativeInfinityValue.toDouble(); else dL = cx->runtime->positiveInfinityValue.toDouble(); } else { dL /= dR; } break; case JSOP_MOD: if (needInt) nL %= nR; else if (dR == 0) dL = js_NaN; else dL = js_fmod(dL, dR); break; case JSOP_RSH: nL >>= (nR & 31); break; default: JS_NOT_REACHED("NYI"); break; } Value v; if (needInt) v.setInt32(nL); else v.setNumber(dL); frame.popn(2); frame.push(v); return true; } void mjit::Compiler::slowLoadConstantDouble(Assembler &masm, FrameEntry *fe, FPRegisterID fpreg) { DoublePatch patch; if (fe->getKnownType() == JSVAL_TYPE_INT32) patch.d = (double)fe->getValue().toInt32(); else patch.d = fe->getValue().toDouble(); patch.label = masm.loadDouble(NULL, fpreg); patch.ool = &masm != &this->masm; JS_ASSERT_IF(patch.ool, &masm == &stubcc.masm); doubleList.append(patch); } void mjit::Compiler::maybeJumpIfNotInt32(Assembler &masm, MaybeJump &mj, FrameEntry *fe, MaybeRegisterID &mreg) { if (!fe->isTypeKnown()) { if (mreg.isSet()) mj.setJump(masm.testInt32(Assembler::NotEqual, mreg.reg())); else mj.setJump(masm.testInt32(Assembler::NotEqual, frame.addressOf(fe))); } else if (fe->getKnownType() != JSVAL_TYPE_INT32) { mj.setJump(masm.jump()); } } void mjit::Compiler::maybeJumpIfNotDouble(Assembler &masm, MaybeJump &mj, FrameEntry *fe, MaybeRegisterID &mreg) { if (!fe->isTypeKnown()) { if (mreg.isSet()) mj.setJump(masm.testDouble(Assembler::NotEqual, mreg.reg())); else mj.setJump(masm.testDouble(Assembler::NotEqual, frame.addressOf(fe))); } else if (fe->getKnownType() != JSVAL_TYPE_DOUBLE) { mj.setJump(masm.jump()); } } void mjit::Compiler::jsop_binary(JSOp op, VoidStub stub) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); if (tryBinaryConstantFold(cx, frame, op, lhs, rhs)) return; /* * Bail out if there are unhandled types or ops. * This is temporary while ops are still being implemented. */ if ((op == JSOP_MOD) || (lhs->isTypeKnown() && (lhs->getKnownType() > JSVAL_UPPER_INCL_TYPE_OF_NUMBER_SET)) || (rhs->isTypeKnown() && (rhs->getKnownType() > JSVAL_UPPER_INCL_TYPE_OF_NUMBER_SET)) #if defined(JS_CPU_ARM) /* ARM cannot detect integer overflow with multiplication. */ || op == JSOP_MUL #endif /* JS_CPU_ARM */ ) { bool isStringResult = (op == JSOP_ADD) && (lhs->isType(JSVAL_TYPE_STRING) || rhs->isType(JSVAL_TYPE_STRING)); prepareStubCall(Uses(2)); INLINE_STUBCALL(stub); frame.popn(2); if (isStringResult) frame.pushSyncedType(JSVAL_TYPE_STRING); else frame.pushSynced(); return; } /* Can do int math iff there is no double constant and the op is not division. */ bool canDoIntMath = op != JSOP_DIV && !((rhs->isTypeKnown() && rhs->getKnownType() == JSVAL_TYPE_DOUBLE) || (lhs->isTypeKnown() && lhs->getKnownType() == JSVAL_TYPE_DOUBLE)); if (canDoIntMath) jsop_binary_full(lhs, rhs, op, stub); else jsop_binary_double(lhs, rhs, op, stub); } static void EmitDoubleOp(JSOp op, FPRegisterID fpRight, FPRegisterID fpLeft, Assembler &masm) { switch (op) { case JSOP_ADD: masm.addDouble(fpRight, fpLeft); break; case JSOP_SUB: masm.subDouble(fpRight, fpLeft); break; case JSOP_MUL: masm.mulDouble(fpRight, fpLeft); break; case JSOP_DIV: masm.divDouble(fpRight, fpLeft); break; default: JS_NOT_REACHED("unrecognized binary op"); } } mjit::MaybeJump mjit::Compiler::loadDouble(FrameEntry *fe, FPRegisterID fpReg) { MaybeJump notNumber; if (fe->isConstant()) { slowLoadConstantDouble(masm, fe, fpReg); } else if (!fe->isTypeKnown()) { frame.tempRegForType(fe); Jump j = frame.testDouble(Assembler::Equal, fe); notNumber = frame.testInt32(Assembler::NotEqual, fe); frame.convertInt32ToDouble(masm, fe, fpReg); Jump converted = masm.jump(); j.linkTo(masm.label(), &masm); // CANDIDATE frame.loadDouble(fe, fpReg, masm); converted.linkTo(masm.label(), &masm); } else if (fe->getKnownType() == JSVAL_TYPE_INT32) { frame.tempRegForData(fe); frame.convertInt32ToDouble(masm, fe, fpReg); } else { JS_ASSERT(fe->getKnownType() == JSVAL_TYPE_DOUBLE); frame.loadDouble(fe, fpReg, masm); } return notNumber; } /* * This function emits a single fast-path for handling numerical arithmetic. * Unlike jsop_binary_full(), all integers are converted to doubles. */ void mjit::Compiler::jsop_binary_double(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub) { FPRegisterID fpLeft = FPRegisters::First; FPRegisterID fpRight = FPRegisters::Second; MaybeJump lhsNotNumber = loadDouble(lhs, fpLeft); if (lhsNotNumber.isSet()) stubcc.linkExit(lhsNotNumber.get(), Uses(2)); MaybeJump rhsNotNumber; if (frame.haveSameBacking(lhs, rhs)) { masm.moveDouble(fpLeft, fpRight); } else { rhsNotNumber = loadDouble(rhs, fpRight); if (rhsNotNumber.isSet()) stubcc.linkExit(rhsNotNumber.get(), Uses(2)); } EmitDoubleOp(op, fpRight, fpLeft, masm); MaybeJump done; /* * Try to convert result to integer. Skip this for 1/x or -1/x, as the * result is unlikely to fit in an int. */ if (op == JSOP_DIV && !(lhs->isConstant() && lhs->isType(JSVAL_TYPE_INT32) && abs(lhs->getValue().toInt32()) == 1)) { RegisterID reg = frame.allocReg(); JumpList isDouble; masm.branchConvertDoubleToInt32(fpLeft, reg, isDouble, fpRight); masm.storeValueFromComponents(ImmType(JSVAL_TYPE_INT32), reg, frame.addressOf(lhs)); frame.freeReg(reg); done.setJump(masm.jump()); isDouble.linkTo(masm.label(), &masm); } masm.storeDouble(fpLeft, frame.addressOf(lhs)); if (done.isSet()) done.getJump().linkTo(masm.label(), &masm); if (lhsNotNumber.isSet() || rhsNotNumber.isSet()) { stubcc.leave(); OOL_STUBCALL(stub); } frame.popn(2); frame.pushNumber(MaybeRegisterID()); if (lhsNotNumber.isSet() || rhsNotNumber.isSet()) stubcc.rejoin(Changes(1)); } /* * Simpler version of jsop_binary_full() for when lhs == rhs. */ void mjit::Compiler::jsop_binary_full_simple(FrameEntry *fe, JSOp op, VoidStub stub) { FrameEntry *lhs = frame.peek(-2); /* Easiest case: known double. Don't bother conversion back yet? */ if (fe->isTypeKnown() && fe->getKnownType() == JSVAL_TYPE_DOUBLE) { loadDouble(fe, FPRegisters::First); EmitDoubleOp(op, FPRegisters::First, FPRegisters::First, masm); frame.popn(2); frame.pushNumber(MaybeRegisterID()); return; } /* Allocate all registers up-front. */ FrameState::BinaryAlloc regs; frame.allocForSameBinary(fe, op, regs); MaybeJump notNumber; MaybeJump doublePathDone; if (!fe->isTypeKnown()) { Jump notInt = masm.testInt32(Assembler::NotEqual, regs.lhsType.reg()); stubcc.linkExitDirect(notInt, stubcc.masm.label()); notNumber = stubcc.masm.testDouble(Assembler::NotEqual, regs.lhsType.reg()); frame.loadDouble(fe, FPRegisters::First, stubcc.masm); EmitDoubleOp(op, FPRegisters::First, FPRegisters::First, stubcc.masm); /* Force the double back to memory. */ Address result = frame.addressOf(lhs); stubcc.masm.storeDouble(FPRegisters::First, result); /* Load the payload into the result reg so the rejoin is safe. */ stubcc.masm.loadPayload(result, regs.result); doublePathDone = stubcc.masm.jump(); } /* Okay - good to emit the integer fast-path. */ MaybeJump overflow; switch (op) { case JSOP_ADD: overflow = masm.branchAdd32(Assembler::Overflow, regs.result, regs.result); break; case JSOP_SUB: overflow = masm.branchSub32(Assembler::Overflow, regs.result, regs.result); break; #if !defined(JS_CPU_ARM) case JSOP_MUL: overflow = masm.branchMul32(Assembler::Overflow, regs.result, regs.result); break; #endif default: JS_NOT_REACHED("unrecognized op"); } JS_ASSERT(overflow.isSet()); /* * Integer overflow path. Separate from the first double path, since we * know never to try and convert back to integer. */ MaybeJump overflowDone; stubcc.linkExitDirect(overflow.get(), stubcc.masm.label()); { if (regs.lhsNeedsRemat) { Address address = masm.payloadOf(frame.addressForDataRemat(lhs)); stubcc.masm.convertInt32ToDouble(address, FPRegisters::First); } else if (!lhs->isConstant()) { stubcc.masm.convertInt32ToDouble(regs.lhsData.reg(), FPRegisters::First); } else { slowLoadConstantDouble(stubcc.masm, lhs, FPRegisters::First); } EmitDoubleOp(op, FPRegisters::First, FPRegisters::First, stubcc.masm); Address address = frame.addressOf(lhs); stubcc.masm.storeDouble(FPRegisters::First, address); stubcc.masm.loadPayload(address, regs.result); overflowDone = stubcc.masm.jump(); } /* Slow paths funnel here. */ if (notNumber.isSet()) notNumber.get().linkTo(stubcc.masm.label(), &stubcc.masm); overflowDone.get().linkTo(stubcc.masm.label(), &stubcc.masm); /* Slow call - use frame.sync to avoid erroneous jump repatching in stubcc. */ frame.sync(stubcc.masm, Uses(2)); stubcc.leave(); OOL_STUBCALL(stub); /* Finish up stack operations. */ frame.popn(2); frame.pushNumber(regs.result, true); /* Merge back OOL double paths. */ if (doublePathDone.isSet()) stubcc.linkRejoin(doublePathDone.get()); stubcc.linkRejoin(overflowDone.get()); stubcc.rejoin(Changes(1)); } /* * This function emits multiple fast-paths for handling numerical arithmetic. * Currently, it handles only ADD, SUB, and MUL, where both LHS and RHS are * known not to be doubles. * * The control flow of the emitted code depends on which types are known. * Given both types are unknown, the full spread looks like: * * Inline OOL * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Is LHS Int32? ------ No --------> Is LHS Double? ----- No -------, * Sync LHS | * Load LHS into XMM1 | * Is RHS Double? ---- Yes --, | * Is RHS Int32? ---- No --|-----| * Convert RHS into XMM0 | | * Else <-------------------' | * Sync RHS | * Load RHS into XMM0 | * [Add,Sub,Mul] XMM0,XMM1 | * Jump ---------------------, | * | | * Is RHS Int32? ------ No -------> Is RHS Double? ----- No --|-----| * Sync RHS | | * Load RHS into XMM0 | | * Convert LHS into XMM1 | | * [Add,Sub,Mul] XMM0,XMM1 | | * Jump ---------------------| Slow Call * | * [Add,Sub,Mul] RHS, LHS | * Overflow ------ Yes -------> Convert RHS into XMM0 | * Convert LHS into XMM1 | * [Add,Sub,Mul] XMM0,XMM1 | * Sync XMM1 to stack <---' * <--------------------------------- Rejoin */ void mjit::Compiler::jsop_binary_full(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub) { if (frame.haveSameBacking(lhs, rhs)) { jsop_binary_full_simple(lhs, op, stub); return; } /* Allocate all registers up-front. */ FrameState::BinaryAlloc regs; frame.allocForBinary(lhs, rhs, op, regs); /* Quick-test some invariants. */ JS_ASSERT_IF(lhs->isTypeKnown(), lhs->getKnownType() == JSVAL_TYPE_INT32); JS_ASSERT_IF(rhs->isTypeKnown(), rhs->getKnownType() == JSVAL_TYPE_INT32); FPRegisterID fpLeft = FPRegisters::First; FPRegisterID fpRight = FPRegisters::Second; MaybeJump lhsNotDouble, rhsNotNumber, lhsUnknownDone; if (!lhs->isTypeKnown()) emitLeftDoublePath(lhs, rhs, regs, lhsNotDouble, rhsNotNumber, lhsUnknownDone); MaybeJump rhsNotNumber2; if (!rhs->isTypeKnown()) emitRightDoublePath(lhs, rhs, regs, rhsNotNumber2); /* Perform the double addition. */ MaybeJump doublePathDone; if (!rhs->isTypeKnown() || lhsUnknownDone.isSet()) { /* If the LHS type was not known, link its path here. */ if (lhsUnknownDone.isSet()) lhsUnknownDone.get().linkTo(stubcc.masm.label(), &stubcc.masm); /* Perform the double operation. */ EmitDoubleOp(op, fpRight, fpLeft, stubcc.masm); /* Force the double back to memory. */ Address result = frame.addressOf(lhs); stubcc.masm.storeDouble(fpLeft, result); /* Load the payload into the result reg so the rejoin is safe. */ stubcc.masm.loadPayload(result, regs.result); /* We'll link this back up later, at the bottom of the op. */ doublePathDone = stubcc.masm.jump(); } /* Time to do the integer path. Figure out the immutable side. */ int32 value = 0; JSOp origOp = op; MaybeRegisterID reg; MaybeJump preOverflow; if (!regs.resultHasRhs) { if (!regs.rhsData.isSet()) value = rhs->getValue().toInt32(); else reg = regs.rhsData.reg(); } else { if (!regs.lhsData.isSet()) value = lhs->getValue().toInt32(); else reg = regs.lhsData.reg(); if (op == JSOP_SUB) { // If the RHS is 0x80000000, the smallest negative value, neg does // not work. Guard against this and treat it as an overflow. preOverflow = masm.branch32(Assembler::Equal, regs.result, Imm32(0x80000000)); masm.neg32(regs.result); op = JSOP_ADD; } } /* Okay - good to emit the integer fast-path. */ MaybeJump overflow, negZeroDone; switch (op) { case JSOP_ADD: if (reg.isSet()) overflow = masm.branchAdd32(Assembler::Overflow, reg.reg(), regs.result); else overflow = masm.branchAdd32(Assembler::Overflow, Imm32(value), regs.result); break; case JSOP_SUB: if (reg.isSet()) overflow = masm.branchSub32(Assembler::Overflow, reg.reg(), regs.result); else overflow = masm.branchSub32(Assembler::Overflow, Imm32(value), regs.result); break; #if !defined(JS_CPU_ARM) case JSOP_MUL: { JS_ASSERT(reg.isSet()); MaybeJump storeNegZero; bool maybeNegZero = true; bool hasConstant = (lhs->isConstant() || rhs->isConstant()); if (hasConstant) { value = (lhs->isConstant() ? lhs : rhs)->getValue().toInt32(); RegisterID nonConstReg = lhs->isConstant() ? regs.rhsData.reg() : regs.lhsData.reg(); if (value > 0) maybeNegZero = false; else if (value < 0) storeNegZero = masm.branchTest32(Assembler::Zero, nonConstReg); else storeNegZero = masm.branch32(Assembler::LessThan, nonConstReg, Imm32(0)); } overflow = masm.branchMul32(Assembler::Overflow, reg.reg(), regs.result); if (maybeNegZero) { if (!hasConstant) { Jump isZero = masm.branchTest32(Assembler::Zero, regs.result); stubcc.linkExitDirect(isZero, stubcc.masm.label()); /* Restore original value. */ if (regs.resultHasRhs) { if (regs.rhsNeedsRemat) stubcc.masm.loadPayload(frame.addressForDataRemat(rhs), regs.result); else stubcc.masm.move(regs.rhsData.reg(), regs.result); } else { if (regs.lhsNeedsRemat) stubcc.masm.loadPayload(frame.addressForDataRemat(lhs), regs.result); else stubcc.masm.move(regs.lhsData.reg(), regs.result); } storeNegZero = stubcc.masm.branchOr32(Assembler::Signed, reg.reg(), regs.result); stubcc.masm.xor32(regs.result, regs.result); stubcc.crossJump(stubcc.masm.jump(), masm.label()); storeNegZero.getJump().linkTo(stubcc.masm.label(), &stubcc.masm); } else { JS_ASSERT(storeNegZero.isSet()); stubcc.linkExitDirect(storeNegZero.get(), stubcc.masm.label()); } stubcc.masm.storeValue(DoubleValue(-0.0), frame.addressOf(lhs)); stubcc.masm.loadPayload(frame.addressOf(lhs), regs.result); negZeroDone = stubcc.masm.jump(); } break; } #endif default: JS_NOT_REACHED("unrecognized op"); } op = origOp; JS_ASSERT(overflow.isSet()); /* * Integer overflow path. Separate from the first double path, since we * know never to try and convert back to integer. */ MaybeJump overflowDone; if (preOverflow.isSet()) stubcc.linkExitDirect(preOverflow.get(), stubcc.masm.label()); stubcc.linkExitDirect(overflow.get(), stubcc.masm.label()); { if (regs.lhsNeedsRemat) { Address address = masm.payloadOf(frame.addressForDataRemat(lhs)); stubcc.masm.convertInt32ToDouble(address, fpLeft); } else if (!lhs->isConstant()) { stubcc.masm.convertInt32ToDouble(regs.lhsData.reg(), fpLeft); } else { slowLoadConstantDouble(stubcc.masm, lhs, fpLeft); } if (regs.rhsNeedsRemat) { Address address = masm.payloadOf(frame.addressForDataRemat(rhs)); stubcc.masm.convertInt32ToDouble(address, fpRight); } else if (!rhs->isConstant()) { stubcc.masm.convertInt32ToDouble(regs.rhsData.reg(), fpRight); } else { slowLoadConstantDouble(stubcc.masm, rhs, fpRight); } EmitDoubleOp(op, fpRight, fpLeft, stubcc.masm); Address address = frame.addressOf(lhs); stubcc.masm.storeDouble(fpLeft, address); stubcc.masm.loadPayload(address, regs.result); overflowDone = stubcc.masm.jump(); } /* The register allocator creates at most one temporary. */ if (regs.extraFree.isSet()) frame.freeReg(regs.extraFree.reg()); /* Slow paths funnel here. */ if (lhsNotDouble.isSet()) { lhsNotDouble.get().linkTo(stubcc.masm.label(), &stubcc.masm); if (rhsNotNumber.isSet()) rhsNotNumber.get().linkTo(stubcc.masm.label(), &stubcc.masm); } if (rhsNotNumber2.isSet()) rhsNotNumber2.get().linkTo(stubcc.masm.label(), &stubcc.masm); /* Slow call - use frame.sync to avoid erroneous jump repatching in stubcc. */ frame.sync(stubcc.masm, Uses(2)); stubcc.leave(); OOL_STUBCALL(stub); /* Finish up stack operations. */ frame.popn(2); frame.pushNumber(regs.result, true); /* Merge back OOL double paths. */ if (doublePathDone.isSet()) stubcc.linkRejoin(doublePathDone.get()); if (negZeroDone.isSet()) stubcc.linkRejoin(negZeroDone.get()); stubcc.linkRejoin(overflowDone.get()); stubcc.rejoin(Changes(1)); } static const uint64 DoubleNegMask = 0x8000000000000000ULL; void mjit::Compiler::jsop_neg() { FrameEntry *fe = frame.peek(-1); if (fe->isTypeKnown() && fe->getKnownType() > JSVAL_UPPER_INCL_TYPE_OF_NUMBER_SET) { prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::Neg); frame.pop(); frame.pushSynced(); return; } JS_ASSERT(!fe->isConstant()); /* Load type information into register. */ MaybeRegisterID feTypeReg; if (!fe->isTypeKnown() && !frame.shouldAvoidTypeRemat(fe)) { /* Safe because only one type is loaded. */ feTypeReg.setReg(frame.tempRegForType(fe)); /* Don't get clobbered by copyDataIntoReg(). */ frame.pinReg(feTypeReg.reg()); } RegisterID reg = frame.copyDataIntoReg(masm, fe); Label feSyncTarget = stubcc.syncExitAndJump(Uses(1)); /* Try a double path (inline). */ MaybeJump jmpNotDbl; { maybeJumpIfNotDouble(masm, jmpNotDbl, fe, feTypeReg); FPRegisterID fpreg = frame.copyEntryIntoFPReg(fe, FPRegisters::First); #if defined JS_CPU_X86 || defined JS_CPU_X64 masm.loadDouble(&DoubleNegMask, FPRegisters::Second); masm.xorDouble(FPRegisters::Second, fpreg); #elif defined JS_CPU_ARM masm.negDouble(fpreg, fpreg); #endif /* Overwrite pushed frame's memory (before push). */ masm.storeDouble(fpreg, frame.addressOf(fe)); } /* Try an integer path (out-of-line). */ MaybeJump jmpNotInt; MaybeJump jmpIntZero; MaybeJump jmpMinInt; MaybeJump jmpIntRejoin; Label lblIntPath = stubcc.masm.label(); { maybeJumpIfNotInt32(stubcc.masm, jmpNotInt, fe, feTypeReg); /* 0 (int) -> -0 (double). */ jmpIntZero.setJump(stubcc.masm.branch32(Assembler::Equal, reg, Imm32(0))); /* int32 negation on (-2147483648) yields (-2147483648). */ jmpMinInt.setJump(stubcc.masm.branch32(Assembler::Equal, reg, Imm32(1 << 31))); stubcc.masm.neg32(reg); /* Sync back with double path. */ stubcc.masm.storeValueFromComponents(ImmType(JSVAL_TYPE_INT32), reg, frame.addressOf(fe)); jmpIntRejoin.setJump(stubcc.masm.jump()); } frame.freeReg(reg); if (feTypeReg.isSet()) frame.unpinReg(feTypeReg.reg()); stubcc.leave(); OOL_STUBCALL(stubs::Neg); frame.pop(); frame.pushSynced(); /* Link jumps. */ if (jmpNotDbl.isSet()) stubcc.linkExitDirect(jmpNotDbl.getJump(), lblIntPath); if (jmpNotInt.isSet()) jmpNotInt.getJump().linkTo(feSyncTarget, &stubcc.masm); if (jmpIntZero.isSet()) jmpIntZero.getJump().linkTo(feSyncTarget, &stubcc.masm); if (jmpMinInt.isSet()) jmpMinInt.getJump().linkTo(feSyncTarget, &stubcc.masm); if (jmpIntRejoin.isSet()) stubcc.crossJump(jmpIntRejoin.getJump(), masm.label()); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_mod() { #if defined(JS_CPU_X86) FrameEntry *lhs = frame.peek(-2); FrameEntry *rhs = frame.peek(-1); if (tryBinaryConstantFold(cx, frame, JSOP_MOD, lhs, rhs)) return; if ((lhs->isTypeKnown() && lhs->getKnownType() != JSVAL_TYPE_INT32) || (rhs->isTypeKnown() && rhs->getKnownType() != JSVAL_TYPE_INT32)) #endif { prepareStubCall(Uses(2)); INLINE_STUBCALL(stubs::Mod); frame.popn(2); frame.pushSynced(); return; } #if defined(JS_CPU_X86) if (!lhs->isTypeKnown()) { Jump j = frame.testInt32(Assembler::NotEqual, lhs); stubcc.linkExit(j, Uses(2)); } if (!rhs->isTypeKnown()) { Jump j = frame.testInt32(Assembler::NotEqual, rhs); stubcc.linkExit(j, Uses(2)); } /* LHS must be in EAX:EDX */ if (!lhs->isConstant()) { frame.copyDataIntoReg(lhs, X86Registers::eax); } else { frame.takeReg(X86Registers::eax); masm.move(Imm32(lhs->getValue().toInt32()), X86Registers::eax); } /* Get RHS into anything but EDX - could avoid more spilling? */ MaybeRegisterID temp; RegisterID rhsReg; if (!rhs->isConstant()) { uint32 mask = Registers::AvailRegs & ~Registers::maskReg(X86Registers::edx); rhsReg = frame.tempRegInMaskForData(rhs, mask); JS_ASSERT(rhsReg != X86Registers::edx); } else { rhsReg = frame.allocReg(Registers::AvailRegs & ~Registers::maskReg(X86Registers::edx)); JS_ASSERT(rhsReg != X86Registers::edx); masm.move(Imm32(rhs->getValue().toInt32()), rhsReg); temp = rhsReg; } frame.takeReg(X86Registers::edx); frame.freeReg(X86Registers::eax); if (temp.isSet()) frame.freeReg(temp.reg()); bool slowPath = !(lhs->isTypeKnown() && rhs->isTypeKnown()); if (rhs->isConstant() && rhs->getValue().toInt32() != 0) { if (rhs->getValue().toInt32() == -1) { /* Guard against -1 / INT_MIN which throws a hardware exception. */ Jump checkDivExc = masm.branch32(Assembler::Equal, X86Registers::eax, Imm32(0x80000000)); stubcc.linkExit(checkDivExc, Uses(2)); slowPath = true; } } else { Jump checkDivExc = masm.branch32(Assembler::Equal, X86Registers::eax, Imm32(0x80000000)); stubcc.linkExit(checkDivExc, Uses(2)); Jump checkZero = masm.branchTest32(Assembler::Zero, rhsReg, rhsReg); stubcc.linkExit(checkZero, Uses(2)); slowPath = true; } /* Perform division. */ masm.idiv(rhsReg); /* ECMA-262 11.5.3 requires the result to have the same sign as the lhs. * Thus, if the remainder of the div instruction is zero and the lhs is * negative, we must return negative 0. */ bool lhsMaybeNeg = true; bool lhsIsNeg = false; if (lhs->isConstant()) { /* This condition is established at the top of this function. */ JS_ASSERT(lhs->getValue().isInt32()); lhsMaybeNeg = lhsIsNeg = (lhs->getValue().toInt32() < 0); } MaybeJump done; if (lhsMaybeNeg) { MaybeRegisterID lhsData; if (!lhsIsNeg) lhsData = frame.tempRegForData(lhs); Jump negZero1 = masm.branchTest32(Assembler::NonZero, X86Registers::edx); MaybeJump negZero2; if (!lhsIsNeg) negZero2 = masm.branchTest32(Assembler::Zero, lhsData.reg(), Imm32(0x80000000)); /* Darn, negative 0. */ masm.storeValue(DoubleValue(-0.0), frame.addressOf(lhs)); /* :TODO: This is wrong, must load into EDX as well. */ done = masm.jump(); negZero1.linkTo(masm.label(), &masm); if (negZero2.isSet()) negZero2.getJump().linkTo(masm.label(), &masm); } /* Better - integer. */ masm.storeTypeTag(ImmType(JSVAL_TYPE_INT32), frame.addressOf(lhs)); if (done.isSet()) done.getJump().linkTo(masm.label(), &masm); if (slowPath) { stubcc.leave(); OOL_STUBCALL(stubs::Mod); } frame.popn(2); frame.pushNumber(X86Registers::edx); if (slowPath) stubcc.rejoin(Changes(1)); #endif } bool mjit::Compiler::jsop_equality_int_string(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); /* Swap the LHS and RHS if it makes register allocation better... or possible. */ if (lhs->isConstant() || (frame.shouldAvoidDataRemat(lhs) && !rhs->isConstant())) { FrameEntry *temp = rhs; rhs = lhs; lhs = temp; } bool lhsInt = lhs->isType(JSVAL_TYPE_INT32); bool rhsInt = rhs->isType(JSVAL_TYPE_INT32); /* Invert the condition if fusing with an IFEQ branch. */ bool flipCondition = (target && fused == JSOP_IFEQ); /* Get the condition being tested. */ Assembler::Condition cond; switch (op) { case JSOP_EQ: cond = flipCondition ? Assembler::NotEqual : Assembler::Equal; break; case JSOP_NE: cond = flipCondition ? Assembler::Equal : Assembler::NotEqual; break; default: JS_NOT_REACHED("wat"); return false; } if (target) { Value rval = UndefinedValue(); /* quiet gcc warning */ bool rhsConst = false; if (rhs->isConstant()) { rhsConst = true; rval = rhs->getValue(); } ValueRemat lvr, rvr; frame.pinEntry(lhs, lvr); frame.pinEntry(rhs, rvr); /* * Sync everything except the top two entries. * We will handle the lhs/rhs in the stub call path. */ frame.syncAndKill(Registers(Registers::AvailRegs), Uses(frame.frameSlots()), Uses(2)); RegisterID tempReg = frame.allocReg(); frame.pop(); frame.pop(); frame.discardFrame(); JaegerSpew(JSpew_Insns, " ---- BEGIN STUB CALL CODE ---- \n"); RESERVE_OOL_SPACE(stubcc.masm); /* Start of the slow path for equality stub call. */ Label stubEntry = stubcc.masm.label(); /* The lhs/rhs need to be synced in the stub call path. */ frame.ensureValueSynced(stubcc.masm, lhs, lvr); frame.ensureValueSynced(stubcc.masm, rhs, rvr); bool needStub = true; #ifdef JS_MONOIC EqualityGenInfo ic; ic.cond = cond; ic.tempReg = tempReg; ic.lvr = lvr; ic.rvr = rvr; ic.stubEntry = stubEntry; ic.stub = stub; bool useIC = !addTraceHints || target >= PC; /* Call the IC stub, which may generate a fast path. */ if (useIC) { /* Adjust for the two values just pushed. */ ic.addrLabel = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1); ic.stubCall = OOL_STUBCALL_LOCAL_SLOTS(ic::Equality, frame.stackDepth() + script->nfixed + 2); needStub = false; } #endif if (needStub) OOL_STUBCALL_LOCAL_SLOTS(stub, frame.stackDepth() + script->nfixed + 2); /* * The stub call has no need to rejoin, since state is synced. * Instead, we can just test the return value. */ Assembler::Condition ncond = (fused == JSOP_IFEQ) ? Assembler::Zero : Assembler::NonZero; Jump stubBranch = stubcc.masm.branchTest32(ncond, Registers::ReturnReg, Registers::ReturnReg); Jump stubFallthrough = stubcc.masm.jump(); JaegerSpew(JSpew_Insns, " ---- END STUB CALL CODE ---- \n"); CHECK_OOL_SPACE(); Jump fast; MaybeJump firstStubJump; if ((!lhs->isTypeKnown() || lhsInt) && (!rhs->isTypeKnown() || rhsInt)) { if (!lhsInt) { Jump lhsFail = masm.testInt32(Assembler::NotEqual, lvr.typeReg()); stubcc.linkExitDirect(lhsFail, stubEntry); firstStubJump = lhsFail; } if (!rhsInt) { Jump rhsFail = masm.testInt32(Assembler::NotEqual, rvr.typeReg()); stubcc.linkExitDirect(rhsFail, stubEntry); if (!firstStubJump.isSet()) firstStubJump = rhsFail; } if (rhsConst) fast = masm.branch32(cond, lvr.dataReg(), Imm32(rval.toInt32())); else fast = masm.branch32(cond, lvr.dataReg(), rvr.dataReg()); if (!jumpInScript(fast, target)) return false; } else { Jump j = masm.jump(); stubcc.linkExitDirect(j, stubEntry); firstStubJump = j; /* This is just a dummy jump. */ fast = masm.jump(); } #ifdef JS_MONOIC ic.jumpToStub = firstStubJump; if (useIC) { ic.fallThrough = masm.label(); ic.jumpTarget = target; equalityICs.append(ic); } #endif /* Jump from the stub call fallthrough to here. */ stubcc.crossJump(stubFallthrough, masm.label()); /* * NB: jumpAndTrace emits to the OOL path, so make sure not to use it * in the middle of an in-progress slow path. */ if (!jumpAndTrace(fast, target, &stubBranch)) return false; } else { /* No fusing. Compare, set, and push a boolean. */ /* Should have filtered these out in the caller. */ JS_ASSERT(!lhs->isType(JSVAL_TYPE_STRING) && !rhs->isType(JSVAL_TYPE_STRING)); /* Test the types. */ if ((lhs->isTypeKnown() && !lhsInt) || (rhs->isTypeKnown() && !rhsInt)) { stubcc.linkExit(masm.jump(), Uses(2)); } else { if (!lhsInt) { Jump lhsFail = frame.testInt32(Assembler::NotEqual, lhs); stubcc.linkExit(lhsFail, Uses(2)); } if (!rhsInt) { Jump rhsFail = frame.testInt32(Assembler::NotEqual, rhs); stubcc.linkExit(rhsFail, Uses(2)); } } stubcc.leave(); OOL_STUBCALL(stub); RegisterID reg = frame.ownRegForData(lhs); /* x86/64's SET instruction can only take single-byte regs.*/ RegisterID resultReg = reg; if (!(Registers::maskReg(reg) & Registers::SingleByteRegs)) resultReg = frame.allocReg(Registers::SingleByteRegs); /* Emit the compare & set. */ if (rhs->isConstant()) { masm.set32(cond, reg, Imm32(rhs->getValue().toInt32()), resultReg); } else if (frame.shouldAvoidDataRemat(rhs)) { masm.set32(cond, reg, masm.payloadOf(frame.addressOf(rhs)), resultReg); } else { masm.set32(cond, reg, frame.tempRegForData(rhs), resultReg); } /* Clean up and push a boolean. */ frame.pop(); frame.pop(); if (reg != resultReg) frame.freeReg(reg); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, resultReg); stubcc.rejoin(Changes(1)); } return true; } /* * Emit an OOL path for a possibly double LHS, and possibly int32 or number RHS. */ void mjit::Compiler::emitLeftDoublePath(FrameEntry *lhs, FrameEntry *rhs, FrameState::BinaryAlloc ®s, MaybeJump &lhsNotDouble, MaybeJump &rhsNotNumber, MaybeJump &lhsUnknownDone) { FPRegisterID fpLeft = FPRegisters::First; FPRegisterID fpRight = FPRegisters::Second; /* If the LHS is not a 32-bit integer, take OOL path. */ Jump lhsNotInt32 = masm.testInt32(Assembler::NotEqual, regs.lhsType.reg()); stubcc.linkExitDirect(lhsNotInt32, stubcc.masm.label()); /* OOL path for LHS as a double - first test LHS is double. */ lhsNotDouble = stubcc.masm.testDouble(Assembler::NotEqual, regs.lhsType.reg()); /* Ensure the RHS is a number. */ MaybeJump rhsIsDouble; if (!rhs->isTypeKnown()) { rhsIsDouble = stubcc.masm.testDouble(Assembler::Equal, regs.rhsType.reg()); rhsNotNumber = stubcc.masm.testInt32(Assembler::NotEqual, regs.rhsType.reg()); } /* If RHS is constant, convert now. */ if (rhs->isConstant()) slowLoadConstantDouble(stubcc.masm, rhs, fpRight); else stubcc.masm.convertInt32ToDouble(regs.rhsData.reg(), fpRight); if (!rhs->isTypeKnown()) { /* Jump past double load, bind double type check. */ Jump converted = stubcc.masm.jump(); rhsIsDouble.get().linkTo(stubcc.masm.label(), &stubcc.masm); /* Load the double. */ frame.loadDouble(regs.rhsType.reg(), regs.rhsData.reg(), rhs, fpRight, stubcc.masm); converted.linkTo(stubcc.masm.label(), &stubcc.masm); } /* Load the LHS. */ frame.loadDouble(regs.lhsType.reg(), regs.lhsData.reg(), lhs, fpLeft, stubcc.masm); lhsUnknownDone = stubcc.masm.jump(); } /* * Emit an OOL path for an integer LHS, possibly double RHS. */ void mjit::Compiler::emitRightDoublePath(FrameEntry *lhs, FrameEntry *rhs, FrameState::BinaryAlloc ®s, MaybeJump &rhsNotNumber2) { FPRegisterID fpLeft = FPRegisters::First; FPRegisterID fpRight = FPRegisters::Second; /* If the RHS is not a double, take OOL path. */ Jump notInt32 = masm.testInt32(Assembler::NotEqual, regs.rhsType.reg()); stubcc.linkExitDirect(notInt32, stubcc.masm.label()); /* Now test if RHS is a double. */ rhsNotNumber2 = stubcc.masm.testDouble(Assembler::NotEqual, regs.rhsType.reg()); /* We know LHS is an integer. */ if (lhs->isConstant()) slowLoadConstantDouble(stubcc.masm, lhs, fpLeft); else stubcc.masm.convertInt32ToDouble(regs.lhsData.reg(), fpLeft); /* Load the RHS. */ frame.loadDouble(regs.rhsType.reg(), regs.rhsData.reg(), rhs, fpRight, stubcc.masm); } static inline Assembler::DoubleCondition DoubleCondForOp(JSOp op, JSOp fused) { bool ifeq = fused == JSOP_IFEQ; switch (op) { case JSOP_GT: return ifeq ? Assembler::DoubleLessThanOrEqualOrUnordered : Assembler::DoubleGreaterThan; case JSOP_GE: return ifeq ? Assembler::DoubleLessThanOrUnordered : Assembler::DoubleGreaterThanOrEqual; case JSOP_LT: return ifeq ? Assembler::DoubleGreaterThanOrEqualOrUnordered : Assembler::DoubleLessThan; case JSOP_LE: return ifeq ? Assembler::DoubleGreaterThanOrUnordered : Assembler::DoubleLessThanOrEqual; default: JS_NOT_REACHED("unrecognized op"); return Assembler::DoubleLessThan; } } bool mjit::Compiler::jsop_relational_double(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); FPRegisterID fpLeft = FPRegisters::First; FPRegisterID fpRight = FPRegisters::Second; JS_ASSERT_IF(!target, fused != JSOP_IFEQ); MaybeJump lhsNotNumber = loadDouble(lhs, fpLeft); MaybeJump rhsNotNumber = loadDouble(rhs, fpRight); Assembler::DoubleCondition dblCond = DoubleCondForOp(op, fused); if (target) { if (lhsNotNumber.isSet()) stubcc.linkExitForBranch(lhsNotNumber.get()); if (rhsNotNumber.isSet()) stubcc.linkExitForBranch(rhsNotNumber.get()); stubcc.leave(); OOL_STUBCALL(stub); frame.popn(2); frame.syncAndForgetEverything(); Jump j = masm.branchDouble(dblCond, fpLeft, fpRight); /* * The stub call has no need to rejoin since the state is synced. * Instead, we can just test the return value. */ Assembler::Condition cond = (fused == JSOP_IFEQ) ? Assembler::Zero : Assembler::NonZero; Jump sj = stubcc.masm.branchTest32(cond, Registers::ReturnReg, Registers::ReturnReg); /* Rejoin from the slow path. */ Jump j2 = stubcc.masm.jump(); stubcc.crossJump(j2, masm.label()); /* * NB: jumpAndTrace emits to the OOL path, so make sure not to use it * in the middle of an in-progress slow path. */ if (!jumpAndTrace(j, target, &sj)) return false; } else { if (lhsNotNumber.isSet()) stubcc.linkExit(lhsNotNumber.get(), Uses(2)); if (rhsNotNumber.isSet()) stubcc.linkExit(rhsNotNumber.get(), Uses(2)); stubcc.leave(); OOL_STUBCALL(stub); frame.popn(2); RegisterID reg = frame.allocReg(); Jump j = masm.branchDouble(dblCond, fpLeft, fpRight); masm.move(Imm32(0), reg); Jump skip = masm.jump(); j.linkTo(masm.label(), &masm); masm.move(Imm32(1), reg); skip.linkTo(masm.label(), &masm); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg); stubcc.rejoin(Changes(1)); } return true; } bool mjit::Compiler::jsop_relational_self(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused) { #ifdef DEBUG FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); JS_ASSERT(frame.haveSameBacking(lhs, rhs)); #endif /* :TODO: optimize this? */ return emitStubCmpOp(stub, target, fused); } /* See jsop_binary_full() for more information on how this works. */ bool mjit::Compiler::jsop_relational_full(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); /* Allocate all registers up-front. */ FrameState::BinaryAlloc regs; frame.allocForBinary(lhs, rhs, op, regs, !target); FPRegisterID fpLeft = FPRegisters::First; FPRegisterID fpRight = FPRegisters::Second; MaybeJump lhsNotDouble, rhsNotNumber, lhsUnknownDone; if (!lhs->isTypeKnown()) emitLeftDoublePath(lhs, rhs, regs, lhsNotDouble, rhsNotNumber, lhsUnknownDone); MaybeJump rhsNotNumber2; if (!rhs->isTypeKnown()) emitRightDoublePath(lhs, rhs, regs, rhsNotNumber2); /* Both double paths will join here. */ bool hasDoublePath = false; if (!rhs->isTypeKnown() || lhsUnknownDone.isSet()) hasDoublePath = true; /* Integer path - figure out the immutable side. */ JSOp cmpOp = op; int32 value = 0; RegisterID cmpReg; MaybeRegisterID reg; if (regs.lhsData.isSet()) { cmpReg = regs.lhsData.reg(); if (!regs.rhsData.isSet()) value = rhs->getValue().toInt32(); else reg = regs.rhsData.reg(); } else { cmpReg = regs.rhsData.reg(); value = lhs->getValue().toInt32(); switch (op) { case JSOP_GT: cmpOp = JSOP_LT; break; case JSOP_GE: cmpOp = JSOP_LE; break; case JSOP_LT: cmpOp = JSOP_GT; break; case JSOP_LE: cmpOp = JSOP_GE; break; default: JS_NOT_REACHED("unrecognized op"); break; } } /* * Emit the actual comparisons. When a fusion is in play, it's faster to * combine the comparison with the jump, so these two cases are implemented * separately. */ if (target) { /* * Emit the double path now, necessary to complete the OOL fast-path * before emitting the slow path. * * Note: doubles have not been swapped yet. Use original op. */ MaybeJump doubleTest, doubleFall; Assembler::DoubleCondition dblCond = DoubleCondForOp(op, fused); if (hasDoublePath) { if (lhsUnknownDone.isSet()) lhsUnknownDone.get().linkTo(stubcc.masm.label(), &stubcc.masm); frame.sync(stubcc.masm, Uses(frame.frameSlots())); doubleTest = stubcc.masm.branchDouble(dblCond, fpLeft, fpRight); doubleFall = stubcc.masm.jump(); /* Link all incoming slow paths to here. */ if (lhsNotDouble.isSet()) { lhsNotDouble.get().linkTo(stubcc.masm.label(), &stubcc.masm); if (rhsNotNumber.isSet()) rhsNotNumber.get().linkTo(stubcc.masm.label(), &stubcc.masm); } if (rhsNotNumber2.isSet()) rhsNotNumber2.get().linkTo(stubcc.masm.label(), &stubcc.masm); /* * For fusions, spill the tracker state. xmm* remain intact. Note * that frame.sync() must be used directly, to avoid syncExit()'s * jumping logic. */ frame.sync(stubcc.masm, Uses(frame.frameSlots())); stubcc.leave(); OOL_STUBCALL(stub); } /* Forget the world, preserving data. */ frame.pinReg(cmpReg); if (reg.isSet()) frame.pinReg(reg.reg()); frame.popn(2); frame.syncAndKillEverything(); frame.unpinKilledReg(cmpReg); if (reg.isSet()) frame.unpinKilledReg(reg.reg()); frame.syncAndForgetEverything(); /* Operands could have been reordered, so use cmpOp. */ Assembler::Condition i32Cond; bool ifeq = fused == JSOP_IFEQ; switch (cmpOp) { case JSOP_GT: i32Cond = ifeq ? Assembler::LessThanOrEqual : Assembler::GreaterThan; break; case JSOP_GE: i32Cond = ifeq ? Assembler::LessThan : Assembler::GreaterThanOrEqual; break; case JSOP_LT: i32Cond = ifeq ? Assembler::GreaterThanOrEqual : Assembler::LessThan; break; case JSOP_LE: i32Cond = ifeq ? Assembler::GreaterThan : Assembler::LessThanOrEqual; break; default: JS_NOT_REACHED("unrecognized op"); return false; } /* Emit the i32 path. */ Jump fast; if (reg.isSet()) fast = masm.branch32(i32Cond, cmpReg, reg.reg()); else fast = masm.branch32(i32Cond, cmpReg, Imm32(value)); /* * The stub call has no need to rejoin since state is synced. Instead, * we can just test the return value. */ Assembler::Condition cond = (fused == JSOP_IFEQ) ? Assembler::Zero : Assembler::NonZero; Jump j = stubcc.masm.branchTest32(cond, Registers::ReturnReg, Registers::ReturnReg); /* Rejoin from the slow path. */ Jump j2 = stubcc.masm.jump(); stubcc.crossJump(j2, masm.label()); /* :TODO: make double path invoke tracer. */ if (hasDoublePath) { j.linkTo(stubcc.masm.label(), &stubcc.masm); doubleTest.get().linkTo(stubcc.masm.label(), &stubcc.masm); j = stubcc.masm.jump(); } /* * NB: jumpAndTrace emits to the OOL path, so make sure not to use it * in the middle of an in-progress slow path. */ if (!jumpAndTrace(fast, target, &j)) return false; /* Rejoin from the double path. */ if (hasDoublePath) stubcc.crossJump(doubleFall.get(), masm.label()); } else { /* * Emit the double path now, necessary to complete the OOL fast-path * before emitting the slow path. */ MaybeJump doubleDone; Assembler::DoubleCondition dblCond = DoubleCondForOp(op, JSOP_NOP); if (hasDoublePath) { if (lhsUnknownDone.isSet()) lhsUnknownDone.get().linkTo(stubcc.masm.label(), &stubcc.masm); /* :FIXME: Use SET if we can? */ Jump test = stubcc.masm.branchDouble(dblCond, fpLeft, fpRight); stubcc.masm.move(Imm32(0), regs.result); Jump skip = stubcc.masm.jump(); test.linkTo(stubcc.masm.label(), &stubcc.masm); stubcc.masm.move(Imm32(1), regs.result); skip.linkTo(stubcc.masm.label(), &stubcc.masm); doubleDone = stubcc.masm.jump(); /* Link all incoming slow paths to here. */ if (lhsNotDouble.isSet()) { lhsNotDouble.get().linkTo(stubcc.masm.label(), &stubcc.masm); if (rhsNotNumber.isSet()) rhsNotNumber.get().linkTo(stubcc.masm.label(), &stubcc.masm); } if (rhsNotNumber2.isSet()) rhsNotNumber2.get().linkTo(stubcc.masm.label(), &stubcc.masm); /* Emit the slow path - note full frame syncage. */ frame.sync(stubcc.masm, Uses(2)); stubcc.leave(); OOL_STUBCALL(stub); } /* Get an integer comparison condition. */ Assembler::Condition i32Cond; switch (cmpOp) { case JSOP_GT: i32Cond = Assembler::GreaterThan; break; case JSOP_GE: i32Cond = Assembler::GreaterThanOrEqual; break; case JSOP_LT: i32Cond = Assembler::LessThan; break; case JSOP_LE: i32Cond = Assembler::LessThanOrEqual; break; default: JS_NOT_REACHED("unrecognized op"); return false; } /* Emit the compare & set. */ if (Registers::maskReg(regs.result) & Registers::SingleByteRegs) { if (reg.isSet()) masm.set32(i32Cond, cmpReg, reg.reg(), regs.result); else masm.set32(i32Cond, cmpReg, Imm32(value), regs.result); } else { Jump j; if (reg.isSet()) j = masm.branch32(i32Cond, cmpReg, reg.reg()); else j = masm.branch32(i32Cond, cmpReg, Imm32(value)); masm.move(Imm32(0), regs.result); Jump skip = masm.jump(); j.linkTo(masm.label(), &masm); masm.move(Imm32(1), regs.result); skip.linkTo(masm.label(), &masm); } frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, regs.result); if (hasDoublePath) stubcc.crossJump(doubleDone.get(), masm.label()); stubcc.rejoin(Changes(1)); } return true; }