Bug 579183 - loosen-up StackSegment invariants - part 5 - CallArgs (r=waldo)
authorLuke Wagner <lw@mozilla.com>
Fri, 30 Jul 2010 10:41:03 -0700
changeset 50468 7acc0fe02f45f647bcf14e4b4a007c26edcb23ad
parent 50467 2de2b501adb014695f6a3288488ebd7075985afd
child 50469 1973d4086362bbb0633ea3bbc5583a1934dd67f2
child 53382 25bff33134218bafd3ca0d2fa38778765e2417be
push idunknown
push userunknown
push dateunknown
reviewerswaldo
bugs579183
milestone2.0b4pre
Bug 579183 - loosen-up StackSegment invariants - part 5 - CallArgs (r=waldo)
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/jsfun.cpp
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsstr.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4866,23 +4866,22 @@ JS_New(JSContext *cx, JSObject *ctor, ui
     // This is not a simple variation of JS_CallFunctionValue because JSOP_NEW
     // is not a simple variation of JSOP_CALL. We have to determine what class
     // of object to create, create it, and clamp the return value to an object,
     // among other details. js_InvokeConstructor does the hard work.
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, argc, args))
         return NULL;
 
-    Value *vp = args.getvp();
-    vp[0].setObject(*ctor);
-    vp[1].setNull();
-    memcpy(vp + 2, argv, argc * sizeof(jsval));
+    args.callee().setObject(*ctor);
+    args.thisv().setNull();
+    memcpy(args.argv(), argv, argc * sizeof(jsval));
 
     bool ok = InvokeConstructor(cx, args);
-    JSObject *obj = ok ? vp[0].toObjectOrNull() : NULL;
+    JSObject *obj = ok ? args.rval().toObjectOrNull() : NULL;
 
     LAST_FRAME_CHECKS(cx, ok);
     return obj;
 }
 
 JS_PUBLIC_API(JSOperationCallback)
 JS_SetOperationCallback(JSContext *cx, JSOperationCallback callback)
 {
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1371,24 +1371,23 @@ array_toString(JSContext *cx, uintN argc
         return true;
     }
 
     LeaveTrace(cx);
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, 0, args))
         return false;
 
-    Value *sp = args.getvp();
-    sp[0] = join;
-    sp[1].setObject(*obj);
+    args.callee() = join;
+    args.thisv().setObject(*obj);
 
     /* Do the call. */
     if (!Invoke(cx, args, 0))
         return false;
