Bug 535656 - remove JSStackFrame::dormantNext and varobj (r=waldo)
authorLuke Wagner <lw@mozilla.com>
Fri, 29 Jan 2010 18:25:16 -0800
changeset 37777 5d8801fe08f504d46ba16d616d487f300be91477
parent 37776 a8dc506b24605ef6e53a085b4f90b28c2f51d237
child 37778 3048d03980e70a1ae7de23dc1ffb2ac7c586fb15
child 38497 a24c3eeac2f3534c1f17b0b350c9856255dcaf9b
push id11426
push userrsayre@mozilla.com
push dateSun, 31 Jan 2010 16:36:36 +0000
treeherdermozilla-central@3048d03980e7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswaldo
bugs535656
milestone1.9.3a1pre
Bug 535656 - remove JSStackFrame::dormantNext and varobj (r=waldo)
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsdbgapi.cpp
js/src/jsemit.cpp
js/src/jsfun.cpp
js/src/jsgc.cpp
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsiter.cpp
js/src/jsiter.h
js/src/jsobj.cpp
js/src/jsops.cpp
js/src/jsprvtd.h
js/src/jsscript.cpp
js/src/jstracer.cpp
js/src/jsxml.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5181,41 +5181,31 @@ JS_PUBLIC_API(JSBool)
 JS_IsConstructing(JSContext *cx)
 {
     return cx->isConstructing();
 }
 
 JS_PUBLIC_API(JSStackFrame *)
 JS_SaveFrameChain(JSContext *cx)
 {
-    JSStackFrame *fp;
-
-    fp = js_GetTopStackFrame(cx);
+    JSStackFrame *fp = js_GetTopStackFrame(cx);
     if (!fp)
-        return fp;
-
-    JS_ASSERT(!fp->dormantNext);
-    fp->dormantNext = cx->dormantFrameChain;
-    cx->dormantFrameChain = fp;
-    cx->fp = NULL;
+        return NULL;
+    cx->saveActiveCallStack();
     return fp;
 }
 
 JS_PUBLIC_API(void)
 JS_RestoreFrameChain(JSContext *cx, JSStackFrame *fp)
 {
     JS_ASSERT_NOT_ON_TRACE(cx);
     JS_ASSERT(!cx->fp);
     if (!fp)
         return;
-
-    JS_ASSERT(fp == cx->dormantFrameChain);
-    cx->fp = fp;
-    cx->dormantFrameChain = fp->dormantNext;
-    fp->dormantNext = NULL;
+    cx->restoreCallStack();
 }
 
 /************************************************************************/
 
 JS_PUBLIC_API(JSString *)
 JS_NewString(JSContext *cx, char *bytes, size_t nbytes)
 {
     size_t length = nbytes;
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -74,16 +74,37 @@
 using namespace js;
 
 static void
 FreeContext(JSContext *cx);
 
 static void
 MarkLocalRoots(JSTracer *trc, JSLocalRootStack *lrs);
 
+#ifdef DEBUG
+bool
+CallStack::contains(JSStackFrame *fp)
+{
+    JSStackFrame *start;
+    JSStackFrame *stop;
+    if (isSuspended()) {
+        start = suspendedFrame;
+        stop = initialFrame->down;
+    } else {
+        start = cx->fp;
+        stop = cx->activeCallStack()->initialFrame->down;
+    }
+    for (JSStackFrame *f = start; f != stop; f = f->down) {
+        if (f == fp)
+            return true;
+    }
+    return false;
+}
+#endif
+
 void
 JSThreadData::init()
 {
 #ifdef DEBUG
     /* The data must be already zeroed. */
     for (size_t i = 0; i != sizeof(*this); ++i)
         JS_ASSERT(reinterpret_cast<uint8*>(this)[i] == 0);
 #endif
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -190,16 +190,117 @@ struct TraceNativeStorage
 /* Holds data to track a single globa. */
 struct GlobalState {
     JSObject*               globalObj;
     uint32                  globalShape;
     SlotList*               globalSlots;
 };
 
 /*
+ * A callstack contains a set of stack frames linked by fp->down. A callstack
+ * is a member of a JSContext and all of a JSContext's callstacks are kept in a
+ * list starting at cx->currentCallStack. A callstack may be active or
+ * suspended. There are zero or one active callstacks for a context and any
+ * number of suspended contexts. If there is an active context, it is the first
+ * in the currentCallStack list, |cx->fp != NULL| and the callstack's newest
+ * (top) stack frame is |cx->fp|. For all other (suspended) callstacks, the
+ * newest frame is pointed to by suspendedFrame.
+ *
+ * While all frames in a callstack are down-linked, not all down-linked frames
+ * are in the same callstack (e.g., calling js_Execute with |down != cx->fp|
+ * will create a new frame in a new active callstack).
+ */
+class CallStack
+{
+#ifdef DEBUG
+    /* The context to which this callstack belongs. */
+    JSContext           *cx;
+#endif
+
+    /* If this callstack is suspended, the top of the callstack. */
+    JSStackFrame        *suspendedFrame;
+
+    /* This callstack was suspended by JS_SaveFrameChain. */
+    bool                saved;
+
+    /* Links members of the JSContext::currentCallStack list. */
+    CallStack           *previous;
+
+    /* The varobj on entry to initialFrame. */
+    JSObject            *initialVarObj;
+
+    /* The first frame executed in this callstack. */
+    JSStackFrame        *initialFrame;
+
+  public:
+    CallStack(JSContext *cx)
+      :
+#ifdef DEBUG
+        cx(cx),
+#endif
+        suspendedFrame(NULL), saved(false), previous(NULL),
+        initialVarObj(NULL), initialFrame(NULL)
+    {}
+
+#ifdef DEBUG
+    bool contains(JSStackFrame *fp);
+#endif
+
+    void suspend(JSStackFrame *fp) {
+        JS_ASSERT(fp && !isSuspended() && contains(fp));
+        suspendedFrame = fp;
+    }
+
+    void resume() {
+        JS_ASSERT(suspendedFrame);
+        suspendedFrame = NULL;
+    }
+
+    JSStackFrame *getSuspendedFrame() const {
+        JS_ASSERT(suspendedFrame);
+        return suspendedFrame;
+    }
+
+    bool isSuspended() const { return suspendedFrame; }
+
+    void setPrevious(CallStack *cs) { previous = cs; }
+    CallStack *getPrevious() const  { return previous; }
+
+    void setInitialVarObj(JSObject *o) { initialVarObj = o; }
+    JSObject *getInitialVarObj() const { return initialVarObj; }
+
+    void setInitialFrame(JSStackFrame *f) { initialFrame = f; }
+    JSStackFrame *getInitialFrame() const { return initialFrame; }
+
+    /*
+     * Saving and restoring is a special case of suspending and resuming
+     * whereby the active callstack becomes suspended without pushing a new
+     * active callstack. This means that if a callstack c1 is pushed on top of a
+     * saved callstack c2, when c1 is popped, c2 must not be made active. In
+     * the normal case, where c2 is not saved, when c1 is popped, c2 is made
+     * active. This distinction is indicated by the |saved| flag.
+     */
+
+    void save(JSStackFrame *fp) {
+        suspend(fp);
+        saved = true;
+    }
+
+    void restore() {
+        saved = false;
+        resume();
+    }
+
+    bool isSaved() const {
+        JS_ASSERT_IF(saved, isSuspended());
+        return saved;
+    }
+};
+
+/*
  * Trace monitor. Every JSThread (if JS_THREADSAFE) or JSRuntime (if not
  * JS_THREADSAFE) has an associated trace monitor that keeps track of loop
  * frequencies for all JavaScript code loaded into that runtime.
  */
 struct TraceMonitor {
     /*
      * The context currently executing JIT-compiled code on this thread, or
      * NULL if none. Among other things, this can in certain cases prevent
@@ -1082,16 +1183,26 @@ typedef struct JSResolvingEntry {
 
 #define JS_PUSH_TEMP_ROOT_SCRIPT(cx,script_,tvr)                              \
     JS_PUSH_TEMP_ROOT_COMMON(cx, script_, tvr, JSTVU_SCRIPT, script)
 
 #define JSRESOLVE_INFER         0xffff  /* infer bits from current bytecode */
 
 extern const JSDebugHooks js_NullDebugHooks;  /* defined in jsdbgapi.cpp */
 
+/*
+ * Wraps a stack frame which has been temporarily popped from its call stack
+ * and needs to be GC-reachable. See JSContext::{push,pop}GCReachableFrame.
+ */
+struct JSGCReachableFrame
+{
+    JSGCReachableFrame  *next;
+    JSStackFrame        *frame;
+};
+
 struct JSContext {
     /*
      * If this flag is set, we were asked to call back the operation callback
      * as soon as possible.
      */
     volatile jsint      operationCallbackFlag;
 
     /* JSRuntime contextList linkage. */
@@ -1199,18 +1310,77 @@ struct JSContext {
 
     /* Interpreter activation count. */
     uintN               interpLevel;
 
     /* Client opaque pointers. */
     void                *data;
     void                *data2;
 
-    /* GC and thread-safe state. */
-    JSStackFrame        *dormantFrameChain; /* dormant stack frame to scan */
+    /* Linked list of frames temporarily popped from their chain. */
+    JSGCReachableFrame  *reachableFrames;
+
+    void pushGCReachableFrame(JSGCReachableFrame &gcrf, JSStackFrame *f) {
+        gcrf.next = reachableFrames;
+        gcrf.frame = f;
+        reachableFrames = &gcrf;
+    }
+
+    void popGCReachableFrame() {
+        reachableFrames = reachableFrames->next;
+    }
+
+  private:
+    friend void js_TraceContext(JSTracer *, JSContext *);
+
+    /* Linked list of callstacks. See CallStack. */
+    js::CallStack       *currentCallStack;
+
+  public:
+    /* Assuming there is an active callstack, return it. */
+    js::CallStack *activeCallStack() const {
+        JS_ASSERT(currentCallStack && !currentCallStack->isSaved());
+        return currentCallStack;
+    }
+
+    /* Add the given callstack to the list as the new active callstack. */
+    void pushCallStack(js::CallStack *newcs) {
+        if (fp)
+            currentCallStack->suspend(fp);
+        else
+            JS_ASSERT_IF(currentCallStack, currentCallStack->isSaved());
+        newcs->setPrevious(currentCallStack);
+        currentCallStack = newcs;
+        JS_ASSERT(!newcs->isSuspended() && !newcs->isSaved());
+    }
+
+    /* Remove the active callstack and make the next callstack active. */
+    void popCallStack() {
+        JS_ASSERT(!currentCallStack->isSuspended() && !currentCallStack->isSaved());
+        currentCallStack = currentCallStack->getPrevious();
+        if (currentCallStack && !currentCallStack->isSaved()) {
+            JS_ASSERT(fp);
+            currentCallStack->resume();
+        }
+    }
+
+    /* Mark the top callstack as suspended, without pushing a new one. */
+    void saveActiveCallStack() {
+        JS_ASSERT(fp && currentCallStack && !currentCallStack->isSuspended());
+        currentCallStack->save(fp);
+        fp = NULL;
+    }
+
+    /* Undoes calls to suspendTopCallStack. */
+    void restoreCallStack() {
+        JS_ASSERT(!fp && currentCallStack && currentCallStack->isSuspended());
+        fp = currentCallStack->getSuspendedFrame();
+        currentCallStack->restore();
+    }
+
 #ifdef JS_THREADSAFE
     JSThread            *thread;
     jsrefcount          requestDepth;
     /* Same as requestDepth but ignoring JS_SuspendRequest/JS_ResumeRequest */
     jsrefcount          outstandingRequests;
     JSTitle             *lockedSealedTitle; /* weak ref, for low-cost sealed
                                                title locking */
     JSCList             threadLinks;        /* JSThread contextList linkage */
@@ -1423,16 +1593,30 @@ private:
      * The allocation code calls the function to indicate either OOM failure
      * when p is null or that a memory pressure counter has reached some
      * threshold when p is not null. The function takes the pointer and not
      * a boolean flag to minimize the amount of code in its inlined callers.
      */
     void checkMallocGCPressure(void *p);
 };
 
+JS_ALWAYS_INLINE JSObject *
+JSStackFrame::varobj(js::CallStack *cs)
+{
+    JS_ASSERT(cs->contains(this));
+    return fun ? callobj : cs->getInitialVarObj();
+}
+
+JS_ALWAYS_INLINE JSObject *
+JSStackFrame::varobj(JSContext *cx)
+{
+    JS_ASSERT(cx->activeCallStack()->contains(this));
+    return fun ? callobj : cx->activeCallStack()->getInitialVarObj();
+}
+
 #ifdef JS_THREADSAFE
 # define JS_THREAD_ID(cx)       ((cx)->thread ? (cx)->thread->id : 0)
 #endif
 
 #ifdef __cplusplus
 
 static inline JSAtom **
 FrameAtomBase(JSContext *cx, JSStackFrame *fp)
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -1216,40 +1216,37 @@ JS_GetFrameCallObject(JSContext *cx, JSS
      *     null returned above or in the #else
      */
     return js_GetCallObject(cx, fp);
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_GetFrameThis(JSContext *cx, JSStackFrame *fp)
 {
-    JSStackFrame *afp;
-
     if (fp->flags & JSFRAME_COMPUTED_THIS)
         return JSVAL_TO_OBJECT(fp->thisv);  /* JSVAL_COMPUTED_THIS invariant */
 
     /* js_ComputeThis gets confused if fp != cx->fp, so set it aside. */
-    if (js_GetTopStackFrame(cx) != fp) {
-        afp = cx->fp;
+    JSStackFrame *afp = js_GetTopStackFrame(cx);
+    JSGCReachableFrame reachable;
+    if (afp != fp) {
         if (afp) {
-            afp->dormantNext = cx->dormantFrameChain;
-            cx->dormantFrameChain = afp;
             cx->fp = fp;
+            cx->pushGCReachableFrame(reachable, afp);
         }
     } else {
         afp = NULL;
     }
 
     if (fp->argv)
         fp->thisv = OBJECT_TO_JSVAL(js_ComputeThis(cx, JS_TRUE, fp->argv));
 
     if (afp) {
         cx->fp = afp;
-        cx->dormantFrameChain = afp->dormantNext;
-        afp->dormantNext = NULL;
+        cx->popGCReachableFrame();
     }
 
     return JSVAL_TO_OBJECT(fp->thisv);
 }
 
 JS_PUBLIC_API(JSFunction *)
 JS_GetFrameFunction(JSContext *cx, JSStackFrame *fp)
 {
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -2094,17 +2094,17 @@ BindNameToSlot(JSContext *cx, JSCodeGene
              * Make sure the variable object used by the compiler to initialize
              * parent links matches the caller's varobj. Compile-n-go compiler-
              * created function objects have the top-level cg's scopeChain set
              * as their parent by JSCompiler::newFunction.
              */
             JSObject *scopeobj = (cg->flags & TCF_IN_FUNCTION)
                                  ? STOBJ_GET_PARENT(FUN_OBJECT(cg->fun))
                                  : cg->scopeChain;
-            if (scopeobj != caller->varobj)
+            if (scopeobj != caller->varobj(cx))
                 return JS_TRUE;
 
             /*
              * We are compiling eval or debug script inside a function frame
              * and the scope chain matches the function's variable object.
              * Optimize access to function's arguments and variable and the
              * arguments object.
              */
@@ -2189,17 +2189,17 @@ BindNameToSlot(JSContext *cx, JSCodeGene
 
         JSTreeContext *tc = cg;
         while (tc->staticLevel != level)
             tc = tc->parent;
         JS_ASSERT(tc->flags & TCF_COMPILING);
 
         JSCodeGenerator *evalcg = (JSCodeGenerator *) tc;
         JS_ASSERT(evalcg->flags & TCF_COMPILE_N_GO);
-        JS_ASSERT(caller->fun && caller->varobj == evalcg->scopeChain);
+        JS_ASSERT(caller->fun && caller->varobj(cx) == evalcg->scopeChain);
 
         /*
          * Don't generate upvars on the left side of a for loop. See
          * bug 470758 and bug 520513.
          */
         if (evalcg->flags & TCF_IN_FOR_INIT)
             return JS_TRUE;
 
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -224,17 +224,19 @@ PutArguments(JSContext *cx, JSObject *ar
 
 JSObject *
 js_GetArgsObject(JSContext *cx, JSStackFrame *fp)
 {
     /*
      * We must be in a function activation; the function must be lightweight
      * or else fp must have a variable object.
      */
-    JS_ASSERT(fp->fun && (!(fp->fun->flags & JSFUN_HEAVYWEIGHT) || fp->varobj));
+    JS_ASSERT(fp->fun);
+    JS_ASSERT_IF(fp->fun->flags & JSFUN_HEAVYWEIGHT,
+                 fp->varobj(js_ContainingCallStack(cx, fp)));
 
     /* Skip eval and debugger frames. */
     while (fp->flags & JSFRAME_SPECIAL)
         fp = fp->down;
 
     /* Create an arguments object for fp only if it lacks one. */
     JSObject *argsobj = JSVAL_TO_OBJECT(fp->argsobj);
     if (argsobj)
@@ -846,17 +848,16 @@ js_GetCallObject(JSContext *cx, JSStackF
     STOBJ_SET_SLOT(callobj, JSSLOT_CALLEE, fp->calleeValue());
     fp->callobj = callobj;
 
     /*
      * Push callobj on the top of the scope chain, and make it the
      * variables object.
      */
     fp->scopeChain = callobj;
-    fp->varobj = callobj;
     return callobj;
 }
 
 JSObject * JS_FASTCALL
 js_CreateCallObjectOnTrace(JSContext *cx, JSFunction *fun, JSObject *callee, JSObject *scopeChain)
 {
     JS_ASSERT(!js_IsNamedLambda(fun));
     JSObject *callobj = NewCallObject(cx, fun, scopeChain);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2277,18 +2277,16 @@ void
 js_TraceStackFrame(JSTracer *trc, JSStackFrame *fp)
 {
     uintN nslots, minargs, skip;
 
     if (fp->callobj)
         JS_CALL_OBJECT_TRACER(trc, fp->callobj, "call");
     if (fp->argsobj)
         JS_CALL_OBJECT_TRACER(trc, JSVAL_TO_OBJECT(fp->argsobj), "arguments");
-    if (fp->varobj)
-        JS_CALL_OBJECT_TRACER(trc, fp->varobj, "variables");
     if (fp->script) {
         js_TraceScript(trc, fp->script);
 
         /* fp->slots is null for watch pseudo-frames, see js_watch_set. */
         if (fp->slots) {
             /*
              * Don't mark what has not been pushed yet, or what has been
              * popped already.
@@ -2362,20 +2360,27 @@ JSWeakRoots::mark(JSTracer *trc)
     }
     if (newbornDouble)
         JS_CALL_DOUBLE_TRACER(trc, newbornDouble, "newborn_double");
     JS_CALL_VALUE_TRACER(trc, lastAtom, "lastAtom");
     JS_SET_TRACING_NAME(trc, "lastInternalResult");
     js_CallValueTracerIfGCThing(trc, lastInternalResult);
 }
 
+static void inline
+TraceFrameChain(JSTracer *trc, JSStackFrame *fp)
+{
+    do {
+        js_TraceStackFrame(trc, fp);
+    } while ((fp = fp->down) != NULL);
+}
+
 JS_REQUIRES_STACK JS_FRIEND_API(void)
 js_TraceContext(JSTracer *trc, JSContext *acx)
 {
-    JSStackFrame *fp, *nextChain;
     JSStackHeader *sh;
     JSTempValueRooter *tvr;
 
     if (IS_GC_MARKING_TRACER(trc)) {
 
 #define FREE_OLD_ARENAS(pool)                                                 \
         JS_BEGIN_MACRO                                                        \
             int64 _age;                                                       \
@@ -2398,48 +2403,49 @@ js_TraceContext(JSTracer *trc, JSContext
         /*
          * Release the regexpPool's arenas based on the same criterion as for
          * the stackPool.
          */
         FREE_OLD_ARENAS(acx->regexpPool);
     }
 
     /*
-     * Iterate frame chain and dormant chains.
-     *
-     * (NB: see comment on this whole "dormant" thing in js_Execute.)
+     * Trace active and suspended callstacks.
      *
      * Since js_GetTopStackFrame needs to dereference cx->thread to check for
      * JIT frames, we check for non-null thread here and avoid null checks
      * there. See bug 471197.
      */
 #ifdef JS_THREADSAFE
     if (acx->thread)
 #endif
     {
-        fp = js_GetTopStackFrame(acx);
-        nextChain = acx->dormantFrameChain;
-        if (!fp)
-            goto next_chain;
-
-        /* The top frame must not be dormant. */
-        JS_ASSERT(!fp->dormantNext);
-        for (;;) {
-            do {
-                js_TraceStackFrame(trc, fp);
-            } while ((fp = fp->down) != NULL);
-
-          next_chain:
-            if (!nextChain)
-                break;
-            fp = nextChain;
-            nextChain = nextChain->dormantNext;
+        /* If |cx->fp|, the active callstack has newest (top) frame |cx->fp|. */
+        JSStackFrame *fp = js_GetTopStackFrame(acx);
+        if (fp) {
+            JS_ASSERT(!acx->activeCallStack()->isSuspended());
+            TraceFrameChain(trc, fp);
+            if (JSObject *o = acx->activeCallStack()->getInitialVarObj())
+                JS_CALL_OBJECT_TRACER(trc, o, "variables");
+        }
+
+        /* Trace suspended frames. */
+        CallStack *cur = acx->currentCallStack;
+        CallStack *cs = fp ? cur->getPrevious() : cur;
+        for (; cs; cs = cs->getPrevious()) {
+            TraceFrameChain(trc, cs->getSuspendedFrame());
+            if (cs->getInitialVarObj())
+                JS_CALL_OBJECT_TRACER(trc, cs->getInitialVarObj(), "var env");
         }
     }
 
+    /* Trace frames that have been temporarily removed but need to be marked. */
+    for (JSGCReachableFrame *rf = acx->reachableFrames; rf; rf = rf->next)
+        TraceFrameChain(trc, rf->frame);
+
     /* Mark other roots-by-definition in acx. */
     if (acx->globalObject && !JS_HAS_OPTION(acx, JSOPTION_UNROOTED_GLOBAL))
         JS_CALL_OBJECT_TRACER(trc, acx->globalObject, "global object");
     acx->weakRoots.mark(trc);
     if (acx->throwing) {
         JS_CALL_VALUE_TRACER(trc, acx->exception, "exception");
     } else {
         /* Avoid keeping GC-ed junk stored in JSContext.exception. */
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -891,50 +891,51 @@ JS_STATIC_INTERPRET JSObject *
 js_ComputeGlobalThis(JSContext *cx, JSBool lazy, jsval *argv)
 {
     JSObject *thisp;
 
     if (JSVAL_IS_PRIMITIVE(argv[-2]) ||
         !OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(argv[-2]))) {
         thisp = cx->globalObject;
     } else {
-        JSStackFrame *fp;
         jsid id;
         jsval v;
         uintN attrs;
         JSBool ok;
         JSObject *parent;
 
         /*
          * Walk up the parent chain, first checking that the running script
          * has access to the callee's parent object. Note that if lazy, the
          * running script whose principals we want to check is the script
          * associated with fp->down, not with fp.
          *
          * FIXME: 417851 -- this access check should not be required, as it
          * imposes a performance penalty on all js_ComputeGlobalThis calls,
          * and it represents a maintenance hazard.
+         *
+         * When the above FIXME is made fixed, the whole GC reachable frame
+         * mechanism can be removed as well.
          */
-        fp = js_GetTopStackFrame(cx);    /* quell GCC overwarning */
+        JSStackFrame *fp = js_GetTopStackFrame(cx);
+        JSGCReachableFrame reachable;
         if (lazy) {
             JS_ASSERT(fp->argv == argv);
-            fp->dormantNext = cx->dormantFrameChain;
-            cx->dormantFrameChain = fp;
             cx->fp = fp->down;
             fp->down = NULL;
+            cx->pushGCReachableFrame(reachable, fp);
         }
         thisp = JSVAL_TO_OBJECT(argv[-2]);
         id = ATOM_TO_JSID(cx->runtime->atomState.parentAtom);
 
         ok = thisp->checkAccess(cx, id, JSACC_PARENT, &v, &attrs);
         if (lazy) {
-            cx->dormantFrameChain = fp->dormantNext;
-            fp->dormantNext = NULL;
             fp->down = cx->fp;
             cx->fp = fp;
+            cx->popGCReachableFrame();
         }
         if (!ok)
             return NULL;
 
         thisp = JSVAL_IS_VOID(v)
                 ? OBJ_GET_PARENT(cx, thisp)
                 : JSVAL_TO_OBJECT(v);
         while ((parent = OBJ_GET_PARENT(cx, thisp)) != NULL)
@@ -1094,30 +1095,32 @@ const uint16 js_PrimitiveTestFlags[] = {
  * under argc arguments on cx's stack, and call the function.  Push missing
  * required arguments, allocate declared local variables, and pop everything
  * when done.  Then push the return value.
  */
 JS_REQUIRES_STACK JS_FRIEND_API(JSBool)
 js_Invoke(JSContext *cx, uintN argc, jsval *vp, uintN flags)
 {
     void *mark;
+    CallStack callStack(cx);
     JSStackFrame frame;
     jsval *sp, *argv, *newvp;
     jsval v;
     JSObject *funobj, *parent;
     JSBool ok;
     JSClass *clasp;
     const JSObjectOps *ops;
     JSNative native;
     JSFunction *fun;
     JSScript *script;
     uintN nslots, i;
     uint32 rootedArgsFlag;
     JSInterpreterHook hook;
     void *hookData;
+    bool pushCall;
 
     JS_ASSERT(argc <= JS_ARGS_LENGTH_MAX);
 
     /* [vp .. vp + 2 + argc) must belong to the last JS stack arena. */
     JS_ASSERT((jsval *) cx->stackPool.current->base <= vp);
     JS_ASSERT(vp + 2 + argc <= (jsval *) cx->stackPool.current->avail);
 
     /* Mark the top of stack and load frequently-used registers. */
@@ -1298,17 +1301,16 @@ have_fun:
         for (jsval *end = sp + fun->u.i.nvars; sp != end; ++sp)
             *sp = JSVAL_VOID;
     }
 
     /*
      * Initialize the frame.
      */
     frame.thisv = vp[1];
-    frame.varobj = NULL;
     frame.callobj = NULL;
     frame.argsobj = NULL;
     frame.script = script;
     frame.fun = fun;
     frame.argc = argc;
     frame.argv = argv;
 
     /* Default return value for a constructor is the new object. */
@@ -1316,36 +1318,42 @@ have_fun:
     frame.down = cx->fp;
     frame.annotation = NULL;
     frame.scopeChain = NULL;    /* set below for real, after cx->fp is set */
     frame.blockChain = NULL;
     frame.regs = NULL;
     frame.imacpc = NULL;
     frame.slots = NULL;
     frame.flags = flags | rootedArgsFlag;
-    frame.dormantNext = NULL;
     frame.displaySave = NULL;
 
     MUST_FLOW_THROUGH("out");
+    pushCall = !cx->fp;
+    if (pushCall) {
+        /*
+         * The initialVarObj is left NULL since fp->callobj is NULL and, for
+         * interpreted functions, fp->varobj() == fp->callobj.
+         */
+        callStack.setInitialFrame(&frame);
+        cx->pushCallStack(&callStack);
+    }
     cx->fp = &frame;
 
     /* Init these now in case we goto out before first hook call. */
     hook = cx->debugHooks->callHook;
     hookData = NULL;
 
     if (native) {
-        /* If native, use caller varobj and scopeChain for eval. */
-        JS_ASSERT(!frame.varobj);
-        JS_ASSERT(!frame.scopeChain);
+        /* Slow natives expect the caller's scopeChain as their scopeChain. */
         if (frame.down) {
-            frame.varobj = frame.down->varobj;
+            JS_ASSERT(!pushCall);
             frame.scopeChain = frame.down->scopeChain;
         }
 
-        /* But ensure that we have a scope chain. */
+        /* Ensure that we have a scope chain. */
         if (!frame.scopeChain)
             frame.scopeChain = parent;
     } else {
         /* Use parent scope so js_GetCallObject can find the right "Call". */
         frame.scopeChain = parent;
         if (JSFUN_HEAVYWEIGHT_TEST(fun->flags)) {
             /* Scope with a call object parented by the callee's parent. */
             if (!js_GetCallObject(cx, &frame)) {
@@ -1403,16 +1411,18 @@ out:
             hook(cx, &frame, JS_FALSE, &ok, hookData);
     }
 
     frame.putActivationObjects(cx);
 
     *vp = frame.rval;
 
     /* Restore cx->fp now that we're done releasing frame objects. */
+    if (pushCall)
+        cx->popCallStack();
     cx->fp = frame.down;
 
 out2:
     /* Pop everything we may have allocated off the stack. */
     JS_ARENA_RELEASE(&cx->stackPool, mark);
     if (!ok)
         *vp = JSVAL_NULL;
     return ok;
@@ -1475,181 +1485,217 @@ js_InternalGetOrSet(JSContext *cx, JSObj
      * js_InternalInvoke could result in another try to get or set the same id
      * again, see bug 355497.
      */
     JS_CHECK_RECURSION(cx, return JS_FALSE);
 
     return js_InternalCall(cx, obj, fval, argc, argv, rval);
 }
 
+CallStack *
+js_ContainingCallStack(JSContext *cx, JSStackFrame *target)
+{
+    JS_ASSERT(cx->fp);
+
+    /* The active callstack's top frame is cx->fp. */
+    CallStack *cs = cx->activeCallStack();
+    JSStackFrame *f = cx->fp;
+    JSStackFrame *stop = cs->getInitialFrame()->down;
+    for (; f != stop; f = f->down) {
+        if (f == target)
+            return cs;
+    }
+
+    /* A suspended callstack's top frame is its suspended frame. */
+    for (cs = cs->getPrevious(); cs; cs = cs->getPrevious()) {
+        f = cs->getSuspendedFrame();
+        stop = cs->getInitialFrame()->down;
+        for (; f != stop; f = f->down) {
+            if (f == target)
+                return cs;
+        }
+    }
+
+    return NULL;
+}
+
 JSBool
 js_Execute(JSContext *cx, JSObject *chain, JSScript *script,
            JSStackFrame *down, uintN flags, jsval *result)
 {
-    JSInterpreterHook hook;
-    void *hookData, *mark;
-    JSStackFrame *oldfp, frame;
-    JSObject *obj, *tmp;
-    JSBool ok;
-
     if (script->isEmpty()) {
         if (result)
             *result = JSVAL_VOID;
         return JS_TRUE;
     }
 
     LeaveTrace(cx);
 
 #ifdef INCLUDE_MOZILLA_DTRACE
-    if (JAVASCRIPT_EXECUTE_START_ENABLED())
-        jsdtrace_execute_start(script);
+    struct JSDNotifyGuard {
+        JSScript *script;
+        JSDNotifyGuard(JSScript *s) : script(s) {
+            if (JAVASCRIPT_EXECUTE_START_ENABLED())
+                jsdtrace_execute_start(script);
+        }
+        ~JSDNotifyGuard() {
+            if (JAVASCRIPT_EXECUTE_DONE_ENABLED())
+                jsdtrace_execute_done(script);
+        }
+
+    } jsdNotifyGuard(script);
 #endif
 
-    hook = cx->debugHooks->executeHook;
-    hookData = mark = NULL;
-    oldfp = js_GetTopStackFrame(cx);
+    JSInterpreterHook hook = cx->debugHooks->executeHook;
+    void *hookData = NULL;
+    JSStackFrame frame;
+    CallStack callStack(cx);
     frame.script = script;
     if (down) {
         /* Propagate arg state for eval and the debugger API. */
         frame.callobj = down->callobj;
         frame.argsobj = down->argsobj;
-        frame.varobj = down->varobj;
         frame.fun = (script->staticLevel > 0) ? down->fun : NULL;
         frame.thisv = down->thisv;
         if (down->flags & JSFRAME_COMPUTED_THIS)
             flags |= JSFRAME_COMPUTED_THIS;
         frame.argc = down->argc;
         frame.argv = down->argv;
         frame.annotation = down->annotation;
+
+        /*
+         * We want to call |down->varobj()|, but this requires knowing the
+         * CallStack of |down|. If |down == cx->fp|, the callstack is simply
+         * the context's active callstack, so we can use |down->varobj(cx)|.
+         * When |down != cx->fp|, we need to do a slow linear search. Luckily,
+         * this only happens with eval and JS_EvaluateInStackFrame.
+         */
+        if (down == cx->fp) {
+            callStack.setInitialVarObj(down->varobj(cx));
+        } else {
+            CallStack *cs = js_ContainingCallStack(cx, down);
+            callStack.setInitialVarObj(down->varobj(cs));
+        }
     } else {
         frame.callobj = NULL;
         frame.argsobj = NULL;
-        obj = chain;
+        JSObject *obj = chain;
         if (cx->options & JSOPTION_VAROBJFIX) {
-            while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL)
+            while (JSObject *tmp = OBJ_GET_PARENT(cx, obj))
                 obj = tmp;
         }
-        frame.varobj = obj;
         frame.fun = NULL;
         frame.thisv = OBJECT_TO_JSVAL(chain);
         frame.argc = 0;
         frame.argv = NULL;
         frame.annotation = NULL;
+        callStack.setInitialVarObj(obj);
     }
 
     frame.imacpc = NULL;
+
+    struct RawStackGuard {
+        JSContext *cx;
+        void *mark;
+        RawStackGuard(JSContext *cx) : cx(cx), mark(NULL) {}
+        ~RawStackGuard() { if (mark) js_FreeRawStack(cx, mark); }
+    } rawStackGuard(cx);
+
     if (script->nslots != 0) {
-        frame.slots = js_AllocRawStack(cx, script->nslots, &mark);
-        if (!frame.slots) {
-            ok = JS_FALSE;
-            goto out;
-        }
+        frame.slots = js_AllocRawStack(cx, script->nslots, &rawStackGuard.mark);
+        if (!frame.slots)
+            return false;
         memset(frame.slots, 0, script->nfixed * sizeof(jsval));
 
 #if JS_HAS_SHARP_VARS
         JS_STATIC_ASSERT(SHARP_NSLOTS == 2);
 
         if (script->hasSharps) {
             JS_ASSERT(script->nfixed >= SHARP_NSLOTS);
             jsval *sharps = &frame.slots[script->nfixed - SHARP_NSLOTS];
 
             if (down && down->script && down->script->hasSharps) {
                 JS_ASSERT(down->script->nfixed >= SHARP_NSLOTS);
                 int base = (down->fun && !(down->flags & JSFRAME_SPECIAL))
                            ? down->fun->sharpSlotBase(cx)
                            : down->script->nfixed - SHARP_NSLOTS;
-                if (base < 0) {
-                    ok = JS_FALSE;
-                    goto out;
-                }
+                if (base < 0)
+                    return false;
                 sharps[0] = down->slots[base];
                 sharps[1] = down->slots[base + 1];
             } else {
                 sharps[0] = sharps[1] = JSVAL_VOID;
             }
         }
 #endif
     } else {
         frame.slots = NULL;
     }
 
     frame.rval = JSVAL_VOID;
     frame.down = down;
     frame.scopeChain = chain;
     frame.regs = NULL;
     frame.flags = flags;
-    frame.dormantNext = NULL;
     frame.blockChain = NULL;
 
     /*
-     * Here we wrap the call to js_Interpret with code to (conditionally)
-     * save and restore the old stack frame chain into a chain of 'dormant'
-     * frame chains.  Since we are replacing cx->fp, we were running into
-     * the problem that if GC was called under this frame, some of the GC
-     * things associated with the old frame chain (available here only in
-     * the C variable 'oldfp') were not rooted and were being collected.
-     *
-     * So, now we preserve the links to these 'dormant' frame chains in cx
-     * before calling js_Interpret and cleanup afterwards.  The GC walks
-     * these dormant chains and marks objects in the same way that it marks
-     * objects in the primary cx->fp chain.
+     * We need to push/pop a new callstack if there is no existing callstack
+     * or the current callstack needs to be suspended (so that its frames are
+     * marked by GC).
      */
-    if (oldfp && oldfp != down) {
-        JS_ASSERT(!oldfp->dormantNext);
-        oldfp->dormantNext = cx->dormantFrameChain;
-        cx->dormantFrameChain = oldfp;
+    JSStackFrame *oldfp = cx->fp;
+    bool newCallStack = !oldfp || oldfp != down;
+    if (newCallStack) {
+        callStack.setInitialFrame(&frame);
+        cx->pushCallStack(&callStack);
     }
-
     cx->fp = &frame;
+
+    struct FinishGuard {
+        JSContext *cx;
+        JSStackFrame *oldfp;
+        bool newCallStack;
+        FinishGuard(JSContext *cx, JSStackFrame *oldfp, bool newCallStack)
+            : cx(cx), oldfp(oldfp), newCallStack(newCallStack) {}
+        ~FinishGuard() {
+            if (newCallStack)
+                cx->popCallStack();
+            cx->fp = oldfp;
+        }
+    } finishGuard(cx, oldfp, newCallStack);
+
     if (!down) {
         OBJ_TO_INNER_OBJECT(cx, chain);
         if (!chain)
-            return JS_FALSE;
+            return false;
         frame.scopeChain = chain;
 
         JSObject *thisp = JSVAL_TO_OBJECT(frame.thisv)->thisObject(cx);
-        if (!thisp) {
-            ok = JS_FALSE;
-            goto out2;
-        }
+        if (!thisp)
+            return false;
         frame.thisv = OBJECT_TO_JSVAL(thisp);
         frame.flags |= JSFRAME_COMPUTED_THIS;
     }
 
     if (hook) {
         hookData = hook(cx, &frame, JS_TRUE, 0,
                         cx->debugHooks->executeHookData);
     }
 
-    ok = js_Interpret(cx);
+    JSBool ok = js_Interpret(cx);
     if (result)
         *result = frame.rval;
 
     if (hookData) {
         hook = cx->debugHooks->executeHook;
         if (hook)
             hook(cx, &frame, JS_FALSE, &ok, hookData);
     }
 
-out2:
-    if (mark)
-        js_FreeRawStack(cx, mark);
-    cx->fp = oldfp;
-
-    if (oldfp && oldfp != down) {
-        JS_ASSERT(cx->dormantFrameChain == oldfp);
-        cx->dormantFrameChain = oldfp->dormantNext;
-        oldfp->dormantNext = NULL;
-    }
-
-out:
-#ifdef INCLUDE_MOZILLA_DTRACE
-    if (JAVASCRIPT_EXECUTE_DONE_ENABLED())
-        jsdtrace_execute_done(script);
-#endif
     return ok;
 }
 
 JSBool
 js_CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs,
                       JSObject **objp, JSProperty **propp)
 {
     JSObject *obj2;
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -51,16 +51,17 @@
 
 JS_BEGIN_EXTERN_C
 
 typedef struct JSFrameRegs {
     jsbytecode      *pc;            /* program counter */
     jsval           *sp;            /* stack pointer */
 } JSFrameRegs;
 
+
 /*
  * JS stack frame, may be allocated on the C stack by native callers.  Always
  * allocated on cx->stackPool for calls from the interpreter to an interpreted
  * function.
  *
  * NB: This struct is manually initialized in jsinterp.c and jsiter.c.  If you
  * add new members, update both files.  But first, try to remove members.  The
  * sharp* and xml* members should be moved onto the stack as local variables
@@ -68,17 +69,16 @@ typedef struct JSFrameRegs {
  */
 struct JSStackFrame {
     JSFrameRegs     *regs;
     jsbytecode      *imacpc;        /* null or interpreter macro call pc */
     jsval           *slots;         /* variables, locals and operand stack */
     JSObject        *callobj;       /* lazily created Call object */
     jsval           argsobj;        /* lazily created arguments object, must be
                                        JSVAL_OBJECT */
-    JSObject        *varobj;        /* variables object, where vars go */
     JSScript        *script;        /* script being interpreted */
     JSFunction      *fun;           /* function being called or null */
     jsval           thisv;          /* "this" pointer if in method */
     uintN           argc;           /* actual argument count */
     jsval           *argv;          /* base of argument stack slots */
     jsval           rval;           /* function return value */
     JSStackFrame    *down;          /* previous frame */
     void            *annotation;    /* used by Java security */
@@ -118,17 +118,16 @@ struct JSStackFrame {
      * This lazy cloning is implemented in js_GetScopeChain, which is
      * also used in some other cases --- entering 'with' blocks, for
      * example.
      */
     JSObject        *scopeChain;
     JSObject        *blockChain;
 
     uint32          flags;          /* frame flags -- see below */
-    JSStackFrame    *dormantNext;   /* next dormant frame chain */
     JSStackFrame    *displaySave;   /* previous value of display entry for
                                        script->staticLevel */
 
     inline void assertValidStackDepth(uintN depth);
 
     void putActivationObjects(JSContext *cx) {
         /*
          * The order of calls here is important as js_PutCallObject needs to
@@ -150,19 +149,36 @@ struct JSStackFrame {
     JSObject *calleeObject() {
         JS_ASSERT(argv);
         return JSVAL_TO_OBJECT(argv[-2]);
     }
 
     JSObject *callee() {
         return argv ? JSVAL_TO_OBJECT(argv[-2]) : NULL;
     }
+
+    /*
+     * Get the object associated with the Execution Context's
+     * VariableEnvironment (ES5 10.3). The given CallStack must contain this
+     * stack frame.
+     */
+    JSObject *varobj(js::CallStack *cs);
+
+    /* Short for: varobj(cx->activeCallStack()). */
+    JSObject *varobj(JSContext *cx);
 };
 
 #ifdef __cplusplus
+/*
+ * Perform a linear search of all frames in all callstacks in the given context
+ * for the given frame, returning the callstack, if found, and NULL otherwise.
+ */
+extern js::CallStack *
+js_ContainingCallStack(JSContext *cx, JSStackFrame *target);
+
 static JS_INLINE uintN
 FramePCOffset(JSStackFrame* fp)
 {
     return uintN((fp->imacpc ? fp->imacpc : fp->regs->pc) - fp->script->code);
 }
 #endif
 
 static JS_INLINE jsval *
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -709,29 +709,30 @@ JS_FRIEND_DATA(JSClass) js_GeneratorClas
 /*
  * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
  * to the frame by which the generator function was activated.  Create a new
  * JSGenerator object, which contains its own JSStackFrame that we populate
  * from *fp.  We know that upon return, the JSOP_GENERATOR opcode will return
  * from the activation in fp, so we can steal away fp->callobj and fp->argsobj
  * if they are non-null.
  */
-JSObject *
-js_NewGenerator(JSContext *cx, JSStackFrame *fp)
+JS_REQUIRES_STACK JSObject *
+js_NewGenerator(JSContext *cx)
 {
     JSObject *obj;
     uintN argc, nargs, nslots;
     JSGenerator *gen;
     jsval *slots;
 
     obj = js_NewObject(cx, &js_GeneratorClass, NULL, NULL);
     if (!obj)
         return NULL;
 
     /* Load and compute stack slot counts. */
+    JSStackFrame *fp = cx->fp;
     argc = fp->argc;
     nargs = JS_MAX(argc, fp->fun->nargs);
     nslots = 2 + nargs + fp->script->nslots;
 
     /* Allocate obj's private data struct. */
     gen = (JSGenerator *)
         cx->malloc(sizeof(JSGenerator) + (nslots - 1) * sizeof(jsval));
     if (!gen)
@@ -747,17 +748,16 @@ js_NewGenerator(JSContext *cx, JSStackFr
     }
     gen->frame.argsobj = fp->argsobj;
     if (fp->argsobj) {
         JSVAL_TO_OBJECT(fp->argsobj)->setPrivate(&gen->frame);
         fp->argsobj = NULL;
     }
 
     /* These two references can be shared with fp until it goes away. */
-    gen->frame.varobj = fp->varobj;
     gen->frame.thisv = fp->thisv;
 
     /* Copy call-invariant script and function references. */
     gen->frame.script = fp->script;
     gen->frame.fun = fp->fun;
 
     /* Use slots to carve space out of gen->slots. */
     slots = gen->slots;
@@ -781,17 +781,16 @@ js_NewGenerator(JSContext *cx, JSStackFr
     gen->frame.imacpc = NULL;
     gen->frame.slots = slots;
     JS_ASSERT(StackBase(fp) == fp->regs->sp);
     gen->savedRegs.sp = slots + fp->script->nfixed;
     gen->savedRegs.pc = fp->regs->pc;
     gen->frame.regs = &gen->savedRegs;
 
     gen->frame.flags = (fp->flags & ~JSFRAME_ROOTED_ARGV) | JSFRAME_GENERATOR;
-    gen->frame.dormantNext = NULL;
 
     /* JSOP_GENERATOR appears in the prologue, outside all blocks.  */
     JS_ASSERT(!fp->blockChain);
     gen->frame.blockChain = NULL;
 
     /* Note that gen is newborn. */
     gen->state = JSGEN_NEWBORN;
 
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -112,17 +112,17 @@ struct JSGenerator {
     JSArena             arena;
     jsval               slots[1];
 };
 
 #define FRAME_TO_GENERATOR(fp) \
     ((JSGenerator *) ((uint8 *)(fp) - offsetof(JSGenerator, frame)))
 
 extern JSObject *
-js_NewGenerator(JSContext *cx, JSStackFrame *fp);
+js_NewGenerator(JSContext *cx);
 
 #endif
 
 extern JS_FRIEND_API(JSClass) js_GeneratorClass;
 extern JSClass                js_IteratorClass;
 extern JSClass                js_StopIterationClass;
 
 static inline bool
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1251,21 +1251,21 @@ obj_eval(JSContext *cx, JSObject *obj, u
     JSPrincipals *principals;
     const char *file;
     uintN line;
     JSString *str;
     JSScript *script;
     JSBool ok;
     JSScript **bucket = NULL;   /* avoid GCC warning with early decl&init */
 #if JS_HAS_EVAL_THIS_SCOPE
-    JSObject *callerScopeChain = NULL, *callerVarObj = NULL;
+    JSObject *callerScopeChain = NULL;
+    JSBool setCallerScopeChain = JS_FALSE;
+    JSTempValueRooter scopetvr;
+#endif
     JSObject *withObject = NULL;
-    JSBool setCallerScopeChain = JS_FALSE, setCallerVarObj = JS_FALSE;
-    JSTempValueRooter scopetvr, varobjtvr;
-#endif
 
     fp = js_GetTopStackFrame(cx);
     caller = js_GetScriptedCaller(cx, fp);
     if (!caller) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_BAD_INDIRECT_CALL, js_eval_str);
         return JS_FALSE;
     }
@@ -1307,17 +1307,17 @@ obj_eval(JSContext *cx, JSObject *obj, u
         return JS_TRUE;
     }
 
     /*
      * If the caller is a lightweight function and doesn't have a variables
      * object, then we need to provide one for the compiler to stick any
      * declared (var) variables into.
      */
-    if (!caller->varobj && !js_GetCallObject(cx, caller))
+    if (caller->fun && !caller->callobj && !js_GetCallObject(cx, caller))
         return JS_FALSE;
 
     /* Accept an optional trailing argument that overrides the scope object. */
     JSObject *scopeobj = NULL;
     if (argc >= 2) {
         if (!js_ValueToObject(cx, argv[1], &scopeobj))
             return JS_FALSE;
         argv[1] = OBJECT_TO_JSVAL(scopeobj);
@@ -1356,32 +1356,21 @@ obj_eval(JSContext *cx, JSObject *obj, u
             ok = js_CheckPrincipalsAccess(cx, obj,
                                           JS_StackFramePrincipals(cx, caller),
                                           cx->runtime->atomState.evalAtom);
             if (!ok)
                 goto out;
 
             /* NB: We know obj is a global object here. */
             JS_ASSERT(!OBJ_GET_PARENT(cx, obj));
-            scopeobj = obj;
-
-            /* Set fp->scopeChain too, for the compiler. */
-            caller->scopeChain = fp->scopeChain = scopeobj;
+            caller->scopeChain = scopeobj = obj;
 
             /* Remember scopeobj so we can null its private when done. */
             setCallerScopeChain = JS_TRUE;
             JS_PUSH_TEMP_ROOT_OBJECT(cx, callerScopeChain, &scopetvr);
-
-            callerVarObj = caller->varobj;
-            if (obj != callerVarObj) {
-                /* Set fp->varobj too, for the compiler. */
-                caller->varobj = fp->varobj = obj;
-                setCallerVarObj = JS_TRUE;
-                JS_PUSH_TEMP_ROOT_OBJECT(cx, callerVarObj, &varobjtvr);
-            }
         } else {
             /*
              * Compile using the caller's current scope object.
              *
              * NB: This means that native callers (who reach this point through
              * the C API) must use the two parameter form.
              */
             scopeobj = callerScopeChain;
@@ -1548,27 +1537,23 @@ obj_eval(JSContext *cx, JSObject *obj, u
     *bucket = script;
 #ifdef CHECK_SCRIPT_OWNER
     script->owner = NULL;
 #endif
 
 out:
 #if JS_HAS_EVAL_THIS_SCOPE
     /* Restore OBJ_GET_PARENT(scopeobj) not callerScopeChain in case of Call. */
-    if (setCallerVarObj) {
-        caller->varobj = callerVarObj;
-        JS_POP_TEMP_ROOT(cx, &varobjtvr);
-    }
     if (setCallerScopeChain) {
         caller->scopeChain = callerScopeChain;
         JS_POP_TEMP_ROOT(cx, &scopetvr);
     }
+#endif
     if (withObject)
         withObject->setPrivate(NULL);
-#endif
     return ok;
 }
 
 #if JS_HAS_OBJ_WATCHPOINT
 
 static JSBool
 obj_watch_handler(JSContext *cx, JSObject *obj, jsval id, jsval old, jsval *nvp,
                   void *closure)
@@ -7098,17 +7083,16 @@ js_DumpStackFrame(JSStackFrame *fp)
             }
         } else {
             fprintf(stderr, "  sp:    %p\n", (void *) sp);
             fprintf(stderr, "  slots: %p\n", (void *) fp->slots);
         }
         fprintf(stderr, "  argv:  %p (argc: %u)\n", (void *) fp->argv, (unsigned) fp->argc);
         MaybeDumpObject("callobj", fp->callobj);
         MaybeDumpObject("argsobj", JSVAL_TO_OBJECT(fp->argsobj));
-        MaybeDumpObject("varobj", fp->varobj);
         MaybeDumpValue("this", fp->thisv);
         fprintf(stderr, "  rval: ");
         dumpValue(fp->rval);
         fputc('\n', stderr);
 
         fprintf(stderr, "  flags:");
         if (fp->flags == 0)
             fprintf(stderr, " none");
@@ -7134,18 +7118,16 @@ js_DumpStackFrame(JSStackFrame *fp)
             fprintf(stderr, " overridden_args");
         fputc('\n', stderr);
 
         if (fp->scopeChain)
             fprintf(stderr, "  scopeChain: (JSObject *) %p\n", (void *) fp->scopeChain);
         if (fp->blockChain)
             fprintf(stderr, "  blockChain: (JSObject *) %p\n", (void *) fp->blockChain);
 
-        if (fp->dormantNext)
-            fprintf(stderr, "  dormantNext: (JSStackFrame *) %p\n", (void *) fp->dormantNext);
         if (fp->displaySave)
             fprintf(stderr, "  displaySave: (JSStackFrame *) %p\n", (void *) fp->displaySave);
 
         fputc('\n', stderr);
     }
 }
 
 #endif
--- a/js/src/jsops.cpp
+++ b/js/src/jsops.cpp
@@ -665,17 +665,17 @@ END_CASE(JSOP_PICK)
             SKIP_POP_AFTER_SET(OP##_LENGTH, spdec);                           \
             rval = FETCH_OPND(-1);                                            \
             regs.sp -= (spdec) - 1;                                           \
             STORE_OPND(-1, rval);                                             \
           END_CASE(OP)
 
 BEGIN_CASE(JSOP_SETCONST)
     LOAD_ATOM(0);
-    obj = fp->varobj;
+    obj = fp->varobj(cx);
     rval = FETCH_OPND(-1);
     if (!obj->defineProperty(cx, ATOM_TO_JSID(atom), rval,
                              JS_PropertyStub, JS_PropertyStub,
                              JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY)) {
         goto error;
     }
 END_SET_CASE(JSOP_SETCONST);
 
@@ -1436,29 +1436,30 @@ BEGIN_CASE(JSOP_GVARINC)
     JS_ASSERT(slot < GlobalVarCount(fp));
     METER_SLOT_OP(op, slot);
     lval = fp->slots[slot];
     if (JSVAL_IS_NULL(lval)) {
         op = op2;
         DO_OP();
     }
     slot = JSVAL_TO_INT(lval);
-    rval = OBJ_GET_SLOT(cx, fp->varobj, slot);
+    JS_ASSERT(fp->varobj(cx) == cx->activeCallStack()->getInitialVarObj());
+    rval = OBJ_GET_SLOT(cx, cx->activeCallStack()->getInitialVarObj(), slot);
     if (JS_LIKELY(CAN_DO_FAST_INC_DEC(rval))) {
         PUSH_OPND(rval + incr2);
         rval += incr;
     } else {
         PUSH_OPND(rval);
         PUSH_OPND(JSVAL_NULL);  /* Extra root */
         if (!js_DoIncDec(cx, &js_CodeSpec[op], &regs.sp[-2], &regs.sp[-1]))
             goto error;
         rval = regs.sp[-1];
         --regs.sp;
     }
-    OBJ_SET_SLOT(cx, fp->varobj, slot, rval);
+    OBJ_SET_SLOT(cx, fp->varobj(cx), slot, rval);
     len = JSOP_INCGVAR_LENGTH;  /* all gvar incops are same length */
     JS_ASSERT(len == js_CodeSpec[op].length);
     DO_NEXT_OP(len);
 }
 
 #define COMPUTE_THIS(cx, fp, obj)                                             \
     JS_BEGIN_MACRO                                                            \
         if (!(obj = js_ComputeThisForFrame(cx, fp)))                          \
@@ -2147,27 +2148,25 @@ BEGIN_CASE(JSOP_APPLY)
                 }
             }
 
             /* Claim space for the stack frame and initialize it. */
             newifp = (JSInlineFrame *) newsp;
             newsp += nframeslots;
             newifp->frame.callobj = NULL;
             newifp->frame.argsobj = NULL;
-            newifp->frame.varobj = NULL;
             newifp->frame.script = script;
             newifp->frame.fun = fun;
             newifp->frame.argc = argc;
             newifp->frame.argv = vp + 2;
             newifp->frame.rval = JSVAL_VOID;
             newifp->frame.down = fp;
             newifp->frame.annotation = NULL;
             newifp->frame.scopeChain = parent = OBJ_GET_PARENT(cx, obj);
             newifp->frame.flags = flags;
-            newifp->frame.dormantNext = NULL;
             newifp->frame.blockChain = NULL;
             if (script->staticLevel < JS_DISPLAY_SIZE) {
                 JSStackFrame **disp = &cx->display[script->staticLevel];
                 newifp->frame.displaySave = *disp;
                 *disp = &newifp->frame;
             }
             newifp->mark = newmark;
 
@@ -2776,30 +2775,32 @@ BEGIN_CASE(JSOP_CALLGVAR)
     slot = GET_SLOTNO(regs.pc);
     JS_ASSERT(slot < GlobalVarCount(fp));
     METER_SLOT_OP(op, slot);
     lval = fp->slots[slot];
     if (JSVAL_IS_NULL(lval)) {
         op = (op == JSOP_GETGVAR) ? JSOP_NAME : JSOP_CALLNAME;
         DO_OP();
     }
-    obj = fp->varobj;
+    JS_ASSERT(fp->varobj(cx) == cx->activeCallStack()->getInitialVarObj());
+    obj = cx->activeCallStack()->getInitialVarObj();
     slot = JSVAL_TO_INT(lval);
     rval = OBJ_GET_SLOT(cx, obj, slot);
     PUSH_OPND(rval);
     if (op == JSOP_CALLGVAR)
         PUSH_OPND(OBJECT_TO_JSVAL(obj));
 END_CASE(JSOP_GETGVAR)
 
 BEGIN_CASE(JSOP_SETGVAR)
     slot = GET_SLOTNO(regs.pc);
     JS_ASSERT(slot < GlobalVarCount(fp));
     METER_SLOT_OP(op, slot);
     rval = FETCH_OPND(-1);
-    obj = fp->varobj;
+    JS_ASSERT(fp->varobj(cx) == cx->activeCallStack()->getInitialVarObj());
+    obj = cx->activeCallStack()->getInitialVarObj();
     lval = fp->slots[slot];
     if (JSVAL_IS_NULL(lval)) {
         /*
          * Inline-clone and deoptimize JSOP_SETNAME code here because
          * JSOP_SETGVAR has arity 1: [rval], not arity 2: [obj, rval]
          * as JSOP_SETNAME does, where [obj] is due to JSOP_BINDNAME.
          */
 #ifdef JS_TRACER
@@ -2828,17 +2829,17 @@ BEGIN_CASE(JSOP_DEFVAR)
     index = GET_INDEX(regs.pc);
     atom = atoms[index];
 
     /*
      * index is relative to atoms at this point but for global var
      * code below we need the absolute value.
      */
     index += atoms - script->atomMap.vector;
-    obj = fp->varobj;
+    obj = fp->varobj(cx);
     JS_ASSERT(obj->map->ops->defineProperty == js_DefineProperty);
     attrs = JSPROP_ENUMERATE;
     if (!(fp->flags & JSFRAME_EVAL))
         attrs |= JSPROP_PERMANENT;
     if (op == JSOP_DEFCONST)
         attrs |= JSPROP_READONLY;
 
     /* Lookup id in order to check for redeclaration problems. */
@@ -2877,21 +2878,20 @@ BEGIN_CASE(JSOP_DEFVAR)
         obj2 == obj &&
         OBJ_IS_NATIVE(obj)) {
         sprop = (JSScopeProperty *) prop;
         if ((sprop->attrs & JSPROP_PERMANENT) &&
             SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj)) &&
             SPROP_HAS_STUB_GETTER_OR_IS_METHOD(sprop) &&
             SPROP_HAS_STUB_SETTER(sprop)) {
             /*
-             * Fast globals use frame variables to map the global
-             * name's atom index to the permanent fp->varobj slot
-             * number, tagged as a jsval. The atom index for the
-             * global's name literal is identical to its variable
-             * index.
+             * Fast globals use frame variables to map the global name's atom
+             * index to the permanent varobj slot number, tagged as a jsval.
+             * The atom index for the global's name literal is identical to its
+             * variable index.
              */
             fp->slots[index] = INT_TO_JSVAL(sprop->slot);
         }
     }
 
     obj2->dropProperty(cx, prop);
 END_CASE(JSOP_DEFVAR)
 
@@ -2986,17 +2986,17 @@ BEGIN_CASE(JSOP_DEFFUN)
             setter = js_CastAsPropertyOp(obj);
     }
 
     /*
      * We define the function as a property of the variable object and not the
      * current scope chain even for the case of function expression statements
      * and functions defined by eval inside let or with blocks.
      */
-    parent = fp->varobj;
+    parent = fp->varobj(cx);
     JS_ASSERT(parent);
 
     /*
      * Check for a const property of the same name -- or any kind of property
      * if executing with the strict option.  We check here at runtime as well
      * as at compile-time, to handle eval as well as multiple HTML script tags.
      */
     id = ATOM_TO_JSID(fun->atom);
@@ -3034,17 +3034,17 @@ BEGIN_CASE(JSOP_DEFFUN)
         }
         pobj->dropProperty(cx, prop);
     }
     ok = doSet
          ? parent->setProperty(cx, id, &rval)
          : parent->defineProperty(cx, id, rval, getter, setter, attrs);
 
   restore_scope:
-    /* Restore fp->scopeChain now that obj is defined in fp->varobj. */
+    /* Restore fp->scopeChain now that obj is defined in fp->callobj. */
     fp->scopeChain = obj2;
     if (!ok)
         goto error;
 }
 END_CASE(JSOP_DEFFUN)
 
 BEGIN_CASE(JSOP_DEFFUN_FC)
 BEGIN_CASE(JSOP_DEFFUN_DBGFC)
@@ -3062,17 +3062,17 @@ BEGIN_CASE(JSOP_DEFFUN_DBGFC)
             : JSPROP_ENUMERATE | JSPROP_PERMANENT;
 
     flags = JSFUN_GSFLAG2ATTR(fun->flags);
     if (flags) {
         attrs |= flags | JSPROP_SHARED;
         rval = JSVAL_VOID;
     }
 
-    parent = fp->varobj;
+    parent = fp->varobj(cx);
     JS_ASSERT(parent);
 
     id = ATOM_TO_JSID(fun->atom);
     ok = js_CheckRedeclaration(cx, parent, id, attrs, NULL, NULL);
     if (ok) {
         if (attrs == JSPROP_ENUMERATE) {
             JS_ASSERT(fp->flags & JSFRAME_EVAL);
             ok = parent->setProperty(cx, id, &rval);
@@ -4104,17 +4104,17 @@ BEGIN_CASE(JSOP_CALLBUILTIN)
     goto bad_opcode;  /* This is an imacro-only opcode. */
 #endif
 END_CASE(JSOP_CALLBUILTIN)
 
 #if JS_HAS_GENERATORS
 BEGIN_CASE(JSOP_GENERATOR)
     ASSERT_NOT_THROWING(cx);
     regs.pc += JSOP_GENERATOR_LENGTH;
-    obj = js_NewGenerator(cx, fp);
+    obj = js_NewGenerator(cx);
     if (!obj)
         goto error;
     JS_ASSERT(!fp->callobj && !fp->argsobj);
     fp->rval = OBJECT_TO_JSVAL(obj);
     ok = JS_TRUE;
     if (inlineCallCount != 0)
         goto inline_return;
     goto exit;
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -143,16 +143,17 @@ typedef struct JSXMLArrayCursor     JSXM
  */
 #ifdef __cplusplus
 extern "C++" {
 
 namespace js {
 
 class TraceRecorder;
 class TraceMonitor;
+class CallStack;
 
 class ContextAllocPolicy;
 class SystemAllocPolicy;
 
 template <class T,
           size_t MinInlineCapacity = 0,
           class AllocPolicy = ContextAllocPolicy>
 class Vector;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -325,36 +325,36 @@ script_exec_sub(JSContext *cx, JSObject 
     /*
      * Emulate eval() by using caller's this, var object, sharp array, etc.,
      * all propagated by js_Execute via a non-null fourth (down) argument to
      * js_Execute.  If there is no scripted caller, js_Execute uses its second
      * (chain) argument to set the exec frame's varobj, thisv, and scopeChain.
      *
      * Unlike eval, which the compiler detects, Script.prototype.exec may be
      * called from a lightweight function, or even from native code (in which
-     * case fp->varobj and fp->scopeChain are null).  If exec is called from
-     * a lightweight function, we will need to get a Call object representing
-     * its frame, to act as the var object and scope chain head.
+     * fp->scopeChain is null).  If exec is called from a lightweight function,
+     * we will need to get a Call object representing its frame, to act as the
+     * var object and scope chain head.
      */
     caller = js_GetScriptedCaller(cx, NULL);
-    if (caller && !caller->varobj) {
+    if (caller && !caller->varobj(cx)) {
         /* Called from a lightweight function. */
         JS_ASSERT(caller->fun && !JSFUN_HEAVYWEIGHT_TEST(caller->fun->flags));
 
         /* Scope chain links from Call object to caller's scope chain. */
         if (!js_GetCallObject(cx, caller))
             return JS_FALSE;
     }
 
     if (!scopeobj) {
         /* No scope object passed in: try to use the caller's scope chain. */
         if (caller) {
             /*
              * Load caller->scopeChain after the conditional js_GetCallObject
-             * call above, which resets scopeChain as well as varobj.
+             * call above, which resets scopeChain as well as the callobj.
              */
             scopeobj = js_GetScopeChain(cx, caller);
             if (!scopeobj)
                 return JS_FALSE;
         } else {
             /*
              * Called from native code, so we don't know what scope object to
              * use.  We could use the caller's scope chain (see above), but Script.prototype.exec
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -3514,18 +3514,16 @@ FlushNativeStackFrame(JSContext* cx, uns
                 JS_ASSERT(GET_FUNCTION_PRIVATE(cx, fp->callee()) == fp->fun);
 
                 if (FUN_INTERPRETED(fp->fun) && 
                     (fp->fun->flags & JSFUN_HEAVYWEIGHT)) {
                     // Iff these fields are NULL, then |fp| was synthesized on trace exit, so
                     // we need to update the frame fields.
                     if (!fp->callobj)
                         fp->callobj = fp->scopeChain;
-                    if (!fp->varobj)
-                        fp->varobj = fp->scopeChain;
 
                     // Iff scope chain's private is NULL, then |fp->scopeChain| was created
                     // on trace for a call, so we set the private field now. (Call objects
                     // that correspond to returned frames also have a NULL private, but such
                     // a call object would not occur as the |scopeChain| member of a frame,
                     // so we cannot be in that case here.)
                     if (!fp->scopeChain->getPrivate())
                         fp->scopeChain->setPrivate(fp);
@@ -5622,17 +5620,16 @@ SynthesizeFrame(JSContext* cx, const Fra
     }
 
     /* Claim space for the stack frame and initialize it. */
     JSInlineFrame* newifp = (JSInlineFrame *) newsp;
     newsp += nframeslots;
 
     newifp->frame.callobj = NULL;
     newifp->frame.argsobj = NULL;
-    newifp->frame.varobj = NULL;
     newifp->frame.script = script;
     newifp->frame.fun = fun;
 
     bool constructing = fi.is_constructing();
     newifp->frame.argc = argc;
     newifp->callerRegs.pc = fi.pc;
     newifp->callerRegs.sp = fp->slots + fi.spdist;
     fp->imacpc = fi.imacpc;
@@ -5654,17 +5651,16 @@ SynthesizeFrame(JSContext* cx, const Fra
 #endif
     JS_ASSERT(newifp->frame.argv >= StackBase(fp) + 2);
 
     newifp->frame.rval = JSVAL_VOID;
     newifp->frame.down = fp;
     newifp->frame.annotation = NULL;
     newifp->frame.scopeChain = NULL; // will be updated in FlushNativeStackFrame
     newifp->frame.flags = constructing ? JSFRAME_CONSTRUCTING : 0;
-    newifp->frame.dormantNext = NULL;
     newifp->frame.blockChain = NULL;
     newifp->mark = newmark;
     newifp->frame.thisv = JSVAL_NULL; // will be updated in FlushNativeStackFrame
 
     newifp->frame.regs = fp->regs;
     newifp->frame.regs->pc = script->code;
     newifp->frame.regs->sp = newsp + script->nfixed;
     newifp->frame.imacpc = NULL;
@@ -5724,30 +5720,28 @@ SynthesizeSlowNativeFrame(InterpState& s
         OutOfMemoryAbort();
 
     JSStackFrame *fp = &ifp->frame;
     fp->regs = NULL;
     fp->imacpc = NULL;
     fp->slots = NULL;
     fp->callobj = NULL;
     fp->argsobj = NULL;
-    fp->varobj = cx->fp->varobj;
     fp->script = NULL;
     fp->thisv = state.nativeVp[1];
     fp->argc = state.nativeVpLen - 2;
     fp->argv = state.nativeVp + 2;
     fp->fun = GET_FUNCTION_PRIVATE(cx, fp->calleeObject());
     fp->rval = JSVAL_VOID;
     fp->down = cx->fp;
     fp->annotation = NULL;
     JS_ASSERT(cx->fp->scopeChain);
     fp->scopeChain = cx->fp->scopeChain;
     fp->blockChain = NULL;
     fp->flags = exit->constructing() ? JSFRAME_CONSTRUCTING : 0;
-    fp->dormantNext = NULL;
     fp->displaySave = NULL;
 
     ifp->mark = mark;
     cx->fp = fp;
 }
 
 static JS_REQUIRES_STACK bool
 RecordTree(JSContext* cx, TraceMonitor* tm, TreeFragment* peer, jsbytecode* outer,
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -7501,18 +7501,18 @@ js_GetFunctionNamespace(JSContext *cx, j
     *vp = OBJECT_TO_JSVAL(obj);
     return JS_TRUE;
 }
 
 /*
  * Note the asymmetry between js_GetDefaultXMLNamespace and js_SetDefaultXML-
  * Namespace.  Get searches fp->scopeChain for JS_DEFAULT_XML_NAMESPACE_ID,
  * while Set sets JS_DEFAULT_XML_NAMESPACE_ID in fp->varobj. There's no
- * requirement that fp->varobj lie directly on fp->scopeChain, although it
- * should be reachable using the prototype chain from a scope object (cf.
+ * requirement that fp->varobj lie directly on fp->scopeChain, although
+ * it should be reachable using the prototype chain from a scope object (cf.
  * JSOPTION_VAROBJFIX in jsapi.h).
  *
  * If Get can't find JS_DEFAULT_XML_NAMESPACE_ID along the scope chain, it
  * creates a default namespace via 'new Namespace()'.  In contrast, Set uses
  * its v argument as the uri of a new Namespace, with "" as the prefix.  See
  * ECMA-357 12.1 and 12.1.1.  Note that if Set is called with a Namespace n,
  * the default XML namespace will be set to ("", n.uri).  So the uri string
  * is really the only usefully stored value of the default namespace.
@@ -7562,19 +7562,20 @@ js_SetDefaultXMLNamespace(JSContext *cx,
     argv[0] = STRING_TO_JSVAL(cx->runtime->emptyString);
     argv[1] = v;
     ns = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL, 2, argv);
     if (!ns)
         return JS_FALSE;
     v = OBJECT_TO_JSVAL(ns);
 
     fp = js_GetTopStackFrame(cx);
-    varobj = fp->varobj;
+    varobj = fp->varobj(cx);
     if (!varobj->defineProperty(cx, JS_DEFAULT_XML_NAMESPACE_ID, v,
-                                JS_PropertyStub, JS_PropertyStub, JSPROP_PERMANENT)) {
+                                JS_PropertyStub, JS_PropertyStub,
+                                JSPROP_PERMANENT)) {
         return JS_FALSE;
     }
     return JS_TRUE;
 }
 
 JSBool
 js_ToAttributeName(JSContext *cx, jsval *vp)
 {