[JAEGER] Combine over-recursion and stack commit checks (bug 586886).
authorDavid Anderson <danderson@mozilla.com>
Fri, 13 Aug 2010 14:22:18 -0700
changeset 53422 d7b81bd995c3dcad118febc2333d73fcce5a542e
parent 53421 ef8903b263d3b1c3cc4978c8aabf6210820de473
child 53423 6a6a72c1961ad8449126fb447e85e47382068ac0
push idunknown
push userunknown
push dateunknown
bugs586886
milestone2.0b4pre
[JAEGER] Combine over-recursion and stack commit checks (bug 586886).
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/MethodJIT.h
js/src/methodjit/TrampolineCompiler.cpp
js/src/trace-test/tests/basic/testStackQuotaExhausted.js
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -790,16 +790,20 @@ class StackSpace
      * Since RAII cannot be used for inline frames, callers must manually
      * call pushInlineFrame/popInlineFrame.
      */
     JS_REQUIRES_STACK
     inline JSStackFrame *getInlineFrame(JSContext *cx, Value *sp,
                                         uintN nmissing, uintN nfixed) const;
 
     JS_REQUIRES_STACK
+    inline JSStackFrame *getInlineFrameUnchecked(JSContext *cx, Value *sp,
+                                                 uintN nmissing) const;
+
+    JS_REQUIRES_STACK
     inline void pushInlineFrame(JSContext *cx, JSStackFrame *fp, jsbytecode *pc,
                                 JSStackFrame *newfp);
 
     JS_REQUIRES_STACK
     inline void popInlineFrame(JSContext *cx, JSStackFrame *up, JSStackFrame *down);
 
     /*
      * For the special case of the slow native stack frame pushed and popped by
@@ -809,16 +813,29 @@ class StackSpace
     void getSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *&seg, JSStackFrame *&fp);
 
     JS_REQUIRES_STACK
     void pushSynthesizedSlowNativeFrame(JSContext *cx, StackSegment *seg, JSStackFrame *fp,
                                         JSFrameRegs &regs);
 
     JS_REQUIRES_STACK
     void popSynthesizedSlowNativeFrame(JSContext *cx);
+
+    /*
+     * Ensure space based on an over-recursion limit.
+     */
+    inline bool ensureSpace(JSContext *maybecx, Value *start, Value *from,
+                            Value *& limit, uint32 nslots) const;
+
+    /*
+     * Create a stack limit for quickly detecting over-recursion and whether
+     * a commit bump is needed.
+     */
+    inline Value *makeStackLimit(Value *start) const;
+
 };
 
 JS_STATIC_ASSERT(StackSpace::CAPACITY_VALS % StackSpace::COMMIT_VALS == 0);
 
 /*
  * While |cx->fp|'s pc/sp are available in |cx->regs|, to compute the saved
  * value of pc/sp for any other frame, it is necessary to know about that
  * frame's up-frame. This iterator maintains this information when walking down
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -1,9 +1,10 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et tw=78:
  *
  * ***** 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/
@@ -102,16 +103,65 @@ StackSpace::isCurrentAndActive(JSContext
                  cx->getCurrentSegment()->maybeContext() == cx);
     cx->assertSegmentsInSync();
 #endif
     return currentSegment &&
            currentSegment->isActive() &&
            currentSegment == cx->getCurrentSegment();
 }
 
+/*
+ * SunSpider and v8bench have roughly an average of 9 slots per script.
+ * Our heuristic for a quick over-recursion check uses a generous slot
+ * count based on this estimate. We take this frame size and multiply it
+ * by the old recursion limit from the interpreter.
+ *
+ * Worst case, if an average size script (<=9 slots) over recurses, it'll
+ * effectively be the same as having increased the old inline call count
+ * to <= 5,000.
+ */
+static const uint32 MAX_STACK_USAGE = (VALUES_PER_STACK_FRAME + 18) * JS_MAX_INLINE_CALL_COUNT;
+
+JS_ALWAYS_INLINE Value *
+StackSpace::makeStackLimit(Value *start) const
+{
+    Value *limit = JS_MIN(start + MAX_STACK_USAGE, end);
+#ifdef XP_WIN
+    limit = JS_MIN(limit, commitEnd);
+#endif
+    return limit;
+}
+
+JS_ALWAYS_INLINE bool
+StackSpace::ensureSpace(JSContext *maybecx, Value *start, Value *from,
+                        Value *& limit, uint32 nslots) const
+{
+    JS_ASSERT(from == firstUnused());
+#ifdef XP_WIN
+    /*
+     * If commitEnd < limit, we're guaranteed that we reached the end of the
+     * commit depth, because stackLimit is MIN(commitEnd, limit). If we did
+     * reach this soft limit, check if we can bump the commit end without
+     * over-recursing.
+     */
+    ptrdiff_t nvals = VALUES_PER_STACK_FRAME + nslots;
+    if (commitEnd < limit && from + nvals < limit) {
+        if (!ensureSpace(maybecx, from, nvals))
+            return false;
+
+        /* Compute a new limit. */
+        limit = makeStackLimit(start);
+
+        return true;
+    }
+#endif
+    js_ReportOverRecursed(maybecx);
+    return false;
+}
+
 JS_ALWAYS_INLINE bool
 StackSpace::ensureSpace(JSContext *maybecx, Value *from, ptrdiff_t nvals) const
 {
     JS_ASSERT(from == firstUnused());
 #ifdef XP_WIN
     JS_ASSERT(from <= commitEnd);
     if (commitEnd - from >= nvals)
         return true;
@@ -271,29 +321,36 @@ JS_REQUIRES_STACK JS_ALWAYS_INLINE
 InvokeFrameGuard::~InvokeFrameGuard()
 {
     if (JS_UNLIKELY(!pushed()))
         return;
     cx->stack().popInvokeFrame(*this);
 }
 
 JS_REQUIRES_STACK JS_ALWAYS_INLINE JSStackFrame *
-StackSpace::getInlineFrame(JSContext *cx, Value *sp,
-                           uintN nmissing, uintN nfixed) const
+StackSpace::getInlineFrameUnchecked(JSContext *cx, Value *sp,
+                                    uintN nmissing) const
 {
     JS_ASSERT(isCurrentAndActive(cx));
     JS_ASSERT(cx->hasActiveSegment());
     JS_ASSERT(cx->regs->sp == sp);
 
+    JSStackFrame *fp = reinterpret_cast<JSStackFrame *>(sp + nmissing);
+    return fp;
+}
+
+JS_REQUIRES_STACK JS_ALWAYS_INLINE JSStackFrame *
+StackSpace::getInlineFrame(JSContext *cx, Value *sp,
+                           uintN nmissing, uintN nfixed) const
+{
     ptrdiff_t nvals = nmissing + VALUES_PER_STACK_FRAME + nfixed;
     if (!ensureSpace(cx, sp, nvals))
         return NULL;
 
-    JSStackFrame *fp = reinterpret_cast<JSStackFrame *>(sp + nmissing);
-    return fp;
+    return getInlineFrameUnchecked(cx, sp, nmissing);
 }
 
 JS_REQUIRES_STACK JS_ALWAYS_INLINE void
 StackSpace::pushInlineFrame(JSContext *cx, JSStackFrame *fp, jsbytecode *pc,
                             JSStackFrame *newfp)
 {
     JS_ASSERT(isCurrentAndActive(cx));
     JS_ASSERT(cx->fp == fp && cx->regs->pc == pc);
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1573,18 +1573,18 @@ mjit::Compiler::jsop_getglobal(uint32 in
 void
 mjit::Compiler::emitReturn()
 {
     /*
      * if (!f.inlineCallCount)
      *     return;
      */
     Jump noInlineCalls = masm.branchPtr(Assembler::Equal,
-                                        FrameAddress(offsetof(VMFrame, inlineCallCount)),
-                                        ImmPtr(0));
+                                        FrameAddress(offsetof(VMFrame, entryFp)),
+                                        JSFrameReg);
     stubcc.linkExit(noInlineCalls, Uses(frame.frameDepth()));
     stubcc.masm.ret();
 
     JS_ASSERT_IF(!fun, JSOp(*PC) == JSOP_STOP);
 
     /*
      * If there's a function object, deal with the fact that it can escape.
      * Note that after we've placed the call object, all tracked state can
@@ -1629,17 +1629,16 @@ mjit::Compiler::emitReturn()
      * a1 = f.cx
      * f.fp = r
      * cx->fp = r
      */
     masm.loadPtr(Address(JSFrameReg, offsetof(JSStackFrame, down)), Registers::ReturnReg);
     masm.loadPtr(FrameAddress(offsetof(VMFrame, cx)), Registers::ArgReg1);
     masm.storePtr(Registers::ReturnReg, FrameAddress(offsetof(VMFrame, fp)));
     masm.storePtr(Registers::ReturnReg, Address(Registers::ArgReg1, offsetof(JSContext, fp)));
-    masm.sub32(Imm32(1), FrameAddress(offsetof(VMFrame, inlineCallCount)));
 
     JS_STATIC_ASSERT(Registers::ReturnReg != JSReturnReg_Data);
     JS_STATIC_ASSERT(Registers::ReturnReg != JSReturnReg_Type);
 
     Address rval(JSFrameReg, offsetof(JSStackFrame, rval));
     masm.loadPayload(rval, JSReturnReg_Data);
     masm.loadTypeTag(rval, JSReturnReg_Type);
     masm.move(Registers::ReturnReg, JSFrameReg);
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -81,17 +81,17 @@ using namespace JSC;
 #define THROWV(v)       \
     do {                \
         void *ptr = JS_FUNC_TO_DATA_PTR(void *, JaegerThrowpoline); \
         *f.returnAddressLocation() = ptr; \
         return v;       \
     } while (0)
 
 static bool
-InlineReturn(JSContext *cx, JSBool ok);
+InlineReturn(VMFrame &f, JSBool ok);
 
 static jsbytecode *
 FindExceptionHandler(JSContext *cx)
 {
     JSStackFrame *fp = cx->fp;
     JSScript *script = fp->script;
 
 top:
@@ -161,49 +161,56 @@ top:
                 }
             }
         }
     }
 
     return NULL;
 }
 
