Merge tracemonkey to mozilla-central.
authorRobert Sayre <sayrer@gmail.com>
Mon, 16 Mar 2009 18:44:07 -0400
changeset 26239 61f02d381db25961cceba5e9f3fcbec375e4efd2
parent 26235 132f022dcdd098ba67472896e7fbfd08b81582e8 (current diff)
parent 26238 74416c7004c5da747ce27bcc6055c9c496f1a717 (diff)
child 26240 676c0455d1841c2704d99d817233dcc37697345a
push idunknown
push userunknown
push dateunknown
milestone1.9.2a1pre
Merge tracemonkey to mozilla-central.
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1033,16 +1033,20 @@ class JSAutoTempValueRooter
     explicit JSAutoTempValueRooter(JSContext *cx, jsval v = JSVAL_NULL)
         : mContext(cx) {
         JS_PUSH_SINGLE_TEMP_ROOT(mContext, v, &mTvr);
     }
     JSAutoTempValueRooter(JSContext *cx, JSString *str)
         : mContext(cx) {
         JS_PUSH_TEMP_ROOT_STRING(mContext, str, &mTvr);
     }
+    JSAutoTempValueRooter(JSContext *cx, JSObject *obj)
+        : mContext(cx) {
+        JS_PUSH_TEMP_ROOT_OBJECT(mContext, obj, &mTvr);
+    }
 
     ~JSAutoTempValueRooter() {
         JS_POP_TEMP_ROOT(mContext, &mTvr);
     }
 
     jsval value() { return mTvr.u.value; }
     jsval * addr() { return &mTvr.u.value; }
 
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -658,96 +658,139 @@ js_FreeStack(JSContext *cx, void *mark)
 
     /* Release the stackPool space allocated since mark was set. */
     JS_ARENA_RELEASE(&cx->stackPool, mark);
 }
 
 JSObject *
 js_GetScopeChain(JSContext *cx, JSStackFrame *fp)
 {
-    JSObject *obj, *cursor, *clonedChild, *parent;
-    JSTempValueRooter tvr;
-
-    obj = fp->blockChain;
-    if (!obj) {
+    JSObject *sharedBlock = fp->blockChain;
+
+    if (!sharedBlock) {
         /*
          * Don't force a call object for a lightweight function call, but do
          * insist that there is a call object for a heavyweight function call.
          */
         JS_ASSERT(!fp->fun ||
                   !(fp->fun->flags & JSFUN_HEAVYWEIGHT) ||
                   fp->callobj);
         JS_ASSERT(fp->scopeChain);
         return fp->scopeChain;
     }
 
+    /* We don't handle cloning blocks on trace.  */
+    js_LeaveTrace(cx);
+
     /*
      * We have one or more lexical scopes to reflect into fp->scopeChain, so
      * make sure there's a call object at the current head of the scope chain,
      * if this frame is a call frame.
+     *
+     * Also, identify the innermost compiler-allocated block we needn't clone.
      */
+    JSObject *limitBlock, *limitClone;
     if (fp->fun && !fp->callobj) {
         JS_ASSERT(OBJ_GET_CLASS(cx, fp->scopeChain) != &js_BlockClass ||
                   OBJ_GET_PRIVATE(cx, fp->scopeChain) != fp);
         if (!js_GetCallObject(cx, fp))
             return NULL;
+
+        /* We know we must clone everything on blockChain.  */ 
+        limitBlock = limitClone = NULL;
+    } else {
+        /*
+         * scopeChain includes all blocks whose static scope we're within that
+         * have already been cloned.  Find the innermost such block.  Its
+         * prototype should appear on blockChain; we'll clone blockChain up
+         * to, but not including, that prototype.
+         */
+        limitClone = fp->scopeChain;
+        while (OBJ_GET_CLASS(cx, limitClone) == &js_WithClass)
+            limitClone = OBJ_GET_PARENT(cx, limitClone);
+        JS_ASSERT(limitClone);
+
+        /* 
+         * It may seem like we don't know enough about limitClone to be able
+         * to just grab its prototype as we do here, but it's actually okay.
+         *
+         * If limitClone is a block object belonging to this frame, then its
+         * prototype is the innermost entry in blockChain that we have already
+         * cloned, and is thus the place to stop when we clone below.
+         *
+         * Otherwise, there are no blocks for this frame on scopeChain, and we
+         * need to clone the whole blockChain.  In this case, limitBlock can
+         * point to any object known not to be on blockChain, since we simply
+         * loop until we hit limitBlock or NULL.  If limitClone is a block, it
+         * isn't a block from this function, since blocks can't be nested
+         * within themselves on scopeChain (recursion is dynamic nesting, not
+         * static nesting).  If limitClone isn't a block, its prototype won't
+         * be a block either.  So we can just grab limitClone's prototype here
+         * regardless of its type or which frame it belongs to.
+         */
+        limitBlock = OBJ_GET_PROTO(cx, limitClone);
+
+        /* If the innermost block has already been cloned, we are done. */
+        if (limitBlock == sharedBlock)
+            return fp->scopeChain;
     }
 
     /*
-     * Clone the block chain. To avoid recursive cloning we set the parent of
-     * the cloned child after we clone the parent. In the following loop when
-     * clonedChild is null it indicates the first iteration when no special GC
-     * rooting is necessary. On the second and the following iterations we
-     * have to protect cloned so far chain against the GC during cloning of
-     * the cursor object.
+     * Special-case cloning the innermost block; this doesn't have enough in
+     * common with subsequent steps to include in the loop.
+     * 
+     * We pass fp->scopeChain and not null even if we override the parent slot
+     * later as null triggers useless calculations of slot's value in
+     * js_NewObject that js_CloneBlockObject calls.
      */
-    cursor = obj;
-    clonedChild = NULL;
+    JSObject *innermostNewChild
+        = js_CloneBlockObject(cx, sharedBlock, fp->scopeChain, fp);
+    if (!innermostNewChild)
+        return NULL;
+    JSAutoTempValueRooter tvr(cx, innermostNewChild);
+
+    /*
+     * Clone our way towards outer scopes until we reach the innermost
+     * enclosing function, or the innermost block we've already cloned.
+     */
+    JSObject *newChild = innermostNewChild;
     for (;;) {
-        parent = OBJ_GET_PARENT(cx, cursor);
+        JS_ASSERT(OBJ_GET_PROTO(cx, newChild) == sharedBlock);
+        sharedBlock = OBJ_GET_PARENT(cx, sharedBlock);
+
+        /* Sometimes limitBlock will be NULL, so check that first.  */
+        if (sharedBlock == limitBlock || !sharedBlock)
+            break;
+
+        /* As in the call above, we don't know the real parent yet.  */
+        JSObject *clone
+            = js_CloneBlockObject(cx, sharedBlock, fp->scopeChain, fp);
+        if (!clone)
+            return NULL;
 
         /*
-         * We pass fp->scopeChain and not null even if we override the parent
-         * slot later as null triggers useless calculations of slot's value in
-         * js_NewObject that js_CloneBlockObject calls.
+         * Avoid OBJ_SET_PARENT overhead as newChild cannot escape to
+         * other threads.
          */
-        cursor = js_CloneBlockObject(cx, cursor, fp->scopeChain, fp);
-        if (!cursor) {
-            if (clonedChild)
-                JS_POP_TEMP_ROOT(cx, &tvr);
-            return NULL;
-        }
-        if (!clonedChild) {
-            /*
-             * The first iteration. Check if other follow and root obj if so
-             * to protect the whole cloned chain against GC.
-             */
-            obj = cursor;
-            if (!parent)
-                break;
-            JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
-        } else {
-            /*
-             * Avoid OBJ_SET_PARENT overhead as clonedChild cannot escape to
-             * other threads.
-             */
-            STOBJ_SET_PARENT(clonedChild, cursor);
-            if (!parent) {
-                JS_ASSERT(tvr.u.value == OBJECT_TO_JSVAL(obj));
-                JS_POP_TEMP_ROOT(cx, &tvr);
-                break;
-            }
-        }
-        clonedChild = cursor;
-        cursor = parent;
+        STOBJ_SET_PARENT(newChild, clone);
+        newChild = clone;
     }
-    fp->flags |= JSFRAME_POP_BLOCKS;
-    fp->scopeChain = obj;
-    fp->blockChain = NULL;
-    return obj;
+
+    /*
+     * If we found a limit block belonging to this frame, then we should have
+     * found it in blockChain.
+     */
+    JS_ASSERT_IF(limitBlock && 
+                 OBJ_GET_CLASS(cx, limitBlock) == &js_BlockClass && 
+                 OBJ_GET_PRIVATE(cx, limitClone) == fp,
+                 sharedBlock);
+
+    /* Place our newly cloned blocks at the head of the scope chain.  */
+    fp->scopeChain = innermostNewChild;
+    return fp->scopeChain;
 }
 
 JSBool
 js_GetPrimitiveThis(JSContext *cx, jsval *vp, JSClass *clasp, jsval *thisvp)
 {
     jsval v;
     JSObject *obj;
 
@@ -6697,61 +6740,68 @@ js_Interpret(JSContext *cx)
             vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj);
             JS_ASSERT(regs.sp < vp);
             JS_ASSERT(vp <= fp->slots + script->nslots);
             while (regs.sp < vp) {
                 STORE_OPND(0, JSVAL_VOID);
                 regs.sp++;
             }
 
+#ifdef DEBUG
+            JS_ASSERT(fp->blockChain == OBJ_GET_PARENT(cx, obj));
+
             /*
-             * If this frame had to reflect the compile-time block chain into
-             * the runtime scope chain, we can't optimize block scopes out of
-             * runtime any longer, because an outer block that parents obj has
-             * been cloned onto the scope chain.  To avoid re-cloning such a
-             * parent and accumulating redundant clones via js_GetScopeChain,
-             * we must clone each block eagerly on entry, and push it on the
-             * scope chain, until this frame pops.
+             * The young end of fp->scopeChain may omit blocks if we
+             * haven't closed over them, but if there are any closure
+             * blocks on fp->scopeChain, they'd better be (clones of)
+             * ancestors of the block we're entering now; anything
+             * else we should have popped off fp->scopeChain when we
+             * left its static scope.
              */
-            if (fp->flags & JSFRAME_POP_BLOCKS) {
-                JS_ASSERT(!fp->blockChain);
-                obj = js_CloneBlockObject(cx, obj, fp->scopeChain, fp);
-                if (!obj)
-                    goto error;
-                fp->scopeChain = obj;
-            } else {
-                JS_ASSERT(!fp->blockChain ||
-                          OBJ_GET_PARENT(cx, obj) == fp->blockChain);
-                fp->blockChain = obj;
+            obj2 = fp->scopeChain;
+            while ((clasp = OBJ_GET_CLASS(cx, obj2)) == &js_WithClass)
+                obj2 = OBJ_GET_PARENT(cx, obj2);
+            if (clasp == &js_BlockClass &&
+                OBJ_GET_PRIVATE(cx, obj2) == fp) {
+                JSObject *youngestProto = OBJ_GET_PROTO(cx, obj2);
+                JS_ASSERT(!OBJ_IS_CLONED_BLOCK(youngestProto));
+                parent = obj;
+                while ((parent = OBJ_GET_PARENT(cx, parent)) != youngestProto)
+                    JS_ASSERT(parent);
             }
+#endif
+
+            fp->blockChain = obj;
           END_CASE(JSOP_ENTERBLOCK)
 
           BEGIN_CASE(JSOP_LEAVEBLOCKEXPR)
           BEGIN_CASE(JSOP_LEAVEBLOCK)
           {
 #ifdef DEBUG
-             uintN blockDepth = OBJ_BLOCK_DEPTH(cx,
-                                                fp->blockChain
-                                                ? fp->blockChain
-                                                : fp->scopeChain);
-
-             JS_ASSERT(blockDepth <= StackDepth(script));
+            JS_ASSERT(OBJ_GET_CLASS(cx, fp->blockChain) == &js_BlockClass);
+            uintN blockDepth = OBJ_BLOCK_DEPTH(cx, fp->blockChain);
+             
+            JS_ASSERT(blockDepth <= StackDepth(script));
 #endif
-            if (fp->blockChain) {
-                JS_ASSERT(OBJ_GET_CLASS(cx, fp->blockChain) == &js_BlockClass);
-                fp->blockChain = OBJ_GET_PARENT(cx, fp->blockChain);
-            } else {
-                /*
-                 * This block was cloned into fp->scopeChain, so clear its
-                 * private data and sync its locals to their property slots.
-                 */
+            /*
+             * If we're about to leave the dynamic scope of a block that has
+             * been cloned onto fp->scopeChain, clear its private data, move
+             * its locals from the stack into the clone, and pop it off the
+             * chain.
+             */
+            obj = fp->scopeChain;
+            if (OBJ_GET_PROTO(cx, obj) == fp->blockChain) {
+                JS_ASSERT (OBJ_GET_CLASS(cx, obj) == &js_BlockClass);
                 if (!js_PutBlockObject(cx, JS_TRUE))
                     goto error;
             }
 
+            /* Pop the block chain, too.  */
+            fp->blockChain = OBJ_GET_PARENT(cx, fp->blockChain);
+
             /*
              * We will move the result of the expression to the new topmost
              * stack slot.
              */
             if (op == JSOP_LEAVEBLOCKEXPR)
                 rval = FETCH_OPND(-1);
             regs.sp -= GET_UINT16(regs.pc);
             if (op == JSOP_LEAVEBLOCKEXPR) {
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -77,23 +77,61 @@ struct JSStackFrame {
     JSScript        *script;        /* script being interpreted */
     JSFunction      *fun;           /* function being called or null */
     JSObject        *thisp;         /* "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 */
-    JSObject        *scopeChain;    /* scope chain */
+
+    /*
+     * We can't determine in advance which local variables can live on
+     * the stack and be freed when their dynamic scope ends, and which
+     * will be closed over and need to live in the heap.  So we place
+     * variables on the stack initially, note when they are closed
+     * over, and copy those that are out to the heap when we leave
+     * their dynamic scope.
+     * 
+     * The bytecode compiler produces a tree of block objects
+     * accompanying each JSScript representing those lexical blocks in
+     * the script that have let-bound variables associated with them.
+     * These block objects are never modified, and never become part
+     * of any function's scope chain.  Their parent slots point to the
+     * innermost block that encloses them, or are NULL in the
+     * outermost blocks within a function or in eval or global code.
+     *
+     * When we are in the static scope of such a block, blockChain
+     * points to its compiler-allocated block object; otherwise, it is
+     * NULL.
+     * 
+     * scopeChain is the current scope chain, including 'call' and
+     * 'block' objects for those function calls and lexical blocks
+     * whose static scope we are currently executing in, and 'with'
+     * objects for with statements; the chain is typically terminated
+     * by a global object.  However, as an optimization, the young end
+     * of the chain omits block objects we have not yet cloned.  To
+     * create a closure, we clone the missing blocks from blockChain
+     * (which is always current), place them at the head of
+     * scopeChain, and use that for the closure's scope chain.  If we
+     * never close over a lexical block, we never place a mutable
+     * clone of it on scopeChain.
+     *
+     * 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;
+
     uintN           sharpDepth;     /* array/object initializer depth */
     JSObject        *sharpArray;    /* scope for #n= initializer vars */
     uint32          flags;          /* frame flags -- see below */
     JSStackFrame    *dormantNext;   /* next dormant frame chain */
     JSObject        *xmlNamespace;  /* null or default xml namespace in E4X */
-    JSObject        *blockChain;    /* active compile-time block scopes */
     JSStackFrame    *displaySave;   /* previous value of display entry for
                                        script->staticDepth */
 #ifdef DEBUG
     jsrefcount      pcDisabledSave; /* for balanced property cache control */
 #endif
 };
 
 #ifdef __cplusplus
@@ -135,17 +173,16 @@ typedef struct JSInlineFrame {
 #define JSFRAME_COMPUTED_THIS  0x02 /* frame.thisp was computed already */
 #define JSFRAME_ASSIGNING      0x04 /* a complex (not simplex JOF_ASSIGNING) op
                                        is currently assigning to a property */
 #define JSFRAME_DEBUGGER       0x08 /* frame for JS_EvaluateInStackFrame */
 #define JSFRAME_EVAL           0x10 /* frame for obj_eval */
 #define JSFRAME_ROOTED_ARGV    0x20 /* frame.argv is rooted by the caller */
 #define JSFRAME_YIELDING       0x40 /* js_Interpret dispatched JSOP_YIELD */
 #define JSFRAME_ITERATOR       0x80 /* trying to get an iterator for for-in */
-#define JSFRAME_POP_BLOCKS    0x100 /* scope chain contains blocks to pop */
 #define JSFRAME_GENERATOR     0x200 /* frame belongs to generator-iterator */
 #define JSFRAME_IMACRO_START  0x400 /* imacro starting -- see jstracer.h */
 
 #define JSFRAME_OVERRIDE_SHIFT 24   /* override bit-set params; see jsfun.c */
 #define JSFRAME_OVERRIDE_BITS  8
 
 #define JSFRAME_SPECIAL       (JSFRAME_DEBUGGER | JSFRAME_EVAL)
 
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -767,16 +767,18 @@ js_NewGenerator(JSContext *cx, JSStackFr
     gen->frame.regs = &gen->savedRegs;
 
     /* Copy remaining state (XXX sharp* and xml* should be local vars). */
     gen->frame.sharpDepth = 0;
     gen->frame.sharpArray = NULL;
     gen->frame.flags = (fp->flags & ~JSFRAME_ROOTED_ARGV) | JSFRAME_GENERATOR;
     gen->frame.dormantNext = NULL;
     gen->frame.xmlNamespace = 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;
 
     if (!JS_SetPrivate(cx, obj, gen)) {
         JS_free(cx, gen);
         goto bad;
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -1201,16 +1201,17 @@ TraceRecorder::TraceRecorder(JSContext* 
         TreeInfo* ti, unsigned stackSlots, unsigned ngslots, uint8* typeMap,
         VMSideExit* innermostNestedGuard, jsbytecode* outer)
 {
     JS_ASSERT(!_fragment->vmprivate && ti);
 
     this->cx = cx;
     this->traceMonitor = &JS_TRACE_MONITOR(cx);
     this->globalObj = JS_GetGlobalForObject(cx, cx->fp->scopeChain);
+    this->lexicalBlock = cx->fp->blockChain;
     this->anchor = _anchor;
     this->fragment = _fragment;
     this->lirbuf = _fragment->lirbuf;
     this->treeInfo = ti;
     this->callDepth = _anchor ? _anchor->calldepth : 0;
     this->atoms = FrameAtomBase(cx, cx->fp);
     this->deepAborted = false;
     this->trashSelf = false;
@@ -3203,17 +3204,16 @@ js_SynthesizeFrame(JSContext* cx, const 
     newifp->frame.fun = fun;
 
     bool constructing = (fi.s.argc & 0x8000) != 0;
     newifp->frame.argc = argc;
     newifp->callerRegs.pc = fi.pc;
     newifp->callerRegs.sp = fp->slots + fi.s.spdist;
     fp->imacpc = fi.imacpc;
 
-    JS_ASSERT(!(fp->flags & JSFRAME_POP_BLOCKS));
 #ifdef DEBUG
     if (fi.block != fp->blockChain) {
         for (JSObject* obj = fi.block; obj != fp->blockChain; obj = STOBJ_GET_PARENT(obj))
             JS_ASSERT(obj);
     }
 #endif
     fp->blockChain = fi.block;
 
@@ -3943,17 +3943,16 @@ js_ExecuteTree(JSContext* cx, Fragment* 
 
     void *reserve;
     state.stackMark = JS_ARENA_MARK(&cx->stackPool);
     JS_ARENA_ALLOCATE(reserve, &cx->stackPool, MAX_INTERP_STACK_BYTES);
     if (!reserve)
         return NULL;
 
 #ifdef DEBUG
-    state.jsframe_pop_blocks_set_on_entry = ((cx->fp->flags & JSFRAME_POP_BLOCKS) != 0);
     memset(stack_buffer, 0xCD, sizeof(stack_buffer));
     memset(state.global, 0xCD, (globalFrameSize+1)*sizeof(double));
 #endif
 
     debug_only(*(uint64*)&state.global[globalFrameSize] = 0xdeadbeefdeadbeefLL;)
     debug_only_v(printf("entering trace at %s:%u@%u, native stack slots: %u code: %p\n",
                         cx->fp->script->filename,
                         js_FramePCToLineNumber(cx, cx->fp),
@@ -4129,18 +4128,16 @@ LeaveTree(InterpState& state, VMSideExit
     }
 
     /* Adjust sp and pc relative to the tree we exited from (not the tree we entered into).
        These are our final values for sp and pc since js_SynthesizeFrame has already taken
        care of all frames in between. But first we recover fp->blockChain, which comes from
        the side exit struct. */
     JSStackFrame* fp = cx->fp;
 
-    JS_ASSERT_IF(fp->flags & JSFRAME_POP_BLOCKS,
-                 calldepth == 0 && state.jsframe_pop_blocks_set_on_entry);
     fp->blockChain = innermost->block;
 
     /* If we are not exiting from an inlined frame the state->sp is spbase, otherwise spbase
        is whatever slots frames around us consume. */
     fp->regs->pc = innermost->pc;
     fp->imacpc = innermost->imacpc;
     fp->regs->sp = StackBase(fp) + (innermost->sp_adj / sizeof(double)) - calldepth_slots;
     JS_ASSERT_IF(!fp->imacpc,
@@ -9128,34 +9125,32 @@ JS_REQUIRES_STACK bool
 TraceRecorder::record_JSOP_TYPEOFEXPR()
 {
     return record_JSOP_TYPEOF();
 }
 
 JS_REQUIRES_STACK bool
 TraceRecorder::record_JSOP_ENTERBLOCK()
 {
-    if (cx->fp->flags & JSFRAME_POP_BLOCKS)
-        ABORT_TRACE("can't trace after js_GetScopeChain");
-
     JSScript* script = cx->fp->script;
     JSFrameRegs& regs = *cx->fp->regs;
     JSObject* obj;
     JS_GET_SCRIPT_OBJECT(script, GET_FULL_INDEX(0), obj);
 
     LIns* void_ins = INS_CONST(JSVAL_TO_PSEUDO_BOOLEAN(JSVAL_VOID));
     for (int i = 0, n = OBJ_BLOCK_COUNT(cx, obj); i < n; i++)
         stack(i, void_ins);
     return true;
 }
 
 JS_REQUIRES_STACK bool
 TraceRecorder::record_JSOP_LEAVEBLOCK()
 {
-    return true;
+    /* We mustn't exit the lexical block we began recording in.  */
+    return cx->fp->blockChain != lexicalBlock;
 }
 
 JS_REQUIRES_STACK bool
 TraceRecorder::record_JSOP_GENERATOR()
 {
     return false;
 #if 0
     JSStackFrame* fp = cx->fp;
--- a/js/src/jstracer.h
+++ b/js/src/jstracer.h
@@ -377,16 +377,17 @@ enum JSMonitorRecordingStatus {
     JSMRS_STOP,
     JSMRS_IMACRO
 };
 
 class TraceRecorder : public avmplus::GCObject {
     JSContext*              cx;
     JSTraceMonitor*         traceMonitor;
     JSObject*               globalObj;
+    JSObject*               lexicalBlock;
     Tracker                 tracker;
     Tracker                 nativeFrameTracker;
     char*                   entryTypeMap;
     unsigned                callDepth;
     JSAtom**                atoms;
     VMSideExit*             anchor;
     nanojit::Fragment*      fragment;
     TreeInfo*               treeInfo;