diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index c2ebf0db1bba..23fc9840bd8f 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -4954,7 +4954,7 @@ MarkIteratorUnknownSlow(JSContext *cx) void TypeMonitorCallSlow(JSContext *cx, JSObject *callee, - const CallArgs &args, bool constructing) + CallArgs &args, bool constructing) { unsigned nargs = callee->getFunctionPrivate()->nargs; JSScript *script = callee->getFunctionPrivate()->script(); @@ -4972,8 +4972,10 @@ TypeMonitorCallSlow(JSContext *cx, JSObject *callee, TypeScript::SetArgument(cx, script, arg, args[arg]); /* Watch for fewer actuals than formals to the call. */ - for (; arg < nargs; arg++) - TypeScript::SetArgument(cx, script, arg, UndefinedValue()); + for (; arg < nargs; arg++) { + Value v = UndefinedValue(); + TypeScript::SetArgument(cx, script, arg, v); + } } static inline bool @@ -5091,8 +5093,10 @@ TypeDynamicResult(JSContext *cx, JSScript *script, jsbytecode *pc, Type type) } void -TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval) +TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, js::Value &rval) { + TryCoerceNumberToInt32(rval); + UntrapOpcode untrap(cx, script, pc); /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */ diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index 1acb405e53ae..b688b5b54761 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -1077,9 +1077,14 @@ class TypeScript * by type inference or where the pc has type barriers. For simplicity, we * always monitor JOF_TYPESET opcodes in the interpreter and stub calls, * and only look at barriers when generating JIT code for the script. + * + * 'val' is passed by reference, and must refer to the location of the op's + * result which will be subsequently used (i.e. not a temporary value). + * This call may change val by coercing a double-representable-as-an-int + * into an integer. */ static inline void Monitor(JSContext *cx, JSScript *script, jsbytecode *pc, - const js::Value &val); + js::Value &val); /* Monitor an assignment at a SETELEM on a non-integer identifier. */ static inline void MonitorAssign(JSContext *cx, JSScript *script, jsbytecode *pc, @@ -1091,7 +1096,7 @@ class TypeScript static inline void SetLocal(JSContext *cx, JSScript *script, unsigned local, Type type); static inline void SetLocal(JSContext *cx, JSScript *script, unsigned local, const js::Value &value); static inline void SetArgument(JSContext *cx, JSScript *script, unsigned arg, Type type); - static inline void SetArgument(JSContext *cx, JSScript *script, unsigned arg, const js::Value &value); + static inline void SetArgument(JSContext *cx, JSScript *script, unsigned arg, js::Value &value); static void Sweep(JSContext *cx, JSScript *script); inline void trace(JSTracer *trc); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index ea93a3ece8fd..82c115883376 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -310,14 +310,15 @@ MarkIteratorUnknown(JSContext *cx) } /* - * Monitor a javascript call, either on entry to the interpreter or made - * from within the interpreter. + * Monitor a javascript call, either on entry to the interpreter or made from + * within the interpreter. As with TypeScript::Monitor, this may coerce double + * arguments to the call into integers. */ inline void -TypeMonitorCall(JSContext *cx, const js::CallArgs &args, bool constructing) +TypeMonitorCall(JSContext *cx, js::CallArgs &args, bool constructing) { extern void TypeMonitorCallSlow(JSContext *cx, JSObject *callee, - const CallArgs &args, bool constructing); + CallArgs &args, bool constructing); JSObject *callee = &args.callee(); if (callee->isFunction()) { @@ -444,7 +445,7 @@ FixObjectType(JSContext *cx, JSObject *obj) } /* Interface helpers for JSScript */ -extern void TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval); +extern void TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, js::Value &rval); extern void TypeDynamicResult(JSContext *cx, JSScript *script, jsbytecode *pc, js::types::Type type); inline bool @@ -559,7 +560,7 @@ TypeScript::InitObject(JSContext *cx, JSScript *script, const jsbytecode *pc, JS } /* static */ inline void -TypeScript::Monitor(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval) +TypeScript::Monitor(JSContext *cx, JSScript *script, jsbytecode *pc, js::Value &rval) { if (cx->typeInferenceEnabled()) TypeMonitorResult(cx, script, pc, rval); @@ -677,9 +678,10 @@ TypeScript::SetArgument(JSContext *cx, JSScript *script, unsigned arg, Type type } /* static */ inline void -TypeScript::SetArgument(JSContext *cx, JSScript *script, unsigned arg, const js::Value &value) +TypeScript::SetArgument(JSContext *cx, JSScript *script, unsigned arg, js::Value &value) { if (cx->typeInferenceEnabled()) { + TryCoerceNumberToInt32(value); Type type = GetValueType(cx, value); SetArgument(cx, script, arg, type); } diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index 91a132470b5a..1a1b09da918e 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -3488,10 +3488,10 @@ BEGIN_CASE(JSOP_LENGTH) } } while (0); - TypeScript::Monitor(cx, script, regs.pc, rval); - regs.sp[-1] = rval; assertSameCompartment(cx, regs.sp[-1]); + + TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]); } END_CASE(JSOP_GETPROP) @@ -3583,7 +3583,7 @@ BEGIN_CASE(JSOP_CALLPROP) goto error; } #endif - TypeScript::Monitor(cx, script, regs.pc, rval); + TypeScript::Monitor(cx, script, regs.pc, regs.sp[-2]); } END_CASE(JSOP_CALLPROP) @@ -4089,7 +4089,7 @@ BEGIN_CASE(JSOP_CALLNAME) } PUSH_COPY(rval); - TypeScript::Monitor(cx, script, regs.pc, rval); + TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]); /* obj must be on the scope chain, thus not a function. */ if (op == JSOP_CALLNAME || op == JSOP_CALLGNAME) diff --git a/js/src/jsnum.h b/js/src/jsnum.h index 57cb6bae807c..c0f764cf59fe 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -658,6 +658,17 @@ ToInteger(JSContext *cx, const js::Value &v, jsdouble *dp) return true; } +/* If v is a double value which fits in an int32, coerce it to that int32. */ +static inline void +TryCoerceNumberToInt32(Value &v) +{ + if (v.isDouble()) { + int32_t res; + if (JSDOUBLE_IS_INT32(v.toDouble(), &res)) + v.setInt32(res); + } +} + } /* namespace js */ #endif /* jsnum_h___ */ diff --git a/js/src/methodjit/BaseAssembler.h b/js/src/methodjit/BaseAssembler.h index 98b0095eb152..9b40ec046664 100644 --- a/js/src/methodjit/BaseAssembler.h +++ b/js/src/methodjit/BaseAssembler.h @@ -230,6 +230,17 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::SparcRegist } #endif + void moveDoubleRegisters(RegisterID data, RegisterID type, Address address, FPRegisterID fpreg) + { +#ifdef JS_CPU_X86 + fastLoadDouble(data, type, fpreg); +#else + /* Store the components, then read it back out as a double. */ + storeValueFromComponents(type, data, address); + loadDouble(address, fpreg); +#endif + } + /* * Move a register pair which may indicate either an int32 or double into fpreg, * converting to double in the int32 case. @@ -1164,6 +1175,29 @@ static const JSC::MacroAssembler::RegisterID JSParamReg_Argc = JSC::SparcRegist } else if (types->hasType(types::Type::Int32Type())) { if (!matches.append(testInt32(Assembler::Equal, address))) return false; + + /* Generate a path to try coercing doubles to integers in place. */ + Jump notDouble = testDouble(Assembler::NotEqual, address); + + Registers tempRegs(Registers::AvailRegs); + RegisterID T1 = tempRegs.takeAnyReg().reg(); + + Registers tempFPRegs(Registers::TempFPRegs); + FPRegisterID FP1 = tempFPRegs.takeAnyReg().fpreg(); + FPRegisterID FP2 = tempFPRegs.takeAnyReg().fpreg(); + + loadDouble(address, FP1); + + JumpList isDouble; + branchConvertDoubleToInt32(FP1, T1, isDouble, FP2); + + storeValueFromComponents(ImmType(JSVAL_TYPE_INT32), T1, address); + + if (!matches.append(jump())) + return false; + + isDouble.linkTo(label(), this); + notDouble.linkTo(label(), this); } if (types->hasType(types::Type::UndefinedType())) { diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 92d3e4b17c0a..86ebcd9f5c40 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -7369,39 +7369,6 @@ mjit::Compiler::pushAddressMaybeBarrier(Address address, JSValueType type, bool return testBarrier(typeReg, dataReg, testUndefined); } -MaybeJump -mjit::Compiler::trySingleTypeTest(types::TypeSet *types, RegisterID typeReg) -{ - /* - * If a type set we have a barrier on is monomorphic, generate a single - * jump taken if a type register has a match. This doesn't handle type sets - * containing objects, as these require two jumps regardless (test for - * object, then test the type of the object). - */ - MaybeJump res; - - switch (types->getKnownTypeTag(cx)) { - case JSVAL_TYPE_INT32: - res.setJump(masm.testInt32(Assembler::NotEqual, typeReg)); - return res; - - case JSVAL_TYPE_DOUBLE: - res.setJump(masm.testNumber(Assembler::NotEqual, typeReg)); - return res; - - case JSVAL_TYPE_BOOLEAN: - res.setJump(masm.testBoolean(Assembler::NotEqual, typeReg)); - return res; - - case JSVAL_TYPE_STRING: - res.setJump(masm.testString(Assembler::NotEqual, typeReg)); - return res; - - default: - return res; - } -} - JSC::MacroAssembler::Jump mjit::Compiler::addTypeTest(types::TypeSet *types, RegisterID typeReg, RegisterID dataReg) { @@ -7413,11 +7380,32 @@ mjit::Compiler::addTypeTest(types::TypeSet *types, RegisterID typeReg, RegisterI Vector matches(CompilerAllocPolicy(cx, *this)); - if (types->hasType(types::Type::Int32Type())) + if (types->hasType(types::Type::DoubleType())) { + matches.append(masm.testNumber(Assembler::Equal, typeReg)); + } else if (types->hasType(types::Type::Int32Type())) { matches.append(masm.testInt32(Assembler::Equal, typeReg)); - if (types->hasType(types::Type::DoubleType())) - matches.append(masm.testDouble(Assembler::Equal, typeReg)); + /* Generate a path to try coercing doubles to integers in place. */ + Jump notDouble = masm.testDouble(Assembler::NotEqual, typeReg); + + FPRegisterID fpTemp = frame.getScratchFPReg(); + masm.moveDoubleRegisters(dataReg, typeReg, frame.addressOfTop(), fpTemp); + + JumpList isDouble; + masm.branchConvertDoubleToInt32(fpTemp, dataReg, isDouble, Registers::FPConversionTemp); + masm.move(ImmType(JSVAL_TYPE_INT32), typeReg); + + frame.restoreScratchFPReg(fpTemp); + + matches.append(masm.jump()); + + isDouble.linkTo(masm.label(), &masm); + + masm.breakDouble(fpTemp, typeReg, dataReg); + frame.restoreScratchFPReg(fpTemp); + + notDouble.linkTo(masm.label(), &masm); + } if (types->hasType(types::Type::UndefinedType())) matches.append(masm.testUndefined(Assembler::Equal, typeReg)); @@ -7511,9 +7499,7 @@ mjit::Compiler::testBarrier(RegisterID typeReg, RegisterID dataReg, /* Cannot have type barriers when the result of the operation is already unknown. */ JS_ASSERT(!types->unknown()); - state.jump = trySingleTypeTest(types, typeReg); - if (!state.jump.isSet()) - state.jump.setJump(addTypeTest(types, typeReg, dataReg)); + state.jump.setJump(addTypeTest(types, typeReg, dataReg)); return state; } diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 2b81037d986f..54e71a63d0e0 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -556,7 +556,6 @@ private: RegisterID dataReg; }; - MaybeJump trySingleTypeTest(types::TypeSet *types, RegisterID typeReg); Jump addTypeTest(types::TypeSet *types, RegisterID typeReg, RegisterID dataReg); BarrierState pushAddressMaybeBarrier(Address address, JSValueType type, bool reuseBase, bool testUndefined = false); diff --git a/js/src/methodjit/FastOps.cpp b/js/src/methodjit/FastOps.cpp index 47b053f46b86..8b44ef7295ba 100644 --- a/js/src/methodjit/FastOps.cpp +++ b/js/src/methodjit/FastOps.cpp @@ -1974,7 +1974,6 @@ mjit::Compiler::jsop_getelem_typed(int atype) frame.popn(2); - BarrierState barrier; if (dataReg.isFPReg()) { frame.pushDouble(dataReg.fpreg()); } else if (typeReg.isSet()) { @@ -1985,8 +1984,6 @@ mjit::Compiler::jsop_getelem_typed(int atype) } stubcc.rejoin(Changes(2)); - finishBarrier(barrier, REJOIN_FALLTHROUGH, 0); - return true; } #endif /* JS_METHODJIT_TYPED_ARRAY */ @@ -2010,7 +2007,12 @@ mjit::Compiler::jsop_getelem(bool isCall) if (cx->typeInferenceEnabled() && id->mightBeType(JSVAL_TYPE_INT32) && !isCall) { types::TypeSet *types = analysis->poppedTypes(PC, 1); if (types->isLazyArguments(cx) && !outerScript->analysis()->modifiesArguments()) { - // Inline arguments path. + // Inline arguments path. This path can access non-canonical args + // if fun->nargs != 0, so we require that the script never has any + // of its arguments directly modified. Note that the canonical and + // formal arg may not be the exact same value if we coerced a + // double actual into an int32 formal from a type barrier at entry, + // but this is ok as the underlying number is the same. jsop_getelem_args(); return true; } diff --git a/js/src/methodjit/FrameState.cpp b/js/src/methodjit/FrameState.cpp index 6b21c72e5cab..431b16828b26 100644 --- a/js/src/methodjit/FrameState.cpp +++ b/js/src/methodjit/FrameState.cpp @@ -195,6 +195,36 @@ FrameState::takeReg(AnyRegisterID reg) } } +JSC::MacroAssembler::FPRegisterID +FrameState::getScratchFPReg() +{ + if (freeRegs.hasRegInMask(Registers::TempFPRegs)) { + FPRegisterID reg = freeRegs.takeAnyReg(Registers::TempFPRegs).fpreg(); + freeRegs.putReg(reg); + return reg; + } + + Registers regs(Registers::TempFPRegs); + FPRegisterID reg; + + do { + reg = regs.takeAnyReg().fpreg(); + } while (!regstate(reg).fe()); + + masm.storeDouble(reg, addressOf(regstate(reg).fe())); + + return reg; +} + +void +FrameState::restoreScratchFPReg(FPRegisterID reg) +{ + if (freeRegs.hasReg(reg)) + return; + + masm.loadDouble(addressOf(regstate(reg).fe()), reg); +} + #ifdef DEBUG const char * FrameState::entryName(const FrameEntry *fe) const diff --git a/js/src/methodjit/FrameState.h b/js/src/methodjit/FrameState.h index 10c97ae52272..6c5c3c0080aa 100644 --- a/js/src/methodjit/FrameState.h +++ b/js/src/methodjit/FrameState.h @@ -613,6 +613,14 @@ class FrameState */ void takeReg(AnyRegisterID reg); + /* + * Gets an FP register which the compiler does not own but can freely write + * over. Can only be used in the inline path, and must be matched with a + * restoreScratchFPReg. (Emits sync/restore code if the reg is in use). + */ + FPRegisterID getScratchFPReg(); + void restoreScratchFPReg(FPRegisterID reg); + /* * Returns a FrameEntry * for a slot on the operation stack. */ diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index 4f18d22b70b6..27a50bbe90b2 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -2367,7 +2367,7 @@ stubs::TypeBarrierHelper(VMFrame &f, uint32 which) void JS_FASTCALL stubs::StubTypeHelper(VMFrame &f, int32 which) { - const Value &result = f.regs.sp[which]; + Value &result = f.regs.sp[which]; if (f.script()->hasAnalysis() && f.script()->analysis()->ranInference()) { AutoEnterTypeInference enter(f.cx);