Bug 602129 - JM: make f.call(...) fast, part 2 (r=dvander)
authorLuke Wagner <lw@mozilla.com>
Fri, 15 Oct 2010 19:11:51 -0700
changeset 57713 d1bf74046ba7970addc532c8c9e9cc9a24a4a619
parent 57712 2f3a0ac5e25129ccea55b42344eb1f4e29e52801
child 57714 e77069ddab0064b2e3af06e0d37b90492f1b7d79
push idunknown
push userunknown
push dateunknown
reviewersdvander
bugs602129
milestone2.0b8pre
Bug 602129 - JM: make f.call(...) fast, part 2 (r=dvander)
js/src/assembler/assembler/AbstractMacroAssembler.h
js/src/jit-test/tests/basic/testCallApply.js
js/src/jsfun.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FrameState-inl.h
js/src/methodjit/FrameState.h
--- a/js/src/assembler/assembler/AbstractMacroAssembler.h
+++ b/js/src/assembler/assembler/AbstractMacroAssembler.h
@@ -76,16 +76,18 @@ public:
         TimesFour,
         TimesEight
     };
 
     // Address:
     //
     // Describes a simple base-offset address.
     struct Address {
+        explicit Address() {}
+
         explicit Address(RegisterID base, int32_t offset = 0)
             : base(base)
             , offset(offset)
         {
         }
 
         RegisterID base;
         int32_t offset;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testCallApply.js
@@ -0,0 +1,168 @@
+function script1() { return arguments.length; }
+function script2(x) { return x; }
+function script3(x) { var o = arguments; return o[0]; }
+function genClosure() { var x = 3; eval("x = 4"); return function(y) { return x + y } };
+var closed1 = genClosure();
+var closed2 = genClosure();
+var closed3 = genClosure();
+var native1 = String.prototype.search;
+var native2 = String.prototype.match;
+var tricky1 = { call:function(x,y) { return y }, apply:function(x,y) { return y } };
+
+test0();
+test1();
+test2();
+test3();
+
+function test0() {
+    assertEq(script1.call(null), 0);
+    assertEq(script1.call(null, 1), 1);
+    assertEq(script1.call(null, 1,2), 2);
+    assertEq(native1.call("aabc", /b/), 2);
+    assertEq(native1.call("abc"), -1);
+    assertEq(tricky1.call(null, 9), 9);
+    assertEq(script1.apply(null), 0);
+    assertEq(script1.apply(null, [1]), 1);
+    assertEq(script1.apply(null, [1,2]), 2);
+    assertEq(native1.apply("aabc", [/b/]), 2);
+    assertEq(native1.apply("abc"), -1);
+    assertEq(tricky1.apply(null, 1), 1);
+}
+test0();
+
+function test1() {
+    function f(arr) {
+        for (var i = 0; i < 10; ++i) {
+            for (var j = 0; j < arr.length; ++j) {
+                arr[j].call('a');
+                arr[j].apply('a', []);
+                var arg0 = [];
+                arr[j].apply('a', arg0);
+                (function() { arr[j].apply('a', arguments); })();
+
+                arr[j].call('a', 1);
+                arr[j].apply('a', [1]);
+                var arg0 = [1];
+                arr[j].apply('a', arg0);
+                (function() { arr[j].apply('a', arguments); })(1);
+
+                arr[j].call('a', 1,'g');
+                arr[j].apply('a', [1,'g']);
+                var arg0 = [1,'g'];
+                arr[j].apply('a', arg0);
+                (function() { arr[j].apply('a', arguments); })(1,'g');
+
+                arr[j].call('a', 1,'g',3,4,5,6,7,8,9);
+                arr[j].apply('a', [1,'g',3,4,5,6,7,8,9]);
+                var arg0 = [1,'g',3,4,5,6,7,8,9];
+                arr[j].apply('a', arg0);
+                (function() { arr[j].apply('a', arguments); })(1,'g',3,4,5,6,7,8,9);
+            }
+        }
+    }
+
+    f([script1, script1, script1, script1, script2, script2, script1, script2]);
+    f([script1, script2, script3, script1, script2, script3, script3, script3]);
+    f([script1, script2, script2, script2, script2, script3, script1, script2]);
+    f([script1, script1, script1, native1, native1, native1, native1, script1]);
+    f([native1, native1, native1, native2, native2, native2, native2, native1]);
+    f([native1, native2, native1, native2, native1, native2, native1, native2]);
+    f([native1, native1, native1, script1, script2, script2, native1, script3]);
+    f([closed1, closed1, closed1, closed2, closed2, closed2, script3, script3]);
+    f([closed1, closed2, closed1, closed2, closed1, closed2, closed1, closed2]);
+    f([closed1, closed2, closed3, closed1, closed2, closed3, script1, script2]);
+    f([closed1, closed1, closed1, closed2, closed2, closed2, native1, native2]);
+    f([closed1, closed1, closed1, closed2, closed2, closed2, native1, native2]);
+    f([native1, native1, native1, closed1, closed2, script1, script2, native2]);
+}
+
+// test things that break our speculation
+function test2() {
+    var threw = false;
+    try {
+        (3).call(null, 1,2);
+    } catch (e) {
+        threw = true;
+    }
+    assertEq(threw, true);
+
+    var threw = false;
+    try {
+        (3).apply(null, [1,2]);
+    } catch (e) {
+        threw = true;
+    }
+    assertEq(threw, true);
+
+    var threw = false;
+    try {
+        var arr = [1,2];
+        (3).apply(null, arr);
+    } catch (e) {
+        threw = true;
+    }
+    assertEq(threw, true);
+
+    function tryAndFail(o) {
+        var threw = false;
+        try {
+            o.call(null, 1,2);
+        } catch(e) {
+            threw = true;
+        }
+        assertEq(threw, true);
+        threw = false;
+        try {
+            o.apply(null, [1,2]);
+        } catch(e) {
+            threw = true;
+        }
+        assertEq(threw, true);
+    }
+
+    tryAndFail(1);
+    tryAndFail({});
+    tryAndFail({call:{}, apply:{}});
+    tryAndFail({call:function() { throw "not js_fun_call"}, apply:function(){ throw "not js_fun_apply" }});
+}
+
+// hit the stubs::CompileFunction path
+function test3() {
+    function genFreshFunction(s) { return new Function(s, "return " + s); }
+
+    function callIt(f) {
+        assertEq(f.call(null, 1,2), 1);
+    }
+    callIt(script2); callIt(script2); callIt(script2); callIt(script2);
+    callIt(genFreshFunction("x"));
+    callIt(genFreshFunction("y"));
+    callIt(genFreshFunction("z"));
+
+    function applyIt(f) {
+        var arr = [1,2];
+        assertEq(f.apply(null, arr), 1);
+    }
+    applyIt(script2); applyIt(script2); applyIt(script2); applyIt(script2);
+    applyIt(genFreshFunction("x"));
+    applyIt(genFreshFunction("y"));
+    applyIt(genFreshFunction("z"));
+
+    function applyIt1(f) {
+        function g() {
+            assertEq(f.apply(null, arguments), 1);
+        }
+        g(1,2);
+    }
+    applyIt1(script2); applyIt1(script2); applyIt1(script2); applyIt1(script2);
+    applyIt1(genFreshFunction("x"));
+    applyIt1(genFreshFunction("y"));
+    applyIt1(genFreshFunction("z"));
+
+    function applyIt2(f) {
+        assertEq(f.apply(null, [1,2]), 1);
+    }
+    applyIt2(script2); applyIt2(script2); applyIt2(script2); applyIt2(script2);
+    applyIt2(genFreshFunction("x"));
+    applyIt2(genFreshFunction("y"));
+    applyIt2(genFreshFunction("z"));
+}
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -146,31 +146,32 @@ struct JSFunction : public JSObject_Slot
     union U {
         struct {
             js::Native  native;   /* native method pointer or null */
             js::Class   *clasp;   /* class of objects constructed
                                      by this function */
             JSNativeTraceInfo *trcinfo;
         } n;
         struct Scripted {
+            JSScript    *script;  /* interpreted bytecode descriptor or null */
             uint16      nvars;    /* number of local variables */
             uint16      nupvars;  /* number of upvars (computable from script
                                      but here for faster access) */
             uint16       skipmin; /* net skip amount up (toward zero) from
                                      script->staticLevel to nearest upvar,
                                      including upvars in nested functions */
             JSPackedBool wrapper; /* true if this function is a wrapper that
                                      rewrites bytecode optimized for a function
                                      judged non-escaping by the compiler, which
                                      then escaped via the debugger or a rogue
                                      indirect eval; if true, then this function
                                      object's proto is the wrapped object */
-            JSScript    *script;  /* interpreted bytecode descriptor or null */
             js::Shape   *names;   /* argument and variable names */
         } i;
+        void            *nativeOrScript;
     } u;
     JSAtom          *atom;        /* name for diagnostics and decompiling */
 
     bool optimizedClosure()  const { return FUN_KIND(this) > JSFUN_INTERPRETED; }
     bool needsWrapper()      const { return FUN_NULL_CLOSURE(this) && u.i.skipmin != 0; }
     bool isInterpreted()     const { return FUN_INTERPRETED(this); }
     bool isNative()          const { return !FUN_INTERPRETED(this); }
     bool isConstructor()     const { return flags & JSFUN_CONSTRUCTOR; }
@@ -303,16 +304,22 @@ struct JSFunction : public JSObject_Slot
         return isInterpreted() ? NULL : u.n.native;
     }
 
     JSScript *script() const {
         JS_ASSERT(isInterpreted());
         return u.i.script;
     }
 