+static inline void
+FixVMFrame(VMFrame &f, JSStackFrame *fp)
+{
+    f.fp->ncode = f.scriptedReturn;
+    JS_ASSERT(f.fp == fp->down);
+    f.fp = fp;
+}
+
 static inline bool
 CreateFrame(VMFrame &f, uint32 flags, uint32 argc)
 {
     JSContext *cx = f.cx;
     JSStackFrame *fp = f.fp;
     Value *vp = f.regs.sp - (argc + 2);
     JSObject *funobj = &vp->toObject();
     JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
 
     JS_ASSERT(FUN_INTERPRETED(fun));
 
     JSScript *newscript = fun->u.i.script;
 
-    if (f.inlineCallCount >= JS_MAX_INLINE_CALL_COUNT) {
-        js_ReportOverRecursed(cx);
-        return false;
-    }
-
     /* Allocate the frame. */
     StackSpace &stack = cx->stack();
     uintN nslots = newscript->nslots;
     uintN funargs = fun->nargs;
     Value *argv = vp + 2;
     JSStackFrame *newfp;
     if (argc < funargs) {
         uintN missing = funargs - argc;
-        newfp = stack.getInlineFrame(cx, f.regs.sp, missing, nslots);
+        if (!f.ensureSpace(missing, nslots))
+            return false;
+        newfp = stack.getInlineFrameUnchecked(cx, f.regs.sp, missing);
         if (!newfp)
             return false;
         for (Value *v = argv + argc, *end = v + missing; v != end; ++v)
             v->setUndefined();
     } else {
-        newfp = stack.getInlineFrame(cx, f.regs.sp, 0, nslots);
+        if (!f.ensureSpace(0, nslots))
+            return false;
+        newfp = stack.getInlineFrameUnchecked(cx, f.regs.sp, 0);
         if (!newfp)
             return false;
     }
 
     /* Initialize the frame. */
     newfp->ncode = NULL;
     newfp->setCallObj(NULL);
     newfp->setArgsObj(NULL);
@@ -238,68 +245,63 @@ CreateFrame(VMFrame &f, uint32 flags, ui
         newfp->hookData = hook(cx, fp, JS_TRUE, 0,
                                cx->debugHooks->callHookData);
         // CHECK_INTERRUPT_HANDLER();
     } else {
         newfp->hookData = NULL;
     }
 
     stack.pushInlineFrame(cx, fp, cx->regs->pc, newfp);
+    FixVMFrame(f, newfp);
 
     return true;
 }
 