-    *vp = *args.getvp();
+    *vp = args.rval();
     return true;
 }
 
 static JSBool
 array_toLocaleString(JSContext *cx, uintN argc, Value *vp)
 {
     JSObject *obj = ComputeThisFromVp(cx, vp);
     if (!obj)
@@ -1722,28 +1721,27 @@ sort_compare(void *arg, const void *a, c
      * come here.
      */
     JS_ASSERT(!av->isMagic() && !av->isUndefined());
     JS_ASSERT(!av->isMagic() && !bv->isUndefined());
 
     if (!JS_CHECK_OPERATION_LIMIT(cx))
         return JS_FALSE;
 
-    Value *invokevp = ca->args.getvp();
-    Value *sp = invokevp;
-    *sp++ = ca->fval;
-    *sp++ = NullValue();
-    *sp++ = *av;
-    *sp++ = *bv;
+    CallArgs &args = ca->args;
+    args.callee() = ca->fval;
+    args.thisv().setNull();
+    args[0] = *av;
+    args[1] = *bv;
 
     if (!Invoke(cx, ca->args, 0))
         return JS_FALSE;
 
     jsdouble cmp;
-    if (!ValueToNumber(cx, *invokevp, &cmp))
+    if (!ValueToNumber(cx, args.rval(), &cmp))
         return JS_FALSE;
 
     /* Clamp cmp to -1, 0, 1. */
     *result = 0;
     if (!JSDOUBLE_IS_NaN(cmp) && cmp != 0)
         *result = cmp > 0 ? 1 : -1;
 
     /*
@@ -2769,17 +2767,16 @@ array_extra(JSContext *cx, ArrayExtraMod
 
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, argc, args))
         return JS_FALSE;
 
     MUST_FLOW_THROUGH("out");
     JSBool ok = JS_TRUE;
     JSBool cond;
-    Value *invokevp = args.getvp();
 
     Value calleev, thisv, objv;
     calleev.setObject(*callable);
     thisv.setObjectOrNull(thisp);
     objv.setObject(*obj);
     AutoValueRooter tvr(cx);
     for (jsint i = start; i != end; i += step) {
         JSBool hole;
@@ -2787,50 +2784,48 @@ array_extra(JSContext *cx, ArrayExtraMod
              GetArrayElement(cx, obj, i, &hole, tvr.addr());
         if (!ok)
             goto out;
         if (hole)
             continue;
 
         /*
          * Push callable and 'this', then args. We must do this for every
-         * iteration around the loop since js_Invoke uses invokevp[0] for return
-         * value storage, while some native functions use invokevp[1] for local
-         * rooting.
+         * iteration around the loop since Invoke clobbers its arguments.
          */
-        Value *sp = invokevp;
-        *sp++ = calleev;
-        *sp++ = thisv;
+        args.callee() = calleev;
+        args.thisv() = thisv;
+        Value *sp = args.argv();
         if (REDUCE_MODE(mode))
             *sp++ = *vp;
-        *sp++ = tvr.value();
-        sp++->setInt32(i);
-        *sp++ = objv;
+        sp[0] = tvr.value();
+        sp[1].setInt32(i);
+        sp[2] = objv;
 
         /* Do the call. */
         ok = Invoke(cx, args, 0);
         if (!ok)
             break;
 
         if (mode > MAP)
-            cond = js_ValueToBoolean(*invokevp);
+            cond = js_ValueToBoolean(args.rval());
 #ifdef __GNUC__ /* quell GCC overwarning */
         else
             cond = JS_FALSE;
 #endif
 
         switch (mode) {
           case FOREACH:
             break;
           case REDUCE:
           case REDUCE_RIGHT:
-            *vp = *invokevp;
+            *vp = args.rval();
             break;
           case MAP:
-            ok = SetArrayElement(cx, newarr, i, *invokevp);
+            ok = SetArrayElement(cx, newarr, i, args.rval());
             if (!ok)
                 goto out;
             break;
           case FILTER:
             if (!cond)
                 break;
             /* The element passed the filter, so push it onto our result. */
             ok = SetArrayElement(cx, newarr, newlen++, tvr.value());
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -232,49 +232,48 @@ StackSpace::mark(JSTracer *trc)
         end = seg->previousSegmentEnd();
     }
 }
 
 JS_REQUIRES_STACK bool
 StackSpace::pushSegmentForInvoke(JSContext *cx, uintN argc, InvokeArgsGuard &ag)
 {
     Value *start = firstUnused();
-    uintN vplen = 2 + argc;
-    ptrdiff_t nvals = VALUES_PER_STACK_SEGMENT + vplen;
+    ptrdiff_t nvals = VALUES_PER_STACK_SEGMENT + 2 + argc;
     if (!ensureSpace(cx, start, nvals))
         return false;
 
     StackSegment *seg = new(start) StackSegment;
     seg->setPreviousInMemory(currentSegment);
     currentSegment = seg;
 
     ag.cx = cx;
     ag.seg = seg;
-    ag.vp = seg->getInitialArgBegin();
-    ag.argc = argc;
+    ag.argv_ = seg->getInitialArgBegin() + 2;
+    ag.argc_ = argc;
 
     /* Use invokeArgEnd to root [vp, vpend) until the frame is pushed. */
 #ifdef DEBUG
     ag.prevInvokeSegment = invokeSegment;
     invokeSegment = seg;
     ag.prevInvokeFrame = invokeFrame;
     invokeFrame = NULL;
 #endif
     ag.prevInvokeArgEnd = invokeArgEnd;
-    invokeArgEnd = ag.vp + vplen;
+    invokeArgEnd = ag.argv() + ag.argc();
     return true;
 }
 
 JS_REQUIRES_STACK void
 StackSpace::popSegmentForInvoke(const InvokeArgsGuard &ag)
 {
     JS_ASSERT(!currentSegment->inContext());
     JS_ASSERT(ag.seg == currentSegment);
     JS_ASSERT(invokeSegment == currentSegment);
-    JS_ASSERT(invokeArgEnd == ag.vp + 2 + ag.argc);
+    JS_ASSERT(invokeArgEnd == ag.argv() + ag.argc());
 
     currentSegment = currentSegment->getPreviousInMemory();
 
 #ifdef DEBUG
     invokeSegment = ag.prevInvokeSegment;
     invokeFrame = ag.prevInvokeFrame;
 #endif
     invokeArgEnd = ag.prevInvokeArgEnd;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -484,40 +484,41 @@ class StackSegment
 #ifdef DEBUG
     JS_REQUIRES_STACK bool contains(const JSStackFrame *fp) const;
 #endif
 };
 
 static const size_t VALUES_PER_STACK_SEGMENT = sizeof(StackSegment) / sizeof(Value);
 JS_STATIC_ASSERT(sizeof(StackSegment) % sizeof(Value) == 0);
 
-/*
- * The ternary constructor is used when arguments are already pushed on the
- * stack (as the sp of the current frame), which should only happen from within
- * Interpret. Otherwise, see StackSpace::pushInvokeArgs. 
- */
-class InvokeArgsGuard
+/* See StackSpace::pushInvokeArgs. */
+class InvokeArgsGuard : public CallArgs
 {
     friend class StackSpace;
     JSContext        *cx;  /* null implies nothing pushed */
     StackSegment     *seg;
-    Value            *vp;
-    uintN            argc;
     Value            *prevInvokeArgEnd;
 #ifdef DEBUG
     StackSegment     *prevInvokeSegment;
     JSStackFrame     *prevInvokeFrame;
 #endif
   public:
     inline InvokeArgsGuard() : cx(NULL), seg(NULL) {}
     inline InvokeArgsGuard(JSContext *cx, Value *vp, uintN argc);
     inline ~InvokeArgsGuard();
     bool pushed() const { return cx != NULL; }
-    Value *getvp() const { JS_ASSERT(pushed()); return vp; }
-    uintN getArgc() const { JS_ASSERT(pushed()); return argc; }
+};
+
+/*
+ * This type can be used to call Invoke when the arguments have already been
+ * pushed onto the stack as part of normal execution.
+ */
+struct InvokeArgsAlreadyOnTheStack : CallArgs
+{
+    InvokeArgsAlreadyOnTheStack(Value *vp, uintN argc) : CallArgs(vp + 2, argc) {}
 };
 
 /* See StackSpace::pushInvokeFrame. */
 class InvokeFrameGuard
 {
     friend class StackSpace;
     JSContext        *cx;  /* null implies nothing pushed */
     JSStackFrame     *fp;
@@ -634,23 +635,22 @@ class StackSpace
     JSStackFrame *invokeFrame;
 #endif
     Value        *invokeArgEnd;
 
     JS_REQUIRES_STACK bool pushSegmentForInvoke(JSContext *cx, uintN argc,
                                                 InvokeArgsGuard &ag);
     JS_REQUIRES_STACK bool pushInvokeFrameSlow(JSContext *cx, const InvokeArgsGuard &ag,
                                                InvokeFrameGuard &fg);
-    JS_REQUIRES_STACK void popInvokeFrameSlow(const InvokeArgsGuard &ag);
+    JS_REQUIRES_STACK void popInvokeFrameSlow(const CallArgs &args);
     JS_REQUIRES_STACK void popSegmentForInvoke(const InvokeArgsGuard &ag);
 
     /* Although guards are friends, XGuard should only call popX(). */
     friend class InvokeArgsGuard;
-    JS_REQUIRES_STACK inline void bumpInvokeArgEnd(InvokeArgsGuard &ag);
-    JS_REQUIRES_STACK inline void popInvokeArgs(const InvokeArgsGuard &ag);
+    JS_REQUIRES_STACK inline void popInvokeArgs(const InvokeArgsGuard &args);
     friend class InvokeFrameGuard;
     JS_REQUIRES_STACK void popInvokeFrame(const InvokeFrameGuard &ag);
     friend class ExecuteFrameGuard;
     JS_REQUIRES_STACK void popExecuteFrame(JSContext *cx);
 
     /* Return a pointer to the first unused slot. */
     JS_REQUIRES_STACK
     inline Value *firstUnused() const;
@@ -727,23 +727,22 @@ class StackSpace
      * the arguments to Invoke. A single allocation can be used for multiple
      * Invoke calls. The InvokeArgumentsGuard passed to Invoke must come from
      * an immediately-enclosing (stack-wise) call to pushInvokeArgs.
      */
     JS_REQUIRES_STACK
     bool pushInvokeArgs(JSContext *cx, uintN argc, InvokeArgsGuard &ag);
 
     /* These functions are called inside Invoke, not Invoke clients. */
-    bool getInvokeFrame(JSContext *cx, const InvokeArgsGuard &ag,
+    bool getInvokeFrame(JSContext *cx, const CallArgs &args,
                         uintN nmissing, uintN nfixed,
                         InvokeFrameGuard &fg) const;
 
     JS_REQUIRES_STACK
-    void pushInvokeFrame(JSContext *cx, const InvokeArgsGuard &ag,
-                         InvokeFrameGuard &fg);
+    void pushInvokeFrame(JSContext *cx, const CallArgs &args, InvokeFrameGuard &fg);
 
     /*
      * For the simpler case when arguments are allocated at the same time as
      * the frame and it is not necessary to have rooted argument values before
      * pushing the frame.
      */
     JS_REQUIRES_STACK
     bool getExecuteFrame(JSContext *cx, JSStackFrame *down,
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -168,53 +168,33 @@ StackSpace::pushInvokeArgs(JSContext *cx
 #ifdef DEBUG
     ag.prevInvokeSegment = invokeSegment;
     invokeSegment = currentSegment;
     ag.prevInvokeFrame = invokeFrame;
     invokeFrame = cx->fp;
 #endif
 
     ag.cx = cx;
-    ag.vp = vp;
-    ag.argc = argc;
+    ag.argv_ = vp + 2;
+    ag.argc_ = argc;
     return true;
 }
 
-JS_ALWAYS_INLINE void
-StackSpace::bumpInvokeArgEnd(InvokeArgsGuard &ag)
-{
-#ifdef DEBUG
-    ag.prevInvokeSegment = invokeSegment;
-    invokeSegment = ag.cx->currentSegment;
-    ag.prevInvokeFrame = invokeFrame;
-    invokeFrame = ag.cx->fp;
-#endif
-    ag.prevInvokeArgEnd = invokeArgEnd;
-    invokeArgEnd = ag.vp + 2 + ag.argc;
-}
-
-JS_ALWAYS_INLINE
-InvokeArgsGuard::InvokeArgsGuard(JSContext *cx, Value *vp, uintN argc)
-  : cx(cx), seg(NULL), vp(vp), argc(argc)
-{
-    cx->stack().bumpInvokeArgEnd(*this);
-}
-
 JS_REQUIRES_STACK JS_ALWAYS_INLINE void
 StackSpace::popInvokeArgs(const InvokeArgsGuard &ag)
 {
     if (JS_UNLIKELY(ag.seg != NULL)) {
         popSegmentForInvoke(ag);
         return;
     }
 
     JS_ASSERT(isCurrentAndActive(ag.cx));
     JS_ASSERT(invokeSegment == currentSegment);
     JS_ASSERT(invokeFrame == ag.cx->fp);
-    JS_ASSERT(invokeArgEnd == ag.vp + 2 + ag.argc);
+    JS_ASSERT(invokeArgEnd == ag.argv() + ag.argc());
 
 #ifdef DEBUG
     invokeSegment = ag.prevInvokeSegment;
     invokeFrame = ag.prevInvokeFrame;
 #endif
     invokeArgEnd = ag.prevInvokeArgEnd;
 }
 
@@ -222,36 +202,35 @@ JS_ALWAYS_INLINE
 InvokeArgsGuard::~InvokeArgsGuard()
 {
     if (JS_UNLIKELY(!pushed()))
         return;
     cx->stack().popInvokeArgs(*this);
 }
 
 JS_REQUIRES_STACK JS_ALWAYS_INLINE bool
-StackSpace::getInvokeFrame(JSContext *cx, const InvokeArgsGuard &ag,
+StackSpace::getInvokeFrame(JSContext *cx, const CallArgs &args,
                            uintN nmissing, uintN nfixed,
                            InvokeFrameGuard &fg) const
 {
-    JS_ASSERT(firstUnused() == invokeArgEnd);
-    JS_ASSERT(invokeArgEnd == ag.vp + 2 + ag.argc);
+    JS_ASSERT(firstUnused() == args.argv() + args.argc());
 
-    Value *start = invokeArgEnd;
+    Value *start = args.argv() + args.argc();
     ptrdiff_t nvals = nmissing + VALUES_PER_STACK_FRAME + nfixed;
     if (!ensureSpace(cx, start, nvals))
         return false;
     fg.fp = reinterpret_cast<JSStackFrame *>(start + nmissing);
     return true;
 }
 
 JS_REQUIRES_STACK JS_ALWAYS_INLINE void
-StackSpace::pushInvokeFrame(JSContext *cx, const InvokeArgsGuard &ag,
+StackSpace::pushInvokeFrame(JSContext *cx, const CallArgs &args,
                             InvokeFrameGuard &fg)
 {
-    JS_ASSERT(firstUnused() == ag.vp + 2 + ag.argc);
+    JS_ASSERT(firstUnused() == args.argv() + args.argc());
 
     JSStackFrame *fp = fg.fp;
     JSStackFrame *down = cx->fp;
     fp->down = down;
     if (JS_UNLIKELY(!currentSegment->inContext())) {
         cx->pushSegmentAndFrame(currentSegment, fp, fg.regs);
     } else {
 #ifdef DEBUG
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2025,22 +2025,22 @@ js_fun_call(JSContext *cx, uintN argc, V
     }
 
     /* Allocate stack space for fval, obj, and the args. */
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, argc, args))
         return JS_FALSE;
 
     /* Push fval, obj, and the args. */
-    args.getvp()[0] = fval;
-    args.getvp()[1] = ObjectOrNullValue(obj);
-    memcpy(args.getvp() + 2, argv, argc * sizeof *argv);
+    args.callee() = fval;
+    args.thisv().setObjectOrNull(obj);
+    memcpy(args.argv(), argv, argc * sizeof *argv);
 
     bool ok = Invoke(cx, args, 0);
-    *vp = *args.getvp();
+    *vp = args.rval();
     return ok;
 }
 
 /* ES5 15.3.4.3 */
 JSBool
 js_fun_apply(JSContext *cx, uintN argc, Value *vp)
 {
     JSObject *obj = ComputeThisFromVp(cx, vp);
@@ -2106,54 +2106,54 @@ js_fun_apply(JSContext *cx, uintN argc, 
     /* Step 6. */
     uintN n = uintN(JS_MIN(length, JS_ARGS_LENGTH_MAX));
 
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, n, args))
         return false;
 
     /* Push fval, obj, and aobj's elements as args. */
-    Value *sp = args.getvp();
-    *sp++ = fval;
-    *sp++ = ObjectOrNullValue(obj);
+    args.callee() = fval;
+    args.thisv().setObjectOrNull(obj);
 
     /* Steps 7-8. */
     if (aobj && aobj->isArguments() && !aobj->isArgsLengthOverridden()) {
         /*
          * Two cases, two loops: note how in the case of an active stack frame
          * backing aobj, even though we copy from fp->argv, we still must check
          * aobj->getArgsElement(i) for a hole, to handle a delete on the
          * corresponding arguments element. See args_delProperty.
          */
         JSStackFrame *fp = (JSStackFrame *) aobj->getPrivate();
+        Value *argv = args.argv();
         if (fp) {
-            memcpy(sp, fp->argv, n * sizeof(Value));
+            memcpy(argv, fp->argv, n * sizeof(Value));
             for (uintN i = 0; i < n; i++) {
                 if (aobj->getArgsElement(i).isMagic(JS_ARGS_HOLE)) // suppress deleted element
-                    sp[i].setUndefined();
+                    argv[i].setUndefined();
             }
         } else {
             for (uintN i = 0; i < n; i++) {
-                sp[i] = aobj->getArgsElement(i);
-                if (sp[i].isMagic(JS_ARGS_HOLE))
-                    sp[i].setUndefined();
+                argv[i] = aobj->getArgsElement(i);
+                if (argv[i].isMagic(JS_ARGS_HOLE))
+                    argv[i].setUndefined();
             }
         }
     } else {
+        Value *argv = args.argv();
         for (uintN i = 0; i < n; i++) {
-            if (!aobj->getProperty(cx, INT_TO_JSID(jsint(i)), sp))
+            if (!aobj->getProperty(cx, INT_TO_JSID(jsint(i)), &argv[i]))
                 return JS_FALSE;
-            sp++;
         }
     }
 
     /* Step 9. */
     if (!Invoke(cx, args, 0))
         return false;
-    *vp = *args.getvp();
+    *vp = args.rval();
     return true;
 }
 
 static JSFunctionSpec function_methods[] = {
 #if JS_HAS_TOSOURCE
     JS_FN(js_toSource_str,   fun_toSource,   0,0),
 #endif
     JS_FN(js_toString_str,   fun_toString,   0,0),
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -407,28 +407,27 @@ NoSuchMethod(JSContext *cx, uintN argc, 
     if (!cx->stack().pushInvokeArgs(cx, 2, args))
         return JS_FALSE;
 
     JS_ASSERT(vp[0].isObject());
     JS_ASSERT(vp[1].isObject());
     JSObject *obj = &vp[0].toObject();
     JS_ASSERT(obj->getClass() == &js_NoSuchMethodClass);
 
-    Value *invokevp = args.getvp();
-    invokevp[0] = obj->fslots[JSSLOT_FOUND_FUNCTION];
-    invokevp[1] = vp[1];
-    invokevp[2] = obj->fslots[JSSLOT_SAVED_ID];
+    args.callee() = obj->fslots[JSSLOT_FOUND_FUNCTION];
+    args.thisv() = vp[1];
+    args[0] = obj->fslots[JSSLOT_SAVED_ID];
     JSObject *argsobj = js_NewArrayObject(cx, argc, vp + 2);
     if (!argsobj)
         return JS_FALSE;
-    invokevp[3].setObject(*argsobj);
+    args[1].setObject(*argsobj);
     JSBool ok = (flags & JSINVOKE_CONSTRUCT)
                 ? InvokeConstructor(cx, args)
                 : Invoke(cx, args, flags);
-    vp[0] = invokevp[0];
+    vp[0] = args.rval();
     return ok;
 }
 
 #endif /* JS_HAS_NO_SUCH_METHOD */
 
 namespace js {
 
 class AutoPreserveEnumerators {
@@ -455,47 +454,46 @@ callJSNative(JSContext *cx, CallOp callO
         return true;
     }
     return false;
 }
 
 template <typename T>
 static JS_REQUIRES_STACK bool
 InvokeCommon(JSContext *cx, JSFunction *fun, JSScript *script, T native,
-       const InvokeArgsGuard &args, uintN flags)
-{
-    uintN argc = args.getArgc();
-    Value *vp = args.getvp();
+             const CallArgs &argsRef, uintN flags)
+{
+    CallArgs args = argsRef;
 
     if (native && fun && fun->isFastNative()) {
 #ifdef DEBUG_NOT_THROWING
         JSBool alreadyThrowing = cx->throwing;
 #endif
-        JSBool ok = callJSFastNative(cx, (FastNative) native, argc, vp);
+        JSBool ok = callJSFastNative(cx, (FastNative) native, args.argc(), args.base());
         JS_RUNTIME_METER(cx->runtime, nativeCalls);
 #ifdef DEBUG_NOT_THROWING
         if (ok && !alreadyThrowing)
             ASSERT_NOT_THROWING(cx);
 #endif
         return ok;
     }
 
     /* Calculate slot usage. */
     uintN nmissing;
     uintN nvars;
     if (fun) {
         if (fun->isInterpreted()) {
             uintN minargs = fun->nargs;
-            nmissing = minargs > argc ? minargs - argc : 0;
+            nmissing = minargs > args.argc() ? minargs - args.argc() : 0;
             nvars = fun->u.i.nvars;
         } else if (fun->isFastNative()) {
             nvars = nmissing = 0;
         } else {
             uintN minargs = fun->nargs;
-            nmissing = (minargs > argc ? minargs - argc : 0) + fun->u.n.extra;
+            nmissing = (minargs > args.argc() ? minargs - args.argc() : 0) + fun->u.n.extra;
             nvars = 0;
         }
     } else {
         nvars = nmissing = 0;
     }
 
     uintN nfixed = script ? script->nslots : 0;
 
@@ -504,28 +502,28 @@ InvokeCommon(JSContext *cx, JSFunction *
      * code before pushInvokeFrame must not reenter the interpreter.
      */
     InvokeFrameGuard frame;
     if (!cx->stack().getInvokeFrame(cx, args, nmissing, nfixed, frame))
         return false;
     JSStackFrame *fp = frame.getFrame();
 
     /* Initialize missing missing arguments and new local variables. */
-    Value *missing = vp + 2 + argc;
+    Value *missing = args.argv() + args.argc();
     SetValueRangeToUndefined(missing, nmissing);
     SetValueRangeToUndefined(fp->slots(), nvars);
 
     /* Initialize frame. */
-    fp->thisv = vp[1];
+    fp->thisv = args.thisv();
     fp->callobj = NULL;
     fp->argsobj = NULL;
     fp->script = script;
     fp->fun = fun;
-    fp->argc = argc;
-    fp->argv = vp + 2;
+    fp->argc = args.argc();
+    fp->argv = args.argv();
     fp->rval = (flags & JSINVOKE_CONSTRUCT) ? fp->thisv : UndefinedValue();
     fp->annotation = NULL;
     fp->scopeChain = NULL;
     fp->blockChain = NULL;
     fp->imacpc = NULL;
     fp->flags = flags;
 
     /* Initialize regs. */
@@ -537,17 +535,17 @@ InvokeCommon(JSContext *cx, JSFunction *
         regs.pc = NULL;
         regs.sp = fp->slots();
     }
 
     /* Officially push |fp|. |frame|'s destructor pops. */
     cx->stack().pushInvokeFrame(cx, args, frame);
 
     /* Now that the frame has been pushed, fix up the scope chain. */
-    JSObject *parent = vp[0].toObject().getParent();
+    JSObject *parent = args.callee().toObject().getParent();
     if (native) {
         /* Slow natives and call ops expect the caller's scopeChain as their scopeChain. */
         if (JSStackFrame *down = fp->down)
             fp->scopeChain = down->scopeChain;
 
         /* Ensure that we have a scope chain. */
         if (!fp->scopeChain)
             fp->scopeChain = parent;
@@ -592,17 +590,17 @@ InvokeCommon(JSContext *cx, JSFunction *
 
     if (hookData) {
         hook = cx->debugHooks->callHook;
         if (hook)
             hook(cx, fp, JS_FALSE, &ok, hookData);
     }
 
     fp->putActivationObjects(cx);
-    *vp = fp->rval;
+    args.rval() = fp->rval;
     return ok;
 }
 
 static JSBool
 DoConstruct(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval)
 {
 
     Class *clasp = argv[-2].toObject().getClass();
@@ -638,96 +636,92 @@ DoSlowCall(JSContext *cx, uintN argc, Va
 
 /*
  * Find a function reference and its 'this' value implicit first parameter
  * 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 bool
-Invoke(JSContext *cx, const InvokeArgsGuard &args, uintN flags)
-{
-    Value *vp = args.getvp();
-    uintN argc = args.getArgc();
-    JS_ASSERT(argc <= JS_ARGS_LENGTH_MAX);
-
-    const Value &v = vp[0];
-    if (v.isPrimitive()) {
-        js_ReportIsNotFunction(cx, vp, flags & JSINVOKE_FUNFLAGS);
+Invoke(JSContext *cx, const CallArgs &args, uintN flags)
+{
+    JS_ASSERT(args.argc() <= JS_ARGS_LENGTH_MAX);
+
+    if (args.callee().isPrimitive()) {
+        js_ReportIsNotFunction(cx, &args.callee(), flags & JSINVOKE_FUNFLAGS);
         return false;
     }
 
-    JSObject *funobj = &v.toObject();
+    JSObject *funobj = &args.callee().toObject();
     Class *clasp = funobj->getClass();
 
     if (clasp == &js_FunctionClass) {
         /* Get private data and set derived locals from it. */
         JSFunction *fun = GET_FUNCTION_PRIVATE(cx, funobj);
         Native native;
         JSScript *script;
         if (FUN_INTERPRETED(fun)) {
             native = NULL;
             script = fun->u.i.script;
             JS_ASSERT(script);
 
             if (script->isEmpty()) {
                 if (flags & JSINVOKE_CONSTRUCT) {
-                    JS_ASSERT(vp[1].isObject());
-                    *vp = vp[1];
+                    JS_ASSERT(args.thisv().isObject());
+                    args.rval() = args.thisv();
                 } else {
-                    vp->setUndefined();
+                    args.rval().setUndefined();
                 }
                 return true;
             }
         } else {
             native = fun->u.n.native;
             script = NULL;
         }
 
         if (JSFUN_BOUND_METHOD_TEST(fun->flags)) {
             /* Handle bound method special case. */
-            vp[1].setObject(*funobj->getParent());
-        } else if (!vp[1].isObjectOrNull()) {
+            args.thisv().setObject(*funobj->getParent());
+        } else if (!args.thisv().isObjectOrNull()) {
             JS_ASSERT(!(flags & JSINVOKE_CONSTRUCT));
-            if (PrimitiveThisTest(fun, vp[1]))
+            if (PrimitiveThisTest(fun, args.thisv()))
                 return InvokeCommon(cx, fun, script, native, args, flags);
         }
 
         if (flags & JSINVOKE_CONSTRUCT) {
-            JS_ASSERT(args.getvp()[1].isObject());
+            JS_ASSERT(args.thisv().isObject());
         } else {
             /*
              * We must call js_ComputeThis in case we are not called from the
              * interpreter, where a prior bytecode has computed an appropriate
              * |this| already.
              *
              * But we need to compute |this| eagerly only for so-called "slow"
              * (i.e., not fast) native functions. Fast natives must use either
              * JS_THIS or JS_THIS_OBJECT, and scripted functions will go through
              * the appropriate this-computing bytecode, e.g., JSOP_THIS.
              */
             if (native && (!fun || !(fun->flags & JSFUN_FAST_NATIVE))) {
-                Value *vp = args.getvp();
-                if (!ComputeThisFromVp(cx, vp))
+                if (!args.computeThis(cx))
                     return false;
                 flags |= JSFRAME_COMPUTED_THIS;
             }
         }
         return InvokeCommon(cx, fun, script, native, args, flags);
     }
 
 #if JS_HAS_NO_SUCH_METHOD
     if (clasp == &js_NoSuchMethodClass)
-        return NoSuchMethod(cx, argc, vp, flags);
+        return NoSuchMethod(cx, args.argc(), args.base(), flags);
 #endif
 
     /* Try a call or construct native object op. */
     if (flags & JSINVOKE_CONSTRUCT) {
-        if (!vp[1].isObjectOrNull()) {
-            if (!js_PrimitiveToObject(cx, &vp[1]))
+        if (!args.thisv().isObjectOrNull()) {
+            if (!js_PrimitiveToObject(cx, &args.thisv()))
                 return false;
         }
         return InvokeCommon(cx, NULL, NULL, DoConstruct, args, flags);
     }
     CallOp callOp = (clasp->flags & Class::CALL_IS_FAST) ? (CallOp) clasp->call : DoSlowCall;
     return InvokeCommon(cx, NULL, NULL, callOp, args, flags);
 }
 
@@ -742,30 +736,30 @@ InternalInvoke(JSContext *cx, const Valu
                   uintN argc, Value *argv, Value *rval)
 {
     LeaveTrace(cx);
 
     InvokeArgsGuard args;
     if (!cx->stack().pushInvokeArgs(cx, argc, args))
         return JS_FALSE;
 
-    args.getvp()[0] = fval;
-    args.getvp()[1] = thisv;
-    memcpy(args.getvp() + 2, argv, argc * sizeof(Value));
+    args.callee() = fval;
+    args.thisv() = thisv;
+    memcpy(args.argv(), argv, argc * sizeof(Value));
 
     if (!Invoke(cx, args, flags))
         return JS_FALSE;
 
     /*
      * Store *rval in the lastInternalResult pigeon-hole GC root, solely
      * so users of js_InternalInvoke and its direct and indirect
      * (js_ValueToString for example) callers do not need to manage roots
      * for local, temporary references to such results.
      */
-    *rval = *args.getvp();
+    *rval = args.rval();
     if (rval->isMarkable())
         cx->weakRoots.lastInternalResult = rval->asGCThing();
 
     return JS_TRUE;
 }
 
 bool
 InternalGetOrSet(JSContext *cx, JSObject *obj, jsid id, const Value &fval,
@@ -1134,74 +1128,64 @@ InstanceOfSlow(JSContext *cx, JSObject *
                                  ? obj->getClass()->name
                                  : js_null_str);
         }
     }
     return false;
 }
 
 JS_REQUIRES_STACK bool
-InvokeConstructor(JSContext *cx, const InvokeArgsGuard &args)
+InvokeConstructor(JSContext *cx, const CallArgs &argsRef)
 {
     JS_ASSERT(!js_FunctionClass.construct);
-    Value *vp = args.getvp();
-
-    if (vp->isPrimitive()) {
+    CallArgs args = argsRef;
+
+    if (args.callee().isPrimitive()) {
         /* Use js_ValueToFunction to report an error. */
-        JS_ALWAYS_TRUE(!js_ValueToFunction(cx, vp, JSV2F_CONSTRUCT));
+        JS_ALWAYS_TRUE(!js_ValueToFunction(cx, &args.callee(), JSV2F_CONSTRUCT));
         return false;
     }
 
-    JSObject *obj2 = &vp->toObject();
-
-    /*
-     * Get the constructor prototype object for this function.
-     * Use the nominal 'this' parameter slot, vp[1], as a local
-     * root to protect this prototype, in case it has no other
-     * strong refs.
-     */
-    if (!obj2->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), &vp[1]))
+    JSObject *obj2 = &args.callee().toObject();
+
+    Value protov;
+    if (!obj2->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), &protov))
         return false;
 
-    const Value &v = vp[1];
-    JSObject *proto = v.isObjectOrNull() ? v.toObjectOrNull() : NULL;
+    JSObject *proto = protov.isObjectOrNull() ? protov.toObjectOrNull() : NULL;
     JSObject *parent = obj2->getParent();
     Class *clasp = &js_ObjectClass;
 
     if (obj2->getClass() == &js_FunctionClass) {
         JSFunction *f = GET_FUNCTION_PRIVATE(cx, obj2);
         if (!f->isInterpreted() && f->u.n.clasp)
             clasp = f->u.n.clasp;
     }
 
     JSObject* obj = NewObject<WithProto::Class>(cx, clasp, proto, parent);
     if (!obj) {
         return JS_FALSE;
     }
 
 
-    /* Keep |obj| rooted in case vp[1] is overwritten with a primitive. */
-    AutoObjectRooter tvr(cx, obj);
-
     /* Now we have an object with a constructor method; call it. */
-    vp[1].setObject(*obj);
+    args.thisv().setObject(*obj);
     if (!Invoke(cx, args, JSINVOKE_CONSTRUCT))
         return JS_FALSE;
 
     /* Check the return value and if it's primitive, force it to be obj. */
-    const Value &rval = *vp;
-    if (rval.isPrimitive()) {
+    if (args.rval().isPrimitive()) {
         if (obj2->getClass() != &js_FunctionClass) {
             /* native [[Construct]] returning primitive is error */
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                  JSMSG_BAD_NEW_RESULT,
-                                 js_ValueToPrintableString(cx, *vp));
+                                 js_ValueToPrintableString(cx, args.rval()));
             return JS_FALSE;
         }
-        vp->setObject(*obj);
+        args.rval().setObject(*obj);
     }
 
     JS_RUNTIME_METER(cx->runtime, constructs);
     return JS_TRUE;
 }
 
 bool
 ValueToId(JSContext *cx, const Value &v, jsid *idp)
@@ -4562,17 +4546,17 @@ BEGIN_CASE(JSOP_NEW)
             }
 
             vp[1].setObject(*obj2);
             flags = JSFRAME_CONSTRUCTING;
             goto inline_call;
         }
     }
 
-    if (!InvokeConstructor(cx, InvokeArgsGuard(cx, vp, argc)))
+    if (!InvokeConstructor(cx, InvokeArgsAlreadyOnTheStack(vp, argc)))
         goto error;
     regs.sp = vp + 1;
     CHECK_INTERRUPT_HANDLER();
     TRACE_0(NativeCallComplete);
 
   end_new:;
 }
 END_CASE(JSOP_NEW)