+    static uintN offsetOfNativeOrScript() {
+        JS_STATIC_ASSERT(offsetof(U, n.native) == offsetof(U, i.script));
+        JS_STATIC_ASSERT(offsetof(U, n.native) == offsetof(U, nativeOrScript));
+        return offsetof(JSFunction, u.nativeOrScript);
+    }
+
     /* Number of extra fixed function object slots. */
     static const uint32 CLASS_RESERVED_SLOTS = JSObject::FUN_CLASS_RESERVED_SLOTS;
 };
 
 /*
  * Trace-annotated native. This expands to a JSFunctionSpec initializer (like
  * JS_FN in jsapi.h). fastcall is a FastNative; trcinfo is a
  * JSNativeTraceInfo*.
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -60,17 +60,32 @@
 #include "jsautooplen.h"
 
 using namespace js;
 using namespace js::mjit;
 #if defined(JS_POLYIC) || defined(JS_MONOIC)
 using namespace js::mjit::ic;
 #endif
 
-#define ADD_CALLSITE(stub) if (debugMode) addCallSite(__LINE__, (stub))
+/* This macro should be used after stub calls (which automatically set callLabel). */
+#define ADD_CALLSITE(stub)                                                    \
+    if (debugMode) addCallSite(__LINE__, (stub))
+
+/* For custom calls/jumps, this macro sets callLabel before adding the callsite. */
+#if (defined(JS_NO_FASTCALL) && defined(JS_CPU_X86)) || defined(_WIN64)
+# define ADD_NON_STUB_CALLSITE(stub)                                          \
+    if (stub)                                                                 \
+        stubcc.masm.callLabel = stubcc.masm.label()                           \
+    else                                                                      \
+        masm.callLabel = masm.label();                                        \
+    ADD_CALLSITE(stub)
+#else
+# define ADD_NON_STUB_CALLSITE(stub)                                          \
+    ADD_CALLSITE(stub)
+#endif
 
 #define RETURN_IF_OOM(retval)                                   \
     JS_BEGIN_MACRO                                              \
         if (oomInVector || masm.oom() || stubcc.masm.oom()) {   \
             js_ReportOutOfMemory(cx);                           \
             return retval;                                      \
         }                                                       \
     JS_END_MACRO
