diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index c3203c3d389d..74797da0e209 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -1038,10 +1038,6 @@ class JSAutoTempValueRooter : 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); diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index fa5d444436cb..df1e092eb727 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -663,9 +663,11 @@ js_FreeStack(JSContext *cx, void *mark) JSObject * js_GetScopeChain(JSContext *cx, JSStackFrame *fp) { - JSObject *sharedBlock = fp->blockChain; + JSObject *obj, *cursor, *clonedChild, *parent; + JSTempValueRooter tvr; - if (!sharedBlock) { + obj = fp->blockChain; + if (!obj) { /* * 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. @@ -677,131 +679,70 @@ js_GetScopeChain(JSContext *cx, JSStackFrame *fp) return fp->scopeChain; } - /* We don't handle cloning blocks on trace, so abort recording. - - It's true that fp->scopeChain could already have everything on - fp->blockChain, in which case we may not actually clone - anything on this call. But we should abort recording anyway, - because whether or not fp->scopeChain is up to date can vary - from one execution to the next. In code like: - let (x = 0) { - if (c) y = function () { return x }; - ... loop that gets traced ... - } - even if c is true and no cloning is necessary when the loop is - first recorded, c might be false and cloning might be necessary - the next time the loop is entered. */ -#ifdef JS_TRACER - if (TRACE_RECORDER(cx)) - js_AbortRecording(cx, "Can't create closure blocks"); -#endif - /* * 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; } /* - * 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. + * 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. */ - 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; + cursor = obj; + clonedChild = NULL; for (;;) { - 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; + parent = OBJ_GET_PARENT(cx, cursor); /* - * Avoid OBJ_SET_PARENT overhead as newChild cannot escape to - * other threads. + * 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. */ - STOBJ_SET_PARENT(newChild, clone); - newChild = clone; + 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; } - - /* - * 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; + fp->flags |= JSFRAME_POP_BLOCKS; + fp->scopeChain = obj; + fp->blockChain = NULL; + return obj; } JSBool @@ -6800,58 +6741,51 @@ js_Interpret(JSContext *cx) regs.sp++; } -#ifdef DEBUG - JS_ASSERT(fp->blockChain == OBJ_GET_PARENT(cx, obj)); - /* - * 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 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. */ - 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); + 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; } -#endif - - fp->blockChain = obj; END_CASE(JSOP_ENTERBLOCK) BEGIN_CASE(JSOP_LEAVEBLOCKEXPR) BEGIN_CASE(JSOP_LEAVEBLOCK) { #ifdef DEBUG - JS_ASSERT(OBJ_GET_CLASS(cx, fp->blockChain) == &js_BlockClass); - uintN blockDepth = OBJ_BLOCK_DEPTH(cx, fp->blockChain); - - JS_ASSERT(blockDepth <= StackDepth(script)); + uintN blockDepth = OBJ_BLOCK_DEPTH(cx, + fp->blockChain + ? fp->blockChain + : fp->scopeChain); + + JS_ASSERT(blockDepth <= StackDepth(script)); #endif - /* - * 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 (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 (!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. diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index 31e111edae4e..115530a83fe9 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -82,51 +82,13 @@ struct JSStackFrame { jsval rval; /* function return value */ JSStackFrame *down; /* previous frame */ void *annotation; /* used by Java security */ - - /* - * 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; - + JSObject *scopeChain; /* scope chain */ 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 @@ -178,6 +140,7 @@ typedef struct JSInlineFrame { #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 */ diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index b1e95ca175ac..b7539a8f35ac 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -786,8 +786,6 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp) 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. */ diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index f5a036c029c5..cedbfce1b4fd 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -1206,7 +1206,6 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag 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; @@ -3209,6 +3208,7 @@ js_SynthesizeFrame(JSContext* cx, const FrameInfo& fi) 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)) @@ -3948,6 +3948,7 @@ js_ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount, 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 @@ -4133,6 +4134,8 @@ LeaveTree(InterpState& state, VMSideExit* lr) 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 @@ -9130,6 +9133,9 @@ TraceRecorder::record_JSOP_TYPEOFEXPR() 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; @@ -9144,8 +9150,7 @@ TraceRecorder::record_JSOP_ENTERBLOCK() JS_REQUIRES_STACK bool TraceRecorder::record_JSOP_LEAVEBLOCK() { - /* We mustn't exit the lexical block we began recording in. */ - return cx->fp->blockChain != lexicalBlock; + return true; } JS_REQUIRES_STACK bool diff --git a/js/src/jstracer.h b/js/src/jstracer.h index b9548b469584..0cbcb567bb6f 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -382,7 +382,6 @@ class TraceRecorder : public avmplus::GCObject { JSContext* cx; JSTraceMonitor* traceMonitor; JSObject* globalObj; - JSObject* lexicalBlock; Tracker tracker; Tracker nativeFrameTracker; char* entryTypeMap;