/* -*- 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 * * 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 "jscntxt.h" #include "jsemit.h" #include "jslibmath.h" #include "jsnum.h" #include "jsscope.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #include "methodjit/MethodJIT.h" #include "methodjit/Compiler.h" #include "methodjit/StubCalls.h" #include "methodjit/FrameState-inl.h" #include "jsautooplen.h" using namespace js; using namespace js::mjit; typedef JSC::MacroAssembler::RegisterID RegisterID; RegisterID mjit::Compiler::rightRegForShift(FrameEntry *rhs) { #if defined(JS_CPU_X86) || defined(JS_CPU_X64) /* * Gross: RHS _must_ be in ECX, on x86. * Note that we take this first so that we can't up with other register * allocations (below) owning ecx before rhs. */ RegisterID reg = JSC::X86Registers::ecx; if (!rhs->isConstant()) frame.copyDataIntoReg(rhs, reg); return reg; #else if (rhs->isConstant()) return frame.allocReg(); return frame.copyDataIntoReg(rhs); #endif } void mjit::Compiler::jsop_rsh_const_int(FrameEntry *lhs, FrameEntry *rhs) { RegisterID rhsData = rightRegForShift(rhs); RegisterID result = frame.allocReg(); masm.move(Imm32(lhs->getValue().toInt32()), result); masm.rshift32(rhsData, result); frame.freeReg(rhsData); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, result); } void mjit::Compiler::jsop_rsh_int_int(FrameEntry *lhs, FrameEntry *rhs) { RegisterID rhsData = rightRegForShift(rhs); RegisterID lhsData = frame.copyDataIntoReg(lhs); masm.rshift32(rhsData, lhsData); frame.freeReg(rhsData); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData); } void mjit::Compiler::jsop_rsh_int_const(FrameEntry *lhs, FrameEntry *rhs) { int32 shiftAmount = rhs->getValue().toInt32(); if (!shiftAmount) { frame.pop(); return; } RegisterID result = frame.copyDataIntoReg(lhs); masm.rshift32(Imm32(shiftAmount), result); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, result); } void mjit::Compiler::jsop_rsh_unknown_const(FrameEntry *lhs, FrameEntry *rhs) { int32 shiftAmount = rhs->getValue().toInt32(); RegisterID lhsType = frame.tempRegForType(lhs); frame.pinReg(lhsType); RegisterID lhsData = frame.copyDataIntoReg(lhs); frame.unpinReg(lhsType); Jump lhsIntGuard = masm.testInt32(Assembler::NotEqual, lhsType); stubcc.linkExitDirect(lhsIntGuard, stubcc.masm.label()); Jump lhsDoubleGuard = stubcc.masm.testDouble(Assembler::NotEqual, lhsType); frame.loadDouble(lhs, FPRegisters::First, stubcc.masm); Jump lhsTruncateGuard = stubcc.masm.branchTruncateDoubleToInt32(FPRegisters::First, lhsData); stubcc.crossJump(stubcc.masm.jump(), masm.label()); lhsDoubleGuard.linkTo(stubcc.masm.label(), &stubcc.masm); lhsTruncateGuard.linkTo(stubcc.masm.label(), &stubcc.masm); frame.sync(stubcc.masm, Uses(2)); OOL_STUBCALL(stubs::Rsh); if (shiftAmount) masm.rshift32(Imm32(shiftAmount), lhsData); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_rsh_const_unknown(FrameEntry *lhs, FrameEntry *rhs) { RegisterID rhsData = rightRegForShift(rhs); RegisterID rhsType = frame.tempRegForType(rhs); frame.pinReg(rhsType); RegisterID result = frame.allocReg(); frame.unpinReg(rhsType); Jump rhsIntGuard = masm.testInt32(Assembler::NotEqual, rhsType); stubcc.linkExit(rhsIntGuard, Uses(2)); stubcc.leave(); OOL_STUBCALL(stubs::Rsh); masm.move(Imm32(lhs->getValue().toInt32()), result); masm.rshift32(rhsData, result); frame.freeReg(rhsData); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, result); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_rsh_int_unknown(FrameEntry *lhs, FrameEntry *rhs) { RegisterID rhsData = rightRegForShift(rhs); RegisterID rhsType = frame.tempRegForType(rhs); frame.pinReg(rhsType); RegisterID lhsData = frame.copyDataIntoReg(lhs); frame.unpinReg(rhsType); Jump rhsIntGuard = masm.testInt32(Assembler::NotEqual, rhsType); stubcc.linkExit(rhsIntGuard, Uses(2)); stubcc.leave(); OOL_STUBCALL(stubs::Rsh); masm.rshift32(rhsData, lhsData); frame.freeReg(rhsData); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_rsh_unknown_any(FrameEntry *lhs, FrameEntry *rhs) { JS_ASSERT(!lhs->isTypeKnown()); JS_ASSERT(!rhs->isNotType(JSVAL_TYPE_INT32)); /* Allocate registers. */ RegisterID rhsData = rightRegForShift(rhs); MaybeRegisterID rhsType; if (!rhs->isTypeKnown()) { rhsType.setReg(frame.tempRegForType(rhs)); frame.pinReg(rhsType.reg()); } RegisterID lhsData = frame.copyDataIntoReg(lhs); MaybeRegisterID lhsType; if (rhsType.isSet() && frame.haveSameBacking(lhs, rhs)) lhsType = rhsType; else lhsType = frame.tempRegForType(lhs); /* Non-integer rhs jumps to stub. */ MaybeJump rhsIntGuard; if (rhsType.isSet()) { rhsIntGuard.setJump(masm.testInt32(Assembler::NotEqual, rhsType.reg())); frame.unpinReg(rhsType.reg()); } /* Non-integer lhs jumps to double guard. */ Jump lhsIntGuard = masm.testInt32(Assembler::NotEqual, lhsType.reg()); stubcc.linkExitDirect(lhsIntGuard, stubcc.masm.label()); /* Attempt to convert lhs double to int32. */ Jump lhsDoubleGuard = stubcc.masm.testDouble(Assembler::NotEqual, lhsType.reg()); frame.loadDouble(lhs, FPRegisters::First, stubcc.masm); Jump lhsTruncateGuard = stubcc.masm.branchTruncateDoubleToInt32(FPRegisters::First, lhsData); stubcc.crossJump(stubcc.masm.jump(), masm.label()); lhsDoubleGuard.linkTo(stubcc.masm.label(), &stubcc.masm); lhsTruncateGuard.linkTo(stubcc.masm.label(), &stubcc.masm); if (rhsIntGuard.isSet()) stubcc.linkExitDirect(rhsIntGuard.getJump(), stubcc.masm.label()); frame.sync(stubcc.masm, Uses(2)); OOL_STUBCALL(stubs::Rsh); masm.rshift32(rhsData, lhsData); frame.freeReg(rhsData); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, lhsData); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_rsh() { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); if (tryBinaryConstantFold(cx, frame, JSOP_RSH, lhs, rhs)) return; if (lhs->isNotType(JSVAL_TYPE_INT32) || rhs->isNotType(JSVAL_TYPE_INT32)) { prepareStubCall(Uses(2)); INLINE_STUBCALL(stubs::Rsh); frame.popn(2); frame.pushSyncedType(JSVAL_TYPE_INT32); return; } JS_ASSERT(!(lhs->isConstant() && rhs->isConstant())); if (lhs->isConstant()) { if (rhs->isType(JSVAL_TYPE_INT32)) jsop_rsh_const_int(lhs, rhs); else jsop_rsh_const_unknown(lhs, rhs); } else if (rhs->isConstant()) { if (lhs->isType(JSVAL_TYPE_INT32)) jsop_rsh_int_const(lhs, rhs); else jsop_rsh_unknown_const(lhs, rhs); } else { if (lhs->isType(JSVAL_TYPE_INT32) && rhs->isType(JSVAL_TYPE_INT32)) jsop_rsh_int_int(lhs, rhs); else if (lhs->isType(JSVAL_TYPE_INT32)) jsop_rsh_int_unknown(lhs, rhs); else jsop_rsh_unknown_any(lhs, rhs); } } void mjit::Compiler::jsop_bitnot() { FrameEntry *top = frame.peek(-1); /* We only want to handle integers here. */ if (top->isTypeKnown() && top->getKnownType() != JSVAL_TYPE_INT32) { prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::BitNot); frame.pop(); frame.pushSyncedType(JSVAL_TYPE_INT32); return; } /* Test the type. */ bool stubNeeded = false; if (!top->isTypeKnown()) { Jump intFail = frame.testInt32(Assembler::NotEqual, top); stubcc.linkExit(intFail, Uses(1)); frame.learnType(top, JSVAL_TYPE_INT32); stubNeeded = true; } if (stubNeeded) { stubcc.leave(); OOL_STUBCALL(stubs::BitNot); } RegisterID reg = frame.ownRegForData(top); masm.not32(reg); frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_INT32, reg); if (stubNeeded) stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_bitop(JSOp op) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); VoidStub stub; switch (op) { case JSOP_BITOR: stub = stubs::BitOr; break; case JSOP_BITAND: stub = stubs::BitAnd; break; case JSOP_BITXOR: stub = stubs::BitXor; break; case JSOP_LSH: stub = stubs::Lsh; break; case JSOP_URSH: stub = stubs::Ursh; break; default: JS_NOT_REACHED("wat"); return; } bool lhsIntOrDouble = !(lhs->isNotType(JSVAL_TYPE_DOUBLE) && lhs->isNotType(JSVAL_TYPE_INT32)); /* Fast-path double to int conversion. */ if (!lhs->isConstant() && rhs->isConstant() && lhsIntOrDouble && rhs->isType(JSVAL_TYPE_INT32) && rhs->getValue().toInt32() == 0 && (op == JSOP_BITOR || op == JSOP_LSH)) { RegisterID reg = frame.copyDataIntoReg(lhs); if (lhs->isType(JSVAL_TYPE_INT32)) { frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, reg); return; } MaybeJump isInt; if (!lhs->isType(JSVAL_TYPE_DOUBLE)) { RegisterID typeReg = frame.tempRegForType(lhs); isInt = masm.testInt32(Assembler::Equal, typeReg); Jump notDouble = masm.testDouble(Assembler::NotEqual, typeReg); stubcc.linkExit(notDouble, Uses(2)); } frame.loadDouble(lhs, FPRegisters::First, masm); Jump truncateGuard = masm.branchTruncateDoubleToInt32(FPRegisters::First, reg); stubcc.linkExit(truncateGuard, Uses(2)); stubcc.leave(); OOL_STUBCALL(stub); if (isInt.isSet()) isInt.get().linkTo(masm.label(), &masm); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_INT32, reg); stubcc.rejoin(Changes(1)); return; } /* We only want to handle integers here. */ if (rhs->isNotType(JSVAL_TYPE_INT32) || lhs->isNotType(JSVAL_TYPE_INT32) || (op == JSOP_URSH && rhs->isConstant() && rhs->getValue().toInt32() % 32 == 0)) { prepareStubCall(Uses(2)); INLINE_STUBCALL(stub); frame.popn(2); if (op == JSOP_URSH) frame.pushSynced(); else frame.pushSyncedType(JSVAL_TYPE_INT32); return; } /* Test the types. */ bool stubNeeded = false; if (!rhs->isTypeKnown()) { Jump rhsFail = frame.testInt32(Assembler::NotEqual, rhs); stubcc.linkExit(rhsFail, Uses(2)); frame.learnType(rhs, JSVAL_TYPE_INT32); stubNeeded = true; } if (!lhs->isTypeKnown() && !frame.haveSameBacking(lhs, rhs)) { Jump lhsFail = frame.testInt32(Assembler::NotEqual, lhs); stubcc.linkExit(lhsFail, Uses(2)); stubNeeded = true; } if (lhs->isConstant() && rhs->isConstant()) { int32 L = lhs->getValue().toInt32(); int32 R = rhs->getValue().toInt32(); frame.popn(2); switch (op) { case JSOP_BITOR: frame.push(Int32Value(L | R)); return; case JSOP_BITXOR: frame.push(Int32Value(L ^ R)); return; case JSOP_BITAND: frame.push(Int32Value(L & R)); return; case JSOP_LSH: frame.push(Int32Value(L << R)); return; case JSOP_URSH: { uint32 unsignedL; if (ValueToECMAUint32(cx, lhs->getValue(), (uint32_t*)&unsignedL)) { frame.push(NumberValue(uint32(unsignedL >> (R & 31)))); return; } break; } default: JS_NOT_REACHED("say wat"); } } RegisterID reg; switch (op) { case JSOP_BITOR: case JSOP_BITXOR: case JSOP_BITAND: { /* Commutative, and we're guaranteed both are ints. */ if (lhs->isConstant()) { JS_ASSERT(!rhs->isConstant()); FrameEntry *temp = rhs; rhs = lhs; lhs = temp; } reg = frame.ownRegForData(lhs); if (rhs->isConstant()) { if (op == JSOP_BITAND) masm.and32(Imm32(rhs->getValue().toInt32()), reg); else if (op == JSOP_BITXOR) masm.xor32(Imm32(rhs->getValue().toInt32()), reg); else masm.or32(Imm32(rhs->getValue().toInt32()), reg); } else if (frame.shouldAvoidDataRemat(rhs)) { if (op == JSOP_BITAND) masm.and32(masm.payloadOf(frame.addressOf(rhs)), reg); else if (op == JSOP_BITXOR) masm.xor32(masm.payloadOf(frame.addressOf(rhs)), reg); else masm.or32(masm.payloadOf(frame.addressOf(rhs)), reg); } else { RegisterID rhsReg = frame.tempRegForData(rhs); if (op == JSOP_BITAND) masm.and32(rhsReg, reg); else if (op == JSOP_BITXOR) masm.xor32(rhsReg, reg); else masm.or32(rhsReg, reg); } break; } case JSOP_LSH: case JSOP_URSH: { /* Not commutative. */ if (rhs->isConstant()) { RegisterID reg = frame.ownRegForData(lhs); int shift = rhs->getValue().toInt32() & 0x1F; if (shift) { if (op == JSOP_LSH) masm.lshift32(Imm32(shift), reg); else masm.urshift32(Imm32(shift), reg); } if (stubNeeded) { stubcc.leave(); OOL_STUBCALL(stub); } frame.popn(2); /* x >>> 0 may result in a double, handled above. */ JS_ASSERT_IF(op == JSOP_URSH, shift >= 1); frame.pushTypedPayload(JSVAL_TYPE_INT32, reg); if (stubNeeded) stubcc.rejoin(Changes(1)); return; } #if defined(JS_CPU_X86) || defined(JS_CPU_X64) /* Grosssssss! RHS _must_ be in ECX, on x86 */ RegisterID rr = frame.tempRegInMaskForData(rhs, Registers::maskReg(JSC::X86Registers::ecx)); #else RegisterID rr = frame.tempRegForData(rhs); #endif if (frame.haveSameBacking(lhs, rhs)) { // It's okay to allocReg(). If |rr| is evicted, it won't result in // a load, and |rr == reg| is fine since this is (x << x). reg = frame.allocReg(); if (rr != reg) masm.move(rr, reg); } else { frame.pinReg(rr); if (lhs->isConstant()) { reg = frame.allocReg(); masm.move(Imm32(lhs->getValue().toInt32()), reg); } else { reg = frame.copyDataIntoReg(lhs); } frame.unpinReg(rr); } if (op == JSOP_LSH) { masm.lshift32(rr, reg); } else { masm.urshift32(rr, reg); Jump isNegative = masm.branch32(Assembler::LessThan, reg, Imm32(0)); stubcc.linkExit(isNegative, Uses(2)); stubNeeded = true; } break; } default: JS_NOT_REACHED("NYI"); return; } if (stubNeeded) { stubcc.leave(); OOL_STUBCALL(stub); } frame.pop(); frame.pop(); if (op == JSOP_URSH) frame.pushNumber(reg, true); else frame.pushTypedPayload(JSVAL_TYPE_INT32, reg); if (stubNeeded) stubcc.rejoin(Changes(1)); } static inline bool CheckNullOrUndefined(FrameEntry *fe) { if (!fe->isTypeKnown()) return false; JSValueType type = fe->getKnownType(); return type == JSVAL_TYPE_NULL || type == JSVAL_TYPE_UNDEFINED; } bool mjit::Compiler::jsop_equality(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); /* The compiler should have handled constant folding. */ JS_ASSERT(!(rhs->isConstant() && lhs->isConstant())); bool lhsTest; if ((lhsTest = CheckNullOrUndefined(lhs)) || CheckNullOrUndefined(rhs)) { /* What's the other mask? */ FrameEntry *test = lhsTest ? rhs : lhs; if (test->isTypeKnown()) return emitStubCmpOp(stub, target, fused); /* The other side must be null or undefined. */ RegisterID reg = frame.ownRegForType(test); frame.pop(); frame.pop(); /* * :FIXME: Easier test for undefined || null? * Maybe put them next to each other, subtract, do a single compare? */ if (target) { frame.syncAndForgetEverything(); if ((op == JSOP_EQ && fused == JSOP_IFNE) || (op == JSOP_NE && fused == JSOP_IFEQ)) { /* * It would be easier to just have two jumpAndTrace calls here, but since * each jumpAndTrace creates a TRACE IC, and since we want the bytecode * to have a reference to the TRACE IC at the top of the loop, it's much * better to have only one TRACE IC per loop, and hence at most one * jumpAndTrace. */ Jump b1 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED)); Jump b2 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_NULL)); Jump j1 = masm.jump(); b1.linkTo(masm.label(), &masm); b2.linkTo(masm.label(), &masm); Jump j2 = masm.jump(); if (!jumpAndTrace(j2, target)) return false; j1.linkTo(masm.label(), &masm); } else { Jump j = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED)); Jump j2 = masm.branchPtr(Assembler::NotEqual, reg, ImmType(JSVAL_TYPE_NULL)); if (!jumpAndTrace(j2, target)) return false; j.linkTo(masm.label(), &masm); } } else { Jump j = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_UNDEFINED)); Jump j2 = masm.branchPtr(Assembler::Equal, reg, ImmType(JSVAL_TYPE_NULL)); masm.move(Imm32(op == JSOP_NE), reg); Jump j3 = masm.jump(); j2.linkTo(masm.label(), &masm); j.linkTo(masm.label(), &masm); masm.move(Imm32(op == JSOP_EQ), reg); j3.linkTo(masm.label(), &masm); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg); } return true; } return emitStubCmpOp(stub, target, fused); } bool mjit::Compiler::jsop_relational(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); /* The compiler should have handled constant folding. */ JS_ASSERT(!(rhs->isConstant() && lhs->isConstant())); /* Always slow path... */ if ((lhs->isNotType(JSVAL_TYPE_INT32) && lhs->isNotType(JSVAL_TYPE_DOUBLE) && lhs->isNotType(JSVAL_TYPE_STRING)) || (rhs->isNotType(JSVAL_TYPE_INT32) && rhs->isNotType(JSVAL_TYPE_DOUBLE) && rhs->isNotType(JSVAL_TYPE_STRING))) { if (op == JSOP_EQ || op == JSOP_NE) return jsop_equality(op, stub, target, fused); return emitStubCmpOp(stub, target, fused); } if (op == JSOP_EQ || op == JSOP_NE) { if ((lhs->isNotType(JSVAL_TYPE_INT32) && lhs->isNotType(JSVAL_TYPE_STRING)) || (rhs->isNotType(JSVAL_TYPE_INT32) && rhs->isNotType(JSVAL_TYPE_STRING))) { return emitStubCmpOp(stub, target, fused); } else if (!target && (lhs->isType(JSVAL_TYPE_STRING) || rhs->isType(JSVAL_TYPE_STRING))) { return emitStubCmpOp(stub, target, fused); } else if (frame.haveSameBacking(lhs, rhs)) { return emitStubCmpOp(stub, target, fused); } else { return jsop_equality_int_string(op, stub, target, fused); } } if (frame.haveSameBacking(lhs, rhs)) { return jsop_relational_self(op, stub, target, fused); } else if (lhs->isType(JSVAL_TYPE_STRING) || rhs->isType(JSVAL_TYPE_STRING)) { return emitStubCmpOp(stub, target, fused); } else if (lhs->isType(JSVAL_TYPE_DOUBLE) || rhs->isType(JSVAL_TYPE_DOUBLE)) { return jsop_relational_double(op, stub, target, fused); } else { return jsop_relational_full(op, stub, target, fused); } } void mjit::Compiler::jsop_not() { FrameEntry *top = frame.peek(-1); if (top->isConstant()) { const Value &v = top->getValue(); frame.pop(); frame.push(BooleanValue(!js_ValueToBoolean(v))); return; } if (top->isTypeKnown()) { JSValueType type = top->getKnownType(); switch (type) { case JSVAL_TYPE_INT32: { RegisterID data = frame.allocReg(Registers::SingleByteRegs); if (frame.shouldAvoidDataRemat(top)) masm.loadPayload(frame.addressOf(top), data); else masm.move(frame.tempRegForData(top), data); masm.set32(Assembler::Equal, data, Imm32(0), data); frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, data); break; } case JSVAL_TYPE_BOOLEAN: { RegisterID reg = frame.ownRegForData(top); masm.xor32(Imm32(1), reg); frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg); break; } case JSVAL_TYPE_OBJECT: { frame.pop(); frame.push(BooleanValue(false)); break; } default: { prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::ValueToBoolean); RegisterID reg = Registers::ReturnReg; frame.takeReg(reg); masm.xor32(Imm32(1), reg); frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg); break; } } return; } RegisterID data = frame.allocReg(Registers::SingleByteRegs); if (frame.shouldAvoidDataRemat(top)) masm.loadPayload(frame.addressOf(top), data); else masm.move(frame.tempRegForData(top), data); RegisterID type = frame.tempRegForType(top); Label syncTarget = stubcc.syncExitAndJump(Uses(1)); /* Inline path is for booleans. */ Jump jmpNotBool = masm.testBoolean(Assembler::NotEqual, type); masm.xor32(Imm32(1), data); /* OOL path is for int + object. */ Label lblMaybeInt32 = stubcc.masm.label(); Jump jmpNotInt32 = stubcc.masm.testInt32(Assembler::NotEqual, type); stubcc.masm.set32(Assembler::Equal, data, Imm32(0), data); Jump jmpInt32Exit = stubcc.masm.jump(); Label lblMaybeObject = stubcc.masm.label(); Jump jmpNotObject = stubcc.masm.testPrimitive(Assembler::Equal, type); stubcc.masm.move(Imm32(0), data); Jump jmpObjectExit = stubcc.masm.jump(); /* Rejoin location. */ Label lblRejoin = masm.label(); /* Patch up jumps. */ stubcc.linkExitDirect(jmpNotBool, lblMaybeInt32); jmpNotInt32.linkTo(lblMaybeObject, &stubcc.masm); stubcc.crossJump(jmpInt32Exit, lblRejoin); jmpNotObject.linkTo(syncTarget, &stubcc.masm); stubcc.crossJump(jmpObjectExit, lblRejoin); /* Leave. */ stubcc.leave(); OOL_STUBCALL(stubs::Not); frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, data); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_typeof() { FrameEntry *fe = frame.peek(-1); if (fe->isTypeKnown()) { JSRuntime *rt = cx->runtime; JSAtom *atom = NULL; switch (fe->getKnownType()) { case JSVAL_TYPE_STRING: atom = rt->atomState.typeAtoms[JSTYPE_STRING]; break; case JSVAL_TYPE_UNDEFINED: atom = rt->atomState.typeAtoms[JSTYPE_VOID]; break; case JSVAL_TYPE_NULL: atom = rt->atomState.typeAtoms[JSTYPE_OBJECT]; break; case JSVAL_TYPE_OBJECT: atom = NULL; break; case JSVAL_TYPE_BOOLEAN: atom = rt->atomState.typeAtoms[JSTYPE_BOOLEAN]; break; default: atom = rt->atomState.typeAtoms[JSTYPE_NUMBER]; break; } if (atom) { frame.pop(); frame.push(StringValue(ATOM_TO_STRING(atom))); return; } } JSOp fused = JSOp(PC[JSOP_TYPEOF_LENGTH]); if (fused == JSOP_STRING && !fe->isTypeKnown()) { JSOp op = JSOp(PC[JSOP_TYPEOF_LENGTH + JSOP_STRING_LENGTH]); if (op == JSOP_STRICTEQ || op == JSOP_EQ || op == JSOP_STRICTNE || op == JSOP_NE) { JSAtom *atom = script->getAtom(fullAtomIndex(PC + JSOP_TYPEOF_LENGTH)); JSRuntime *rt = cx->runtime; JSValueType type = JSVAL_TYPE_BOXED; Assembler::Condition cond = (op == JSOP_STRICTEQ || op == JSOP_EQ) ? Assembler::Equal : Assembler::NotEqual; if (atom == rt->atomState.typeAtoms[JSTYPE_VOID]) { type = JSVAL_TYPE_UNDEFINED; } else if (atom == rt->atomState.typeAtoms[JSTYPE_STRING]) { type = JSVAL_TYPE_STRING; } else if (atom == rt->atomState.typeAtoms[JSTYPE_BOOLEAN]) { type = JSVAL_TYPE_BOOLEAN; } else if (atom == rt->atomState.typeAtoms[JSTYPE_NUMBER]) { type = JSVAL_TYPE_INT32; /* JSVAL_TYPE_DOUBLE is 0x0 and JSVAL_TYPE_INT32 is 0x1, use <= or > to match both */ cond = (cond == Assembler::Equal) ? Assembler::BelowOrEqual : Assembler::Above; } if (type != JSVAL_TYPE_BOXED) { PC += JSOP_STRING_LENGTH;; PC += JSOP_EQ_LENGTH; RegisterID result = frame.allocReg(Registers::SingleByteRegs); #if defined JS_NUNBOX32 if (frame.shouldAvoidTypeRemat(fe)) masm.set32(cond, masm.tagOf(frame.addressOf(fe)), ImmType(type), result); else masm.set32(cond, frame.tempRegForType(fe), ImmType(type), result); #elif defined JS_PUNBOX64 masm.setPtr(cond, frame.tempRegForType(fe), ImmType(type), result); #endif frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result); return; } } } prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::TypeOf); frame.pop(); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_STRING, Registers::ReturnReg); } bool mjit::Compiler::booleanJumpScript(JSOp op, jsbytecode *target) { FrameEntry *fe = frame.peek(-1); MaybeRegisterID type; MaybeRegisterID data; if (!fe->isTypeKnown() && !frame.shouldAvoidTypeRemat(fe)) type.setReg(frame.copyTypeIntoReg(fe)); data.setReg(frame.copyDataIntoReg(fe)); frame.syncAndForgetEverything(); Assembler::Condition cond = (op == JSOP_IFNE || op == JSOP_OR) ? Assembler::NonZero : Assembler::Zero; Assembler::Condition ncond = (op == JSOP_IFNE || op == JSOP_OR) ? Assembler::Zero : Assembler::NonZero; /* Inline path: Boolean guard + call script. */ MaybeJump jmpNotBool; MaybeJump jmpNotExecScript; if (type.isSet()) { jmpNotBool.setJump(masm.testBoolean(Assembler::NotEqual, type.reg())); } else { if (!fe->isTypeKnown()) { jmpNotBool.setJump(masm.testBoolean(Assembler::NotEqual, frame.addressOf(fe))); } else if (fe->isNotType(JSVAL_TYPE_BOOLEAN) && fe->isNotType(JSVAL_TYPE_INT32)) { jmpNotBool.setJump(masm.jump()); } } /* * TODO: We don't need the second jump if * jumpInScript() can go from ool path to inline path. */ jmpNotExecScript.setJump(masm.branchTest32(ncond, data.reg(), data.reg())); Label lblExecScript = masm.label(); Jump j = masm.jump(); /* OOL path: Conversion to boolean. */ MaybeJump jmpCvtExecScript; MaybeJump jmpCvtRejoin; Label lblCvtPath = stubcc.masm.label(); if (!fe->isTypeKnown() || !(fe->isType(JSVAL_TYPE_BOOLEAN) || fe->isType(JSVAL_TYPE_INT32))) { stubcc.masm.infallibleVMCall(JS_FUNC_TO_DATA_PTR(void *, stubs::ValueToBoolean), frame.localSlots()); jmpCvtExecScript.setJump(stubcc.masm.branchTest32(cond, Registers::ReturnReg, Registers::ReturnReg)); jmpCvtRejoin.setJump(stubcc.masm.jump()); } /* Rejoin tag. */ Label lblAfterScript = masm.label(); /* Patch up jumps. */ if (jmpNotBool.isSet()) stubcc.linkExitDirect(jmpNotBool.getJump(), lblCvtPath); if (jmpNotExecScript.isSet()) jmpNotExecScript.getJump().linkTo(lblAfterScript, &masm); if (jmpCvtExecScript.isSet()) stubcc.crossJump(jmpCvtExecScript.getJump(), lblExecScript); if (jmpCvtRejoin.isSet()) stubcc.crossJump(jmpCvtRejoin.getJump(), lblAfterScript); frame.pop(); return jumpAndTrace(j, target); } bool mjit::Compiler::jsop_ifneq(JSOp op, jsbytecode *target) { FrameEntry *fe = frame.peek(-1); if (fe->isConstant()) { JSBool b = js_ValueToBoolean(fe->getValue()); frame.pop(); if (op == JSOP_IFEQ) b = !b; if (b) { frame.syncAndForgetEverything(); if (!jumpAndTrace(masm.jump(), target)) return false; } return true; } return booleanJumpScript(op, target); } bool mjit::Compiler::jsop_andor(JSOp op, jsbytecode *target) { FrameEntry *fe = frame.peek(-1); if (fe->isConstant()) { JSBool b = js_ValueToBoolean(fe->getValue()); /* Short-circuit. */ if ((op == JSOP_OR && b == JS_TRUE) || (op == JSOP_AND && b == JS_FALSE)) { frame.syncAndForgetEverything(); if (!jumpAndTrace(masm.jump(), target)) return false; } frame.pop(); return true; } return booleanJumpScript(op, target); } void mjit::Compiler::jsop_localinc(JSOp op, uint32 slot, bool popped) { if (popped || (op == JSOP_INCLOCAL || op == JSOP_DECLOCAL)) { int amt = (op == JSOP_LOCALINC || op == JSOP_INCLOCAL) ? -1 : 1; // Before: // After: V frame.pushLocal(slot); // Before: V // After: V 1 frame.push(Int32Value(amt)); // Note, SUB will perform integer conversion for us. // Before: V 1 // After: N+1 jsop_binary(JSOP_SUB, stubs::Sub); // Before: N+1 // After: N+1 frame.storeLocal(slot, popped); if (popped) frame.pop(); } else { int amt = (op == JSOP_LOCALINC || op == JSOP_INCLOCAL) ? 1 : -1; // Before: // After: V frame.pushLocal(slot); // Before: V // After: N jsop_pos(); // Before: N // After: N N frame.dup(); // Before: N N // After: N N 1 frame.push(Int32Value(amt)); // Before: N N 1 // After: N N+1 jsop_binary(JSOP_ADD, stubs::Add); // Before: N N+1 // After: N N+1 frame.storeLocal(slot, true); // Before: N N+1 // After: N frame.pop(); } } void mjit::Compiler::jsop_arginc(JSOp op, uint32 slot, bool popped) { if (popped || (op == JSOP_INCARG || op == JSOP_DECARG)) { int amt = (op == JSOP_ARGINC || op == JSOP_INCARG) ? -1 : 1; // Before: // After: V frame.pushArg(slot); // Before: V // After: V 1 frame.push(Int32Value(amt)); // Note, SUB will perform integer conversion for us. // Before: V 1 // After: N+1 jsop_binary(JSOP_SUB, stubs::Sub); // Before: N+1 // After: N+1 frame.storeArg(slot, popped); if (popped) frame.pop(); } else { int amt = (op == JSOP_ARGINC || op == JSOP_INCARG) ? 1 : -1; // Before: // After: V frame.pushArg(slot); // Before: V // After: N jsop_pos(); // Before: N // After: N N frame.dup(); // Before: N N // After: N N 1 frame.push(Int32Value(amt)); // Before: N N 1 // After: N N+1 jsop_binary(JSOP_ADD, stubs::Add); // Before: N N+1 // After: N N+1 frame.storeArg(slot, true); // Before: N N+1 // After: N frame.pop(); } } static inline bool IsCacheableSetElem(FrameEntry *obj, FrameEntry *id, FrameEntry *value) { if (obj->isNotType(JSVAL_TYPE_OBJECT)) return false; if (id->isNotType(JSVAL_TYPE_INT32)) return false; if (id->isConstant() && id->getValue().toInt32() < 0) return false; // obj[obj] * is not allowed, since it will never optimize. // obj[id] = id is allowed. // obj[id] = obj is allowed. if (obj->hasSameBacking(id)) return false; return true; } bool mjit::Compiler::jsop_setelem(bool popGuaranteed) { FrameEntry *obj = frame.peek(-3); FrameEntry *id = frame.peek(-2); FrameEntry *value = frame.peek(-1); if (!IsCacheableSetElem(obj, id, value)) { jsop_setelem_slow(); return true; } SetElementICInfo ic = SetElementICInfo(JSOp(*PC)); // One by one, check if the most important stack entries have registers, // and if so, pin them. This is to avoid spilling and reloading from the // stack as we incrementally allocate other registers. MaybeRegisterID pinnedValueType = frame.maybePinType(value); MaybeRegisterID pinnedValueData = frame.maybePinData(value); // Pin |obj| if it doesn't share a backing with |value|. MaybeRegisterID pinnedObjData; if (!obj->hasSameBacking(value)) pinnedObjData = frame.maybePinData(obj); // Pin |id| if it doesn't share a backing with |value|. MaybeRegisterID pinnedIdData; if (!id->hasSameBacking(value)) pinnedIdData = frame.maybePinData(id); // Note: The fact that |obj| and |value|, or |id| and |value| can be // copies, is a little complicated, but it is safe. Explanations // follow at each point. Keep in mind two points: // 1) maybePin() never allocates a register, it only pins if a register // already existed. // 2) tempRegForData() will work fine on a pinned register. // Guard that the object is an object. if (!obj->isTypeKnown()) { Jump j = frame.testObject(Assembler::NotEqual, obj); stubcc.linkExit(j, Uses(3)); } // Guard that the id is int32. if (!id->isTypeKnown()) { Jump j = frame.testInt32(Assembler::NotEqual, id); stubcc.linkExit(j, Uses(3)); } // Grab a register for the object. It's safe to unpin |obj| because it // won't have been pinned if it shares a backing with |value|. However, // it would not be safe to copyDataIntoReg() if the value was pinned, // since this could evict the register. So we special case. frame.maybeUnpinReg(pinnedObjData); if (obj->hasSameBacking(value) && pinnedValueData.isSet()) { ic.objReg = frame.allocReg(); masm.move(pinnedValueData.reg(), ic.objReg); } else { ic.objReg = frame.copyDataIntoReg(obj); } // pinEntry() will ensure pinned registers for |value|. To avoid a // double-pin assert, first unpin any registers that |value| had. frame.maybeUnpinReg(pinnedValueType); frame.maybeUnpinReg(pinnedValueData); frame.pinEntry(value, ic.vr); // Store rematerialization information about the key. This is the final // register we allocate, and thus it can use tempRegForData() without // the worry of being spilled. Once again, this is safe even if |id| // shares a backing with |value|, because tempRegForData() will work on // the pinned register, and |pinnedIdData| will not double-pin. frame.maybeUnpinReg(pinnedIdData); if (id->isConstant()) ic.key = Int32Key::FromConstant(id->getValue().toInt32()); else ic.key = Int32Key::FromRegister(frame.tempRegForData(id)); // Unpin the value since register allocation is complete. frame.unpinEntry(ic.vr); // Now it's also safe to grab remat info for obj (all exits that can // generate stubs must have the same register state). ic.objRemat = frame.dataRematInfo(obj); // All patchable guards must occur after this point. RESERVE_IC_SPACE(masm); ic.fastPathStart = masm.label(); // Create the common out-of-line sync block, taking care to link previous // guards here after. RESERVE_OOL_SPACE(stubcc.masm); ic.slowPathStart = stubcc.syncExit(Uses(3)); // Guard obj is a dense array. ic.claspGuard = masm.testObjClass(Assembler::NotEqual, ic.objReg, &js_ArrayClass); stubcc.linkExitDirect(ic.claspGuard, ic.slowPathStart); // Guard capacity in range. Jump capacityGuard = masm.guardArrayCapacity(ic.objReg, ic.key); stubcc.linkExitDirect(capacityGuard, ic.slowPathStart); // Load the dynamic slots vector. masm.loadPtr(Address(ic.objReg, offsetof(JSObject, slots)), ic.objReg); // Guard there's no hole, then store directly to the slot. if (ic.key.isConstant()) { Address slot(ic.objReg, ic.key.index() * sizeof(Value)); ic.holeGuard = masm.guardNotHole(slot); masm.storeValue(ic.vr, slot); } else { BaseIndex slot(ic.objReg, ic.key.reg(), Assembler::JSVAL_SCALE); ic.holeGuard = masm.guardNotHole(slot); masm.storeValue(ic.vr, slot); } stubcc.linkExitDirect(ic.holeGuard, ic.slowPathStart); stubcc.leave(); #if defined JS_POLYIC passICAddress(&ic); ic.slowPathCall = OOL_STUBCALL(STRICT_VARIANT(ic::SetElement)); #else OOL_STUBCALL(STRICT_VARIANT(stubs::SetElem)); #endif ic.fastPathRejoin = masm.label(); // When generating typed array stubs, it may be necessary to call // js_DoubleToECMAInt32(), which would clobber registers. To deal with // this, we tell the IC exactly which registers need to be saved // across calls. ic.volatileMask = frame.regsInUse(); // If the RHS will be popped, and doesn't overlap any live values, then // there's no need to save it across calls. Note that this is not true of // |obj| or |key|, which will be used to compute the LHS reference for // assignment. // // Note that the IC wants to clobber |vr.dataReg| to convert for typed // arrays. If this clobbering is necessary, we must preserve dataReg, // even if it's not in a volatile register. if (popGuaranteed && !ic.vr.isConstant() && !value->isCopy() && !frame.haveSameBacking(value, obj) && !frame.haveSameBacking(value, id)) { ic.volatileMask &= ~Registers::maskReg(ic.vr.dataReg()); if (!ic.vr.isTypeKnown()) ic.volatileMask &= ~Registers::maskReg(ic.vr.typeReg()); } else if (!ic.vr.isConstant()) { ic.volatileMask |= Registers::maskReg(ic.vr.dataReg()); } frame.freeReg(ic.objReg); frame.shimmy(2); stubcc.rejoin(Changes(2)); #if defined JS_POLYIC if (!setElemICs.append(ic)) return false; #endif return true; } static inline bool IsCacheableGetElem(FrameEntry *obj, FrameEntry *id) { if (obj->isTypeKnown() && obj->getKnownType() != JSVAL_TYPE_OBJECT) return false; if (id->isTypeKnown() && !(id->getKnownType() == JSVAL_TYPE_INT32 #if defined JS_POLYIC || id->getKnownType() == JSVAL_TYPE_STRING #endif )) { return false; } if (id->isTypeKnown() && id->getKnownType() == JSVAL_TYPE_INT32 && id->isConstant() && id->getValue().toInt32() < 0) { return false; } // obj[obj] is not allowed, since it will never optimize. if (obj->hasSameBacking(id)) return false; return true; } bool mjit::Compiler::jsop_getelem(bool isCall) { FrameEntry *obj = frame.peek(-2); FrameEntry *id = frame.peek(-1); if (!IsCacheableGetElem(obj, id)) { if (isCall) jsop_callelem_slow(); else jsop_getelem_slow(); return true; } GetElementICInfo ic = GetElementICInfo(JSOp(*PC)); // Pin the top of the stack to avoid spills, before allocating registers. MaybeRegisterID pinnedIdData = frame.maybePinData(id); MaybeRegisterID pinnedIdType = frame.maybePinType(id); MaybeJump objTypeGuard; if (!obj->isTypeKnown()) { // Test the type of the object without spilling the payload. MaybeRegisterID pinnedObjData = frame.maybePinData(obj); Jump guard = frame.testObject(Assembler::NotEqual, obj); frame.maybeUnpinReg(pinnedObjData); // Create a sync path, which we'll rejoin manually later. This is safe // as long as the IC does not build a stub; it won't, because |obj| // won't be an object. If we extend this IC to support strings, all // that needs to change is a little code movement. stubcc.linkExit(guard, Uses(2)); objTypeGuard = stubcc.masm.jump(); } // Get a mutable register for the object. This will be the data reg. ic.objReg = frame.copyDataIntoReg(obj); // For potential dense array calls, grab an extra reg to save the // outgoing object. MaybeRegisterID thisReg; if (isCall && id->mightBeType(JSVAL_TYPE_INT32)) { thisReg = frame.allocReg(); masm.move(ic.objReg, thisReg.reg()); } // Get a mutable register for pushing the result type. We kill two birds // with one stone by making sure, if the key type is not known, to be loaded // into this register. In this case it is both an input and an output. frame.maybeUnpinReg(pinnedIdType); if (id->isConstant() || id->isTypeKnown()) ic.typeReg = frame.allocReg(); else ic.typeReg = frame.copyTypeIntoReg(id); // Fill in the id value. frame.maybeUnpinReg(pinnedIdData); if (id->isConstant()) { ic.id = ValueRemat::FromConstant(id->getValue()); } else { RegisterID dataReg = frame.tempRegForData(id); if (id->isTypeKnown()) ic.id = ValueRemat::FromKnownType(id->getKnownType(), dataReg); else ic.id = ValueRemat::FromRegisters(ic.typeReg, dataReg); } RESERVE_IC_SPACE(masm); ic.fastPathStart = masm.label(); // Note: slow path here is safe, since the frame will not be modified. RESERVE_OOL_SPACE(stubcc.masm); ic.slowPathStart = stubcc.masm.label(); frame.sync(stubcc.masm, Uses(2)); if (id->mightBeType(JSVAL_TYPE_INT32)) { // Always test the type first (see comment in PolyIC.h). if (!id->isTypeKnown()) { ic.typeGuard = masm.testInt32(Assembler::NotEqual, ic.typeReg); stubcc.linkExitDirect(ic.typeGuard.get(), ic.slowPathStart); } // Guard on the clasp. ic.claspGuard = masm.testObjClass(Assembler::NotEqual, ic.objReg, &js_ArrayClass); stubcc.linkExitDirect(ic.claspGuard, ic.slowPathStart); Int32Key key = id->isConstant() ? Int32Key::FromConstant(id->getValue().toInt32()) : Int32Key::FromRegister(ic.id.dataReg()); Assembler::FastArrayLoadFails fails = masm.fastArrayLoad(ic.objReg, key, ic.typeReg, ic.objReg); // Store the object back to sp[-1] for calls. This must occur after // all guards because otherwise sp[-1] will be clobbered. if (isCall) { Address thisSlot = frame.addressOf(id); masm.storeValueFromComponents(ImmType(JSVAL_TYPE_OBJECT), thisReg.reg(), thisSlot); frame.freeReg(thisReg.reg()); } stubcc.linkExitDirect(fails.rangeCheck, ic.slowPathStart); stubcc.linkExitDirect(fails.holeCheck, ic.slowPathStart); } else { // The type is known to not be dense-friendly ahead of time, so always // fall back to a slow path. ic.claspGuard = masm.jump(); stubcc.linkExitDirect(ic.claspGuard, ic.slowPathStart); } stubcc.leave(); if (objTypeGuard.isSet()) objTypeGuard.get().linkTo(stubcc.masm.label(), &stubcc.masm); #ifdef JS_POLYIC passICAddress(&ic); if (isCall) ic.slowPathCall = OOL_STUBCALL(ic::CallElement); else ic.slowPathCall = OOL_STUBCALL(ic::GetElement); #else if (isCall) ic.slowPathCall = OOL_STUBCALL(stubs::CallElem); else ic.slowPathCall = OOL_STUBCALL(stubs::GetElem); #endif ic.fastPathRejoin = masm.label(); frame.popn(2); frame.pushRegs(ic.typeReg, ic.objReg); if (isCall) frame.pushSynced(); stubcc.rejoin(Changes(2)); #ifdef JS_POLYIC if (!getElemICs.append(ic)) return false; #endif return true; } static inline bool ReallySimpleStrictTest(FrameEntry *fe) { if (!fe->isTypeKnown()) return false; JSValueType type = fe->getKnownType(); return type == JSVAL_TYPE_NULL || type == JSVAL_TYPE_UNDEFINED; } static inline bool BooleanStrictTest(FrameEntry *fe) { return fe->isConstant() && fe->getKnownType() == JSVAL_TYPE_BOOLEAN; } void mjit::Compiler::jsop_stricteq(JSOp op) { FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); Assembler::Condition cond = (op == JSOP_STRICTEQ) ? Assembler::Equal : Assembler::NotEqual; /* * NB: x64 can do full-Value comparisons. This is beneficial * to do if the payload/type are not yet in registers. */ /* Constant-fold. */ if (lhs->isConstant() && rhs->isConstant()) { JSBool b; StrictlyEqual(cx, lhs->getValue(), rhs->getValue(), &b); frame.popn(2); frame.push(BooleanValue((op == JSOP_STRICTEQ) ? b : !b)); return; } if (frame.haveSameBacking(lhs, rhs)) { /* False iff NaN. */ if (lhs->isTypeKnown() && lhs->isNotType(JSVAL_TYPE_DOUBLE)) { frame.popn(2); frame.push(BooleanValue(op == JSOP_STRICTEQ)); return; } /* Assume NaN is in canonical form. */ RegisterID result = frame.allocReg(Registers::SingleByteRegs); RegisterID treg = frame.tempRegForType(lhs); Assembler::Condition oppositeCond = (op == JSOP_STRICTEQ) ? Assembler::NotEqual : Assembler::Equal; #if defined JS_CPU_X86 || defined JS_CPU_ARM static const int CanonicalNaNType = 0x7FF80000; masm.setPtr(oppositeCond, treg, Imm32(CanonicalNaNType), result); #elif defined JS_CPU_X64 static const void *CanonicalNaNType = (void *)0x7FF8000000000000; masm.move(ImmPtr(CanonicalNaNType), JSC::X86Registers::r11); masm.setPtr(oppositeCond, treg, JSC::X86Registers::r11, result); #endif frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result); return; } /* Comparison against undefined or null is super easy. */ bool lhsTest; if ((lhsTest = ReallySimpleStrictTest(lhs)) || ReallySimpleStrictTest(rhs)) { FrameEntry *test = lhsTest ? rhs : lhs; FrameEntry *known = lhsTest ? lhs : rhs; if (test->isTypeKnown()) { frame.popn(2); frame.push(BooleanValue((test->getKnownType() == known->getKnownType()) == (op == JSOP_STRICTEQ))); return; } /* This is only true if the other side is |null|. */ RegisterID result = frame.allocReg(Registers::SingleByteRegs); #if defined JS_CPU_X86 || defined JS_CPU_ARM JSValueTag mask = known->getKnownTag(); if (frame.shouldAvoidTypeRemat(test)) masm.set32(cond, masm.tagOf(frame.addressOf(test)), Imm32(mask), result); else masm.set32(cond, frame.tempRegForType(test), Imm32(mask), result); #elif defined JS_CPU_X64 RegisterID maskReg = frame.allocReg(); masm.move(ImmTag(known->getKnownTag()), maskReg); RegisterID r = frame.tempRegForType(test); masm.setPtr(cond, r, maskReg, result); frame.freeReg(maskReg); #endif frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result); return; } /* Hardcoded booleans are easy too. */ if ((lhsTest = BooleanStrictTest(lhs)) || BooleanStrictTest(rhs)) { FrameEntry *test = lhsTest ? rhs : lhs; if (test->isTypeKnown() && test->isNotType(JSVAL_TYPE_BOOLEAN)) { frame.popn(2); frame.push(BooleanValue(op == JSOP_STRICTNE)); return; } if (test->isConstant()) { frame.popn(2); const Value &L = lhs->getValue(); const Value &R = rhs->getValue(); frame.push(BooleanValue((L.toBoolean() == R.toBoolean()) == (op == JSOP_STRICTEQ))); return; } RegisterID data = frame.copyDataIntoReg(test); RegisterID result = data; if (!(Registers::maskReg(data) & Registers::SingleByteRegs)) result = frame.allocReg(Registers::SingleByteRegs); Jump notBoolean; if (!test->isTypeKnown()) notBoolean = frame.testBoolean(Assembler::NotEqual, test); /* Do a dynamic test. */ bool val = lhsTest ? lhs->getValue().toBoolean() : rhs->getValue().toBoolean(); masm.set32(cond, data, Imm32(val), result); if (!test->isTypeKnown()) { Jump done = masm.jump(); notBoolean.linkTo(masm.label(), &masm); masm.move(Imm32((op == JSOP_STRICTNE)), result); done.linkTo(masm.label(), &masm); } if (data != result) frame.freeReg(data); frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result); return; } /* Is it impossible that both Values are ints? */ if ((lhs->isTypeKnown() && lhs->isNotType(JSVAL_TYPE_INT32)) || (rhs->isTypeKnown() && rhs->isNotType(JSVAL_TYPE_INT32))) { prepareStubCall(Uses(2)); if (op == JSOP_STRICTEQ) INLINE_STUBCALL(stubs::StrictEq); else INLINE_STUBCALL(stubs::StrictNe); frame.popn(2); frame.pushSyncedType(JSVAL_TYPE_BOOLEAN); return; } #ifndef JS_CPU_ARM /* Try an integer fast-path. */ bool needStub = false; if (!lhs->isTypeKnown()) { Jump j = frame.testInt32(Assembler::NotEqual, lhs); stubcc.linkExit(j, Uses(2)); needStub = true; } if (!rhs->isTypeKnown() && !frame.haveSameBacking(lhs, rhs)) { Jump j = frame.testInt32(Assembler::NotEqual, rhs); stubcc.linkExit(j, Uses(2)); needStub = true; } FrameEntry *test = lhs->isConstant() ? rhs : lhs; FrameEntry *other = lhs->isConstant() ? lhs : rhs; /* ReturnReg is safely usable with set32, since %ah can be accessed. */ RegisterID resultReg = Registers::ReturnReg; frame.takeReg(resultReg); RegisterID testReg = frame.tempRegForData(test); frame.pinReg(testReg); JS_ASSERT(resultReg != testReg); /* Set boolean in resultReg. */ if (other->isConstant()) { masm.set32(cond, testReg, Imm32(other->getValue().toInt32()), resultReg); } else if (frame.shouldAvoidDataRemat(other)) { masm.set32(cond, testReg, frame.addressOf(other), resultReg); } else { RegisterID otherReg = frame.tempRegForData(other); JS_ASSERT(otherReg != resultReg); JS_ASSERT(otherReg != testReg); masm.set32(cond, testReg, otherReg, resultReg); } frame.unpinReg(testReg); if (needStub) { stubcc.leave(); if (op == JSOP_STRICTEQ) OOL_STUBCALL(stubs::StrictEq); else OOL_STUBCALL(stubs::StrictNe); } frame.popn(2); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, resultReg); if (needStub) stubcc.rejoin(Changes(1)); #else /* TODO: Port set32() logic to ARM. */ prepareStubCall(Uses(2)); if (op == JSOP_STRICTEQ) INLINE_STUBCALL(stubs::StrictEq); else INLINE_STUBCALL(stubs::StrictNe); frame.popn(2); frame.pushSyncedType(JSVAL_TYPE_BOOLEAN); return; #endif } void mjit::Compiler::jsop_pos() { FrameEntry *top = frame.peek(-1); if (top->isTypeKnown()) { if (top->getKnownType() <= JSVAL_TYPE_INT32) return; prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::Pos); frame.pop(); frame.pushSynced(); return; } frame.giveOwnRegs(top); Jump j; if (frame.shouldAvoidTypeRemat(top)) j = masm.testNumber(Assembler::NotEqual, frame.addressOf(top)); else j = masm.testNumber(Assembler::NotEqual, frame.tempRegForType(top)); stubcc.linkExit(j, Uses(1)); stubcc.leave(); OOL_STUBCALL(stubs::Pos); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_initmethod() { #ifdef DEBUG FrameEntry *obj = frame.peek(-2); #endif JSAtom *atom = script->getAtom(fullAtomIndex(PC)); /* Initializers with INITMETHOD are not fast yet. */ JS_ASSERT(!obj->initializerObject()); prepareStubCall(Uses(2)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stubs::InitMethod); } void mjit::Compiler::jsop_initprop() { FrameEntry *obj = frame.peek(-2); FrameEntry *fe = frame.peek(-1); JSAtom *atom = script->getAtom(fullAtomIndex(PC)); JSObject *baseobj = obj->initializerObject(); if (!baseobj) { prepareStubCall(Uses(2)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stubs::InitProp); return; } JSObject *holder; JSProperty *prop = NULL; #ifdef DEBUG int res = #endif js_LookupPropertyWithFlags(cx, baseobj, ATOM_TO_JSID(atom), JSRESOLVE_QUALIFIED, &holder, &prop); JS_ASSERT(res >= 0 && prop && holder == baseobj); RegisterID objReg = frame.copyDataIntoReg(obj); masm.loadPtr(Address(objReg, offsetof(JSObject, slots)), objReg); /* Perform the store. */ Shape *shape = (Shape *) prop; frame.storeTo(fe, Address(objReg, shape->slot * sizeof(Value))); frame.freeReg(objReg); } void mjit::Compiler::jsop_initelem() { FrameEntry *obj = frame.peek(-3); FrameEntry *id = frame.peek(-2); FrameEntry *fe = frame.peek(-1); /* * The initialized index is always a constant, but we won't remember which * constant if there are branches inside the code computing the initializer * expression (e.g. the expression uses the '?' operator). Slow path those * cases, as well as those where INITELEM is used on an object initializer * or a non-fast array initializer. */ if (!id->isConstant() || !obj->initializerArray()) { JSOp next = JSOp(PC[JSOP_INITELEM_LENGTH]); prepareStubCall(Uses(3)); masm.move(Imm32(next == JSOP_ENDINIT ? 1 : 0), Registers::ArgReg1); INLINE_STUBCALL(stubs::InitElem); return; } JS_ASSERT(id->getValue().isInt32()); if (fe->isConstant() && fe->getValue().isMagic(JS_ARRAY_HOLE)) { /* The array already has the correct length, nothing to do. */ return; } RegisterID objReg = frame.copyDataIntoReg(obj); masm.loadPtr(Address(objReg, offsetof(JSObject, slots)), objReg); /* Perform the store. */ frame.storeTo(fe, Address(objReg, id->getValue().toInt32() * sizeof(Value))); frame.freeReg(objReg); }