@@ -585,17 +600,18 @@ mjit::Compiler::finishThisUp(JITScript *
             stubCode.patch(traceICs[i].addrLabel, &scriptTICs[i]);
         }
     }
 #endif /* JS_MONOIC */
 
     for (size_t i = 0; i < callPatches.length(); i++) {
         CallPatchInfo &patch = callPatches[i];
 
-        fullCode.patch(patch.fastNcodePatch, fullCode.locationOf(patch.joinPoint));
+        if (patch.hasFastNcode)
+            fullCode.patch(patch.fastNcodePatch, fullCode.locationOf(patch.joinPoint));
         if (patch.hasSlowNcode)
             stubCode.patch(patch.slowNcodePatch, fullCode.locationOf(patch.joinPoint));
     }
 
 #if defined JS_POLYIC
     jit->nGetElems = getElemICs.length();
     if (getElemICs.length()) {
         jit->getElems = (ic::GetElementIC *)cursor;
@@ -2281,133 +2297,242 @@ mjit::Compiler::interruptCheckHelper()
 
     frame.freeReg(reg);
 }
 
 void
 mjit::Compiler::emitUncachedCall(uint32 argc, bool callingNew)
 {
     CallPatchInfo callPatch;
-    callPatch.hasSlowNcode = false;
 
     RegisterID r0 = Registers::ReturnReg;
     VoidPtrStubUInt32 stub = callingNew ? stubs::UncachedNew : stubs::UncachedCall;
 
     frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2));
     prepareStubCall(Uses(argc + 2));
     masm.move(Imm32(argc), Registers::ArgReg1);
     stubCall(stub);
     ADD_CALLSITE(false);
 
     Jump notCompiled = masm.branchTestPtr(Assembler::Zero, r0, r0);
 
     masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg);