-static inline void
-FixVMFrame(VMFrame &f, JSStackFrame *fp)
-{
-    f.inlineCallCount++;
-    f.fp->ncode = f.scriptedReturn;
-    JS_ASSERT(f.fp == fp->down);
-    f.fp = fp;
-}
-
 static inline bool
 InlineCall(VMFrame &f, uint32 flags, void **pret, uint32 argc)
 {
     if (!CreateFrame(f, flags, argc))
         return false;
 
     JSContext *cx = f.cx;
     JSStackFrame *fp = cx->fp;
     JSScript *script = fp->script;
     f.regs.pc = script->code;
     f.regs.sp = fp->base();
 
     if (cx->options & JSOPTION_METHODJIT) {
         if (!script->ncode) {
             if (mjit::TryCompile(cx, script, fp->fun, fp->scopeChain) == Compile_Error) {
-                InlineReturn(cx, JS_FALSE);
+                InlineReturn(f, JS_FALSE);
                 return false;
             }
         }
         JS_ASSERT(script->ncode);
         if (script->ncode != JS_UNJITTABLE_METHOD) {
-            FixVMFrame(f, fp);
             *pret = script->nmap[-1];
             return true;
         }
     }
 
     bool ok = !!Interpret(cx, cx->fp);
-    InlineReturn(cx, JS_TRUE);
+    InlineReturn(f, JS_TRUE);
 
     *pret = NULL;
     return ok;
 }
 
 static bool