@@ -4719,34 +4703,34 @@ BEGIN_CASE(JSOP_APPLY)
             if (!ok)
                 goto error;
             TRACE_0(NativeCallComplete);
             goto end_call;
         }
     }
 
     bool ok;
-    ok = Invoke(cx, InvokeArgsGuard(cx, vp, argc), 0);
+    ok = Invoke(cx, InvokeArgsAlreadyOnTheStack(vp, argc), 0);
     regs.sp = vp + 1;
     CHECK_INTERRUPT_HANDLER();
     if (!ok)
         goto error;
     JS_RUNTIME_METER(rt, nonInlineCalls);
     TRACE_0(NativeCallComplete);
 
   end_call:;
 }
 END_CASE(JSOP_CALL)
 }
 
 BEGIN_CASE(JSOP_SETCALL)
 {
     uintN argc = GET_ARGC(regs.pc);
     Value *vp = regs.sp - argc - 2;
-    JSBool ok = Invoke(cx, InvokeArgsGuard(cx, vp, argc), 0);
+    JSBool ok = Invoke(cx, InvokeArgsAlreadyOnTheStack(vp, argc), 0);
     if (ok)
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_LEFTSIDE_OF_ASS);
     goto error;
 }
 END_CASE(JSOP_SETCALL)
 
 BEGIN_CASE(JSOP_NAME)
 BEGIN_CASE(JSOP_CALLNAME)
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -288,25 +288,50 @@ PrimitiveThisTest(JSFunction *fun, const
 {
     uint16 flags = fun->flags;
     return (v.isString() && !!(flags & JSFUN_THISP_STRING)) ||
            (v.isNumber() && !!(flags & JSFUN_THISP_NUMBER)) ||
            (v.isBoolean() && !!(flags & JSFUN_THISP_BOOLEAN));
 }
 
 /*
+ * Abstracts the layout of the stack passed to natives from the engine and from
+ * natives to js::Invoke.
+ */
+struct CallArgs
+{
+    Value *argv_;
+    uintN argc_;
+  protected:
+    CallArgs() {}
+    CallArgs(Value *argv, uintN argc) : argv_(argv), argc_(argc) {}
+  public:
+    Value *base() const { return argv_ - 2; }
+    Value &callee() const { return argv_[-2]; }
+    Value &thisv() const { return argv_[-1]; }
+    Value &operator[](unsigned i) const { JS_ASSERT(i < argc_); return argv_[i]; }
+    Value *argv() const { return argv_; }
+    uintN argc() const { return argc_; }
+    Value &rval() const { return argv_[-2]; }
+
+    JSObject *computeThis(JSContext *cx) const {
+        return ComputeThisFromArgv(cx, argv_);
+    }
+};
+
+/*
  * The js::InvokeArgumentsGuard passed to js_Invoke must come from an
  * immediately-enclosing successful call to js::StackSpace::pushInvokeArgs,
  * i.e., there must have been no un-popped pushes to cx->stack(). Furthermore,
  * |args.getvp()[0]| should be the callee, |args.getvp()[1]| should be |this|,
  * and the range [args.getvp() + 2, args.getvp() + 2 + args.getArgc()) should
  * be initialized actual arguments.
  */
 extern JS_REQUIRES_STACK bool
-Invoke(JSContext *cx, const InvokeArgsGuard &args, uintN flags);
+Invoke(JSContext *cx, const CallArgs &args, uintN flags);
 
 /*
  * Consolidated js_Invoke flags simply rename certain JSFRAME_* flags, so that
  * we can share bits stored in JSStackFrame.flags and passed to:
  *
  *   js_Invoke
  *   js_InternalInvoke
  *   js_ValueToFunction
@@ -349,17 +374,17 @@ extern bool
 InternalGetOrSet(JSContext *cx, JSObject *obj, jsid id, const Value &fval,
                  JSAccessMode mode, uintN argc, Value *argv, Value *rval);
 
 extern JS_FORCES_STACK bool
 Execute(JSContext *cx, JSObject *chain, JSScript *script,
         JSStackFrame *down, uintN flags, Value *result);
 
 extern JS_REQUIRES_STACK bool
-InvokeConstructor(JSContext *cx, const InvokeArgsGuard &args);
+InvokeConstructor(JSContext *cx, const CallArgs &args);
 
 extern JS_REQUIRES_STACK bool
 Interpret(JSContext *cx);
 
 #define JSPROP_INITIALIZER 0x100   /* NB: Not a valid property attribute. */
 
 extern bool
 CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs,
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1968,24 +1968,24 @@ InterpretDollar(JSContext *cx, jschar *d
         return &res->leftContext;
       case '\'':
         return &res->rightContext;
     }
     return NULL;
 }
 
 static JS_ALWAYS_INLINE bool