+    callPatch.hasFastNcode = true;
     callPatch.fastNcodePatch =
         masm.storePtrWithPatch(ImmPtr(NULL),
                                Address(JSFrameReg, JSStackFrame::offsetOfncode()));
 
     masm.jump(r0);
-
-#if (defined(JS_NO_FASTCALL) && defined(JS_CPU_X86)) || defined(_WIN64)
-    masm.callLabel = masm.label();
-#endif
-    ADD_CALLSITE(false);
+    ADD_NON_STUB_CALLSITE(false);
 
     callPatch.joinPoint = masm.label();
     masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfPrev()), JSFrameReg);
 
     frame.popn(argc + 2);
     frame.takeReg(JSReturnReg_Type);
     frame.takeReg(JSReturnReg_Data);
     frame.pushRegs(JSReturnReg_Type, JSReturnReg_Data);
 
     stubcc.linkExitDirect(notCompiled, stubcc.masm.label());
     stubcc.rejoin(Changes(0));
     callPatches.append(callPatch);
 }
 
+static bool
+IsLowerableFunCall(jsbytecode *pc)
+{
+#ifdef JS_MONOIC
+    return *pc == JSOP_FUNCALL && GET_ARGC(pc) >= 1;
+#else
+    return false;
+#endif
+}
+
+void
+mjit::Compiler::checkCallSpeculation(uint32 argc, FrameEntry *origCallee, FrameEntry *origThis,
+                                     MaybeRegisterID origCalleeType, RegisterID origCalleeData,
+                                     MaybeRegisterID origThisType, RegisterID origThisData,
+                                     Jump *uncachedCallSlowRejoin, CallPatchInfo *uncachedCallPatch)
+{
+    JS_ASSERT(IsLowerableFunCall(PC));
+
+    /*
+     * if (origCallee.isObject() &&
+     *     origCallee.toObject().isFunction &&
+     *     origCallee.toObject().getFunctionPrivate() == js_fun_call)
+     */
+    MaybeJump isObj;
+    if (origCalleeType.isSet())
+        isObj = masm.testObject(Assembler::NotEqual, origCalleeType.reg());
+    Jump isFun = masm.testFunction(Assembler::NotEqual, origCalleeData);
+    masm.loadFunctionPrivate(origCalleeData, origCalleeData);
+    Jump isNative = masm.branchPtr(Assembler::NotEqual,
+                                   Address(origCalleeData, JSFunction::offsetOfNativeOrScript()),
+                                   ImmPtr(JS_FUNC_TO_DATA_PTR(void *, js_fun_call)));
+
+    /*
+     * If speculation fails, we can't use the ic, since it is compiled on the
+     * assumption that speculation succeeds. Instead, just do an uncached call.
+     */
+    {
+        if (isObj.isSet())
+            stubcc.linkExitDirect(isObj.getJump(), stubcc.masm.label());
+        stubcc.linkExitDirect(isFun, stubcc.masm.label());
+        stubcc.linkExitDirect(isNative, stubcc.masm.label());
+
+        stubcc.masm.move(Imm32(argc), Registers::ArgReg1);
+        stubcc.call(stubs::UncachedCall);
+        ADD_CALLSITE(true);
+
+        RegisterID r0 = Registers::ReturnReg;
+        Jump notCompiled = stubcc.masm.branchTestPtr(Assembler::Zero, r0, r0);
+
+        stubcc.masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg);
+        Address ncodeAddr(JSFrameReg, JSStackFrame::offsetOfncode());
+        uncachedCallPatch->hasSlowNcode = true;
+        uncachedCallPatch->slowNcodePatch = stubcc.masm.storePtrWithPatch(ImmPtr(NULL), ncodeAddr);
+
+        stubcc.masm.jump(r0);
+        ADD_NON_STUB_CALLSITE(true);
+
+        notCompiled.linkTo(stubcc.masm.label(), &stubcc.masm);
+
+        /*
+         * inlineCallHelper will link uncachedCallSlowRejoin to the join point
+         * at the end of the ic. At that join point, the return value of the
+         * call is assumed to be in registers, so load them before jumping.
+         */
+        JaegerSpew(JSpew_Insns, " ---- BEGIN SLOW RESTORE CODE ---- \n");
+        Address rval = frame.addressOf(origCallee);  /* vp[0] == rval */
+        stubcc.masm.loadValueAsComponents(rval, JSReturnReg_Type, JSReturnReg_Data);
+        *uncachedCallSlowRejoin = stubcc.masm.jump();
+        JaegerSpew(JSpew_Insns, " ---- END SLOW RESTORE CODE ---- \n");
+    }
+}
+
 /* See MonoIC.cpp, CallCompiler for more information on call ICs. */
 void
 mjit::Compiler::inlineCallHelper(uint32 argc, bool callingNew)
 {
     /* Check for interrupts on function call */
     interruptCheckHelper();
 
-    // |thisv| does not need to be synced for constructing.
+    FrameEntry *origCallee = frame.peek(-int(argc + 2));
+    FrameEntry *origThis = frame.peek(-int(argc + 1));
+
+    /* 'this' does not need to be synced for constructing. */
     if (callingNew)
-        frame.discardFe(frame.peek(-int(argc + 1)));
-
-    FrameEntry *fe = frame.peek(-int(argc + 2));
-
-    /* Currently, we don't support constant functions. */
-    if (fe->isConstant() || fe->isNotType(JSVAL_TYPE_OBJECT) || debugMode) {
+        frame.discardFe(origThis);
+
+    /*
+     * Currently, constant values are not functions, so don't even try to
+     * optimize. This lets us assume that callee/this have regs below.
+     */
+#ifdef JS_MONOIC
+    if (origCallee->isConstant() || origCallee->isNotType(JSVAL_TYPE_OBJECT) || debugMode) {
+#endif
         emitUncachedCall(argc, callingNew);
         return;
+#ifdef JS_MONOIC
     }
 
-#ifdef JS_MONOIC
-    CallGenInfo callIC(argc);
-    CallPatchInfo callPatch;
-
     /*
-     * Save constant |this| to optimize thisv stores for common call cases
-     * like CALL[LOCAL, GLOBAL, ARG] which push NULL.
+     * From the presence of JSOP_FUNCALL, we speculate that we are going to
+     * call js_fun_call. Normally, this call would go through js::Invoke to
+     * ultimately call 'this'. We can do much better by having the callIC cache
+     * and call 'this' directly.
      */
-    callIC.pc = PC;
-    callIC.frameDepth = frame.frameDepth();
-
-    /* Grab type and data registers up-front. */
-    MaybeRegisterID typeReg, maybeDataReg;
-    frame.ensureFullRegs(fe, &typeReg, &maybeDataReg);
-    RegisterID dataReg = maybeDataReg.reg();
-    if (!fe->isTypeKnown())
-        frame.pinReg(typeReg.reg());
-    frame.pinReg(dataReg);
-
-    /*
-     * We rely on the fact that syncAndKill() is not allowed to touch the
-     * registers we've preserved.
-     */
-    frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2));
-    frame.unpinKilledReg(dataReg);
-    if (typeReg.isSet())
-        frame.unpinKilledReg(typeReg.reg());
-
-    Registers tempRegs;
+    bool lowerFunCall = IsLowerableFunCall(PC);
+
+    /* Initialized by both branches below. */
+    CallGenInfo     callIC(PC);
+    CallPatchInfo   callPatch;
+    MaybeRegisterID icCalleeType; /* type to test for function-ness */
+    RegisterID      icCalleeData; /* data to call */
+    Address         icRvalAddr;   /* return slot on slow-path rejoin */
+
+    /* Initialized only on lowerFunCall branch. */
+    Jump            uncachedCallSlowRejoin;
+    CallPatchInfo   uncachedCallPatch;
+
+    {
+        MaybeRegisterID origCalleeType, maybeOrigCalleeData;
+        RegisterID origCalleeData;
+
+        /* Get the callee in registers. */
+        frame.ensureFullRegs(origCallee, &origCalleeType, &maybeOrigCalleeData);
+        origCalleeData = maybeOrigCalleeData.reg();
+        PinRegAcrossSyncAndKill p1(frame, origCalleeData), p2(frame, origCalleeType);
+
+        if (lowerFunCall) {
+            MaybeRegisterID origThisType, maybeOrigThisData;
+            RegisterID origThisData;
+            {
+                /* Get thisv in registers. */
+                frame.ensureFullRegs(origThis, &origThisType, &maybeOrigThisData);
+                origThisData = maybeOrigThisData.reg();
+                PinRegAcrossSyncAndKill p3(frame, origThisData), p4(frame, origThisType);
+
+                /* Leaves pinned regs untouched. */
+                frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2));
+            }
+
+            checkCallSpeculation(argc,origCallee, origThis,
+                                 origCalleeType, origCalleeData,
+                                 origThisType, origThisData,
+                                 &uncachedCallSlowRejoin, &uncachedCallPatch);
+
+            icCalleeType = origThisType;
+            icCalleeData = origThisData;
+            icRvalAddr = frame.addressOf(origThis);
+            callIC.argc = argc - 1;
+            callIC.frameDepth = frame.frameDepth();
+        } else {
+            /* Leaves pinned regs untouched. */
+            frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2));
+
+            icCalleeType = origCalleeType;
+            icCalleeData = origCalleeData;
+            icRvalAddr = frame.addressOf(origCallee);
+            callIC.argc = argc;
+            callIC.frameDepth = frame.frameDepth();
+        }
+    }
 
     /* Test the type if necessary. Failing this always takes a really slow path. */
     MaybeJump notObjectJump;