-InlineReturn(JSContext *cx, JSBool ok)
+InlineReturn(VMFrame &f, JSBool ok)
 {
+    JSContext *cx = f.cx;
     JSStackFrame *fp = cx->fp;
 
+    JS_ASSERT(f.fp == cx->fp);
+    JS_ASSERT(f.fp != f.entryFp);
+
     JS_ASSERT(!fp->blockChain);
     JS_ASSERT(!js_IsActiveWithOrBlock(cx, fp->scopeChain, 0));
 
     // Marker for debug support.
     void *hookData = fp->hookData;
     if (JS_UNLIKELY(hookData != NULL)) {
         JSInterpreterHook hook;
         JSBool status;
@@ -322,16 +324,17 @@ InlineReturn(JSContext *cx, JSBool ok)
     /* :TODO: version stuff */
 
     if (fp->flags & JSFRAME_CONSTRUCTING && fp->rval.isPrimitive())
         fp->rval = fp->thisv;
 
     Value *newsp = fp->argv - 1;
 
     cx->stack().popInlineFrame(cx, fp, fp->down);
+    f.fp = cx->fp;
 
     cx->regs->sp = newsp;
     cx->regs->sp[-1] = fp->rval;
 
     return ok;
 }
 
 static inline JSObject *
@@ -469,36 +472,35 @@ CreateLightFrame(VMFrame &f, uint32 flag
     Value *vp = f.regs.sp - (argc + 2);
     JSObject *funobj = &vp->toObject();
     JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
 
     JS_ASSERT(FUN_INTERPRETED(fun));
 
     JSScript *newscript = fun->u.i.script;
 
-    if (f.inlineCallCount >= JS_MAX_INLINE_CALL_COUNT) {
-        js_ReportOverRecursed(cx);
-        return false;
-    }
-
     /* Allocate the frame. */
     StackSpace &stack = cx->stack();
     uintN nslots = newscript->nslots;
     uintN funargs = fun->nargs;
     Value *argv = vp + 2;
     JSStackFrame *newfp;
     if (argc < funargs) {
         uintN missing = funargs - argc;
-        newfp = stack.getInlineFrame(cx, f.regs.sp, missing, nslots);
+        if (!f.ensureSpace(missing, nslots))
+            return false;
+        newfp = stack.getInlineFrameUnchecked(cx, f.regs.sp, missing);
         if (!newfp)
             return false;
         for (Value *v = argv + argc, *end = v + missing; v != end; ++v)
             v->setUndefined();
     } else {
-        newfp = stack.getInlineFrame(cx, f.regs.sp, 0, nslots);
+        if (!f.ensureSpace(0, nslots))
+            return false;
+        newfp = stack.getInlineFrameUnchecked(cx, f.regs.sp, 0);
         if (!newfp)
             return false;
     }
 
     /* Initialize the frame. */
     newfp->ncode = NULL;
     newfp->setCallObj(NULL);
     newfp->setArgsObj(NULL);
@@ -522,31 +524,30 @@ CreateLightFrame(VMFrame &f, uint32 flag
 #endif
 
 #ifdef DEBUG
     newfp->savedPC = JSStackFrame::sInvalidPC;
 #endif
     newfp->down = fp;
     fp->savedPC = f.regs.pc;
     cx->setCurrentFrame(newfp);
+    FixVMFrame(f, newfp);
 
     return true;
 }
 
 /*
  * stubs::Call is guaranteed to be called on a scripted call with JIT'd code.
  */
 void * JS_FASTCALL
 stubs::Call(VMFrame &f, uint32 argc)
 {
     if (!CreateLightFrame(f, 0, argc))
         THROWV(NULL);
 
-    FixVMFrame(f, f.cx->fp);
-
     return f.fp->script->ncode;
 }
 
 /*
  * stubs::New is guaranteed to be called on a scripted call with JIT'd code.
  */
 void * JS_FASTCALL
 stubs::New(VMFrame &f, uint32 argc)
@@ -554,18 +555,16 @@ stubs::New(VMFrame &f, uint32 argc)
     JSObject *obj = InlineConstruct(f, argc);
     if (!obj)
         THROWV(NULL);
 
     f.regs.sp[-int(argc + 1)].setObject(*obj);
     if (!CreateLightFrame(f, JSFRAME_CONSTRUCTING, argc))
         THROWV(NULL);
 
-    FixVMFrame(f, f.cx->fp);
-
     return f.fp->script->ncode;
 }
 
 void JS_FASTCALL
 stubs::PutCallObject(VMFrame &f)
 {
     JS_ASSERT(f.fp->hasCallObj());
     js_PutCallObject(f.cx, f.fp);
@@ -620,29 +619,27 @@ js_InternalThrow(VMFrame &f)
     }
 
     jsbytecode *pc = NULL;
     for (;;) {
         pc = FindExceptionHandler(cx);
         if (pc)
             break;
 
-        // If |f.inlineCallCount == 0|, then we are on the 'topmost' frame (where
-        // topmost means the first frame called into through js_Interpret). In this
-        // case, we still unwind, but we shouldn't return from a JS function, because
-        // we're not in a JS function.
-        bool lastFrame = (f.inlineCallCount == 0);
+        // If on the 'topmost' frame (where topmost means the first frame
+        // called into through js_Interpret). In this case, we still unwind,
+        // but we shouldn't return from a JS function, because we're not in a
+        // JS function.
+        bool lastFrame = (f.entryFp == f.fp);
         js_UnwindScope(cx, 0, cx->throwing);
         if (lastFrame)
             break;
 
         JS_ASSERT(f.regs.sp == cx->regs->sp);
-        InlineReturn(f.cx, JS_FALSE);
-        f.inlineCallCount--;
-        f.fp = cx->fp;
+        InlineReturn(f, JS_FALSE);
         f.scriptedReturn = cx->fp->ncode;
     }
 
     JS_ASSERT(f.regs.sp == cx->regs->sp);
 
     if (!pc) {
         *f.oldRegs = f.regs;
         f.cx->setCurrentRegs(f.oldRegs);
@@ -658,21 +655,17 @@ stubs::GetCallObject(VMFrame &f)
     JS_ASSERT(f.fp->fun->isHeavyweight());
     if (!js_GetCallObject(f.cx, f.fp))
         THROW();
 }
 
 static inline void
 AdvanceReturnPC(JSContext *cx)
 {
-    /*
-     * Simulate an inline_return by advancing the pc, then
-     * finish off the imacro. The inlineCallCount passed is
-     * bogus here, but it doesn't really matter.
-     */
+    /* Simulate an inline_return by advancing the pc. */
     JS_ASSERT(*cx->regs->pc == JSOP_CALL ||
               *cx->regs->pc == JSOP_NEW ||
               *cx->regs->pc == JSOP_EVAL ||
               *cx->regs->pc == JSOP_APPLY);
     cx->regs->pc += JSOP_CALL_LENGTH;
 }
 
 #ifdef JS_TRACER
@@ -705,17 +698,17 @@ SwallowErrors(VMFrame &f, JSStackFrame *
         }
 
         /* Don't unwind if this was the entry frame. */
         if (fp == stopFp)
             break;
 
         /* Unwind and return. */
         ok &= bool(js_UnwindScope(cx, 0, cx->throwing));
-        InlineReturn(cx, ok);
+        InlineReturn(f, ok);
     }
 
     /* Update the VMFrame before leaving. */
     JS_ASSERT(&f.regs == cx->regs);
 
     JS_ASSERT_IF(!ok, cx->fp == stopFp);
     return ok;
 }