-PushRegExpSubstr(JSContext *cx, const JSSubString &sub, Value *&sp)
+PushRegExpSubstr(JSContext *cx, const JSSubString &sub, Value *sp)
 {
     JSString *whole = cx->regExpStatics.input;
     size_t off = sub.chars - whole->chars();
     JSString *str = js_NewDependentString(cx, whole, off, sub.length);
     if (!str)
         return false;
-    sp++->setString(str);
+    sp->setString(str);
     return true;
 }
 
 class PreserveRegExpStatics {
     JSContext *cx;
     JSRegExpStatics save;
 
   public:
@@ -2023,27 +2023,29 @@ FindReplaceLength(JSContext *cx, Replace
         uintN argc = 1 + p + 2;
 
         if (!rdata.args.pushed() && !cx->stack().pushInvokeArgs(cx, argc, rdata.args))
             return false;
 
         PreserveRegExpStatics save(cx);
 
         /* Push lambda and its 'this' parameter. */
-        Value *sp = rdata.args.getvp();
-        sp++->setObject(*lambda);
-        sp++->setObjectOrNull(lambda->getParent());
+        CallArgs &args = rdata.args;
+        args.callee().setObject(*lambda);
+        args.thisv().setObjectOrNull(lambda->getParent());
+
+        Value *sp = args.argv();
 
         /* Push $&, $1, $2, ... */
-        if (!PushRegExpSubstr(cx, cx->regExpStatics.lastMatch, sp))
+        if (!PushRegExpSubstr(cx, cx->regExpStatics.lastMatch, sp++))
             return false;
 
         uintN i = 0;
         for (uintN n = cx->regExpStatics.parens.length(); i < n; i++) {
-            if (!PushRegExpSubstr(cx, cx->regExpStatics.parens[i], sp))
+            if (!PushRegExpSubstr(cx, cx->regExpStatics.parens[i], sp++))
                 return false;
         }
 
         /* Make sure to push undefined for any unmatched parens. */
         for (; i < p; i++)
             *sp++ = UndefinedValue();
 
         /* Push match index and input string. */
@@ -2053,17 +2055,17 @@ FindReplaceLength(JSContext *cx, Replace
         if (!Invoke(cx, rdata.args, 0))
             return false;
 
         /*
          * NB: we count on the newborn string root to hold any string
          * created by this js_ValueToString that would otherwise be GC-
          * able, until we use rdata.repstr in DoReplace.
          */
-        repstr = js_ValueToString(cx, *rdata.args.getvp());
+        repstr = js_ValueToString(cx, args.rval());
         if (!repstr)
             return false;
 
         rdata.repstr = repstr;
         *sizep = repstr->length();
 
         return true;
     }