-    if (typeReg.isSet())
-        notObjectJump = masm.testObject(Assembler::NotEqual, typeReg.reg());
-
-    tempRegs.takeReg(dataReg);
+    if (icCalleeType.isSet())
+        notObjectJump = masm.testObject(Assembler::NotEqual, icCalleeType.reg());
+
+    Registers tempRegs;
+    tempRegs.takeReg(icCalleeData);
     RegisterID t0 = tempRegs.takeAnyReg();
     RegisterID t1 = tempRegs.takeAnyReg();
 
     /*
      * Guard on the callee identity. This misses on the first run. If the
      * callee is scripted, compiled/compilable, and argc == nargs, then this
      * guard is patched, and the compiled code address is baked in.
      */
-    Jump j = masm.branchPtrWithPatch(Assembler::NotEqual, dataReg, callIC.funGuard);
+    Jump j = masm.branchPtrWithPatch(Assembler::NotEqual, icCalleeData, callIC.funGuard);
     callIC.funJump = j;
 
     Jump rejoin1, rejoin2;
     {
         stubcc.linkExitDirect(j, stubcc.masm.label());
         callIC.slowPathStart = stubcc.masm.label();
 
         /*
          * Test if the callee is even a function. If this doesn't match, we
          * take a _really_ slow path later.
          */
-        Jump notFunction = stubcc.masm.testFunction(Assembler::NotEqual, dataReg);
+        Jump notFunction = stubcc.masm.testFunction(Assembler::NotEqual, icCalleeData);
 
         /* Test if the function is scripted. */
-        stubcc.masm.loadFunctionPrivate(dataReg, t0);
+        stubcc.masm.loadFunctionPrivate(icCalleeData, t0);
         stubcc.masm.load16(Address(t0, offsetof(JSFunction, flags)), t1);
         stubcc.masm.and32(Imm32(JSFUN_KINDMASK), t1);
         Jump isNative = stubcc.masm.branch32(Assembler::Below, t1, Imm32(JSFUN_INTERPRETED));
 
         /*
          * No-op jump that gets re-patched. This is so ArgReg1 won't be
          * clobbered, with the added bonus that the generated stub doesn't
          * need to pop its own return address.
@@ -2415,30 +2540,30 @@ mjit::Compiler::inlineCallHelper(uint32 
         Jump toPatch = stubcc.masm.jump();
         toPatch.linkTo(stubcc.masm.label(), &stubcc.masm);
         callIC.oolJump = toPatch;
 
         /* At this point the function is definitely scripted. Call the link routine. */
         callIC.addrLabel1 = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1);
         callIC.oolCall = stubcc.call(callingNew ? ic::New : ic::Call);
 