@@ -775,32 +768,32 @@ RemoveExcessFrames(VMFrame &f, JSStackFr
         if (AtSafePoint(cx)) {
             if (!JaegerShot(cx)) {
                 if (!SwallowErrors(f, entryFrame))
                     return false;
 
                 /* Could be anywhere - restart outer loop. */
                 continue;
             }
-            InlineReturn(cx, JS_TRUE);
+            InlineReturn(f, JS_TRUE);
             AdvanceReturnPC(cx);
         } else {
             if (!PartialInterpret(cx)) {
                 if (!SwallowErrors(f, entryFrame))
                     return false;
             } else {
                 /*
                  * Partial interpret could have dropped us anywhere. Deduce the
                  * edge case: at a RETURN, needing to pop a frame.
                  */
                 if (!cx->fp->imacpc && FrameIsFinished(cx)) {
                     JSOp op = JSOp(*cx->regs->pc);
                     if (op == JSOP_RETURN && !(cx->fp->flags & JSFRAME_BAILED_AT_RETURN))
                         fp->rval = f.regs.sp[-1];
-                    InlineReturn(cx, JS_TRUE);
+                    InlineReturn(f, JS_TRUE);
                     AdvanceReturnPC(cx);
                 }
             }
         }
     }
 
     return true;
 }
@@ -846,21 +839,21 @@ RunTracer(VMFrame &f)
     JSContext *cx = f.cx;
     JSStackFrame *entryFrame = f.fp;
     TracePointAction tpa;
 
     /* :TODO: nuke PIC? */
     if (!cx->jitEnabled)
         return NULL;
 
