diff --git a/js/src/jit-test/tests/basic/testBug634590ma.js b/js/src/jit-test/tests/basic/testBug634590ma.js new file mode 100644 index 00000000000..413f3a2fc78 --- /dev/null +++ b/js/src/jit-test/tests/basic/testBug634590ma.js @@ -0,0 +1,14 @@ +// |jit-test| mjitalways; + +this.name = "outer"; +var sb = evalcx(''); +sb.name = "inner"; +sb.parent = this; +function f() { return this.name; } +assertEq(evalcx('this.f = parent.f;\n' + + 'var s = "";\n' + + 'for (i = 0; i < 10; ++i)\n' + + ' s += f();\n' + + 's', + sb), + "innerinnerinnerinnerinnerinnerinnerinnerinnerinner"); diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index f2c694008b6..5d7769296b2 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -1889,7 +1889,7 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_CALLGNAME) jsop_getgname(fullAtomIndex(PC)); if (op == JSOP_CALLGNAME) - frame.push(UndefinedValue()); + jsop_callgname_epilogue(); END_CASE(JSOP_GETGNAME) BEGIN_CASE(JSOP_SETGNAME) @@ -4368,6 +4368,77 @@ mjit::Compiler::jsop_getgname(uint32 index) #endif } +/* + * Generate just the epilogue code that is specific to callgname. The rest + * is shared with getgname. + */ +void +mjit::Compiler::jsop_callgname_epilogue() +{ + /* + * This slow path does the same thing as the interpreter. + */ + if (!script->compileAndGo) { + prepareStubCall(Uses(1)); + INLINE_STUBCALL(stubs::PushImplicitThisForGlobal); + frame.pushSynced(); + return; + } + + /* Fast path for known-not-an-object callee. */ + FrameEntry *fval = frame.peek(-1); + if (fval->isNotType(JSVAL_TYPE_OBJECT)) { + frame.push(UndefinedValue()); + return; + } + + /* + * Optimized version. This inlines the common case, calling a + * (non-proxied) function that has the same global as the current + * script. To make the code simpler, we: + * 1. test the stronger property that the callee's parent is + * equal to the global of the current script, and + * 2. bake in the global of the current script, which is why + * this optimized path requires compile-and-go. + */ + + /* If the callee is not an object, jump to the inline fast path. */ + MaybeRegisterID typeReg = frame.maybePinType(fval); + RegisterID objReg = frame.copyDataIntoReg(fval); + + MaybeJump isNotObj; + if (!fval->isType(JSVAL_TYPE_OBJECT)) { + isNotObj = frame.testObject(Assembler::NotEqual, fval); + frame.maybeUnpinReg(typeReg); + } + + /* + * If the callee is not a function, jump to OOL slow path. + */ + Jump notFunction = masm.testFunction(Assembler::NotEqual, objReg); + stubcc.linkExit(notFunction, Uses(1)); + + /* + * If the callee's parent is not equal to the global, jump to + * OOL slow path. + */ + masm.loadPtr(Address(objReg, offsetof(JSObject, parent)), objReg); + Jump globalMismatch = masm.branchPtr(Assembler::NotEqual, objReg, ImmPtr(globalObj)); + stubcc.linkExit(globalMismatch, Uses(1)); + frame.freeReg(objReg); + + /* OOL stub call path. */ + stubcc.leave(); + OOL_STUBCALL(stubs::PushImplicitThisForGlobal); + + /* Fast path. */ + if (isNotObj.isSet()) + isNotObj.getJump().linkTo(masm.label(), &masm); + frame.pushUntypedValue(UndefinedValue()); + + stubcc.rejoin(Changes(1)); +} + void mjit::Compiler::jsop_setgname_slow(JSAtom *atom, bool usePropertyCache) { diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 8ac24d764ce..95d807497f5 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -446,6 +446,7 @@ class Compiler : public BaseCompiler void jsop_eleminc(JSOp op, VoidStub); void jsop_getgname(uint32 index); void jsop_getgname_slow(uint32 index); + void jsop_callgname_epilogue(); void jsop_setgname(JSAtom *atom, bool usePropertyCache); void jsop_setgname_slow(JSAtom *atom, bool usePropertyCache); void jsop_bindgname(); diff --git a/js/src/methodjit/FrameState-inl.h b/js/src/methodjit/FrameState-inl.h index 2d4fb7ddbf6..1b8c335c780 100644 --- a/js/src/methodjit/FrameState-inl.h +++ b/js/src/methodjit/FrameState-inl.h @@ -375,6 +375,26 @@ FrameState::pushUntypedPayload(JSValueType type, RegisterID payload) regstate[payload].associate(fe, RematInfo::DATA); } +inline void +FrameState::pushUntypedValue(Value &v) +{ + FrameEntry *fe = rawPush(); + + fe->clear(); + + masm.storeValue(v, addressOf(fe)); + + /* The forceful type sync will assert otherwise. */ +#ifdef DEBUG + fe->type.unsync(); +#endif + fe->type.setMemory(); + fe->data.unsync(); + fe->data.setMemory(); + fe->setNotCopied(); + fe->setCopyOf(NULL); +} + inline JSC::MacroAssembler::RegisterID FrameState::tempRegForType(FrameEntry *fe, RegisterID fallback) { diff --git a/js/src/methodjit/FrameState.h b/js/src/methodjit/FrameState.h index 15d5f7ff0c6..a323abe1ad2 100644 --- a/js/src/methodjit/FrameState.h +++ b/js/src/methodjit/FrameState.h @@ -288,6 +288,13 @@ class FrameState */ inline void pushUntypedPayload(JSValueType type, RegisterID payload); + /* + * Pushes a value onto the operation stack. This must be used when the + * value is known, but its type cannot be propagated because it is not + * known to be correct at a slow-path merge point. + */ + inline void pushUntypedValue(Value &value); + /* * Pushes a number onto the operation stack. * diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index e077cdb5142..5741f298c2e 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -319,6 +319,16 @@ stubs::SetGlobalName(VMFrame &f, JSAtom *atom) template void JS_FASTCALL stubs::SetGlobalName(VMFrame &f, JSAtom *atom); template void JS_FASTCALL stubs::SetGlobalName(VMFrame &f, JSAtom *atom); +static inline void +PushImplicitThis(VMFrame &f, JSObject *obj, Value &rval) +{ + Value thisv; + + if (!ComputeImplicitThis(f.cx, obj, rval, &thisv)) + return; + *f.regs.sp++ = thisv; +} + static JSObject * NameOp(VMFrame &f, JSObject *obj, bool callname = false) { @@ -377,13 +387,9 @@ NameOp(VMFrame &f, JSObject *obj, bool callname = false) *f.regs.sp++ = rval; - if (callname) { - Value thisv; + if (callname) + PushImplicitThis(f, obj, rval); - if (!ComputeImplicitThis(cx, obj, rval, &thisv)) - return NULL; - *f.regs.sp++ = thisv; - } return obj; } @@ -579,6 +585,17 @@ stubs::CallName(VMFrame &f) THROW(); } +/* + * Push the implicit this value, with the assumption that the callee + * (which is on top of the stack) was read as a property from the + * global object. + */ +void JS_FASTCALL +stubs::PushImplicitThisForGlobal(VMFrame &f) +{ + return PushImplicitThis(f, f.fp()->scopeChain().getGlobal(), f.regs.sp[-1]); +} + void JS_FASTCALL stubs::BitOr(VMFrame &f) { diff --git a/js/src/methodjit/StubCalls.h b/js/src/methodjit/StubCalls.h index 6a0cc859751..a58436b0cd0 100644 --- a/js/src/methodjit/StubCalls.h +++ b/js/src/methodjit/StubCalls.h @@ -136,6 +136,7 @@ void JS_FASTCALL CallElem(VMFrame &f); template void JS_FASTCALL SetElem(VMFrame &f); void JS_FASTCALL Length(VMFrame &f); void JS_FASTCALL CallName(VMFrame &f); +void JS_FASTCALL PushImplicitThisForGlobal(VMFrame &f); void JS_FASTCALL GetUpvar(VMFrame &f, uint32 index); void JS_FASTCALL GetGlobalName(VMFrame &f);