-        callIC.funObjReg = dataReg;
+        callIC.funObjReg = icCalleeData;
         callIC.funPtrReg = t0;
 
         /*
          * The IC call either returns NULL, meaning call completed, or a
          * function pointer to jump to. Caveat: Must restore JSFrameReg
          * because a new frame has been pushed.
          *
          * This function only executes once. If hit, it will generate a stub
          * to compile and execute calls on demand.
          */
         rejoin1 = stubcc.masm.branchTestPtr(Assembler::Zero, Registers::ReturnReg,
                                             Registers::ReturnReg);
-        stubcc.masm.move(Imm32(argc), JSParamReg_Argc);
+        stubcc.masm.move(Imm32(callIC.argc), JSParamReg_Argc);
         stubcc.masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg);
         callPatch.hasSlowNcode = true;
         callPatch.slowNcodePatch =
             stubcc.masm.storePtrWithPatch(ImmPtr(NULL),
                                           Address(JSFrameReg, JSStackFrame::offsetOfncode()));
         stubcc.masm.jump(Registers::ReturnReg);
 
         /* Catch-all case, for natives this will turn into a MIC. */
@@ -2459,36 +2584,51 @@ mjit::Compiler::inlineCallHelper(uint32 
      */
     callIC.hotPathLabel = masm.label();
 
     uint32 flags = 0;
     if (callingNew)
         flags |= JSFRAME_CONSTRUCTING;
 
     InlineFrameAssembler inlFrame(masm, callIC, flags);
+    callPatch.hasFastNcode = true;
     callPatch.fastNcodePatch = inlFrame.assemble(NULL);
 
     callIC.hotJump = masm.jump();
     callIC.joinPoint = callPatch.joinPoint = masm.label();
+    if (lowerFunCall)
+        uncachedCallPatch.joinPoint = callIC.joinPoint;
     masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfPrev()), JSFrameReg);
 
     frame.popn(argc + 2);
     frame.takeReg(JSReturnReg_Type);
     frame.takeReg(JSReturnReg_Data);
     frame.pushRegs(JSReturnReg_Type, JSReturnReg_Data);
 