-    JS_ASSERT_IF(f.inlineCallCount,
+    JS_ASSERT_IF(f.fp != f.entryFp,
                  entryFrame->down->script->isValidJitCode(f.scriptedReturn));
 
     bool blacklist;
-    uintN inlineCallCount = f.inlineCallCount;
+    uintN inlineCallCount = 0;
     tpa = MonitorTracePoint(f.cx, inlineCallCount, blacklist);
     JS_ASSERT(!TRACE_RECORDER(cx));
 
 #if JS_MONOIC
     if (blacklist)
         DisableTraceHint(f, mic);
 #endif
 
@@ -929,22 +922,20 @@ RunTracer(VMFrame &f)
 
     /* Step 3.2. If entryFrame is at a RETURN, then leave slightly differently. */
     if (JSOp op = FrameIsFinished(cx)) {
         /* We're not guaranteed that the RETURN was run. */
         if (op == JSOP_RETURN && !(entryFrame->flags & JSFRAME_BAILED_AT_RETURN))
             entryFrame->rval = f.regs.sp[-1];
 
         /* Don't pop the frame if it's maybe owned by an Invoke. */
-        if (f.inlineCallCount) {
-            if (!InlineReturn(cx, JS_TRUE))
+        if (f.fp != f.entryFp) {
+            if (!InlineReturn(f, JS_TRUE))
                 THROWV(NULL);
-            f.inlineCallCount--;
         }
-        f.fp = cx->fp;
         entryFrame->ncode = f.fp->ncode;
         void *retPtr = JS_FUNC_TO_DATA_PTR(void *, JaegerFromTracer);
         *f.returnAddressLocation() = retPtr;
         return NULL;
     }
 
     /* Step 3.3. Do a partial interp, then restart the whole process. */
     if (!PartialInterpret(cx)) {
--- a/js/src/methodjit/MethodJIT.cpp
+++ b/js/src/methodjit/MethodJIT.cpp
@@ -39,16 +39,17 @@
 #include "MethodJIT.h"
 #include "Logging.h"
 #include "assembler/jit/ExecutableAllocator.h"
 #include "jstracer.h"
 #include "BaseAssembler.h"
 #include "MonoIC.h"
 #include "PolyIC.h"
 #include "TrampolineCompiler.h"
+#include "jscntxtinlines.h"
 
 using namespace js;
 using namespace js::mjit;
 
 #ifdef JS_METHODJIT_PROFILE_STUBS
 static const size_t STUB_CALLS_FOR_OP_COUNT = 255;
 static uint32 StubCallsForOp[STUB_CALLS_FOR_OP_COUNT];
 #endif
@@ -115,17 +116,17 @@ JS_STATIC_ASSERT(sizeof(VMFrame) % 16 ==
 # if defined(JS_CPU_X64)
 
 /*
  *    *** DANGER ***
  * If these assertions break, update the constants below.
  *    *** DANGER ***
  */
 JS_STATIC_ASSERT(offsetof(VMFrame, savedRBX) == 0x58);
-JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x40);
+JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x38);
 
 asm volatile (
 ".text\n"
 ".globl " SYMBOL_STRING(JaegerTrampoline) "\n"
 SYMBOL_STRING(JaegerTrampoline) ":"       "\n"
     /* Prologue. */
     "pushq %rbp"                         "\n"
     "movq %rsp, %rbp"                    "\n"
@@ -137,23 +138,24 @@ SYMBOL_STRING(JaegerTrampoline) ":"     
     "pushq %rbx"                         "\n"
 
     /* Build the JIT frame.
      * rdi = cx
      * rsi = fp
      * rcx = inlineCallCount
      * fp must go into rbx
      */
-    "pushq %rcx"                         "\n"
-    "pushq %rdi"                         "\n"
-    "pushq %rsi"                         "\n"
+    "pushq %rsi"                         "\n" /* entryFp */
+    "pushq %rcx"                         "\n" /* inlineCallCount */
+    "pushq %rdi"                         "\n" /* cx */
+    "pushq %rsi"                         "\n" /* fp */
     "movq  %rsi, %rbx"                   "\n"
 
     /* Space for the rest of the VMFrame. */
-    "subq  $0x38, %rsp"                  "\n"
+    "subq  $0x30, %rsp"                  "\n"
 
     /* Set cx->regs and set the active frame (requires saving rdx). */
     "pushq %rdx"                         "\n"
     "movq  %rsp, %rdi"                   "\n"
     "call " SYMBOL_STRING_RELOC(SetVMFrameRegs) "\n"
     "movq  %rsp, %rdi"                   "\n"
     "call " SYMBOL_STRING_RELOC(PushActiveVMFrame) "\n"
     "popq  %rdx"                         "\n"
@@ -199,17 +201,17 @@ SYMBOL_STRING(JaegerThrowpoline) ":"    
     "popq %r12"                             "\n"
     "popq %rbp"                             "\n"
     "xorq %rax,%rax"                        "\n"
     "ret"                                   "\n"
 );
 
 JS_STATIC_ASSERT(offsetof(JSStackFrame, rval) == 0x40);
 JS_STATIC_ASSERT(offsetof(JSStackFrame, ncode) == 0x60);
-JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x40);
+JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x38);
 
 JS_STATIC_ASSERT(JSVAL_TAG_MASK == 0xFFFF800000000000LL);
 JS_STATIC_ASSERT(JSVAL_PAYLOAD_MASK == 0x00007FFFFFFFFFFFLL);
 
 asm volatile (
 ".text\n"
 ".globl " SYMBOL_STRING(JaegerFromTracer)   "\n"
 SYMBOL_STRING(JaegerFromTracer) ":"         "\n"
@@ -217,51 +219,52 @@ SYMBOL_STRING(JaegerFromTracer) ":"     
     "movq $0xFFFF800000000000, %r11"         "\n" /* load type mask (JSVAL_TAG_MASK) */
     "andq %r11, %rcx"                       "\n" /* extract type */
 
     "movq 0x40(%rbx), %rdx"                 "\n" /* fp->rval type */
     "movq $0x00007FFFFFFFFFFF, %r11"        "\n" /* load payload mask (JSVAL_PAYLOAD_MASK) */
     "andq %r11, %rdx"                       "\n" /* extract payload */
 
     "movq 0x60(%rbx), %rax"                 "\n" /* fp->ncode */
-    "movq 0x40(%rsp), %rbx"                 "\n" /* f.fp */
+    "movq 0x38(%rsp), %rbx"                 "\n" /* f.fp */
     "ret"                                   "\n"
 );
 
 # elif defined(JS_CPU_X86)
 
 /*
  *    *** DANGER ***
  * If these assertions break, update the constants below. The throwpoline
  * should have the offset of savedEBX plus 4, because it needs to clean
  * up the argument.
  *    *** DANGER ***
  */
 JS_STATIC_ASSERT(offsetof(VMFrame, savedEBX) == 0x2c);
-JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x20);
+JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x1C);
 
 asm volatile (
 ".text\n"
 ".globl " SYMBOL_STRING(JaegerTrampoline) "\n"
 SYMBOL_STRING(JaegerTrampoline) ":"       "\n"
     /* Prologue. */
     "pushl %ebp"                         "\n"
     "movl %esp, %ebp"                    "\n"
     /* Save non-volatile registers. */
     "pushl %esi"                         "\n"
     "pushl %edi"                         "\n"
     "pushl %ebx"                         "\n"
 
     /* Build the JIT frame. Push fields in order, 
      * then align the stack to form esp == VMFrame. */
-    "pushl 20(%ebp)"                     "\n"
+    "movl  12(%ebp), %ebx"               "\n"   /* fp */
+    "pushl %ebx"                         "\n"   /* entryFp */
+    "pushl 20(%ebp)"                     "\n"   /* inlineCallCount */
     "pushl 8(%ebp)"                      "\n"
-    "pushl 12(%ebp)"                     "\n"
-    "movl  12(%ebp), %ebx"               "\n"
-    "subl $0x1c, %esp"                   "\n"
+    "pushl %ebx"                         "\n"
+    "subl $0x18, %esp"                   "\n"
 
     /* Jump into the JIT'd code. */
     "pushl 16(%ebp)"                     "\n"
     "movl  %esp, %ecx"                   "\n"
     "call " SYMBOL_STRING_RELOC(SetVMFrameRegs) "\n"
     "movl  %esp, %ecx"                   "\n"
     "call " SYMBOL_STRING_RELOC(PushActiveVMFrame) "\n"
     "popl  %edx"                         "\n"
@@ -307,26 +310,26 @@ SYMBOL_STRING(JaegerThrowpoline) ":"    
     "popl %esi"                          "\n"
     "popl %ebp"                          "\n"
     "xorl %eax, %eax"                    "\n"
     "ret"                                "\n"
 );
 
 JS_STATIC_ASSERT(offsetof(JSStackFrame, rval) == 0x28);
 JS_STATIC_ASSERT(offsetof(JSStackFrame, ncode) == 0x3C);
-JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x20);
+JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x1C);
 
 asm volatile (
 ".text\n"
 ".globl " SYMBOL_STRING(JaegerFromTracer)   "\n"
 SYMBOL_STRING(JaegerFromTracer) ":"         "\n"
     "movl 0x28(%ebx), %edx"                 "\n" /* fp->rval data */
     "movl 0x2C(%ebx), %ecx"                 "\n" /* fp->rval type */
     "movl 0x3C(%ebx), %eax"                 "\n" /* fp->ncode */
-    "movl 0x20(%esp), %ebx"                 "\n" /* f.fp */
+    "movl 0x1C(%esp), %ebx"                 "\n" /* f.fp */
     "ret"                                   "\n"
 );
 
 # elif defined(JS_CPU_ARM)
 
 JS_STATIC_ASSERT(offsetof(VMFrame, savedLR) == (sizeof(VMFrame)-4));
 JS_STATIC_ASSERT(sizeof(VMFrame) == 80);
 
@@ -453,50 +456,51 @@ SYMBOL_STRING(JaegerStubVeneer) ":"     
 /*
  *    *** DANGER ***
  * If these assertions break, update the constants below. The throwpoline
  * should have the offset of savedEBX plus 4, because it needs to clean
  * up the argument.
  *    *** DANGER ***
  */
 JS_STATIC_ASSERT(offsetof(VMFrame, savedEBX) == 0x2c);
-JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x20);
+JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x1C);
 
 extern "C" {
 
     __declspec(naked) void JaegerFromTracer()
     {
         __asm {
             mov edx, [ebx + 0x28];
             mov ecx, [ebx + 0x2C];
             mov eax, [ebx + 0x3C];
-            mov ebx, [esp + 0x20];
+            mov ebx, [esp + 0x1C];
             ret;
         }
     }
 
     __declspec(naked) JSBool JaegerTrampoline(JSContext *cx, JSStackFrame *fp, void *code,
-                                              uintptr_t inlineCallCount)
+                                              Value *stackLimit)
     {
         __asm {
             /* Prologue. */
             push ebp;
             mov ebp, esp;
             /* Save non-volatile registers. */
             push esi;
             push edi;
             push ebx;
 
             /* Build the JIT frame. Push fields in order, 
              * then align the stack to form esp == VMFrame. */
-            push [ebp+20];
-            push [ebp+8];
-            push [ebp+12];
-            mov  ebx, [ebp+12];
-            sub  esp, 0x1c;
+            mov  ebx, [ebp + 12];
+            push ebx;
+            push [ebp + 20];
+            push [ebp + 8];
+            push ebx;
+            sub  esp, 0x18;
 
             /* Jump into into the JIT'd code. */
             push [ebp+16];
             mov  ecx, esp;
             call SetVMFrameRegs;
             mov  ecx, esp;
             call PushActiveVMFrame;
             pop  edx;
@@ -601,30 +605,29 @@ ThreadData::Finish()
     fprintf(fp, "%03d %s %d\n", val, #op, StubCallsForOp[val]);
 # include "jsopcode.tbl"
 # undef OPDEF
     fclose(fp);
 #endif
 }
 
 extern "C" JSBool JaegerTrampoline(JSContext *cx, JSStackFrame *fp, void *code,
-                                   uintptr_t inlineCallCount);
+                                   Value *stackLimit);
 
 JSBool
 mjit::JaegerShot(JSContext *cx)
 {
     JS_ASSERT(cx->regs);
 
     JS_CHECK_RECURSION(cx, return JS_FALSE;);
 
     void *code;
     jsbytecode *pc = cx->regs->pc;
     JSStackFrame *fp = cx->fp;
     JSScript *script = fp->script;
-    uintptr_t inlineCallCount = 0;
 
     JS_ASSERT(script->ncode && script->ncode != JS_UNJITTABLE_METHOD);
 
 #ifdef JS_TRACER
     if (TRACE_RECORDER(cx))
         AbortRecording(cx, "attempt to enter method JIT while recording");
 #endif
 
@@ -641,24 +644,21 @@ mjit::JaegerShot(JSContext *cx)
     JaegerSpew(JSpew_Prof, "entering jaeger script: %s, line %d\n", fp->script->filename,
                fp->script->lineno);
     prof.start();
 #endif
 
 #ifdef DEBUG
     JSStackFrame *checkFp = fp;
 #endif
-#if 0
-    uintptr_t iCC = inlineCallCount;
-    while (iCC--)
-        checkFp = checkFp->down;
-#endif
+
+    Value *stackLimit = cx->stack().makeStackLimit(reinterpret_cast<Value*>(fp));
 
     JSAutoResolveFlags rf(cx, JSRESOLVE_INFER);
-    JSBool ok = JaegerTrampoline(cx, fp, code, inlineCallCount);
+    JSBool ok = JaegerTrampoline(cx, fp, code, stackLimit);
 
     JS_ASSERT(checkFp == cx->fp);
 
 #ifdef JS_METHODJIT_SPEW
     prof.stop();
     JaegerSpew(JSpew_Prof, "script run took %d ms\n", prof.time_ms());
 #endif
 
@@ -725,8 +725,15 @@ mjit::ReleaseScriptCode(JSContext *cx, J
 void JS_FASTCALL
 mjit::ProfileStubCall(VMFrame &f)
 {
     JSOp op = JSOp(*f.regs.pc);
     StubCallsForOp[op]++;
 }
 #endif
 
+bool
+VMFrame::slowEnsureSpace(uint32 nslots)
+{
+    return cx->stack().ensureSpace(cx, reinterpret_cast<Value*>(fp), regs.sp,
+                                   stackLimit, nslots);
+}
+
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -55,33 +55,30 @@
 
 namespace js {
 
 struct VMFrame
 {
     /* This must be the first entry on CPUs which push return addresses. */
     void *scriptedReturn;
 
-#if defined(JS_CPU_X86) || defined(JS_CPU_X64) || defined(JS_CPU_ARM)
-    uint32 padding;
-#endif
-
     union Arguments {
         struct {
             void *ptr;
             void *ptr2;
         } x;
     } u;
 
     VMFrame      *previous;
     JSFrameRegs  *oldRegs;
     JSFrameRegs  regs;
     JSStackFrame *fp;
     JSContext    *cx;
-    uint32       inlineCallCount;
+    Value        *stackLimit;
+    JSStackFrame *entryFp;
 
 #if defined(JS_CPU_X86)
     void *savedEBX;
     void *savedEDI;
     void *savedESI;
     void *savedEBP;
     void *savedEIP;
 
@@ -131,16 +128,27 @@ struct VMFrame
     inline void** returnAddressLocation() {
         return reinterpret_cast<void**>(this) - 1;
     }
 #else
 # error "The VMFrame layout isn't defined for your processor architecture!"
 #endif
 
     JSRuntime *runtime() { return cx->runtime; }
+
+    bool slowEnsureSpace(uint32 nslots);
+
+    inline bool ensureSpace(uint32 nmissing, uint32 nslots) {
+        /* Fast check - if it's below the limit, it's safe to just get a frame. */
+        if (JS_LIKELY(regs.sp + nmissing + nslots < stackLimit))
+            return true;
+
+        /* Slower check that might have to commit memory or throw an error. */
+        return slowEnsureSpace(nmissing + nslots);
+    }
 };
 
 #ifdef JS_CPU_ARM
 // WARNING: Do not call this function directly from C(++) code because it is not ABI-compliant.
 extern "C" void JaegerStubVeneer(void);
 #endif
 
 typedef void (JS_FASTCALL *VoidStub)(VMFrame &);
--- a/js/src/methodjit/TrampolineCompiler.cpp
+++ b/js/src/methodjit/TrampolineCompiler.cpp
@@ -128,17 +128,16 @@ TrampolineCompiler::generateForceReturn(
      * a1 = f.cx
      * f.fp = r
      * cx->fp = r
      */
     masm.loadPtr(Address(JSFrameReg, offsetof(JSStackFrame, down)), Registers::ReturnReg);
     masm.loadPtr(FrameAddress(offsetof(VMFrame, cx)), Registers::ArgReg1);
     masm.storePtr(Registers::ReturnReg, FrameAddress(offsetof(VMFrame, fp)));
     masm.storePtr(Registers::ReturnReg, Address(Registers::ArgReg1, offsetof(JSContext, fp)));
-    masm.sub32(Imm32(1), FrameAddress(offsetof(VMFrame, inlineCallCount)));
 
     Address rval(JSFrameReg, offsetof(JSStackFrame, rval));
     masm.loadPayload(rval, JSReturnReg_Data);
     masm.loadTypeTag(rval, JSReturnReg_Type);
     masm.move(Registers::ReturnReg, JSFrameReg);
     masm.loadPtr(Address(JSFrameReg, offsetof(JSStackFrame, ncode)), Registers::ReturnReg);
 #ifdef DEBUG
     masm.storePtr(ImmPtr(JSStackFrame::sInvalidPC),
--- a/js/src/trace-test/tests/basic/testStackQuotaExhausted.js
+++ b/js/src/trace-test/tests/basic/testStackQuotaExhausted.js
@@ -12,32 +12,38 @@ function maybeTrace(x) {
         return 0;
     return maybeTrace(x-1);
 }
 
 function fatStack() {
     return maybeTrace(traceDepth);
 }
 
+function assertRightFailure(e) {
+    assertEq(e.toString() == "InternalError: script stack space quota is exhausted" ||
+             e.toString() == "InternalError: too much recursion",
+	     true);
+}
+
 // This tests that we conservatively guard against stack space exhaustion
 // before entering trace.
 exception = false;
 try {
     fatStack.apply(null, new Array(numFatArgs));
 } catch (e) {
-    assertEq(e.toString(), "InternalError: script stack space quota is exhausted");
+    assertRightFailure(e);
     exception = true;
 }
 assertEq(exception, true);
 
 // No more trace recursion w/ JM
 checkStats({traceCompleted:0});
 
 // This tests that, without tracing, we exhaust stack space.
 trace = false;
 var exception = false;
 try {
     fatStack.apply(null, new Array(numFatArgs));
 } catch (e) {
-    assertEq(e.toString(), "InternalError: script stack space quota is exhausted");
+    assertRightFailure(e);
     exception = true;
 }
 assertEq(exception, true);