Bug 595884 - JM: make f.apply(x, arguments) fast (r=dvander)
authorLuke Wagner <lw@mozilla.com>, Jan de Mooij <jandemooij@gmail.com>
Thu, 21 Oct 2010 11:42:28 -0700
changeset 57718 92af3359a18ffd2bd1dc259593bca527e68a0881
parent 57717 d9aceaabef2867a10835263c6ac6ee619a4883dc
child 57720 5d2de219ba728a0af05049f00c3bb506017a08d3
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersdvander
bugs595884
milestone2.0b8pre
Bug 595884 - JM: make f.apply(x, arguments) fast (r=dvander)
js/src/jsarray.cpp
js/src/jsexn.cpp
js/src/jsinterpinlines.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/MethodJIT.h
js/src/methodjit/MonoIC.cpp
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -386,30 +386,16 @@ GetElement(JSContext *cx, JSObject *obj,
     } else {
         if (!obj->getProperty(cx, idr.id(), vp))
             return JS_FALSE;
         *hole = JS_FALSE;
     }
     return JS_TRUE;
 }
 
-struct STATIC_SKIP_INFERENCE CopyNonHoleArgs
-{
-    CopyNonHoleArgs(JSObject *aobj, Value *dst) : aobj(aobj), dst(dst) {}
-    JSObject *aobj;
-    Value *dst;
-    void operator()(uintN argi, Value *src) {
-        if (aobj->getArgsElement(argi).isMagic(JS_ARGS_HOLE))
-            dst->setUndefined();
-        else
-            *dst = *src;
-        ++dst;
-    }
-};
-
 namespace js {
 
 bool
 GetElements(JSContext *cx, JSObject *aobj, jsuint length, Value *vp)
 {
     if (aobj->isDenseArray() && length <= aobj->getDenseArrayCapacity()) {
         Value *srcbeg = aobj->getDenseArrayElements();
         Value *srcend = srcbeg + length;
@@ -419,17 +405,17 @@ GetElements(JSContext *cx, JSObject *aob
         /*
          * Two cases, two loops: note how in the case of an active stack frame
          * backing aobj, even though we copy from fp->argv, we still must check
          * aobj->getArgsElement(i) for a hole, to handle a delete on the
          * corresponding arguments element. See args_delProperty.
          */
         if (JSStackFrame *fp = (JSStackFrame *) aobj->getPrivate()) {
             JS_ASSERT(fp->numActualArgs() <= JS_ARGS_LENGTH_MAX);
-            fp->forEachCanonicalActualArg(CopyNonHoleArgs(aobj, vp));
+            fp->forEachCanonicalActualArg(CopyNonHoleArgsTo(aobj, vp));
         } else {
             Value *srcbeg = aobj->getArgsElements();
             Value *srcend = srcbeg + length;
             for (Value *dst = vp, *src = srcbeg; src < srcend; ++dst, ++src)
                 *dst = src->isMagic(JS_ARGS_HOLE) ? UndefinedValue() : *src;
         }
     } else {
         for (uintN i = 0; i < length; i++) {
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -255,25 +255,16 @@ GetStackTraceValueBuffer(JSExnPrivate *p
      * assert allows us to assume that no gap after stackElems is necessary to
      * align the buffer properly.
      */
     JS_STATIC_ASSERT(sizeof(JSStackTraceElem) % sizeof(jsval) == 0);
 
     return (jsval *)(priv->stackElems + priv->stackDepth);
 }
 
-struct CopyTo
-{
-    Value *dst;
-    CopyTo(jsval *dst) : dst(Valueify(dst)) {}
-    void operator()(uintN, Value *src) {
-        *dst++ = *src;
-    }
-};
-
 static JSBool
 InitExnPrivate(JSContext *cx, JSObject *exnObject, JSString *message,
                JSString *filename, uintN lineno, JSErrorReport *report)
 {
     JSSecurityCallbacks *callbacks;
     CheckAccessOp checkAccess;
     JSErrorReporter older;
     JSExceptionState *state;
@@ -349,17 +340,17 @@ InitExnPrivate(JSContext *cx, JSObject *
         if (!fp->isFunctionFrame() || fp->isEvalFrame()) {
             elem->funName = NULL;
             elem->argc = 0;
         } else {
             elem->funName = fp->fun()->atom
                             ? ATOM_TO_STRING(fp->fun()->atom)
                             : cx->runtime->emptyString;
             elem->argc = fp->numActualArgs();
-            fp->forEachCanonicalActualArg(CopyTo(values));
+            fp->forEachCanonicalActualArg(CopyTo(Valueify(values)));
             values += elem->argc;
         }
         elem->ulineno = 0;
         elem->filename = NULL;
         if (fp->isScriptFrame()) {
             elem->filename = fp->script()->filename;
             if (fp->pc(cx))
                 elem->ulineno = js_FramePCToLineNumber(cx, fp);
--- a/js/src/jsinterpinlines.h
+++ b/js/src/jsinterpinlines.h
@@ -309,16 +309,43 @@ JSStackFrame::forEachFormalArg(Op op)
 {
     js::Value *formals = formalArgsEnd() - fun()->nargs;
     js::Value *formalsEnd = formalArgsEnd();
     uintN i = 0;
     for (js::Value *p = formals; p != formalsEnd; ++p, ++i)
         op(i, p);
 }
 
+namespace js {
+
+struct STATIC_SKIP_INFERENCE CopyNonHoleArgsTo
+{
+    CopyNonHoleArgsTo(JSObject *aobj, Value *dst) : aobj(aobj), dst(dst) {}
+    JSObject *aobj;
+    Value *dst;
+    void operator()(uintN argi, Value *src) {
+        if (aobj->getArgsElement(argi).isMagic(JS_ARGS_HOLE))
+            dst->setUndefined();
+        else
+            *dst = *src;
+        ++dst;
+    }
+};
+
+struct CopyTo
+{
+    Value *dst;
+    CopyTo(Value *dst) : dst(dst) {}
+    void operator()(uintN, Value *src) {
+        *dst++ = *src;
+    }
+};
+
+}
+
 JS_ALWAYS_INLINE void
 JSStackFrame::clearMissingArgs()
 {
     if (flags_ & JSFRAME_UNDERFLOW_ARGS)
         SetValueRangeToUndefined(formalArgs() + numActualArgs(), formalArgsEnd());
 }
 
 inline bool
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -18,16 +18,17 @@
  * May 28, 2008.
  *
  * The Initial Developer of the Original Code is
  *   Brendan Eich <brendan@mozilla.org>
  *
  * Contributor(s):
  *   David Anderson <danderson@mozilla.com>
  *   David Mandelin <dmandelin@mozilla.com>
+ *   Jan de Mooij <jandemooij@gmail.com>
  *
  * 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
@@ -120,21 +121,22 @@ mjit::Compiler::Compiler(JSContext *cx, 
     pics(CompilerAllocPolicy(cx, *thisFromCtor())), 
     getElemICs(CompilerAllocPolicy(cx, *thisFromCtor())),
     setElemICs(CompilerAllocPolicy(cx, *thisFromCtor())),
 #endif
     callPatches(CompilerAllocPolicy(cx, *thisFromCtor())),
     callSites(CompilerAllocPolicy(cx, *thisFromCtor())), 
     doubleList(CompilerAllocPolicy(cx, *thisFromCtor())),
     stubcc(cx, *thisFromCtor(), frame, script),
-    debugMode(cx->compartment->debugMode)
+    debugMode(cx->compartment->debugMode),
 #if defined JS_TRACER
-    , addTraceHints(cx->traceJitEnabled)
+    addTraceHints(cx->traceJitEnabled),
 #endif
-    , oomInVector(false)
+    oomInVector(false),
+    applyTricks(NoApplyTricks)
 {
 }
 
 CompileStatus
 mjit::Compiler::compile()
 {
     JS_ASSERT(!script->isEmpty());
     JS_ASSERT_IF(isConstructing, !script->jitCtor);
@@ -898,18 +900,27 @@ mjit::Compiler::generateMethod()
 
           BEGIN_CASE(JSOP_IFEQ)
           BEGIN_CASE(JSOP_IFNE)
             if (!jsop_ifneq(op, PC + GET_JUMP_OFFSET(PC)))
                 return Compile_Error;
           END_CASE(JSOP_IFNE)
 
           BEGIN_CASE(JSOP_ARGUMENTS)
-            prepareStubCall(Uses(0));
-            stubCall(stubs::Arguments);
+            /*
+             * For calls of the form 'f.apply(x, arguments)' we can avoid
+             * creating an args object by having ic::SplatApplyArgs pull
+             * directly from the stack. To do this, we speculate here that
+             * 'apply' actually refers to js_fun_apply. If this is not true,
+             * the slow path in JSOP_FUNAPPLY will create the args object.
+             */
+            if (canUseApplyTricks())
+                applyTricks = LazyArgsObj;
+            else
+                jsop_arguments();
             frame.pushSynced();
           END_CASE(JSOP_ARGUMENTS)
 
           BEGIN_CASE(JSOP_FORARG)
             iterNext();
             jsop_setarg(GET_SLOTNO(PC), true);
             frame.pop();
           END_CASE(JSOP_FORARG)
@@ -2342,17 +2353,18 @@ IsLowerableFunCallOrApply(jsbytecode *pc
     return (*pc == JSOP_FUNCALL && GET_ARGC(pc) >= 1) ||
            (*pc == JSOP_FUNAPPLY && GET_ARGC(pc) == 2);
 #else
     return false;
 #endif
 }
 
 void
-mjit::Compiler::checkCallApplySpeculation(uint32 argc, FrameEntry *origCallee, FrameEntry *origThis,
+mjit::Compiler::checkCallApplySpeculation(uint32 callImmArgc, uint32 speculatedArgc,
+                                          FrameEntry *origCallee, FrameEntry *origThis,
                                           MaybeRegisterID origCalleeType, RegisterID origCalleeData,
                                           MaybeRegisterID origThisType, RegisterID origThisData,
                                           Jump *uncachedCallSlowRejoin, CallPatchInfo *uncachedCallPatch)
 {
     JS_ASSERT(IsLowerableFunCallOrApply(PC));
 
     /*
      * if (origCallee.isObject() &&
@@ -2374,18 +2386,29 @@ mjit::Compiler::checkCallApplySpeculatio
      * 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);
+        int32 frameDepthAdjust;
+        if (applyTricks == LazyArgsObj) {
+            stubcc.call(stubs::Arguments);
+            frameDepthAdjust = +1;
+        } else {
+            frameDepthAdjust = 0;
+        }
+
+        stubcc.masm.move(Imm32(callImmArgc), Registers::ArgReg1);
+        JaegerSpew(JSpew_Insns, " ---- BEGIN SLOW CALL CODE ---- \n");
+        stubcc.masm.stubCall(JS_FUNC_TO_DATA_PTR(void *, stubs::UncachedCall),
+                             PC, frame.frameDepth() + frameDepthAdjust);
+        JaegerSpew(JSpew_Insns, " ---- END SLOW CALL CODE ---- \n");
         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;
@@ -2402,53 +2425,90 @@ mjit::Compiler::checkCallApplySpeculatio
          * 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");
     }
+
+    /*
+     * For simplicity, we don't statically specialize calls to
+     * ic::SplatApplyArgs based on applyTricks. Rather, this state is
+     * communicated dynamically through the VMFrame.
+     */
+    if (*PC == JSOP_FUNAPPLY) {
+        masm.store32(Imm32(applyTricks == LazyArgsObj),
+                     FrameAddress(offsetof(VMFrame, u.call.lazyArgsObj)));
+    }
+}
+
+/* This predicate must be called before the current op mutates the FrameState. */
+bool
+mjit::Compiler::canUseApplyTricks()
+{
+    JS_ASSERT(*PC == JSOP_ARGUMENTS);
+    jsbytecode *nextpc = PC + JSOP_ARGUMENTS_LENGTH;
+    return *nextpc == JSOP_FUNAPPLY &&
+           IsLowerableFunCallOrApply(nextpc) &&
+           !debugMode;
 }
 
 /* See MonoIC.cpp, CallCompiler for more information on call ICs. */
 void
-mjit::Compiler::inlineCallHelper(uint32 argc, bool callingNew)
+mjit::Compiler::inlineCallHelper(uint32 callImmArgc, bool callingNew)
 {
     /* Check for interrupts on function call */
     interruptCheckHelper();
 
-    FrameEntry *origCallee = frame.peek(-int(argc + 2));
-    FrameEntry *origThis = frame.peek(-int(argc + 1));
+    int32 speculatedArgc;
+    if (applyTricks == LazyArgsObj) {
+        frame.pop();
+        speculatedArgc = 1;
+    } else {
+        speculatedArgc = callImmArgc;
+    }
+
+    FrameEntry *origCallee = frame.peek(-(speculatedArgc + 2));
+    FrameEntry *origThis = frame.peek(-(speculatedArgc + 1));
 
     /* 'this' does not need to be synced for constructing. */
     if (callingNew)
         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
-    }
-
-    /*
      * From the presence of JSOP_FUN{CALL,APPLY}, we speculate that we are
      * going to call js_fun_{call,apply}. 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. However, if it turns out that
      * we are not actually calling js_fun_call, the callIC must act as normal.
      */
     bool lowerFunCallOrApply = IsLowerableFunCallOrApply(PC);
 
+    /*
+     * 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 (debugMode ||
+        origCallee->isConstant() || origCallee->isNotType(JSVAL_TYPE_OBJECT) ||
+        (lowerFunCallOrApply &&
+         (origThis->isConstant() || origThis->isNotType(JSVAL_TYPE_OBJECT)))) {
+#endif
+        if (applyTricks == LazyArgsObj) {
+            /* frame.pop() above reset us to pre-JSOP_ARGUMENTS state */
+            jsop_arguments();
+            frame.pushSynced();
+        }
+        emitUncachedCall(callImmArgc, callingNew);
+        return;
+#ifdef JS_MONOIC
+    }
+
     /* 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 lowerFunCallOrApply branch. */
@@ -2469,46 +2529,47 @@ mjit::Compiler::inlineCallHelper(uint32 
             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));
+                frame.syncAndKill(Registers(Registers::AvailRegs), Uses(speculatedArgc + 2));
             }
 
-            checkCallApplySpeculation(argc, origCallee, origThis,
+            checkCallApplySpeculation(callImmArgc, speculatedArgc,
+                                      origCallee, origThis,
                                       origCalleeType, origCalleeData,
                                       origThisType, origThisData,
                                       &uncachedCallSlowRejoin, &uncachedCallPatch);
 
             icCalleeType = origThisType;
             icCalleeData = origThisData;
             icRvalAddr = frame.addressOf(origThis);
 
             /*
              * For f.call(), since we compile the ic under the (checked)
              * assumption that call == js_fun_call, we still have a static
              * frame size. For f.apply(), the frame size depends on the dynamic
              * length of the array passed to apply.
              */
             if (*PC == JSOP_FUNCALL)
-                callIC.frameSize.initStatic(frame.frameDepth(), argc - 1);
+                callIC.frameSize.initStatic(frame.frameDepth(), speculatedArgc - 1);
             else
                 callIC.frameSize.initDynamic();
         } else {
             /* Leaves pinned regs untouched. */
-            frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2));
+            frame.syncAndKill(Registers(Registers::AvailRegs), Uses(speculatedArgc + 2));
 
             icCalleeType = origCalleeType;
             icCalleeData = origCalleeData;
             icRvalAddr = frame.addressOf(origCallee);
-            callIC.frameSize.initStatic(frame.frameDepth(), argc);
+            callIC.frameSize.initStatic(frame.frameDepth(), speculatedArgc);
         }
     }
 
     /* Test the type if necessary. Failing this always takes a really slow path. */
     MaybeJump notObjectJump;
     if (icCalleeType.isSet())
         notObjectJump = masm.testObject(Assembler::NotEqual, icCalleeType.reg());
 
@@ -2635,17 +2696,17 @@ mjit::Compiler::inlineCallHelper(uint32 
     callPatch.fastNcodePatch = inlFrame.assemble(NULL);
 
     callIC.hotJump = masm.jump();
     callIC.joinPoint = callPatch.joinPoint = masm.label();
     if (lowerFunCallOrApply)
         uncachedCallPatch.joinPoint = callIC.joinPoint;
     masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfPrev()), JSFrameReg);
 
-    frame.popn(argc + 2);
+    frame.popn(speculatedArgc + 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
      * lowerFunCallOrApply, we cannot just call 'stubcc.rejoin' since the return
      * value has been placed at vp[1] which is not the stack address associated
@@ -2661,16 +2722,18 @@ mjit::Compiler::inlineCallHelper(uint32 
 
     if (lowerFunCallOrApply)
         stubcc.crossJump(uncachedCallSlowRejoin, masm.label());
 
     callICs.append(callIC);
     callPatches.append(callPatch);
     if (lowerFunCallOrApply)
         callPatches.append(uncachedCallPatch);
+
+    applyTricks = NoApplyTricks;
 #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.
  */
@@ -4575,16 +4638,23 @@ mjit::Compiler::emitEval(uint32 argc)
     frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2));
     prepareStubCall(Uses(argc + 2));
     masm.move(Imm32(argc), Registers::ArgReg1);
     stubCall(stubs::Eval);
     frame.popn(argc + 2);
     frame.pushSynced();
 }
 
+void
+mjit::Compiler::jsop_arguments()
+{
+    prepareStubCall(Uses(0));
+    stubCall(stubs::Arguments);
+}
+
 /*
  * Note: This function emits tracer hooks into the OOL path. This means if
  * it is used in the middle of an in-progress slow path, the stream will be
  * hopelessly corrupted. Take care to only call this before linkExits() and
  * after rejoin()s.
  */
 bool
 mjit::Compiler::jumpAndTrace(Jump j, jsbytecode *target, Jump *slow)
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -283,16 +283,17 @@ class Compiler : public BaseCompiler
     js::Vector<InternalCallSite, 64, CompilerAllocPolicy> callSites;
     js::Vector<DoublePatch, 16, CompilerAllocPolicy> doubleList;
     StubCompiler stubcc;
     Label invokeLabel;
     Label arityLabel;
     bool debugMode;
     bool addTraceHints;
     bool oomInVector;       // True if we have OOM'd appending to a vector. 
+    enum { NoApplyTricks, LazyArgsObj } applyTricks;
 
     Compiler *thisFromCtor() { return this; }
 
     friend class CompilerAllocPolicy;
   public:
     // Special atom index used to indicate that the atom is 'length'. This
     // follows interpreter usage in JSOP_LENGTH.
     enum { LengthAtomIndex = uint32(-2) };
@@ -315,16 +316,17 @@ class Compiler : public BaseCompiler
     CompileStatus generateEpilogue();
     CompileStatus finishThisUp(JITScript **jitp);
 
     /* Non-emitting helpers. */
     uint32 fullAtomIndex(jsbytecode *pc);
     bool jumpInScript(Jump j, jsbytecode *pc);
     bool compareTwoValues(JSContext *cx, JSOp op, const Value &lhs, const Value &rhs);
     void addCallSite(uint32 id, bool stub);
+    bool canUseApplyTricks();
 
     /* Emitting helpers. */
     void restoreFrameRegs(Assembler &masm);
     bool emitStubCmpOp(BoolStub stub, jsbytecode *target, JSOp fused);
     void iter(uintN flags);
     void iterNext();
     bool iterMore();
     void iterEnd();
@@ -348,17 +350,18 @@ 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 checkCallApplySpeculation(uint32 argc, FrameEntry *origCallee, FrameEntry *origThis,
+    void checkCallApplySpeculation(uint32 callImmArgc, uint32 speculatedArgc,
+                                   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);
@@ -382,16 +385,17 @@ class Compiler : public BaseCompiler
     bool jsop_callprop_str(JSAtom *atom);
     bool jsop_callprop_generic(JSAtom *atom);
     bool jsop_instanceof();
     void jsop_name(JSAtom *atom);
     bool jsop_xname(JSAtom *atom);
     void enterBlock(JSObject *obj);
     void leaveBlock();
     void emitEval(uint32 argc);
+    void jsop_arguments();
 
     /* Fast arithmetic. */
     void jsop_binary(JSOp op, VoidStub stub);
     void jsop_binary_full(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub);
     void jsop_binary_full_simple(FrameEntry *fe, JSOp op, VoidStub stub);
     void jsop_binary_double(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub);
     void slowLoadConstantDouble(Assembler &masm, FrameEntry *fe,
                                 FPRegisterID fpreg);
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -59,16 +59,17 @@ struct VMFrame
 {
     union Arguments {
         struct {
             void *ptr;
             void *ptr2;
             void *ptr3;
         } x;
         struct {
+            uint32 lazyArgsObj;
             uint32 dynamicArgc;
         } call;
     } u;
 
     VMFrame      *previous;
     void         *unused;
     JSFrameRegs  regs;
     JSContext    *cx;
--- a/js/src/methodjit/MonoIC.cpp
+++ b/js/src/methodjit/MonoIC.cpp
@@ -615,30 +615,32 @@ class CallCompiler : public BaseCompiler
 
         ic.hasJsFunCheck = true;
 
         return true;
     }
 
     bool generateNativeStub()
     {
+        /* Snapshot the frameDepth before SplatApplyArgs modifies it. */
+        uintN initialFrameDepth = f.regs.sp - f.regs.fp->slots();
+
         /*
          * SplatApplyArgs has not been called, so we call it here before
          * potentially touching f.u.call.dynamicArgc.
          */
-        uintN staticFrameDepth = f.regs.sp - f.regs.fp->slots();
         Value *vp;
         if (ic.frameSize.isStatic()) {
-            JS_ASSERT(staticFrameDepth == ic.frameSize.staticFrameDepth());
+            JS_ASSERT(f.regs.sp - f.regs.fp->slots() == (int)ic.frameSize.staticFrameDepth());
             vp = f.regs.sp - (2 + ic.frameSize.staticArgc());
         } else {
             JS_ASSERT(*f.regs.pc == JSOP_FUNAPPLY && GET_ARGC(f.regs.pc) == 2);
-            if (!ic::SplatApplyArgs(f))  /* updates regs.sp */
+            if (!ic::SplatApplyArgs(f))       /* updates regs.sp */
                 THROWV(true);
-            vp = f.regs.fp->slots() + (staticFrameDepth - 3);  /* this, arg1, arg2 */
+            vp = f.regs.sp - (2 + f.u.call.dynamicArgc);
         }
 
         JSObject *obj;
         if (!IsFunctionObject(*vp, &obj))
             return false;
 
         JSFunction *fun = obj->getFunctionPrivate();
         if ((!callingNew && !fun->isNative()) || (callingNew && !fun->isConstructor()))
@@ -663,34 +665,35 @@ class CallCompiler : public BaseCompiler
         /* Generate fast-path for calling this native. */
         Assembler masm;
 
         /* Guard on the function object identity, for now. */
         Jump funGuard = masm.branchPtr(Assembler::NotEqual, ic.funObjReg, ImmPtr(obj));
 
         /* N.B. After this call, the frame will have a dynamic frame size. */
         if (ic.frameSize.isDynamic()) {
-            masm.stubCall(JS_FUNC_TO_DATA_PTR(void *, ic::SplatApplyArgs), f.regs.pc, staticFrameDepth);
+            masm.stubCall(JS_FUNC_TO_DATA_PTR(void *, ic::SplatApplyArgs),
+                          f.regs.pc, initialFrameDepth);
         }
 
         Registers tempRegs;
 #ifndef JS_CPU_X86
         tempRegs.takeReg(Registers::ArgReg0);
         tempRegs.takeReg(Registers::ArgReg1);
         tempRegs.takeReg(Registers::ArgReg2);
 #endif
         RegisterID t0 = tempRegs.takeAnyReg();
 
         /* Store pc. */
         masm.storePtr(ImmPtr(cx->regs->pc),
                        FrameAddress(offsetof(VMFrame, regs.pc)));
 
         /* Store sp (if not already set by ic::SplatApplyArgs). */
         if (ic.frameSize.isStatic()) {
-            uint32 spOffset = sizeof(JSStackFrame) + staticFrameDepth * sizeof(Value);
+            uint32 spOffset = sizeof(JSStackFrame) + initialFrameDepth * sizeof(Value);
             masm.addPtr(Imm32(spOffset), JSFrameReg, t0);
             masm.storePtr(t0, FrameAddress(offsetof(VMFrame, regs.sp)));
         }
 
         /* Store fp. */
         masm.storePtr(JSFrameReg, FrameAddress(offsetof(VMFrame, regs.fp)));
 
         /* Grab cx early on to avoid stack mucking on x86. */
@@ -961,19 +964,64 @@ BumpStack(VMFrame &f, uintN inc)
  * Additionally, the callee has already been checked to be the native apply.
  * All successful paths through SplatApplyArgs must set f.u.call.dynamicArgc
  * and f.regs.sp.
  */
 JSBool JS_FASTCALL
 ic::SplatApplyArgs(VMFrame &f)
 {
     JSContext *cx = f.cx;
+    JS_ASSERT(GET_ARGC(f.regs.pc) == 2);
+
+    /*
+     * The lazyArgsObj flag indicates an optimized call |f.apply(x, arguments)|
+     * where the args obj has not been created or pushed on the stack. Thus,
+     * if lazyArgsObj is set, the stack for |f.apply(x, arguments)| is:
+     *
+     *  | Function.prototype.apply | f | x |
+     *
+     * Otherwise, if !lazyArgsObj, the stack is a normal 2-argument apply:
+     *
+     *  | Function.prototype.apply | f | x | arguments |
+     */
+    if (f.u.call.lazyArgsObj) {
+        Value *vp = f.regs.sp - 3;
+        JS_ASSERT(JS_CALLEE(cx, vp).toObject().getFunctionPrivate()->u.n.native == js_fun_apply);
+
+        JSStackFrame *fp = f.regs.fp;
+        if (!fp->hasOverriddenArgs() &&
+            (!fp->hasArgsObj() ||
+             (fp->hasArgsObj() && !fp->argsObj().isArgsLengthOverridden()))) {
+
+            uintN n = fp->numActualArgs();
+            if (!BumpStack(f, n))
+                THROWV(false);
+            f.regs.sp += n;
+
+            Value *argv = JS_ARGV(cx, vp + 1 /* vp[1]'s argv */);
+            if (fp->hasArgsObj())
+                fp->forEachCanonicalActualArg(CopyNonHoleArgsTo(&fp->argsObj(), argv));
+            else
+                fp->forEachCanonicalActualArg(CopyTo(argv));
+
+            f.u.call.dynamicArgc = n;
+            return true;
+        }
+
+        /*
+         * Can't optimize; push the arguments object so that the stack matches
+         * the !lazyArgsObj stack state described above.
+         */
+        f.regs.sp++;
+        if (!js_GetArgsValue(cx, fp, &vp[3]))
+            THROWV(false);
+    }
+
     Value *vp = f.regs.sp - 4;
     JS_ASSERT(JS_CALLEE(cx, vp).toObject().getFunctionPrivate()->u.n.native == js_fun_apply);
-    JS_ASSERT(GET_ARGC(f.regs.pc) == 2);
 
     /*
      * This stub should mimic the steps taken by js_fun_apply. Step 1 and part
      * of Step 2 have already been taken care of by calling jit code.
      */
 
     /* Step 2 (part 2). */
     if (vp[3].isNullOrUndefined()) {