+    /*
+     * Now that the frame state is set, generate the rejoin path. Note that, if
+     * lowerFunCall, we cannot just call 'stubcc.rejoin' since the return
+     * value has been placed at vp[1] which is not the stack address associated
+     * with frame.peek(-1).
+     */
     callIC.slowJoinPoint = stubcc.masm.label();
     rejoin1.linkTo(callIC.slowJoinPoint, &stubcc.masm);
     rejoin2.linkTo(callIC.slowJoinPoint, &stubcc.masm);
-    stubcc.rejoin(Changes(0));
+    JaegerSpew(JSpew_Insns, " ---- BEGIN SLOW RESTORE CODE ---- \n");
+    stubcc.masm.loadValueAsComponents(icRvalAddr, JSReturnReg_Type, JSReturnReg_Data);
+    stubcc.crossJump(stubcc.masm.jump(), masm.label());
+    JaegerSpew(JSpew_Insns, " ---- END SLOW RESTORE CODE ---- \n");
+
+    if (lowerFunCall)
+        stubcc.crossJump(uncachedCallSlowRejoin, masm.label());
 
     callICs.append(callIC);
     callPatches.append(callPatch);
-#else
-    emitUncachedCall(argc, callingNew);
+    if (lowerFunCall)
+        callPatches.append(uncachedCallPatch);
 #endif
 }
 
 /*
  * This function must be called immediately after any instruction which could
  * cause a new JSStackFrame to be pushed and could lead to a new debug trap
  * being set. This includes any API callbacks and any scripted or native call.
  */
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -114,19 +114,17 @@ class Compiler : public BaseCompiler
         MaybeJump slowTraceHint;
 
         TraceGenInfo() : initialized(false) {}
     };
 
     /* InlineFrameAssembler wants to see this. */
   public:
     struct CallGenInfo {
-        CallGenInfo(uint32 argc)
-          : argc(argc)
-        { }
+        CallGenInfo(jsbytecode *pc) : pc(pc) {}
 
         /*
          * These members map to members in CallICInfo. See that structure for
          * more comments.
          */
         jsbytecode   *pc;
         uint32       argc;
         DataLabelPtr funGuard;
@@ -148,19 +146,21 @@ class Compiler : public BaseCompiler
   private:
 #endif
 
     /*
      * Writes of call return addresses which needs to be delayed until the final
      * absolute address of the join point is known.
      */
     struct CallPatchInfo {
+        CallPatchInfo() : hasFastNcode(false), hasSlowNcode(false) {}
         Label joinPoint;
         DataLabelPtr fastNcodePatch;
         DataLabelPtr slowNcodePatch;
+        bool hasFastNcode;
         bool hasSlowNcode;
     };
 
     struct BaseICInfo {
         BaseICInfo(JSOp op) : op(op)
         { }
         Label fastPathStart;
         Label fastPathRejoin;
@@ -349,16 +349,20 @@ class Compiler : public BaseCompiler
     void jsop_this();
     void emitReturn(FrameEntry *fe);
     void emitFinalReturn(Assembler &masm);
     void loadReturnValue(Assembler *masm, FrameEntry *fe);
     void emitReturnValue(Assembler *masm, FrameEntry *fe);
     void dispatchCall(VoidPtrStubUInt32 stub, uint32 argc);
     void interruptCheckHelper();
     void emitUncachedCall(uint32 argc, bool callingNew);
+    void checkCallSpeculation(uint32 argc, FrameEntry *origCallee, FrameEntry *origThis,
+                              MaybeRegisterID origCalleeType, RegisterID origCalleeData,
+                              MaybeRegisterID origThisType, RegisterID origThisData,
+                              Jump *uncachedCallSlowRejoin, CallPatchInfo *uncachedCallPatch);
     void inlineCallHelper(uint32 argc, bool callingNew);
     void fixPrimitiveReturn(Assembler *masm, FrameEntry *fe);
     void jsop_gnameinc(JSOp op, VoidStubAtom stub, uint32 index);
     bool jsop_nameinc(JSOp op, VoidStubAtom stub, uint32 index);
     bool jsop_propinc(JSOp op, VoidStubAtom stub, uint32 index);
     void jsop_eleminc(JSOp op, VoidStub);
     void jsop_getgname(uint32 index);
     void jsop_getgname_slow(uint32 index);
--- a/js/src/methodjit/FrameState-inl.h
+++ b/js/src/methodjit/FrameState-inl.h
@@ -1011,13 +1011,36 @@ FrameState::loadDouble(FrameEntry *fe, F
 }
 
 inline bool
 FrameState::isClosedVar(uint32 slot)
 {
     return closedVars[slot];
 }
 
+class PinRegAcrossSyncAndKill
+{
+    typedef JSC::MacroAssembler::RegisterID RegisterID;
+    FrameState &frame;
+    MaybeRegisterID maybeReg;
+  public:
+    PinRegAcrossSyncAndKill(FrameState &frame, RegisterID reg)
+      : frame(frame), maybeReg(reg)
+    {
+        frame.pinReg(reg);
+    }
+    PinRegAcrossSyncAndKill(FrameState &frame, MaybeRegisterID maybeReg)
+      : frame(frame), maybeReg(maybeReg)
+    {
+        if (maybeReg.isSet())
+            frame.pinReg(maybeReg.reg());
+    }
+    ~PinRegAcrossSyncAndKill() {
+        if (maybeReg.isSet())
+            frame.unpinKilledReg(maybeReg.reg());
+    }
+};
+
 } /* namespace mjit */
 } /* namespace js */
 
 #endif /* include */
 
--- a/js/src/methodjit/FrameState.h
+++ b/js/src/methodjit/FrameState.h
@@ -854,13 +854,15 @@ class FrameState
     mutable ImmutableSync reifier;
 #endif
 
     JSPackedBool *closedVars;
     bool eval;
     bool inTryBlock;
 };
 
+class AutoPreserveAcrossSyncAndKill;
+
 } /* namespace mjit */
 } /* namespace js */
 
 #endif /* jsjaeger_framestate_h__ */