Bug 692274, part 3 - Remove JSOP_BLOCKCHAIN and JSOP_NULLBLOCKCHAIN, which produces incorrect let scoping until the next patch (r=jorendorff)
authorLuke Wagner <luke@mozilla.com>
Fri, 07 Oct 2011 12:02:50 -0700
changeset 83258 9272bb82eebac5e30a16af20e4443ee30fc26c33
parent 83257 2454b814b09f447371e2908d5440f8889f5e3558
child 83259 38344f96b3e3763be2d1d4d919e3615ac2fa640d
push id21744
push userbmo@edmorley.co.uk
push dateFri, 23 Dec 2011 23:56:40 +0000
treeherdermozilla-central@ede336ccaed0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs692274
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 692274, part 3 - Remove JSOP_BLOCKCHAIN and JSOP_NULLBLOCKCHAIN, which produces incorrect let scoping until the next patch (r=jorendorff)
js/src/frontend/BytecodeEmitter-inl.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/jit-test/tests/basic/testBug683470.js
js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js
js/src/jit-test/tests/jaeger/bug563000/trap-from-add-inline.js
js/src/jit-test/tests/jaeger/bug563000/trap-from-add-ool.js
js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js
js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsinfer.cpp
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/jsopcodeinlines.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/vm/StackSpace.h
--- a/js/src/frontend/BytecodeEmitter-inl.h
+++ b/js/src/frontend/BytecodeEmitter-inl.h
@@ -44,17 +44,17 @@
 #include "frontend/ParseNode.h"
 #include "frontend/TokenStream.h"
 
 namespace js {
 
 inline
 TreeContext::TreeContext(Parser *prs)
   : flags(0), bodyid(0), blockidGen(0), parenDepth(0), yieldCount(0), argumentsCount(0),
-    topStmt(NULL), topScopeStmt(NULL), blockChainBox(NULL), blockNode(NULL),
+    topStmt(NULL), topScopeStmt(NULL), blockChain(NULL), blockNode(NULL),
     decls(prs->context), parser(prs), yieldNode(NULL), argumentsNode(NULL), scopeChain_(NULL),
     lexdeps(prs->context), parent(prs->tc), staticLevel(0), funbox(NULL), functionList(NULL),
     innermostWith(NULL), bindings(prs->context), sharpSlotBase(-1)
 {
     prs->tc = this;
 }
 
 /*
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -92,19 +92,16 @@ using namespace js::frontend;
 static JSBool
 NewTryNote(JSContext *cx, BytecodeEmitter *bce, JSTryNoteKind kind, uintN stackDepth,
            size_t start, size_t end);
 
 static bool
 EmitIndexOp(JSContext *cx, JSOp op, uintN index, BytecodeEmitter *bce, JSOp *psuffix = NULL);
 
 static JSBool
-EmitLeaveBlock(JSContext *cx, BytecodeEmitter *bce, JSOp op, ObjectBox *box);
-
-static JSBool
 SetSrcNoteOffset(JSContext *cx, BytecodeEmitter *bce, uintN index, uintN which, ptrdiff_t offset);
 
 void
 TreeContext::trace(JSTracer *trc)
 {
     bindings.trace(trc);
 }
 
@@ -294,34 +291,16 @@ frontend::Emit3(JSContext *cx, BytecodeE
         next[2] = op2;
         bce->current->next = next + 3;
         UpdateDepth(cx, bce, offset);
     }
     return offset;
 }
 
 ptrdiff_t
-frontend::Emit5(JSContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t op1, uint16_t op2)
-{
-    ptrdiff_t offset = EmitCheck(cx, bce, 5);
-
-    if (offset >= 0) {
-        jsbytecode *next = bce->next();
-        next[0] = (jsbytecode)op;
-        next[1] = UINT16_HI(op1);
-        next[2] = UINT16_LO(op1);
-        next[3] = UINT16_HI(op2);
-        next[4] = UINT16_LO(op2);
-        bce->current->next = next + 5;
-        UpdateDepth(cx, bce, offset);
-    }
-    return offset;
-}
-
-ptrdiff_t
 frontend::EmitN(JSContext *cx, BytecodeEmitter *bce, JSOp op, size_t extra)
 {
     ptrdiff_t length = 1 + (ptrdiff_t)extra;
     ptrdiff_t offset = EmitCheck(cx, bce, length);
 
     if (offset >= 0) {
         jsbytecode *next = bce->next();
         *next = (jsbytecode)op;
@@ -1361,38 +1340,37 @@ frontend::GenerateBlockId(TreeContext *t
 void
 frontend::PushStatement(TreeContext *tc, StmtInfo *stmt, StmtType type, ptrdiff_t top)
 {
     stmt->type = type;
     stmt->flags = 0;
     stmt->blockid = tc->blockid();
     SET_STATEMENT_TOP(stmt, top);
     stmt->label = NULL;
-    JS_ASSERT(!stmt->blockBox);
+    JS_ASSERT(!stmt->blockObj);
     stmt->down = tc->topStmt;
     tc->topStmt = stmt;
     if (STMT_LINKS_SCOPE(stmt)) {
         stmt->downScope = tc->topScopeStmt;
         tc->topScopeStmt = stmt;
     } else {
         stmt->downScope = NULL;
     }
 }
 
 void
-frontend::PushBlockScope(TreeContext *tc, StmtInfo *stmt, ObjectBox *blockBox, ptrdiff_t top)
+frontend::PushBlockScope(TreeContext *tc, StmtInfo *stmt, JSObject *blockObj, ptrdiff_t top)
 {
     PushStatement(tc, stmt, STMT_BLOCK, top);
     stmt->flags |= SIF_SCOPE;
-    blockBox->parent = tc->blockChainBox;
-    blockBox->object->setStaticBlockScopeChain(tc->blockChain());
+    blockObj->setStaticBlockScopeChain(tc->blockChain);
     stmt->downScope = tc->topScopeStmt;
     tc->topScopeStmt = stmt;
-    tc->blockChainBox = blockBox;
-    stmt->blockBox = blockBox;
+    tc->blockChain = blockObj;
+    stmt->blockObj = blockObj;
 }
 
 /*
  * Emit a backpatch op with offset pointing to the previous jump of this type,
  * so that we can walk back up the chain fixing up the op and jump offset.
  */
 static ptrdiff_t
 EmitBackPatchOp(JSContext *cx, BytecodeEmitter *bce, JSOp op, ptrdiff_t *lastp)
@@ -1576,42 +1554,28 @@ EmitNonLocalJumpFixup(JSContext *cx, Byt
           default:;
         }
 
         if (stmt->flags & SIF_SCOPE) {
             /* There is a Block object with locals on the stack to pop. */
             FLUSH_POPS();
             if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
                 return JS_FALSE;
-            if (!EmitLeaveBlock(cx, bce, JSOP_LEAVEBLOCK, stmt->blockBox))
-                return JS_FALSE;
+            uintN i = OBJ_BLOCK_COUNT(cx, stmt->blockObj);
+            EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, i);
         }
     }
 
     FLUSH_POPS();
     bce->stackDepth = depth;
     return JS_TRUE;
 
 #undef FLUSH_POPS
 }
 
-static JSBool
-EmitKnownBlockChain(JSContext *cx, BytecodeEmitter *bce, ObjectBox *box)
-{
-    if (box)
-        return EmitIndexOp(cx, JSOP_BLOCKCHAIN, box->index, bce);
-    return Emit1(cx, bce, JSOP_NULLBLOCKCHAIN) >= 0;
-}
-
-static JSBool
-EmitBlockChain(JSContext *cx, BytecodeEmitter *bce)
-{
-    return EmitKnownBlockChain(cx, bce, bce->blockChainBox);
-}
-
 static const jsatomid INVALID_ATOMID = -1;
 
 static ptrdiff_t
 EmitGoto(JSContext *cx, BytecodeEmitter *bce, StmtInfo *toStmt, ptrdiff_t *lastp,
          jsatomid labelIndex = INVALID_ATOMID, SrcNoteType noteType = SRC_NULL)
 {
     intN index;
 
@@ -1622,24 +1586,17 @@ EmitGoto(JSContext *cx, BytecodeEmitter 
         index = NewSrcNote2(cx, bce, noteType, ptrdiff_t(labelIndex));
     else if (noteType != SRC_NULL)
         index = NewSrcNote(cx, bce, noteType);
     else
         index = 0;
     if (index < 0)
         return -1;
 
-    ptrdiff_t result = EmitBackPatchOp(cx, bce, JSOP_BACKPATCH, lastp);
-    if (result < 0)
-        return result;
-
-    if (!EmitBlockChain(cx, bce))
-        return -1;
-
-    return result;
+    return EmitBackPatchOp(cx, bce, JSOP_BACKPATCH, lastp);
 }
 
 static JSBool
 BackPatch(JSContext *cx, BytecodeEmitter *bce, ptrdiff_t last, jsbytecode *target, jsbytecode op)
 {
     jsbytecode *pc, *stop;
     ptrdiff_t delta, span;
 
@@ -1663,19 +1620,18 @@ BackPatch(JSContext *cx, BytecodeEmitter
 
 void
 frontend::PopStatementTC(TreeContext *tc)
 {
     StmtInfo *stmt = tc->topStmt;
     tc->topStmt = stmt->down;
     if (STMT_LINKS_SCOPE(stmt)) {
         tc->topScopeStmt = stmt->downScope;
-        if (stmt->flags & SIF_SCOPE) {
-            tc->blockChainBox = stmt->blockBox->parent;
-        }
+        if (stmt->flags & SIF_SCOPE)
+            tc->blockChain = stmt->blockObj->staticBlockScopeChain();
     }
 }
 
 JSBool
 frontend::PopStatementBCE(JSContext *cx, BytecodeEmitter *bce)
 {
     StmtInfo *stmt = bce->topStmt;
     if (!STMT_IS_TRYING(stmt) &&
@@ -1707,17 +1663,17 @@ frontend::LexicalLookup(TreeContext *tc,
     for (; stmt; stmt = stmt->downScope) {
         if (stmt->type == STMT_WITH)
             break;
 
         /* Skip "maybe scope" statements that don't contain let bindings. */
         if (!(stmt->flags & SIF_SCOPE))
             continue;
 
-        JSObject *obj = stmt->blockBox->object;
+        JSObject *obj = stmt->blockObj;
         JS_ASSERT(obj->isStaticBlock());
 
         const Shape *shape = obj->nativeLookup(tc->parser->context, ATOM_TO_JSID(atom));
         if (shape) {
             JS_ASSERT(shape->hasShortID());
 
             if (slotp) {
                 JS_ASSERT(obj->getSlot(JSSLOT_BLOCK_DEPTH).isInt32());
@@ -2026,30 +1982,16 @@ EmitEnterBlock(JSContext *cx, ParseNode 
         if (!Shape::setExtensibleParents(cx, &shape))
             return false;
         blockObj->setLastPropertyInfallible(shape);
     }
 
     return true;
 }
 
-static JSBool
-EmitLeaveBlock(JSContext *cx, BytecodeEmitter *bce, JSOp op, ObjectBox *box)
-{
-    JSOp bigSuffix;
-    uintN count = OBJ_BLOCK_COUNT(cx, box->object);
-    
-    bigSuffix = EmitBigIndexPrefix(cx, bce, box->index);
-    if (bigSuffix == JSOP_FALSE)
-        return JS_FALSE;
-    if (Emit5(cx, bce, op, count, box->index) < 0)
-        return JS_FALSE;
-    return bigSuffix == JSOP_NOP || Emit1(cx, bce, bigSuffix) >= 0;
-}
-
 /*
  * Try to convert a *NAME op to a *GNAME op, which optimizes access to
  * undeclared globals. Return true if a conversion was made.
  *
  * This conversion is not made if we are in strict mode.  In eval code nested
  * within (strict mode) eval code, access to an undeclared "global" might
  * merely be to a binding local to that outer eval:
  *
@@ -3218,81 +3160,31 @@ EmitNumberOp(JSContext *cx, jsdouble dva
  * of large switches, which seems unlikely.
  */
 static Value *
 AllocateSwitchConstant(JSContext *cx)
 {
     return cx->tempLifoAlloc().new_<Value>();
 }
 
-/*
- * Sometimes, let-slots are pushed to the JS stack before we logically enter
- * the let scope. For example,
- *     let (x = EXPR) BODY
- * compiles to roughly {enterblock; EXPR; setlocal x; BODY; leaveblock} even
- * though EXPR is evaluated in the enclosing scope; it does not see x.
- *
- * In those cases we use TempPopScope around the code to emit EXPR. It
- * temporarily removes the let-scope from the BytecodeEmitter's scope stack and
- * emits extra bytecode to ensure that js::GetBlockChain also finds the correct
- * scope at run time.
- */
-class TempPopScope {
-    StmtInfo *savedStmt;
-    StmtInfo *savedScopeStmt;
-    ObjectBox *savedBlockBox;
-
-  public:
-    TempPopScope() : savedStmt(NULL), savedScopeStmt(NULL), savedBlockBox(NULL) {}
-
-    bool popBlock(JSContext *cx, BytecodeEmitter *bce) {
-        savedStmt = bce->topStmt;
-        savedScopeStmt = bce->topScopeStmt;
-        savedBlockBox = bce->blockChainBox;
-
-        if (bce->topStmt->type == STMT_FOR_LOOP || bce->topStmt->type == STMT_FOR_IN_LOOP)
-            PopStatementTC(bce);
-        JS_ASSERT(STMT_LINKS_SCOPE(bce->topStmt));
-        JS_ASSERT(bce->topStmt->flags & SIF_SCOPE);
-        PopStatementTC(bce);
-
-        /*
-         * Since we have changed the block chain, emit an instruction marking
-         * the change for the benefit of dynamic GetScopeChain callers such as
-         * the debugger.
-         *
-         * FIXME bug 671360 - The JSOP_NOP instruction should not be necessary.
-         */
-        return Emit1(cx, bce, JSOP_NOP) >= 0 && EmitBlockChain(cx, bce);
-    }
-
-    bool repushBlock(JSContext *cx, BytecodeEmitter *bce) {
-        JS_ASSERT(savedStmt);
-        bce->topStmt = savedStmt;
-        bce->topScopeStmt = savedScopeStmt;
-        bce->blockChainBox = savedBlockBox;
-        return Emit1(cx, bce, JSOP_NOP) >= 0 && EmitBlockChain(cx, bce);
-    }
-};
-
 static JSBool
 EmitSwitch(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JSOp switchOp;
     JSBool ok, hasDefault, constPropagated;
     ptrdiff_t top, off, defaultOffset;
     ParseNode *pn2, *pn3, *pn4;
     uint32_t caseCount, tableLength;
     ParseNode **table;
     int32_t i, low, high;
     intN noteIndex;
     size_t switchSize, tableSize;
     jsbytecode *pc, *savepc;
 #if JS_HAS_BLOCK_SCOPE
-    ObjectBox *box;
+    int count;
 #endif
     StmtInfo stmtInfo;
 
     /* Try for most optimal, fall back if not dense ints, and per ECMAv2. */
     switchOp = JSOP_TABLESWITCH;
     ok = JS_TRUE;
     hasDefault = constPropagated = JS_FALSE;
     defaultOffset = -1;
@@ -3300,42 +3192,34 @@ EmitSwitch(JSContext *cx, BytecodeEmitte
     /*
      * If the switch contains let variables scoped by its body, model the
      * resulting block on the stack first, before emitting the discriminant's
      * bytecode (in case the discriminant contains a stack-model dependency
      * such as a let expression).
      */
     pn2 = pn->pn_right;
 #if JS_HAS_BLOCK_SCOPE
-    TempPopScope tps;
     if (pn2->isKind(PNK_LEXICALSCOPE)) {
         /*
          * Push the body's block scope before discriminant code-gen to reflect
          * the order of slots on the stack. The block's locals must lie under
          * the discriminant on the stack so that case-dispatch bytecodes can
          * find the discriminant on top of stack.
          */
-        box = pn2->pn_objbox;
-        PushBlockScope(bce, &stmtInfo, box, -1);
+        count = OBJ_BLOCK_COUNT(cx, pn2->pn_objbox->object);
+        PushBlockScope(bce, &stmtInfo, pn2->pn_objbox->object, -1);
         stmtInfo.type = STMT_SWITCH;
 
         /* Emit JSOP_ENTERBLOCK before code to evaluate the discriminant. */
         if (!EmitEnterBlock(cx, pn2, bce))
             return JS_FALSE;
-
-        /*
-         * Pop the switch's statement info around discriminant code-gen, which
-         * belongs in the enclosing scope.
-         */
-        if (!tps.popBlock(cx, bce))
-            return JS_FALSE;
     }
 #ifdef __GNUC__
     else {
-        box = NULL;
+        count = 0;
     }
 #endif
 #endif
 
     /*
      * Emit code for the discriminant first (or nearly first, in the case of a
      * switch whose body is a block scope).
      */
@@ -3345,20 +3229,16 @@ EmitSwitch(JSContext *cx, BytecodeEmitte
     /* Switch bytecodes run from here till end of final case. */
     top = bce->offset();
 #if !JS_HAS_BLOCK_SCOPE
     PushStatement(bce, &stmtInfo, STMT_SWITCH, top);
 #else
     if (pn2->isKind(PNK_STATEMENTLIST)) {
         PushStatement(bce, &stmtInfo, STMT_SWITCH, top);
     } else {
-        /* Re-push the switch's statement info record. */
-        if (!tps.repushBlock(cx, bce))
-            return JS_FALSE;
-
         /*
          * Set the statement info record's idea of top. Reset top too, since
          * repushBlock emits code.
          */
         stmtInfo.update = top = bce->offset();
 
         /* Advance pn2 to refer to the switch case list. */
         pn2 = pn2->expr();
@@ -3801,17 +3681,17 @@ EmitSwitch(JSContext *cx, BytecodeEmitte
 out:
     if (table)
         cx->free_(table);
     if (ok) {
         ok = PopStatementBCE(cx, bce);
 
 #if JS_HAS_BLOCK_SCOPE
         if (ok && pn->pn_right->isKind(PNK_LEXICALSCOPE))
-            ok = EmitLeaveBlock(cx, bce, JSOP_LEAVEBLOCK, box);
+            EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
 #endif
     }
     return ok;
 
 bad:
     ok = JS_FALSE;
     goto out;
 }
@@ -4951,26 +4831,24 @@ EmitTry(JSContext *cx, BytecodeEmitter *
     /* Emit (hidden) jump over catch and/or finally. */
     if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
         return false;
     if (EmitBackPatchOp(cx, bce, JSOP_BACKPATCH, &catchJump) < 0)
         return false;
 
     ptrdiff_t tryEnd = bce->offset();
 
-    ObjectBox *prevBox = NULL;
     /* If this try has a catch block, emit it. */
     ParseNode *lastCatch = NULL;
     if (ParseNode *pn2 = pn->pn_kid2) {
         uintN count = 0;    /* previous catch block's population */
 
         /*
          * The emitted code for a catch block looks like:
          *
-         * blockchain
          * [throwing]                          only if 2nd+ catch block
          * [leaveblock]                        only if 2nd+ catch block
          * enterblock                          with SRC_CATCH
          * exception
          * [dup]                               only if catchguard
          * setlocalpop <slot>                  or destructuring code
          * [< catchguard code >]               if there's a catchguard
          * [ifeq <offset to next catch block>]         " "
@@ -4986,19 +4864,16 @@ EmitTry(JSContext *cx, BytecodeEmitter *
          * thrown from catch{} blocks.
          */
         for (ParseNode *pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
             ptrdiff_t guardJump, catchNote;
 
             JS_ASSERT(bce->stackDepth == depth);
             guardJump = GUARDJUMP(stmtInfo);
             if (guardJump != -1) {
-                if (EmitKnownBlockChain(cx, bce, prevBox) < 0)
-                    return false;
-            
                 /* Fix up and clean up previous catch block. */
                 CHECK_AND_SET_JUMP_OFFSET_AT(cx, bce, guardJump);
 
                 /*
                  * Account for JSOP_ENTERBLOCK (whose block object count
                  * is saved below) and pushed exception object that we
                  * still have after the jumping from the previous guard.
                  */
@@ -5011,18 +4886,17 @@ EmitTry(JSContext *cx, BytecodeEmitter *
                  * start of the previous guarded catch.
                  */
                 if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0 ||
                     Emit1(cx, bce, JSOP_THROWING) < 0) {
                     return false;
                 }
                 if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
                     return false;
-                if (!EmitLeaveBlock(cx, bce, JSOP_LEAVEBLOCK, prevBox))
-                    return false;
+                EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
                 JS_ASSERT(bce->stackDepth == depth);
             }
 
             /*
              * Annotate the JSOP_ENTERBLOCK that's about to be generated
              * by the call to EmitTree immediately below.  Save this
              * source note's index in stmtInfo for use by the PNK_CATCH:
              * case, where the length of the catch guard is set as the
@@ -5035,17 +4909,16 @@ EmitTry(JSContext *cx, BytecodeEmitter *
 
             /*
              * Emit the lexical scope and catch body.  Save the catch's
              * block object population via count, for use when targeting
              * guardJump at the next catch (the guard mismatch case).
              */
             JS_ASSERT(pn3->isKind(PNK_LEXICALSCOPE));
             count = OBJ_BLOCK_COUNT(cx, pn3->pn_objbox->object);
-            prevBox = pn3->pn_objbox;
             if (!EmitTree(cx, bce, pn3))
                 return false;
 
             /* gosub <finally>, if required */
             if (pn->pn_kid3) {
                 if (EmitBackPatchOp(cx, bce, JSOP_BACKPATCH, &GOSUBS(stmtInfo)) < 0)
                     return false;
                 JS_ASSERT(bce->stackDepth == depth);
@@ -5070,34 +4943,28 @@ EmitTry(JSContext *cx, BytecodeEmitter *
 
     /*
      * Last catch guard jumps to the rethrow code sequence if none of the
      * guards match. Target guardJump at the beginning of the rethrow
      * sequence, just in case a guard expression throws and leaves the
      * stack unbalanced.
      */
     if (lastCatch && lastCatch->pn_kid2) {
-        if (EmitKnownBlockChain(cx, bce, prevBox) < 0)
-            return false;
-        
         CHECK_AND_SET_JUMP_OFFSET_AT(cx, bce, GUARDJUMP(stmtInfo));
 
         /* Sync the stack to take into account pushed exception. */
         JS_ASSERT(bce->stackDepth == depth);
         bce->stackDepth = depth + 1;
 
         /*
          * Rethrow the exception, delegating executing of finally if any
          * to the exception handler.
          */
         if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0 || Emit1(cx, bce, JSOP_THROW) < 0)
             return false;
-
-        if (EmitBlockChain(cx, bce) < 0)
-            return false;
     }
 
     JS_ASSERT(bce->stackDepth == depth);
 
     /* Emit finally handler if any. */
     ptrdiff_t finallyStart = 0;   /* to quell GCC uninitialized warnings */
     if (pn->pn_kid3) {
         /*
@@ -5256,36 +5123,22 @@ EmitLet(JSContext *cx, BytecodeEmitter *
     ParseNode *pn2;
     if (pn->isArity(PN_BINARY)) {
         pn2 = pn->pn_right;
         pn = pn->pn_left;
     } else {
         pn2 = NULL;
     }
 
-    /*
-     * Non-null pn2 means that pn is the variable list from a let head.
-     *
-     * Use TempPopScope to evaluate the expressions in the enclosing scope.
-     * This also causes the initializing assignments to be emitted in the
-     * enclosing scope, but the assignment opcodes emitted here
-     * (essentially just setlocal, though destructuring assignment uses
-     * other additional opcodes) do not care about the block chain.
-     */
+    /* Non-null pn2 means that pn is the variable list from a let head. */
     JS_ASSERT(pn->isArity(PN_LIST));
-    TempPopScope tps;
-    bool popScope = pn2 || (bce->flags & TCF_IN_FOR_INIT);
-    if (popScope && !tps.popBlock(cx, bce))
-        return false;
     ptrdiff_t noteIndex;
     if (!EmitVariables(cx, bce, pn, pn2 != NULL, &noteIndex))
         return false;
     ptrdiff_t tmp = bce->offset();
-    if (popScope && !tps.repushBlock(cx, bce))
-        return false;
 
     /* Thus non-null pn2 is the body of the let block or expression. */
     if (pn2 && !EmitTree(cx, bce, pn2))
         return false;
 
     if (noteIndex >= 0 && !SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, bce->offset() - tmp))
         return false;
 
@@ -5369,17 +5222,17 @@ EmitXMLProcessingInstruction(JSContext *
 #endif
 
 static bool
 EmitLexicalScope(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfo stmtInfo;
     StmtInfo *stmt;
     ObjectBox *objbox = pn->pn_objbox;
-    PushBlockScope(bce, &stmtInfo, objbox, bce->offset());
+    PushBlockScope(bce, &stmtInfo, objbox->object, bce->offset());
 
     /*
      * If this lexical scope is not for a catch block, let block or let
      * expression, or any kind of for loop (where the scope starts in the
      * head after the first part if for (;;), else in the body if for-in);
      * and if our container is top-level but not a function body, or else
      * a block statement; then emit a SRC_BRACE note.  All other container
      * statements get braces by default from the decompiler.
@@ -5414,35 +5267,32 @@ EmitLexicalScope(JSContext *cx, Bytecode
         if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - top) < 0)
             return false;
     } else {
         if (noteIndex >= 0 && !SetSrcNoteOffset(cx, bce, (uintN)noteIndex, 0, bce->offset() - top))
             return false;
     }
 
     /* Emit the JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR opcode. */
-    if (!EmitLeaveBlock(cx, bce, op, objbox))
-        return false;
+    uintN count = OBJ_BLOCK_COUNT(cx, objbox->object);
+    EMIT_UINT16_IMM_OP(op, count);
 
     return PopStatementBCE(cx, bce);
 }
 
 static bool
 EmitWith(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfo stmtInfo;
     if (!EmitTree(cx, bce, pn->pn_left))
         return false;
     PushStatement(bce, &stmtInfo, STMT_WITH, bce->offset());
     if (Emit1(cx, bce, JSOP_ENTERWITH) < 0)
         return false;
 
-    /* Make blockChain determination quicker. */
-    if (EmitBlockChain(cx, bce) < 0)
-        return false;
     if (!EmitTree(cx, bce, pn->pn_right))
         return false;
     if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0)
         return false;
     return PopStatementBCE(cx, bce);
 }
 
 static bool
@@ -5490,36 +5340,27 @@ EmitForIn(JSContext *cx, BytecodeEmitter
 
     /*
      * If the left part is 'var x', emit code to define x if necessary
      * using a prolog opcode, but do not emit a pop. If the left part
      * was originally 'var x = i', the parser will have rewritten it;
      * see Parser::forStatement. 'for (let x = i in o)' is mercifully
      * banned.
      */
-    bool forLet = false;
     if (ParseNode *decl = forHead->pn_kid1) {
         JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
-        forLet = decl->isKind(PNK_LET);
         bce->flags |= TCF_IN_FOR_INIT;
         if (!EmitTree(cx, bce, decl))
             return false;
         bce->flags &= ~TCF_IN_FOR_INIT;
     }
 
     /* Compile the object expression to the right of 'in'. */
-    {
-        TempPopScope tps;
-        if (forLet && !tps.popBlock(cx, bce))
-            return false;
-        if (!EmitTree(cx, bce, forHead->pn_kid3))
-            return false;
-        if (forLet && !tps.repushBlock(cx, bce))
-            return false;
-    }
+    if (!EmitTree(cx, bce, forHead->pn_kid3))
+        return JS_FALSE;
 
     /*
      * Emit a bytecode to convert top of stack value to the iterator
      * object depending on the loop variant (for-in, for-each-in, or
      * destructuring for-in).
      */
     JS_ASSERT(pn->isOp(JSOP_ITER));
     if (Emit2(cx, bce, JSOP_ITER, (uint8_t) pn->pn_iflags) < 0)
@@ -5820,19 +5661,17 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
     /* Emit a bytecode pointing to the closure object in its immediate. */
     if (pn->getOp() != JSOP_NOP) {
         if ((pn->pn_funbox->tcflags & TCF_GENEXP_LAMBDA) &&
             NewSrcNote(cx, bce, SRC_GENEXP) < 0)
         {
             return false;
         }
         EMIT_INDEX_OP(pn->getOp(), index);
-
-        /* Make blockChain determination quicker. */
-        return EmitBlockChain(cx, bce) >= 0;
+        return true;
     }
 
     /*
      * For a script we emit the code as we parse. Thus the bytecode for
      * top-level functions should go in the prolog to predefine their
      * names in the variable object before the already-generated main code
      * is executed. This extra work for top-level scripts is not necessary
      * when we emit the code for a function. It is fully parsed prior to
@@ -5842,20 +5681,16 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
     if (!bce->inFunction()) {
         JS_ASSERT(!bce->topStmt);
         if (!BindGlobal(cx, bce, pn, fun->atom))
             return false;
         if (pn->pn_cookie.isFree()) {
             bce->switchToProlog();
             JSOp op = fun->isFlatClosure() ? JSOP_DEFFUN_FC : JSOP_DEFFUN;
             EMIT_INDEX_OP(op, index);
-
-            /* Make blockChain determination quicker. */
-            if (EmitBlockChain(cx, bce) < 0)
-                return false;
             bce->switchToMain();
         }
 
         /* Emit NOP for the decompiler. */
         if (!EmitFunctionDefNop(cx, bce, index))
             return false;
     } else {
         uintN slot;
@@ -5865,21 +5700,17 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
         pn->pn_index = index;
         JSOp op = fun->isFlatClosure() ? JSOP_DEFLOCALFUN_FC : JSOP_DEFLOCALFUN;
         if (pn->isClosed() &&
             !bce->callsEval() &&
             !bce->closedVars.append(pn->pn_cookie.slot()))
         {
             return false;
         }
-        if (!EmitSlotIndexOp(cx, op, slot, index, bce))
-            return false;
-
-        /* Make blockChain determination quicker. */
-        return EmitBlockChain(cx, bce) >= 0;
+        return EmitSlotIndexOp(cx, op, slot, index, bce);
     }
 
     return true;
 }
 
 static bool
 EmitDo(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
@@ -6063,18 +5894,16 @@ EmitReturn(JSContext *cx, BytecodeEmitte
     if (Emit1(cx, bce, JSOP_RETURN) < 0)
         return false;
     if (!EmitNonLocalJumpFixup(cx, bce, NULL))
         return false;
     if (top + JSOP_RETURN_LENGTH != bce->offset()) {
         bce->base()[top] = JSOP_SETRVAL;
         if (Emit1(cx, bce, JSOP_RETRVAL) < 0)
             return false;
-        if (EmitBlockChain(cx, bce) < 0)
-            return false;
     }
 
     return true;
 }
 
 static bool
 EmitStatementList(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
@@ -6376,21 +6205,18 @@ EmitCallOrNew(JSContext *cx, BytecodeEmi
     bce->flags |= oldflags & TCF_IN_FOR_INIT;
     if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - off) < 0)
         return false;
 
     uint32_t argc = pn->pn_count - 1;
     if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
         return false;
     CheckTypeSet(cx, bce, pn->getOp());
-    if (pn->isOp(JSOP_EVAL)) {
+    if (pn->isOp(JSOP_EVAL))
         EMIT_UINT16_IMM_OP(JSOP_LINENO, pn->pn_pos.begin.lineno);
-        if (EmitBlockChain(cx, bce) < 0)
-            return false;
-    }
     if (pn->pn_xflags & PNX_SETCALL) {
         if (Emit1(cx, bce, JSOP_SETCALL) < 0)
             return false;
     }
     return true;
 }
 
 static bool
@@ -7207,20 +7033,16 @@ frontend::EmitTree(JSContext *cx, Byteco
         top = EmitTraceOp(cx, bce, pn->pn_right);
         if (top < 0)
             return JS_FALSE;
         if (!EmitTree(cx, bce, pn->pn_right))
             return JS_FALSE;
         CHECK_AND_SET_JUMP_OFFSET_AT(cx, bce, jmp);
         if (EmitJump(cx, bce, JSOP_ENDFILTER, top - bce->offset()) < 0)
             return JS_FALSE;
-
-        /* Make blockChain determination quicker. */
-        if (EmitBlockChain(cx, bce) < 0)
-            return JS_FALSE;
         break;
 #endif
 
       case PNK_DOT:
         /*
          * Pop a stack operand, convert it to object, get a property named by
          * this bytecode's immediate-indexed atom operand, and push its value
          * (not a reference to it).
@@ -7847,18 +7669,17 @@ frontend::FinishTakingTryNotes(BytecodeE
  * the pre-compilation prototype, a pigeon-hole problem for instanceof tests.
  */
 uintN
 CGObjectList::index(ObjectBox *objbox)
 {
     JS_ASSERT(!objbox->emitLink);
     objbox->emitLink = lastbox;
     lastbox = objbox;
-    objbox->index = length++;
-    return objbox->index;
+    return length++;
 }
 
 void
 CGObjectList::finish(JSObjectArray *array)
 {
     JS_ASSERT(length <= INDEX_LIMIT);
     JS_ASSERT(length == array->length);
 
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -133,17 +133,17 @@ struct StmtInfo {
     uint16_t        type;           /* statement type */
     uint16_t        flags;          /* flags, see below */
     uint32_t        blockid;        /* for simplified dominance computation */
     ptrdiff_t       update;         /* loop update offset (top if none) */
     ptrdiff_t       breaks;         /* offset of last break in loop */
     ptrdiff_t       continues;      /* offset of last continue in loop */
     union {
         JSAtom      *label;         /* name of LABEL */
-        ObjectBox   *blockBox;      /* block scope object */
+        JSObject    *blockObj;      /* block scope object */
     };
     StmtInfo        *down;          /* info for enclosing statement */
     StmtInfo        *downScope;     /* next enclosing lexical scope */
 };
 
 #define SIF_SCOPE        0x0001     /* statement has its own lexical scope */
 #define SIF_BODY_BLOCK   0x0002     /* STMT_BLOCK type is a function body */
 #define SIF_FOR_BLOCK    0x0004     /* for (let ...) induced block scope */
@@ -294,17 +294,17 @@ struct TreeContext {                /* t
     uint32_t        parenDepth;     /* nesting depth of parens that might turn out
                                        to be generator expressions */
     uint32_t        yieldCount;     /* number of |yield| tokens encountered at
                                        non-zero depth in current paren tree */
     uint32_t        argumentsCount; /* number of |arguments| references encountered
                                        at non-zero depth in current paren tree */
     StmtInfo        *topStmt;       /* top of statement info stack */
     StmtInfo        *topScopeStmt;  /* top lexical scope statement */
-    ObjectBox       *blockChainBox; /* compile time block scope chain (NB: one
+    JSObject        *blockChain;    /* compile time block scope chain (NB: one
                                        deeper than the topScopeStmt/downScope
                                        chain when in head of let block/expr) */
     ParseNode       *blockNode;     /* parse node for a block with let declarations
                                        (block with its own lexical scope)  */
     AtomDecls       decls;          /* function, const, and var declarations */
     Parser          *parser;        /* ptr to common parsing and lexing data */
     ParseNode       *yieldNode;     /* parse node for a yield expression that might
                                        be an error if we turn out to be inside a
@@ -371,20 +371,16 @@ struct TreeContext {                /* t
     bool init(JSContext *cx, InitBehavior ib = USED_AS_TREE_CONTEXT) {
         if (ib == USED_AS_CODE_GENERATOR)
             return true;
         return decls.init() && lexdeps.ensureMap(cx);
     }
 
     uintN blockid() { return topStmt ? topStmt->blockid : bodyid; }
 
-    JSObject *blockChain() {
-        return blockChainBox ? blockChainBox->object : NULL;
-    }
-
     /*
      * True if we are at the topmost level of a entire script or function body.
      * For example, while parsing this code we would encounter f1 and f2 at
      * body level, but we would not encounter f3 or f4 at body level:
      *
      *   function f1() { function f2() { } }
      *   if (cond) { function f3() { if (cond) { function f4() { } } } }
      */
@@ -792,22 +788,16 @@ Emit2(JSContext *cx, BytecodeEmitter *bc
 
 /*
  * Emit three bytecodes, an opcode with two bytes of immediate operands.
  */
 ptrdiff_t
 Emit3(JSContext *cx, BytecodeEmitter *bce, JSOp op, jsbytecode op1, jsbytecode op2);
 
 /*
- * Emit five bytecodes, an opcode with two 16-bit immediates.
- */
-ptrdiff_t
-Emit5(JSContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t op1, uint16_t op2);
-
-/*
  * Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
  */
 ptrdiff_t
 EmitN(JSContext *cx, BytecodeEmitter *bce, JSOp op, size_t extra);
 
 /*
  * Unsafe macro to call SetJumpOffset and return false if it does.
  */
@@ -838,17 +828,17 @@ void
 PushStatement(TreeContext *tc, StmtInfo *stmt, StmtType type, ptrdiff_t top);
 
 /*
  * Push a block scope statement and link blockObj into tc->blockChain. To pop
  * this statement info record, use PopStatementTC as usual, or if appropriate
  * (if generating code), PopStatementBCE.
  */
 void
-PushBlockScope(TreeContext *tc, StmtInfo *stmt, ObjectBox *blockBox, ptrdiff_t top);
+PushBlockScope(TreeContext *tc, StmtInfo *stmt, JSObject *blockObj, ptrdiff_t top);
 
 /*
  * Pop tc->topStmt. If the top StmtInfo struct is not stack-allocated, it
  * is up to the caller to free it.
  */
 void
 PopStatementTC(TreeContext *tc);
 
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -1288,18 +1288,16 @@ LinkUseToDef(ParseNode *pn, Definition *
     pn->setUsed(true);
     pn->pn_lexdef = dn;
 }
 
 struct ObjectBox {
     ObjectBox           *traceLink;
     ObjectBox           *emitLink;
     JSObject            *object;
-    ObjectBox           *parent;
-    uintN               index;
     bool                isFunctionBox;
 };
 
 #define JSFB_LEVEL_BITS 14
 
 struct FunctionBox : public ObjectBox
 {
     ParseNode           *node;
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1877,17 +1877,17 @@ MatchLabel(JSContext *cx, TokenStream *t
     return true;
 }
 
 /*
  * Define a let-variable in a block, let-expression, or comprehension scope. tc
  * must already be in such a scope.
  *
  * Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a
- * property for the new variable on the block object, tc->blockChain();
+ * property for the new variable on the block object, tc->blockChain;
  * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to
  * data->pn in a slot of the block object.
  */
 static JSBool
 BindLet(JSContext *cx, BindData *data, JSAtom *atom, TreeContext *tc)
 {
     ParseNode *pn;
     JSObject *blockObj;
@@ -1898,17 +1898,17 @@ BindLet(JSContext *cx, BindData *data, J
      * successor standard to ES5 that specifies 'let'.
      */
     JS_ASSERT(!tc->atBodyLevel());
 
     pn = data->pn;
     if (!CheckStrictBinding(cx, tc, atom->asPropertyName(), pn))
         return false;
 
-    blockObj = tc->blockChain();
+    blockObj = tc->blockChain;
     Definition *dn = tc->decls.lookupFirst(atom);
     if (dn && dn->pn_blockid == tc->blockid()) {
         JSAutoByteString name;
         if (js_AtomToPrintableString(cx, atom, &name)) {
             ReportCompileErrorNumber(cx, TS(tc->parser), pn,
                                      JSREPORT_ERROR, JSMSG_REDECLARED_VAR,
                                      dn->isConst() ? "const" : "variable",
                                      name.ptr());
@@ -1961,17 +1961,17 @@ BindLet(JSContext *cx, BindData *data, J
 }
 
 static void
 PopStatement(TreeContext *tc)
 {
     StmtInfo *stmt = tc->topStmt;
 
     if (stmt->flags & SIF_SCOPE) {
-        JSObject *obj = stmt->blockBox->object;
+        JSObject *obj = stmt->blockObj;
         JS_ASSERT(!obj->isClonedBlock());
 
         for (Shape::Range r = obj->lastProperty()->all(); !r.empty(); r.popFront()) {
             JSAtom *atom = JSID_TO_ATOM(r.front().propid());
 
             /* Beware the empty destructuring dummy. */
             if (atom == tc->parser->context->runtime->atomState.emptyAtom)
                 continue;
@@ -2608,18 +2608,18 @@ CheckDestructuring(JSContext *cx, BindDa
      * slots, would be always positive.
      *
      * Note that we add such a property even if the block has locals due to
      * later let declarations in it. We optimize for code simplicity here,
      * not the fastest runtime performance with empty [] or {}.
      */
     if (data &&
         data->binder == BindLet &&
-        OBJ_BLOCK_COUNT(cx, tc->blockChain()) == 0 &&
-        !DefineNativeProperty(cx, tc->blockChain(),
+        OBJ_BLOCK_COUNT(cx, tc->blockChain) == 0 &&
+        !DefineNativeProperty(cx, tc->blockChain,
                               ATOM_TO_JSID(cx->runtime->atomState.emptyAtom),
                               UndefinedValue(), NULL, NULL,
                               JSPROP_ENUMERATE | JSPROP_PERMANENT,
                               Shape::HAS_SHORTID, 0)) {
         return false;
     }
 
     return true;
@@ -2773,17 +2773,17 @@ PushLexicalScope(JSContext *cx, TokenStr
     JSObject *obj = js_NewBlockObject(cx);
     if (!obj)
         return NULL;
 
     ObjectBox *blockbox = tc->parser->newObjectBox(obj);
     if (!blockbox)
         return NULL;
 
-    PushBlockScope(tc, stmt, blockbox, -1);
+    PushBlockScope(tc, stmt, obj, -1);
     pn->setOp(JSOP_LEAVEBLOCK);
     pn->pn_objbox = blockbox;
     pn->pn_cookie.makeFree();
     pn->pn_dflags = 0;
     if (!GenerateBlockId(tc, stmt->blockid))
         return NULL;
     pn->pn_blockid = stmt->blockid;
     return pn;
@@ -3634,17 +3634,17 @@ Parser::letStatement()
         StmtInfo *stmt = tc->topStmt;
         if (stmt &&
             (!STMT_MAYBE_SCOPE(stmt) || (stmt->flags & SIF_FOR_BLOCK))) {
             reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_LET_DECL_NOT_IN_BLOCK);
             return NULL;
         }
 
         if (stmt && (stmt->flags & SIF_SCOPE)) {
-            JS_ASSERT(tc->blockChainBox == stmt->blockBox);
+            JS_ASSERT(tc->blockChain == stmt->blockObj);
         } else {
             if (!stmt || (stmt->flags & SIF_BODY_BLOCK)) {
                 /*
                  * ES4 specifies that let at top level and at body-block scope
                  * does not shadow var, so convert back to var.
                  */
                 pn = variables(PNK_VAR, false);
                 if (!pn)
@@ -3680,20 +3680,19 @@ Parser::letStatement()
              * list stack, if it isn't already there.  If it is there, but it
              * lacks the SIF_SCOPE flag, it must be a try, catch, or finally
              * block.
              */
             stmt->flags |= SIF_SCOPE;
             stmt->downScope = tc->topScopeStmt;
             tc->topScopeStmt = stmt;
 
-            obj->setStaticBlockScopeChain(tc->blockChain());
-            blockbox->parent = tc->blockChainBox;
-            tc->blockChainBox = blockbox;
-            stmt->blockBox = blockbox;
+            obj->setStaticBlockScopeChain(tc->blockChain);
+            tc->blockChain = obj;
+            stmt->blockObj = obj;
 
 #ifdef DEBUG
             ParseNode *tmp = tc->blockNode;
             JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
 #endif
 
             /* Create a new lexical scope node for these statements. */
             ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, tc);
@@ -4191,17 +4190,17 @@ Parser::variables(ParseNodeKind kind, bo
     pn->makeEmpty();
 
     /*
      * SpiderMonkey const is really "write once per initialization evaluation"
      * var, whereas let is block scoped. ES-Harmony wants block-scoped const so
      * this code will change soon.
      */
     if (let) {
-        JS_ASSERT(tc->blockChainBox == scopeStmt->blockBox);
+        JS_ASSERT(tc->blockChain == scopeStmt->blockObj);
         data.binder = BindLet;
         data.let.overflow = JSMSG_TOO_MANY_LOCALS;
     } else {
         data.binder = BindVarOrConst;
     }
 
     ParseNode *pn2;
     do {
--- a/js/src/jit-test/tests/basic/testBug683470.js
+++ b/js/src/jit-test/tests/basic/testBug683470.js
@@ -6,10 +6,10 @@ f = (function() {
     Object.defineProperty(this, "x", ({}));
   }
   for each(let d in [0, 0]) {
     try {
       b(d);
     } catch (e) {}
   }
 })
-trap(f, 52, undefined);
+trap(f, 39, undefined);
 f()
--- a/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js
@@ -1,10 +1,10 @@
 // |jit-test| mjitalways;debug
 setDebug(true);
 
 function nop(){}
 function caller(code, obj) {
   eval(code); // Make the compiler give up on binding analysis.
   return x;
 }
-trap(caller, 14, "var x = 'success'; nop()");
+trap(caller, 12, "var x = 'success'; nop()");
 assertEq(caller("var y = 'ignominy'", this), "success");
--- a/js/src/jit-test/tests/jaeger/bug563000/trap-from-add-inline.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/trap-from-add-inline.js
@@ -1,13 +1,13 @@
 // |jit-test| debug
 setDebug(true);
 x = "notset";
 function main() {
   /* The JSOP_STOP in main. */
-  a = { valueOf: function () { trap(main, 36, "success()"); } };
+  a = { valueOf: function () { trap(main, 34, "success()"); } };
   a + "";
   x = "failure";
 }
 function success() { x = "success"; }
 
 main();
 assertEq(x, "success");
--- a/js/src/jit-test/tests/jaeger/bug563000/trap-from-add-ool.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/trap-from-add-ool.js
@@ -1,14 +1,14 @@
 // |jit-test| debug
 setDebug(true);
 x = "notset";
 function main() {
   /* The JSOP_STOP in main. */
-  a = { valueOf: function () { trap(main, 57, "success()"); } };
+  a = { valueOf: function () { trap(main, 55, "success()"); } };
   b = "";
   eval();
   a + b;
   x = "failure";
 }
 function success() { x = "success"; }
 
 main();
--- a/js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js
@@ -1,16 +1,16 @@
 // |jit-test| debug
 setDebug(true);
 x = "notset";
 
 function myparent(nested) {
   if (nested) {
     /* noop call in myparent */
-    trap(myparent, 50, "success()");
+    trap(myparent, 49, "success()");
   } else {
     myparent(true);
     x = "failure";
     noop();
   }
 }
 function noop() { }
 function success() { x = "success"; }
--- a/js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js
+++ b/js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js
@@ -9,16 +9,16 @@ function myparent(nested) {
     /* JSOP_CALL to doNothing in myparent with nested = true. */
     trap(myparent, 24, "success()");
     doNothing();
   } else {
     doNothing();
   }
 }
 /* JSOP_CALL to doNothing in myparent with nested = false. */
-trap(myparent, 35, "myparent(true)");
+trap(myparent, 34, "myparent(true)");
 
 function success() {
   x = "success";
 }
 
 myparent(false);
 assertEq(x, "success");
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -2276,17 +2276,17 @@ js_AllocFlatClosure(JSContext *cx, JSFun
     if (!data)
         return NULL;
 
     closure->setExtendedSlot(JSFunction::FLAT_CLOSURE_UPVARS_SLOT, PrivateValue(data));
     return closure;
 }
 
 JSFunction *
-js_NewFlatClosure(JSContext *cx, JSFunction *fun, JSOp op, size_t oplen)
+js_NewFlatClosure(JSContext *cx, JSFunction *fun)
 {
     /*
      * Flat closures cannot yet be partial, that is, all upvars must be copied,
      * or the closure won't be flattened. Therefore they do not need to search
      * enclosing scope objects via JSOP_NAME, etc.
      */
     JSObject *scopeChain = &cx->fp()->scopeChain();
 
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -43,17 +43,16 @@
  * JS function definitions.
  */
 #include "jsprvtd.h"
 #include "jspubtd.h"
 #include "jsobj.h"
 #include "jsatom.h"
 #include "jsscript.h"
 #include "jsstr.h"
-#include "jsopcode.h"
 
 #include "gc/Barrier.h"
 
 /*
  * The high two bits of JSFunction.flags encode whether the function is native
  * or interpreted, and if interpreted, what kind of optimized closure form (if
  * any) it might be.
  *
@@ -317,17 +316,17 @@ js_NewFunction(JSContext *cx, JSObject *
 extern JSFunction * JS_FASTCALL
 js_CloneFunctionObject(JSContext *cx, JSFunction *fun, JSObject *parent, JSObject *proto,
                        js::gc::AllocKind kind = JSFunction::FinalizeKind);
 
 extern JSFunction * JS_FASTCALL
 js_AllocFlatClosure(JSContext *cx, JSFunction *fun, JSObject *scopeChain);
 
 extern JSFunction *
-js_NewFlatClosure(JSContext *cx, JSFunction *fun, JSOp op, size_t oplen);
+js_NewFlatClosure(JSContext *cx, JSFunction *fun);
 
 extern JSFunction *
 js_DefineFunction(JSContext *cx, JSObject *obj, jsid id, JSNative native,
                   uintN nargs, uintN flags,
                   js::gc::AllocKind kind = JSFunction::FinalizeKind);
 
 /*
  * Flags for js_ValueToFunction and js_ReportIsNotFunction.
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3400,18 +3400,16 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
       case JSOP_DEFXMLNS:
       case JSOP_SHARPINIT:
       case JSOP_INDEXBASE:
       case JSOP_INDEXBASE1:
       case JSOP_INDEXBASE2:
       case JSOP_INDEXBASE3:
       case JSOP_RESETBASE:
       case JSOP_RESETBASE0:
-      case JSOP_BLOCKCHAIN:
-      case JSOP_NULLBLOCKCHAIN:
       case JSOP_POPV:
       case JSOP_DEBUGGER:
       case JSOP_SETCALL:
       case JSOP_TABLESWITCH:
       case JSOP_TABLESWITCHX:
       case JSOP_LOOKUPSWITCH:
       case JSOP_LOOKUPSWITCHX:
       case JSOP_TRY:
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -78,17 +78,16 @@
 #include "methodjit/Logging.h"
 #endif
 #include "vm/Debugger.h"
 
 #include "jsatominlines.h"
 #include "jsinferinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
-#include "jsopcodeinlines.h"
 #include "jsprobes.h"
 #include "jspropertycacheinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
 #include "jstypedarrayinlines.h"
 
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
@@ -102,134 +101,16 @@
 #if defined(JS_METHODJIT) && defined(JS_MONOIC)
 #include "methodjit/MonoIC.h"
 #endif
 
 using namespace js;
 using namespace js::gc;
 using namespace js::types;
 
-JSObject *
-js::GetScopeChain(JSContext *cx)
-{
-    /*
-     * Note: we don't need to expand inline frames here, because frames are
-     * only inlined when the caller and callee share the same scope chain.
-     */
-    StackFrame *fp = js_GetTopStackFrame(cx, FRAME_EXPAND_NONE);
-    if (!fp) {
-        /*
-         * There is no code active on this context. In place of an actual
-         * scope chain, use the context's global object, which is set in
-         * js_InitFunctionAndObjectClasses, and which represents the default
-         * scope chain for the embedding. See also js_FindClassObject.
-         *
-         * For embeddings that use the inner and outer object hooks, the inner
-         * object represents the ultimate global object, with the outer object
-         * acting as a stand-in.
-         */
-        JSObject *obj = cx->globalObject;
-        if (!obj) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INACTIVE);
-            return NULL;
-        }
-
-        OBJ_TO_INNER_OBJECT(cx, obj);
-        return obj;
-    }
-    return GetScopeChain(cx, fp);
-}
-
-/*
- * This computes the blockChain by iterating through the bytecode
- * of the current script until it reaches the PC. Each time it sees
- * an ENTERBLOCK or LEAVEBLOCK instruction, it records the new
- * blockChain. A faster variant of this function that doesn't
- * require bytecode scanning appears below.
- */
-JSObject *
-js::GetBlockChain(JSContext *cx, StackFrame *fp)
-{
-    if (!fp->isScriptFrame())
-        return NULL;
-
-    jsbytecode *target = fp->pcQuadratic(cx->stack);
-
-    JSScript *script = fp->script();
-    jsbytecode *start = script->code;
-
-    /*
-     * If the debugger asks for the scope chain at a pc where we are about to
-     * fix it up, advance target past the fixup. See bug 672804.
-     */
-    JSOp op = JSOp(*target);
-    while (op == JSOP_NOP || op == JSOP_INDEXBASE || op == JSOP_INDEXBASE1 ||
-           op == JSOP_INDEXBASE2 || op == JSOP_INDEXBASE3 ||
-           op == JSOP_BLOCKCHAIN || op == JSOP_NULLBLOCKCHAIN)
-    {
-        target += js_CodeSpec[op].length;
-        op = JSOp(*target);
-    }
-    JS_ASSERT(target >= start && target < start + script->length);
-
-    JSObject *blockChain = NULL;
-    uintN indexBase = 0;
-    ptrdiff_t oplen;
-    for (jsbytecode *pc = start; pc < target; pc += oplen) {
-        JSOp op = JSOp(*pc);
-        const JSCodeSpec *cs = &js_CodeSpec[op];
-        oplen = cs->length;
-        if (oplen < 0)
-            oplen = js_GetVariableBytecodeLength(pc);
-
-        if (op == JSOP_INDEXBASE)
-            indexBase = GET_INDEXBASE(pc);
-        else if (op == JSOP_INDEXBASE1 || op == JSOP_INDEXBASE2 || op == JSOP_INDEXBASE3)
-            indexBase = (op - JSOP_INDEXBASE1 + 1) << 16;
-        else if (op == JSOP_RESETBASE || op == JSOP_RESETBASE0)
-            indexBase = 0;
-        else if (op == JSOP_ENTERBLOCK)
-            blockChain = script->getObject(indexBase + GET_INDEX(pc));
-        else if (op == JSOP_LEAVEBLOCK || op == JSOP_LEAVEBLOCKEXPR)
-            blockChain = blockChain->getStaticBlockScopeChain();
-        else if (op == JSOP_BLOCKCHAIN)
-            blockChain = script->getObject(indexBase + GET_INDEX(pc));
-        else if (op == JSOP_NULLBLOCKCHAIN)
-            blockChain = NULL;
-    }
-
-    return blockChain;
-}
-
-/*
- * This function computes the current blockChain, but only in
- * the special case where a BLOCKCHAIN or NULLBLOCKCHAIN
- * instruction appears immediately after the current PC.
- * We ensure this happens for a few important ops like DEFFUN.
- * |oplen| is the length of opcode at the current PC.
- */
-JSObject *
-js::GetBlockChainFast(JSContext *cx, StackFrame *fp, JSOp op, size_t oplen)
-{
-    /* Assume that we're in a script frame. */
-    jsbytecode *pc = fp->pcQuadratic(cx->stack);
-    JS_ASSERT(JSOp(*pc) == op);
-
-    pc += oplen;
-    op = JSOp(*pc);
-
-    /* The fast paths assume no JSOP_RESETBASE/INDEXBASE noise. */
-    if (op == JSOP_NULLBLOCKCHAIN)
-        return NULL;
-    if (op == JSOP_BLOCKCHAIN)
-        return fp->script()->getObject(GET_INDEX(pc));
-
-    return GetBlockChain(cx, fp);
-}
-
 /*
  * 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
@@ -250,20 +131,20 @@ js::GetBlockChainFast(JSContext *cx, Sta
  * 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 GetScopeChain, which is also used in
  * some other cases --- entering 'with' blocks, for example.
  */
-static JSObject *
-GetScopeChainFull(JSContext *cx, StackFrame *fp, JSObject *blockChain)
+JSObject *
+js::GetScopeChain(JSContext *cx, StackFrame *fp)
 {
-    JSObject *sharedBlock = blockChain;
+    JSObject *sharedBlock = fp->maybeBlockChain();
 
     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_IF(fp->isNonEvalFunctionFrame() && fp->fun()->isHeavyweight(),
                      fp->hasCallObj());
@@ -336,17 +217,17 @@ GetScopeChainFull(JSContext *cx, StackFr
 
     /*
      * 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 (;;) {
         JS_ASSERT(newChild->getProto() == sharedBlock);
-        sharedBlock = sharedBlock->getStaticBlockScopeChain();
+        sharedBlock = sharedBlock->staticBlockScopeChain();
 
         /* 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);
         if (!clone)
@@ -370,25 +251,44 @@ GetScopeChainFull(JSContext *cx, StackFr
                  sharedBlock);
 
     /* Place our newly cloned blocks at the head of the scope chain.  */
     fp->setScopeChainNoCallObj(*innermostNewChild);
     return innermostNewChild;
 }
 
 JSObject *
-js::GetScopeChain(JSContext *cx, StackFrame *fp)
+js::GetScopeChain(JSContext *cx)
 {
-    return GetScopeChainFull(cx, fp, GetBlockChain(cx, fp));
-}
-
-JSObject *
-js::GetScopeChainFast(JSContext *cx, StackFrame *fp, JSOp op, size_t oplen)
-{
-    return GetScopeChainFull(cx, fp, GetBlockChainFast(cx, fp, op, oplen));
+    /*
+     * Note: we don't need to expand inline frames here, because frames are
+     * only inlined when the caller and callee share the same scope chain.
+     */
+    StackFrame *fp = js_GetTopStackFrame(cx, FRAME_EXPAND_NONE);
+    if (!fp) {
+        /*
+         * There is no code active on this context. In place of an actual
+         * scope chain, use the context's global object, which is set in
+         * js_InitFunctionAndObjectClasses, and which represents the default
+         * scope chain for the embedding. See also js_FindClassObject.
+         *
+         * For embeddings that use the inner and outer object hooks, the inner
+         * object represents the ultimate global object, with the outer object
+         * acting as a stand-in.
+         */
+        JSObject *obj = cx->globalObject;
+        if (!obj) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INACTIVE);
+            return NULL;
+        }
+
+        OBJ_TO_INNER_OBJECT(cx, obj);
+        return obj;
+    }
+    return GetScopeChain(cx, fp);
 }
 
 /* Some objects (e.g., With) delegate 'this' to another object. */
 static inline JSObject *
 CallThisObjectHook(JSContext *cx, JSObject *obj, Value *argv)
 {
     JSObject *thisp = obj->thisObject(cx);
     if (!thisp)
@@ -1157,34 +1057,34 @@ js::ValueToId(JSContext *cx, const Value
     return js_ValueToStringId(cx, v, idp);
 }
 
 /*
  * Enter the new with scope using an object at sp[-1] and associate the depth
  * of the with block with sp + stackIndex.
  */
 static bool
-EnterWith(JSContext *cx, jsint stackIndex, JSOp op, size_t oplen)
+EnterWith(JSContext *cx, jsint stackIndex)
 {
     StackFrame *fp = cx->fp();
     Value *sp = cx->regs().sp;
     JS_ASSERT(stackIndex < 0);
     JS_ASSERT(fp->base() <= sp + stackIndex);
 
     JSObject *obj;
     if (sp[-1].isObject()) {
         obj = &sp[-1].toObject();
     } else {
         obj = js_ValueToNonNullObject(cx, sp[-1]);
         if (!obj)
             return JS_FALSE;
         sp[-1].setObject(*obj);
     }
 
-    JSObject *parent = GetScopeChainFast(cx, fp, op, oplen);
+    JSObject *parent = GetScopeChain(cx, fp);
     if (!parent)
         return JS_FALSE;
 
     OBJ_TO_INNER_OBJECT(cx, obj);
     if (!obj)
         return JS_FALSE;
 
     JSObject *withobj = js_NewWithObject(cx, obj, parent,
@@ -1223,16 +1123,25 @@ js::IsActiveWithOrBlock(JSContext *cx, J
  */
 bool
 js::UnwindScope(JSContext *cx, jsint stackDepth, JSBool normalUnwind)
 {
     JS_ASSERT(stackDepth >= 0);
     JS_ASSERT(cx->fp()->base() + stackDepth <= cx->regs().sp);
 
     StackFrame *fp = cx->fp();
+    JSObject *obj = fp->maybeBlockChain();
+    while (obj) {
+        JS_ASSERT(obj->isStaticBlock());
+        if (OBJ_BLOCK_DEPTH(cx, obj) < stackDepth)
+            break;
+        obj = obj->staticBlockScopeChain();
+    }
+    fp->setBlockChain(obj);
+
     for (;;) {
         JSObject &scopeChain = fp->scopeChain();
         if (!IsActiveWithOrBlock(cx, scopeChain, stackDepth))
             break;
         if (scopeChain.isBlock()) {
             /* Don't fail until after we've updated all stacks. */
             normalUnwind &= js_PutBlockObject(cx, normalUnwind);
         } else {
@@ -1944,23 +1853,24 @@ js::Interpret(JSContext *cx, StackFrame 
         goto do_switch;
 #endif
     }
 
 /* No-ops for ease of decompilation. */
 ADD_EMPTY_CASE(JSOP_NOP)
 ADD_EMPTY_CASE(JSOP_UNUSED0)
 ADD_EMPTY_CASE(JSOP_UNUSED1)
+ADD_EMPTY_CASE(JSOP_UNUSED2)
+ADD_EMPTY_CASE(JSOP_UNUSED3)
 ADD_EMPTY_CASE(JSOP_CONDSWITCH)
 ADD_EMPTY_CASE(JSOP_TRY)
 #if JS_HAS_XML_SUPPORT
 ADD_EMPTY_CASE(JSOP_STARTXML)
 ADD_EMPTY_CASE(JSOP_STARTXMLEXPR)
 #endif
-ADD_EMPTY_CASE(JSOP_NULLBLOCKCHAIN)
 ADD_EMPTY_CASE(JSOP_LOOPHEAD)
 END_EMPTY_CASES
 
 BEGIN_CASE(JSOP_LABEL)
 END_CASE(JSOP_LABEL)
 
 BEGIN_CASE(JSOP_LABELX)
 END_CASE(JSOP_LABELX)
@@ -1996,33 +1906,30 @@ check_backedge:
 
     DO_OP();
 }
 
 /* ADD_EMPTY_CASE is not used here as JSOP_LINENO_LENGTH == 3. */
 BEGIN_CASE(JSOP_LINENO)
 END_CASE(JSOP_LINENO)
 
-BEGIN_CASE(JSOP_BLOCKCHAIN)
-END_CASE(JSOP_BLOCKCHAIN)
-
 BEGIN_CASE(JSOP_UNDEFINED)
     PUSH_UNDEFINED();
 END_CASE(JSOP_UNDEFINED)
 
 BEGIN_CASE(JSOP_POP)
     regs.sp--;
 END_CASE(JSOP_POP)
 
 BEGIN_CASE(JSOP_POPN)
 {
     regs.sp -= GET_UINT16(regs.pc);
 #ifdef DEBUG
     JS_ASSERT(regs.fp()->base() <= regs.sp);
-    JSObject *obj = GetBlockChain(cx, regs.fp());
+    JSObject *obj = regs.fp()->maybeBlockChain();
     JS_ASSERT_IF(obj,
                  OBJ_BLOCK_DEPTH(cx, obj) + OBJ_BLOCK_COUNT(cx, obj)
                  <= (size_t) (regs.sp - regs.fp()->base()));
     for (obj = &regs.fp()->scopeChain(); obj; obj = obj->scopeChain()) {
         if (!obj->isBlock() || !obj->isWith())
             continue;
         if (obj->getPrivate() != js_FloatingFrameIfGenerator(cx, regs.fp()))
             break;
@@ -2035,17 +1942,17 @@ BEGIN_CASE(JSOP_POPN)
 END_CASE(JSOP_POPN)
 
 BEGIN_CASE(JSOP_SETRVAL)
 BEGIN_CASE(JSOP_POPV)
     POP_RETURN_VALUE();
 END_CASE(JSOP_POPV)
 
 BEGIN_CASE(JSOP_ENTERWITH)
-    if (!EnterWith(cx, -1, JSOP_ENTERWITH, JSOP_ENTERWITH_LENGTH))
+    if (!EnterWith(cx, -1))
         goto error;
 
     /*
      * We must ensure that different "with" blocks have different stack depth
      * associated with them. This allows the try handler search to properly
      * recover the scope chain. Thus we must keep the stack at least at the
      * current level.
      *
@@ -2073,16 +1980,17 @@ BEGIN_CASE(JSOP_STOP)
      * false after the inline_return label.
      */
     CHECK_BRANCH();
 
     interpReturnOK = true;
     if (entryFrame != regs.fp())
   inline_return:
     {
+        JS_ASSERT(!regs.fp()->hasBlockChain());
         JS_ASSERT(!IsActiveWithOrBlock(cx, regs.fp()->scopeChain(), 0));
 
         if (cx->compartment->debugMode())
             interpReturnOK = ScriptDebugEpilogue(cx, regs.fp(), interpReturnOK);
  
         interpReturnOK = ScriptEpilogue(cx, regs.fp(), interpReturnOK);
 
         /* The JIT inlines ScriptEpilogue. */
@@ -4059,17 +3967,17 @@ BEGIN_CASE(JSOP_DEFFUN)
          * Even a null closure needs a parent for principals finding.
          * FIXME: bug 476950, although debugger users may also demand some kind
          * of scope link for debugger-assisted eval-in-frame.
          */
         obj2 = &regs.fp()->scopeChain();
     } else {
         JS_ASSERT(!fun->isFlatClosure());
 
-        obj2 = GetScopeChainFast(cx, regs.fp(), JSOP_DEFFUN, JSOP_DEFFUN_LENGTH);
+        obj2 = GetScopeChain(cx, regs.fp());
         if (!obj2)
             goto error;
     }
 
     /*
      * If static link is not current scope, clone fun's object to link to the
      * current scope via parent. We do this to enable sharing of compiled
      * functions among multiple equivalent scopes, amortizing the cost of
@@ -4158,17 +4066,17 @@ BEGIN_CASE(JSOP_DEFFUN)
 }
 END_CASE(JSOP_DEFFUN)
 
 BEGIN_CASE(JSOP_DEFFUN_FC)
 {
     JSFunction *fun;
     LOAD_FUNCTION(0);
 
-    JSObject *obj = js_NewFlatClosure(cx, fun, JSOP_DEFFUN_FC, JSOP_DEFFUN_FC_LENGTH);
+    JSObject *obj = js_NewFlatClosure(cx, fun);
     if (!obj)
         goto error;
 
     Value rval = ObjectValue(*obj);
 
     uintN attrs = regs.fp()->isEvalFrame()
                   ? JSPROP_ENUMERATE
                   : JSPROP_ENUMERATE | JSPROP_PERMANENT;
@@ -4202,18 +4110,17 @@ BEGIN_CASE(JSOP_DEFLOCALFUN)
     LOAD_FUNCTION(SLOTNO_LEN);
     JS_ASSERT(fun->isInterpreted());
     JS_ASSERT(!fun->isFlatClosure());
 
     JSObject *parent;
     if (fun->isNullClosure()) {
         parent = &regs.fp()->scopeChain();
     } else {
-        parent = GetScopeChainFast(cx, regs.fp(), JSOP_DEFLOCALFUN,
-                                   JSOP_DEFLOCALFUN_LENGTH);
+        parent = GetScopeChain(cx, regs.fp());
         if (!parent)
             goto error;
     }
     JSObject *obj = CloneFunctionObjectIfNotSingleton(cx, fun, parent);
     if (!obj)
         goto error;
 
     JS_ASSERT_IF(script->hasGlobal(), obj->getProto() == fun->getProto());
@@ -4223,17 +4130,17 @@ BEGIN_CASE(JSOP_DEFLOCALFUN)
 }
 END_CASE(JSOP_DEFLOCALFUN)
 
 BEGIN_CASE(JSOP_DEFLOCALFUN_FC)
 {
     JSFunction *fun;
     LOAD_FUNCTION(SLOTNO_LEN);
 
-    JSObject *obj = js_NewFlatClosure(cx, fun, JSOP_DEFLOCALFUN_FC, JSOP_DEFLOCALFUN_FC_LENGTH);
+    JSObject *obj = js_NewFlatClosure(cx, fun);
     if (!obj)
         goto error;
 
     uint32_t slot = GET_SLOTNO(regs.pc);
     regs.fp()->slots()[slot].setObject(*obj);
 }
 END_CASE(JSOP_DEFLOCALFUN_FC)
 
@@ -4246,43 +4153,43 @@ BEGIN_CASE(JSOP_LAMBDA)
 
     /* do-while(0) so we can break instead of using a goto. */
     do {
         JSObject *parent;
         if (fun->isNullClosure()) {
             parent = &regs.fp()->scopeChain();
 
             if (fun->joinable()) {
-                jsbytecode *pc2 = AdvanceOverBlockchainOp(regs.pc + JSOP_LAMBDA_LENGTH);
+                jsbytecode *pc2 = regs.pc + JSOP_LAMBDA_LENGTH;
                 JSOp op2 = JSOp(*pc2);
 
                 /*
                  * Optimize var obj = {method: function () { ... }, ...},
                  * this.method = function () { ... }; and other significant
                  * single-use-of-null-closure bytecode sequences.
                  */
                 if (op2 == JSOP_INITMETHOD) {
 #ifdef DEBUG
                     const Value &lref = regs.sp[-1];
                     JS_ASSERT(lref.isObject());
                     JSObject *obj2 = &lref.toObject();
                     JS_ASSERT(obj2->isObject());
 #endif
-                    JS_ASSERT(fun->methodAtom() == script->getAtom(GET_FULL_INDEX(pc2 - regs.pc)));
+                    JS_ASSERT(fun->methodAtom() == script->getAtom(GET_FULL_INDEX(JSOP_LAMBDA_LENGTH)));
                     break;
                 }
 
                 if (op2 == JSOP_SETMETHOD) {
 #ifdef DEBUG
                     op2 = JSOp(pc2[JSOP_SETMETHOD_LENGTH]);
                     JS_ASSERT(op2 == JSOP_POP || op2 == JSOP_POPV);
 #endif
                     const Value &lref = regs.sp[-1];
                     if (lref.isObject() && lref.toObject().canHaveMethodBarrier()) {
-                        JS_ASSERT(fun->methodAtom() == script->getAtom(GET_FULL_INDEX(pc2 - regs.pc)));
+                        JS_ASSERT(fun->methodAtom() == script->getAtom(GET_FULL_INDEX(JSOP_LAMBDA_LENGTH)));
                         break;
                     }
                 } else if (op2 == JSOP_CALL) {
                     /*
                      * Array.prototype.sort and String.prototype.replace are
                      * optimized as if they are special form. We know that they
                      * won't leak the joined function object in obj, therefore
                      * we don't need to clone that compiler-created function
@@ -4310,17 +4217,17 @@ BEGIN_CASE(JSOP_LAMBDA)
                     pc2 += JSOP_NULL_LENGTH;
                     op2 = JSOp(*pc2);
 
                     if (op2 == JSOP_CALL && GET_ARGC(pc2) == 0)
                         break;
                 }
             }
         } else {
-            parent = GetScopeChainFast(cx, regs.fp(), JSOP_LAMBDA, JSOP_LAMBDA_LENGTH);
+            parent = GetScopeChain(cx, regs.fp());
             if (!parent)
                 goto error;
         }
 
         obj = CloneFunctionObjectIfNotSingleton(cx, fun, parent);
         if (!obj)
             goto error;
     } while (0);
@@ -4332,17 +4239,17 @@ BEGIN_CASE(JSOP_LAMBDA)
 }
 END_CASE(JSOP_LAMBDA)
 
 BEGIN_CASE(JSOP_LAMBDA_FC)
 {
     JSFunction *fun;
     LOAD_FUNCTION(0);
 
-    JSObject *obj = js_NewFlatClosure(cx, fun, JSOP_LAMBDA_FC, JSOP_LAMBDA_FC_LENGTH);
+    JSObject *obj = js_NewFlatClosure(cx, fun);
     if (!obj)
         goto error;
     JS_ASSERT_IF(script->hasGlobal(), obj->getProto() == fun->getProto());
 
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_LAMBDA_FC)
 
@@ -5074,17 +4981,17 @@ BEGIN_CASE(JSOP_ENDFILTER)
     if (!js_StepXMLListFilter(cx, cond))
         goto error;
     if (!regs.sp[-1].isNull()) {
         /*
          * Decrease sp after EnterWith returns as we use sp[-1] there to root
          * temporaries.
          */
         JS_ASSERT(IsXML(regs.sp[-1]));
-        if (!EnterWith(cx, -2, JSOP_ENDFILTER, JSOP_ENDFILTER_LENGTH))
+        if (!EnterWith(cx, -2))
             goto error;
         regs.sp--;
         len = GET_JUMP_OFFSET(regs.pc);
         JS_ASSERT(len < 0);
         BRANCH(len);
     }
     regs.sp--;
 }
@@ -5209,16 +5116,18 @@ BEGIN_CASE(JSOP_ENTERBLOCK)
     JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp);
     Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj);
     JS_ASSERT(regs.sp < vp);
     JS_ASSERT(vp <= regs.fp()->slots() + script->nslots);
     SetValueRangeToUndefined(regs.sp, vp);
     regs.sp = vp;
 
 #ifdef DEBUG
+    JS_ASSERT(regs.fp()->maybeBlockChain() == obj->staticBlockScopeChain());
+
     /*
      * 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.
      */
     JSObject *obj2 = &regs.fp()->scopeChain();
@@ -5228,41 +5137,44 @@ BEGIN_CASE(JSOP_ENTERBLOCK)
         obj2->getPrivate() == js_FloatingFrameIfGenerator(cx, regs.fp())) {
         JSObject *youngestProto = obj2->getProto();
         JS_ASSERT(youngestProto->isStaticBlock());
         JSObject *parent = obj;
         while ((parent = parent->scopeChain()) != youngestProto)
             JS_ASSERT(parent);
     }
 #endif
+
+    regs.fp()->setBlockChain(obj);
 }
 END_CASE(JSOP_ENTERBLOCK)
 
 BEGIN_CASE(JSOP_LEAVEBLOCKEXPR)
 BEGIN_CASE(JSOP_LEAVEBLOCK)
 {
-    JSObject *blockChain;
-    LOAD_OBJECT(UINT16_LEN, blockChain);
 #ifdef DEBUG
-    JS_ASSERT(blockChain->isStaticBlock());
-    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, blockChain);
+    JS_ASSERT(regs.fp()->blockChain().isStaticBlock());
+    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, &regs.fp()->blockChain());
     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.
      */
     JSObject &obj = regs.fp()->scopeChain();
-    if (obj.getProto() == blockChain) {
+    if (obj.getProto() == &regs.fp()->blockChain()) {
         JS_ASSERT(obj.isClonedBlock());
         if (!js_PutBlockObject(cx, JS_TRUE))
             goto error;
     }
 
+    /* Pop the block chain, too. */
+    regs.fp()->setBlockChain(regs.fp()->blockChain().staticBlockScopeChain());
+
     /* Move the result of the expression to the new topmost stack slot. */
     Value *vp = NULL;  /* silence GCC warnings */
     if (op == JSOP_LEAVEBLOCKEXPR)
         vp = &regs.sp[-1];
     regs.sp -= GET_UINT16(regs.pc);
     if (op == JSOP_LEAVEBLOCKEXPR) {
         JS_ASSERT(regs.fp()->base() + blockDepth == regs.sp - 1);
         regs.sp[-1] = *vp;
@@ -5473,16 +5385,18 @@ END_CASE(JSOP_ARRAYPUSH)
                  * Restart the handler search with updated pc and stack depth
                  * to properly notify the debugger.
                  */
                 goto error;
             }
 
             switch (tn->kind) {
               case JSTRY_CATCH:
+                  JS_ASSERT(*regs.pc == JSOP_ENTERBLOCK);
+
 #if JS_HAS_GENERATORS
                 /* Catch cannot intercept the closing of a generator. */
                   if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
                     break;
 #endif
 
                 /*
                  * Don't clear exceptions to save cx->exception from GC
@@ -5560,19 +5474,20 @@ END_CASE(JSOP_ARRAYPUSH)
      * (b) a js_Execute activation;
      * (c) a generator (SendToGenerator, jsiter.c).
      *
      * We must not be in an inline frame. The check above ensures that for the
      * error case and for a normal return, the code jumps directly to parent's
      * frame pc.
      */
     JS_ASSERT(entryFrame == regs.fp());
-
-    JS_ASSERT_IF(!regs.fp()->isGeneratorFrame(),
-                 !IsActiveWithOrBlock(cx, regs.fp()->scopeChain(), 0));
+    if (!regs.fp()->isGeneratorFrame()) {
+        JS_ASSERT(!IsActiveWithOrBlock(cx, regs.fp()->scopeChain(), 0));
+        JS_ASSERT(!regs.fp()->hasBlockChain());
+    }
 
 #ifdef JS_METHODJIT
     /*
      * This path is used when it's guaranteed the method can be finished
      * inside the JIT.
      */
   leave_on_safe_point:
 #endif
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -46,38 +46,30 @@
 #include "jsprvtd.h"
 #include "jspubtd.h"
 #include "jsopcode.h"
 
 #include "vm/Stack.h"
 
 namespace js {
 
-extern JSObject *
-GetBlockChain(JSContext *cx, StackFrame *fp);
-
-extern JSObject *
-GetBlockChainFast(JSContext *cx, StackFrame *fp, JSOp op, size_t oplen);
-
-extern JSObject *
-GetScopeChain(JSContext *cx);
-
 /*
  * Refresh and return fp->scopeChain.  It may be stale if block scopes are
  * active but not yet reflected by objects in the scope chain.  If a block
  * scope contains a with, eval, XML filtering predicate, or similar such
  * dynamically scoped construct, then compile-time block scope at fp->blocks
  * must reflect at runtime.
  */
+
+extern JSObject *
+GetScopeChain(JSContext *cx);
+
 extern JSObject *
 GetScopeChain(JSContext *cx, StackFrame *fp);
 
-extern JSObject *
-GetScopeChainFast(JSContext *cx, StackFrame *fp, JSOp op, size_t oplen);
-
 /*
  * ScriptPrologue/ScriptEpilogue must be called in pairs. ScriptPrologue
  * must be called before the script executes. ScriptEpilogue must be called
  * after the script returns or exits via exception.
  */
 
 inline bool
 ScriptPrologue(JSContext *cx, StackFrame *fp, JSScript *script);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1313,22 +1313,24 @@ DirectEval(JSContext *cx, const CallArgs
     /* Direct eval can assume it was called from an interpreted frame. */
     StackFrame *caller = cx->fp();
     JS_ASSERT(caller->isScriptFrame());
     JS_ASSERT(IsBuiltinEvalForScope(&caller->scopeChain(), args.calleev()));
     JS_ASSERT(JSOp(*cx->regs().pc) == JSOP_EVAL);
 
     AutoFunctionCallProbe callProbe(cx, args.callee().toFunction(), caller->script());
 
-    JSObject *scopeChain =
-        GetScopeChainFast(cx, caller, JSOP_EVAL, JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);
-
-    return scopeChain &&
-           WarnOnTooManyArgs(cx, args) &&
-           EvalKernel(cx, args, DIRECT_EVAL, caller, *scopeChain);
+    JSObject *scopeChain = GetScopeChain(cx, caller);
+    if (!scopeChain)
+        return false;
+
+    if (!WarnOnTooManyArgs(cx, args))
+        return false;
+
+    return EvalKernel(cx, args, DIRECT_EVAL, caller, *scopeChain);
 }
 
 bool
 IsBuiltinEvalForScope(JSObject *scopeChain, const Value &v)
 {
     return scopeChain->getGlobal()->getOriginalEval() == v;
 }
 
@@ -4129,17 +4131,17 @@ js_XDRBlockObject(JSXDRState *xdr, JSObj
 
     cx = xdr->cx;
 #ifdef __GNUC__
     obj = NULL;         /* quell GCC overwarning */
 #endif
 
     if (xdr->mode == JSXDR_ENCODE) {
         obj = *objp;
-        parent = obj->getStaticBlockScopeChain();
+        parent = obj->staticBlockScopeChain();
         parentId = JSScript::isValidOffset(xdr->script->objectsOffset)
                    ? FindObjectIndex(xdr->script->objects(), parent)
                    : NO_PARENT_INDEX;
         depth = uint16_t(OBJ_BLOCK_DEPTH(cx, obj));
         count = uint16_t(OBJ_BLOCK_COUNT(cx, obj));
         depthAndCount = uint32_t(depth << 16) | count;
     }
 #ifdef __GNUC__ /* suppress bogus gcc warnings */
@@ -7453,16 +7455,17 @@ js_DumpStackFrame(JSContext *cx, StackFr
             fprintf(stderr, "  formals: %p (%u)\n", (void *) fp->formalArgs(), (unsigned) fp->numFormalArgs());
         }
         if (fp->hasCallObj()) {
             fprintf(stderr, "  has call obj: ");
             dumpValue(ObjectValue(fp->callObj()));
             fprintf(stderr, "\n");
         }
         MaybeDumpObject("argsobj", fp->maybeArgsObj());
+        MaybeDumpObject("blockChain", fp->maybeBlockChain());
         if (!fp->isDummyFrame()) {
             MaybeDumpValue("this", fp->thisValue());
             fprintf(stderr, "  rval: ");
             dumpValue(fp->returnValue());
         } else {
             fprintf(stderr, "dummy frame");
         }
         fputc('\n', stderr);
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -903,17 +903,17 @@ struct JSObject : js::gc::Cell
     inline bool setInternalScopeChain(JSContext *cx, JSObject *obj);
     static inline size_t offsetOfInternalScopeChain();
 
     /*
      * Access the scope chain of a static block object. These do not appear
      * on scope chains but mirror their structure, and can have a NULL
      * scope chain.
      */
-    inline JSObject *getStaticBlockScopeChain() const;
+    inline JSObject *staticBlockScopeChain() const;
     inline void setStaticBlockScopeChain(JSObject *obj);
 
     /* Common fixed slot for the scope chain of internal scope objects. */
     static const uint32_t SCOPE_CHAIN_SLOT = 0;
 
     /* Private data accessors. */
 
     inline bool hasPrivate() const;
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -319,17 +319,17 @@ JSObject::offsetOfInternalScopeChain()
 
 inline JSObject *
 JSObject::scopeChain() const
 {
     return isInternalScope() ? internalScopeChain() : getParent();
 }
 
 inline JSObject *
-JSObject::getStaticBlockScopeChain() const
+JSObject::staticBlockScopeChain() const
 {
     JS_ASSERT(isStaticBlock());
     return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
 }
 
 inline void
 JSObject::setStaticBlockScopeChain(JSObject *obj)
 {
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -2897,24 +2897,16 @@ Decompile(SprintStack *ss, jsbytecode *p
                      * exactly how long rval lives, or else copies it down via
                      * SprintCString.
                      */
                     rval = OFF2STR(&ss->sprinter, todo);
                     rvalpc = NULL;
                     todo = -2;
                     pc2 = pc + oplen;
 
-                    /* Skip a block chain annotation if one appears here. */
-                    if (*pc2 == JSOP_NOP) {
-                        if (pc2[JSOP_NOP_LENGTH] == JSOP_NULLBLOCKCHAIN)
-                            pc2 += JSOP_NOP_LENGTH + JSOP_NULLBLOCKCHAIN_LENGTH;
-                        else if (pc2[JSOP_NOP_LENGTH] == JSOP_BLOCKCHAIN)
-                            pc2 += JSOP_NOP_LENGTH + JSOP_BLOCKCHAIN_LENGTH;
-                    }
-
                     if (*pc2 == JSOP_NOP) {
                         sn = js_GetSrcNote(jp->script, pc2);
                         if (sn) {
                             if (SN_TYPE(sn) == SRC_FOR) {
                                 op = JSOP_NOP;
                                 pc = pc2;
                                 todo = SprintNormalFor(cx, jp, ss, rval, rvalpc, &pc, &len);
                                 break;
@@ -4404,22 +4396,16 @@ Decompile(SprintStack *ss, jsbytecode *p
                     if (!ok)
                         return NULL;
 
                     /*
                      * Advance over this op and its global |this| push, and
                      * arrange to advance over the call to this lambda.
                      */
                     pc += len;
-                    if (*pc == JSOP_BLOCKCHAIN) {
-                        pc += JSOP_BLOCKCHAIN_LENGTH;
-                    } else {
-                        LOCAL_ASSERT(*pc == JSOP_NULLBLOCKCHAIN);
-                        pc += JSOP_NULLBLOCKCHAIN_LENGTH;
-                    }
                     LOCAL_ASSERT(*pc == JSOP_UNDEFINED);
                     pc += JSOP_UNDEFINED_LENGTH;
                     LOCAL_ASSERT(*pc == JSOP_CALL);
                     LOCAL_ASSERT(GET_ARGC(pc) == 0);
                     len = JSOP_CALL_LENGTH;
 
                     /*
                      * Arrange to parenthesize this genexp unless:
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -227,17 +227,17 @@ OPDEF(JSOP_FUNAPPLY,  79, "funapply",   
 OPDEF(JSOP_OBJECT,    80, "object",     NULL,         3,  0,  1, 19,  JOF_OBJECT)
 
 /* Pop value and discard it. */
 OPDEF(JSOP_POP,       81, "pop",        NULL,         1,  1,  0,  2,  JOF_BYTE)
 
 /* Call a function as a constructor; operand is argc. */
 OPDEF(JSOP_NEW,       82, js_new_str,   NULL,         3, -1,  1, 17,  JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
 
-OPDEF(JSOP_UNUSED1,   83, "unused1",    NULL,         1,  0,  0,  0,  JOF_BYTE)
+OPDEF(JSOP_UNUSED0,   83, "unused1",    NULL,         1,  0,  0,  0,  JOF_BYTE)
 
 /* Fast get/set ops for function arguments and local variables. */
 OPDEF(JSOP_GETARG,    84, "getarg",     NULL,         3,  0,  1, 19,  JOF_QARG |JOF_NAME)
 OPDEF(JSOP_SETARG,    85, "setarg",     NULL,         3,  1,  1,  3,  JOF_QARG |JOF_NAME|JOF_SET)
 OPDEF(JSOP_GETLOCAL,  86,"getlocal",    NULL,         3,  0,  1, 19,  JOF_LOCAL|JOF_NAME)
 OPDEF(JSOP_SETLOCAL,  87,"setlocal",    NULL,         3,  1,  1,  3,  JOF_LOCAL|JOF_NAME|JOF_SET|JOF_DETECTING)
 
 /* Push unsigned 16-bit int constant. */
@@ -266,17 +266,17 @@ OPDEF(JSOP_DECARG,    98, "decarg",     
 OPDEF(JSOP_ARGINC,    99, "arginc",     NULL,         3,  0,  1, 15,  JOF_QARG |JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3)
 OPDEF(JSOP_ARGDEC,   100, "argdec",     NULL,         3,  0,  1, 15,  JOF_QARG |JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3)
 
 OPDEF(JSOP_INCLOCAL,  101,"inclocal",   NULL,         3,  0,  1, 15,  JOF_LOCAL|JOF_NAME|JOF_INC|JOF_TMPSLOT3)
 OPDEF(JSOP_DECLOCAL,  102,"declocal",   NULL,         3,  0,  1, 15,  JOF_LOCAL|JOF_NAME|JOF_DEC|JOF_TMPSLOT3)
 OPDEF(JSOP_LOCALINC,  103,"localinc",   NULL,         3,  0,  1, 15,  JOF_LOCAL|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3)
 OPDEF(JSOP_LOCALDEC,  104,"localdec",   NULL,         3,  0,  1, 15,  JOF_LOCAL|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3)
 
-OPDEF(JSOP_UNUSED0,   105,"unused0",    NULL,         1,  0,  0,  0,  JOF_BYTE)
+OPDEF(JSOP_UNUSED1,   105,"unused0",    NULL,         1,  0,  0,  0,  JOF_BYTE)
 
 /* The argument is the offset to the next statement and is used by IonMonkey. */
 OPDEF(JSOP_LABEL,     106,"label",     NULL,          3,  0,  0,  0,  JOF_JUMP)
 OPDEF(JSOP_LABELX,    107,"labelx",    NULL,          5,  0,  0,  0,  JOF_JUMPX)
 
 /* Like JSOP_FUNAPPLY but for f.call instead of f.apply. */
 OPDEF(JSOP_FUNCALL,   108,"funcall",    NULL,         3, -1,  1, 18,  JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
 
@@ -435,25 +435,18 @@ OPDEF(JSOP_XMLTAGEXPR,    178,"xmltagexp
 OPDEF(JSOP_XMLELTEXPR,    179,"xmleltexpr", NULL,     1,  1,  1,  0,  JOF_BYTE)
 OPDEF(JSOP_XMLCDATA,      180,"xmlcdata",   NULL,     3,  0,  1, 19,  JOF_ATOM)
 OPDEF(JSOP_XMLCOMMENT,    181,"xmlcomment", NULL,     3,  0,  1, 19,  JOF_ATOM)
 OPDEF(JSOP_XMLPI,         182,"xmlpi",      NULL,     3,  1,  1, 19,  JOF_ATOM)
 OPDEF(JSOP_DELDESC,       183,"deldesc",    NULL,     1,  2,  1, 15,  JOF_BYTE|JOF_ELEM|JOF_DEL)
 
 OPDEF(JSOP_CALLPROP,      184,"callprop",   NULL,     3,  1,  2, 18,  JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_CALLOP|JOF_TMPSLOT3)
 
-/*
- * These opcodes contain a reference to the current blockChain object.
- * They are emitted directly after instructions, such as DEFFUN, that need fast access to
- * the blockChain. The special NULLBLOCKCHAIN is needed because the JOF_OBJECT
- * does not permit NULL object references, since it stores an index into a table of
- * objects.
- */
-OPDEF(JSOP_BLOCKCHAIN,    185,"blockchain",    NULL,  3,  0,  0,  0, JOF_OBJECT)
-OPDEF(JSOP_NULLBLOCKCHAIN,186,"nullblockchain",NULL,  1,  0,  0,  0, JOF_BYTE)
+OPDEF(JSOP_UNUSED2,       185,"unused1",    NULL,     1,  0,  0,  0, JOF_BYTE)
+OPDEF(JSOP_UNUSED3,       186,"unused2",    NULL,     1,  0,  0,  0, JOF_BYTE)
 
 /*
  * Opcode to hold 24-bit immediate integer operands.
  */
 OPDEF(JSOP_UINT24,        187,"uint24",     NULL,     4,  0,  1, 16,  JOF_UINT24)
 
 /*
  * Opcodes to allow 24-bit atom or object indexes. Whenever an index exceeds
@@ -491,17 +484,17 @@ OPDEF(JSOP_CALLXMLNAME,   196, "callxmln
  * Specialized JSOP_TYPEOF to avoid reporting undefined for typeof(0, undef).
  */
 OPDEF(JSOP_TYPEOFEXPR,    197,"typeofexpr",  NULL,    1,  1,  1, 15,  JOF_BYTE|JOF_DETECTING)
 
 /*
  * Block-local scope support.
  */
 OPDEF(JSOP_ENTERBLOCK,    198,"enterblock",  NULL,    3,  0, -1,  0,  JOF_OBJECT)
-OPDEF(JSOP_LEAVEBLOCK,    199,"leaveblock",  NULL,    5, -1,  0,  0,  JOF_UINT16)
+OPDEF(JSOP_LEAVEBLOCK,    199,"leaveblock",  NULL,    3, -1,  0,  0,  JOF_UINT16)
 
 /* Jump to target if top of stack value isn't callable. */
 OPDEF(JSOP_IFCANTCALLTOP, 200,"ifcantcalltop",NULL,   3,  1,  1,  0,  JOF_JUMP|JOF_DETECTING)
 
 /* Throws a TypeError if the value at the top of the stack is not primitive. */
 OPDEF(JSOP_PRIMTOP,       201,"primtop",     NULL,    2,  1,  1,  0,  JOF_INT8)
 
 /*
@@ -520,17 +513,17 @@ OPDEF(JSOP_GETFUNNS,      205,"getfunns"
  * Variant of JSOP_ENUMELEM for destructuring const (const [a, b] = ...).
  */
 OPDEF(JSOP_ENUMCONSTELEM, 206,"enumconstelem",NULL,   1,  3,  0,  3,  JOF_BYTE|JOF_SET)
 
 /*
  * Variant of JSOP_LEAVEBLOCK has a result on the stack above the locals,
  * which must be moved down when the block pops.
  */
-OPDEF(JSOP_LEAVEBLOCKEXPR,207,"leaveblockexpr",NULL,  5, -1,  1,  3,  JOF_UINT16)
+OPDEF(JSOP_LEAVEBLOCKEXPR,207,"leaveblockexpr",NULL,  3, -1,  1,  3,  JOF_UINT16)
 \
 /*
  * Optimize atom segments 1-3.  These must be followed by JSOP_RESETBASE0 after
  * the opcode that they prefix.
  */
 OPDEF(JSOP_INDEXBASE1,    208,"indexbase1",    NULL,  1,  0,  0,  0,  JOF_BYTE |JOF_INDEXBASE)
 OPDEF(JSOP_INDEXBASE2,    209,"indexbase2",    NULL,  1,  0,  0,  0,  JOF_BYTE |JOF_INDEXBASE)
 OPDEF(JSOP_INDEXBASE3,    210,"indexbase3",    NULL,  1,  0,  0,  0,  JOF_BYTE |JOF_INDEXBASE)
--- a/js/src/jsopcodeinlines.h
+++ b/js/src/jsopcodeinlines.h
@@ -52,30 +52,16 @@ class BytecodeRange {
     size_t frontOffset() const { return pc - script->code; }
     void popFront() { pc += GetBytecodeLength(pc); }
 
   private:
     JSScript *script;
     jsbytecode *pc, *end;
 };
 
-/* 
- * Warning: this does not skip JSOP_RESETBASE* or JSOP_INDEXBASE* ops, so it is
- * useful only when checking for optimization opportunities.
- */
-JS_ALWAYS_INLINE jsbytecode *
-AdvanceOverBlockchainOp(jsbytecode *pc)
-{
-    if (*pc == JSOP_NULLBLOCKCHAIN)
-        return pc + JSOP_NULLBLOCKCHAIN_LENGTH;
-    if (*pc == JSOP_BLOCKCHAIN)
-        return pc + JSOP_BLOCKCHAIN_LENGTH;
-    return pc;
-}
-
 class SrcNoteLineScanner
 {
     /* offset of the current JSOp in the bytecode */
     ptrdiff_t offset;
 
     /* next src note to process */
     jssrcnote *sn;
 
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -2465,22 +2465,16 @@ mjit::Compiler::generateMethod()
             if (!jsop_setelem(true))
                 return Compile_Error;
 
             // Before: VALUE VALUE
             // After:
             frame.popn(2);
           END_CASE(JSOP_ENUMELEM)
 
-          BEGIN_CASE(JSOP_BLOCKCHAIN)
-          END_CASE(JSOP_BLOCKCHAIN)
-
-          BEGIN_CASE(JSOP_NULLBLOCKCHAIN)
-          END_CASE(JSOP_NULLBLOCKCHAIN)
-
           BEGIN_CASE(JSOP_CONDSWITCH)
             /* No-op for the decompiler. */
           END_CASE(JSOP_CONDSWITCH)
 
           BEGIN_CASE(JSOP_LABEL)
           END_CASE(JSOP_LABEL)
 
           BEGIN_CASE(JSOP_LABELX)
@@ -2539,17 +2533,17 @@ mjit::Compiler::generateMethod()
           {
             JSFunction *fun = script->getFunction(fullAtomIndex(PC));
 
             JSObjStubFun stub = stubs::Lambda;
             uint32_t uses = 0;
 
             jsbytecode *pc2 = NULL;
             if (fun->joinable()) {
-                pc2 = AdvanceOverBlockchainOp(PC + JSOP_LAMBDA_LENGTH);
+                pc2 = PC + JSOP_LAMBDA_LENGTH;
                 JSOp next = JSOp(*pc2);
 
                 if (next == JSOP_INITMETHOD) {
                     stub = stubs::LambdaJoinableForInit;
                 } else if (next == JSOP_SETMETHOD) {
                     stub = stubs::LambdaJoinableForSet;
                     uses = 1;
                 } else if (next == JSOP_CALL) {
@@ -2563,19 +2557,16 @@ mjit::Compiler::generateMethod()
                     if (JSOp(*pc2) == JSOP_CALL && GET_ARGC(pc2) == 0)
                         stub = stubs::LambdaJoinableForNull;
                 }
             }
 
             prepareStubCall(Uses(uses));
             masm.move(ImmPtr(fun), Registers::ArgReg1);
 
-            if (stub != stubs::Lambda)
-                masm.storePtr(ImmPtr(pc2), FrameAddress(offsetof(VMFrame, scratch)));
-
             INLINE_STUBCALL(stub, REJOIN_PUSH_OBJECT);
 
             frame.takeReg(Registers::ReturnReg);
             frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg);
           }
           END_CASE(JSOP_LAMBDA)
 
           BEGIN_CASE(JSOP_TRY)
@@ -7151,19 +7142,17 @@ mjit::Compiler::enterBlock(JSObject *obj
 void
 mjit::Compiler::leaveBlock()
 {
     /*
      * Note: After bug 535912, we can pass the block obj directly, inline
      * PutBlockObject, and do away with the muckiness in PutBlockObject.
      */
     uint32_t n = js_GetVariableStackUses(JSOP_LEAVEBLOCK, PC);
-    JSObject *obj = script->getObject(fullAtomIndex(PC + UINT16_LEN));
     prepareStubCall(Uses(n));
-    masm.move(ImmPtr(obj), Registers::ArgReg1);
     INLINE_STUBCALL(stubs::LeaveBlock, REJOIN_NONE);
     frame.leaveBlock(n);
 }
 
 // Creates the new object expected for constructors, and places it in |thisv|.
 // It is broken down into the following operations:
 //   CALLEE
 //   GETPROP "prototype"
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -172,16 +172,17 @@ top:
 /*
  * Clean up a frame and return.
  */
 static void
 InlineReturn(VMFrame &f)
 {
     JS_ASSERT(f.fp() != f.entryfp);
     JS_ASSERT(!IsActiveWithOrBlock(f.cx, f.fp()->scopeChain(), 0));
+    JS_ASSERT(!f.fp()->hasBlockChain());
     f.cx->stack.popInlineFrame(f.regs);
 
     DebugOnly<JSOp> op = JSOp(*f.regs.pc);
     JS_ASSERT(op == JSOP_CALL ||
               op == JSOP_NEW ||
               op == JSOP_EVAL ||
               op == JSOP_FUNCALL ||
               op == JSOP_FUNAPPLY);
@@ -633,16 +634,17 @@ js_InternalThrow(VMFrame &f)
         Value *vp = cx->regs().sp + OBJ_BLOCK_COUNT(cx, obj);
         SetValueRangeToUndefined(cx->regs().sp, vp);
         cx->regs().sp = vp;
         JS_ASSERT(JSOp(pc[JSOP_ENTERBLOCK_LENGTH]) == JSOP_EXCEPTION);
         cx->regs().sp[0] = cx->getPendingException();
         cx->clearPendingException();
         cx->regs().sp++;
         cx->regs().pc = pc + JSOP_ENTERBLOCK_LENGTH + JSOP_EXCEPTION_LENGTH;
+        cx->regs().fp()->setBlockChain(obj);
     }
 
     *f.oldregs = f.regs;
 
     return NULL;
 }
 
 void JS_FASTCALL
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -668,17 +668,17 @@ stubs::DefFun(VMFrame &f, JSFunction *fu
          * Even a null closure needs a parent for principals finding.
          * FIXME: bug 476950, although debugger users may also demand some kind
          * of scope link for debugger-assisted eval-in-frame.
          */
         obj2 = &fp->scopeChain();
     } else {
         JS_ASSERT(!fun->isFlatClosure());
 
-        obj2 = GetScopeChainFast(cx, fp, JSOP_DEFFUN, JSOP_DEFFUN_LENGTH);
+        obj2 = GetScopeChain(cx, fp);
         if (!obj2)
             THROW();
     }
 
     /*
      * If static link is not current scope, clone fun's object to link to the
      * current scope via parent. We do this to enable sharing of compiled
      * functions among multiple equivalent scopes, amortizing the cost of
@@ -1328,34 +1328,33 @@ stubs::DefLocalFun(VMFrame &f, JSFunctio
      */
     JS_ASSERT(fun->isInterpreted());
     JS_ASSERT(!fun->isFlatClosure());
 
     JSObject *parent;
     if (fun->isNullClosure()) {
         parent = &f.fp()->scopeChain();
     } else {
-        parent = GetScopeChainFast(f.cx, f.fp(), JSOP_DEFLOCALFUN,
-                                   JSOP_DEFLOCALFUN_LENGTH);
+        parent = GetScopeChain(f.cx, f.fp());
         if (!parent)
             THROWV(NULL);
     }
     JSObject *obj = CloneFunctionObjectIfNotSingleton(f.cx, fun, parent);
     if (!obj)
         THROWV(NULL);
 
     JS_ASSERT_IF(f.script()->compileAndGo, obj->getGlobal() == fun->getGlobal());
 
     return obj;
 }
 
 JSObject * JS_FASTCALL
 stubs::DefLocalFun_FC(VMFrame &f, JSFunction *fun)
 {
-    JSObject *obj = js_NewFlatClosure(f.cx, fun, JSOP_DEFLOCALFUN_FC, JSOP_DEFLOCALFUN_FC_LENGTH);
+    JSObject *obj = js_NewFlatClosure(f.cx, fun);
     if (!obj)
         THROWV(NULL);
     return obj;
 }
 
 void JS_FASTCALL
 stubs::RegExp(VMFrame &f, JSObject *regex)
 {
@@ -1371,48 +1370,47 @@ stubs::RegExp(VMFrame &f, JSObject *rege
     if (!obj)
         THROW();
     f.regs.sp[0].setObject(*obj);
 }
 
 JSObject * JS_FASTCALL
 stubs::LambdaJoinableForInit(VMFrame &f, JSFunction *fun)
 {
-    DebugOnly<jsbytecode*> nextpc = (jsbytecode *) f.scratch;
     JS_ASSERT(fun->joinable());
+    DebugOnly<jsbytecode*> nextpc = f.regs.pc + JSOP_LAMBDA_LENGTH;
     JS_ASSERT(fun->methodAtom() == f.script()->getAtom(GET_SLOTNO(nextpc)));
     return fun;
 }
 
 JSObject * JS_FASTCALL
 stubs::LambdaJoinableForSet(VMFrame &f, JSFunction *fun)
 {
     JS_ASSERT(fun->joinable());
-    DebugOnly<jsbytecode*> nextpc = (jsbytecode *) f.scratch;
     const Value &lref = f.regs.sp[-1];
     if (lref.isObject() && lref.toObject().canHaveMethodBarrier()) {
+        DebugOnly<jsbytecode*> nextpc = f.regs.pc + JSOP_LAMBDA_LENGTH;
         JS_ASSERT(fun->methodAtom() == f.script()->getAtom(GET_SLOTNO(nextpc)));
         return fun;
     }
     return Lambda(f, fun);
 }
 
 JSObject * JS_FASTCALL
 stubs::LambdaJoinableForCall(VMFrame &f, JSFunction *fun)
 {
     JS_ASSERT(fun->joinable());
-    jsbytecode *nextpc = (jsbytecode *) f.scratch;
 
     /*
      * Array.prototype.sort and String.prototype.replace are optimized as if
      * they are special form. We know that they won't leak the joined function
      * object fun, therefore we don't need to clone that compiler-created
      * function object for identity/mutation reasons.
      */
-    int iargc = GET_ARGC(nextpc);
+    int iargc = GET_ARGC(f.regs.pc + JSOP_LAMBDA_LENGTH);
 
     /*
      * Note that we have not yet pushed fun as the final argument, so
      * regs.sp[1 - (iargc + 2)], and not regs.sp[-(iargc + 2)], is the callee
      * for this JSOP_CALL.
      */
     const Value &cref = f.regs.sp[1 - (iargc + 2)];
     JSFunction *callee;
@@ -1439,17 +1437,17 @@ stubs::LambdaJoinableForNull(VMFrame &f,
 
 JSObject * JS_FASTCALL
 stubs::Lambda(VMFrame &f, JSFunction *fun)
 {
     JSObject *parent;
     if (fun->isNullClosure()) {
         parent = &f.fp()->scopeChain();
     } else {
-        parent = GetScopeChainFast(f.cx, f.fp(), JSOP_LAMBDA, JSOP_LAMBDA_LENGTH);
+        parent = GetScopeChain(f.cx, f.fp());
         if (!parent)
             THROWV(NULL);
     }
 
     JSObject *obj = CloneFunctionObjectIfNotSingleton(f.cx, fun, parent);
     if (!obj)
         THROWV(NULL);
 
@@ -1758,17 +1756,17 @@ stubs::Throw(VMFrame &f)
     JS_ASSERT(!cx->isExceptionPending());
     cx->setPendingException(f.regs.sp[-1]);
     THROW();
 }
 
 JSObject * JS_FASTCALL
 stubs::FlatLambda(VMFrame &f, JSFunction *fun)
 {
-    JSObject *obj = js_NewFlatClosure(f.cx, fun, JSOP_LAMBDA_FC, JSOP_LAMBDA_FC_LENGTH);
+    JSObject *obj = js_NewFlatClosure(f.cx, fun);
     if (!obj)
         THROWV(NULL);
     return obj;
 }
 
 void JS_FASTCALL
 stubs::Arguments(VMFrame &f)
 {
@@ -1814,31 +1812,30 @@ stubs::FastInstanceOf(VMFrame &f)
 
     f.regs.sp[-3].setBoolean(js_IsDelegate(f.cx, &lref.toObject(), f.regs.sp[-3]));
 }
 
 void JS_FASTCALL
 stubs::EnterBlock(VMFrame &f, JSObject *obj)
 {
     FrameRegs &regs = f.regs;
-#ifdef DEBUG
     StackFrame *fp = f.fp();
-#endif
 
     JS_ASSERT(!f.regs.inlined());
     JS_ASSERT(obj->isStaticBlock());
     JS_ASSERT(fp->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp);
     Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj);
     JS_ASSERT(regs.sp < vp);
     JS_ASSERT(vp <= fp->slots() + fp->script()->nslots);
     SetValueRangeToUndefined(regs.sp, vp);
     regs.sp = vp;
 
 #ifdef DEBUG
     JSContext *cx = f.cx;
+    JS_ASSERT(fp->maybeBlockChain() == obj->staticBlockScopeChain());
 
     /*
      * 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.
      */
@@ -1849,41 +1846,46 @@ stubs::EnterBlock(VMFrame &f, JSObject *
         obj2->getPrivate() == js_FloatingFrameIfGenerator(cx, fp)) {
         JSObject *youngestProto = obj2->getProto();
         JS_ASSERT(youngestProto->isStaticBlock());
         JSObject *parent = obj;
         while ((parent = parent->scopeChain()) != youngestProto)
             JS_ASSERT(parent);
     }
 #endif
+
+    fp->setBlockChain(obj);
 }
 
 void JS_FASTCALL
-stubs::LeaveBlock(VMFrame &f, JSObject *blockChain)
+stubs::LeaveBlock(VMFrame &f)
 {
     JSContext *cx = f.cx;
     StackFrame *fp = f.fp();
 
 #ifdef DEBUG
-    JS_ASSERT(blockChain->isStaticBlock());
-    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, blockChain);
+    JS_ASSERT(fp->blockChain().isBlock());
+    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, &fp->blockChain());
 
     JS_ASSERT(blockDepth <= StackDepth(fp->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.
      */
-    JSObject *obj = &fp->scopeChain();
-    if (obj->getProto() == blockChain) {
-        JS_ASSERT(obj->isBlock());
+    JSObject &obj = fp->scopeChain();
+    JSObject &blockChain = fp->blockChain();
+    if (obj.getProto() == &blockChain) {
+        JS_ASSERT(obj.isBlock());
         if (!js_PutBlockObject(cx, JS_TRUE))
             THROW();
     }
+
+    fp->setBlockChain(blockChain.staticBlockScopeChain());
 }
 
 void * JS_FASTCALL
 stubs::LookupSwitch(VMFrame &f, jsbytecode *pc)
 {
     jsbytecode *jpc = pc;
     JSScript *script = f.fp()->script();
     bool ctor = f.fp()->isConstructing();
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -149,17 +149,17 @@ void JS_FASTCALL RegExp(VMFrame &f, JSOb
 JSObject * JS_FASTCALL Lambda(VMFrame &f, JSFunction *fun);
 JSObject * JS_FASTCALL LambdaJoinableForInit(VMFrame &f, JSFunction *fun);
 JSObject * JS_FASTCALL LambdaJoinableForSet(VMFrame &f, JSFunction *fun);
 JSObject * JS_FASTCALL LambdaJoinableForCall(VMFrame &f, JSFunction *fun);
 JSObject * JS_FASTCALL LambdaJoinableForNull(VMFrame &f, JSFunction *fun);
 JSObject * JS_FASTCALL FlatLambda(VMFrame &f, JSFunction *fun);
 void JS_FASTCALL Arguments(VMFrame &f);
 void JS_FASTCALL EnterBlock(VMFrame &f, JSObject *obj);
-void JS_FASTCALL LeaveBlock(VMFrame &f, JSObject *blockChain);
+void JS_FASTCALL LeaveBlock(VMFrame &f);
 
 JSBool JS_FASTCALL LessThan(VMFrame &f);
 JSBool JS_FASTCALL LessEqual(VMFrame &f);
 JSBool JS_FASTCALL GreaterThan(VMFrame &f);
 JSBool JS_FASTCALL GreaterEqual(VMFrame &f);
 JSBool JS_FASTCALL Equal(VMFrame &f);
 JSBool JS_FASTCALL NotEqual(VMFrame &f);
 
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -149,22 +149,24 @@ StackFrame::initCallFrame(JSContext *cx,
 {
     JS_ASSERT((flagsArg & ~(CONSTRUCTING |
                             LOWERED_CALL_APPLY |
                             OVERFLOW_ARGS |
                             UNDERFLOW_ARGS)) == 0);
     JS_ASSERT(script == callee.toFunction()->script());
 
     /* Initialize stack frame members. */
-    flags_ = FUNCTION | HAS_PREVPC | HAS_SCOPECHAIN | flagsArg;
+    flags_ = FUNCTION | HAS_PREVPC | HAS_SCOPECHAIN | HAS_BLOCKCHAIN | flagsArg;
     exec.fun = &callee;
     args.nactual = nactual;
     scopeChain_ = callee.toFunction()->environment();
     ncode_ = NULL;
     initPrev(cx);
+    blockChain_= NULL;
+    JS_ASSERT(!hasBlockChain());
     JS_ASSERT(!hasHookData());
     JS_ASSERT(annotation() == NULL);
     JS_ASSERT(!hasCallObj());
 
     SetValueRangeToUndefined(slots(), script->nfixed);
 }
 
 inline void
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -73,17 +73,17 @@ void
 StackFrame::initExecuteFrame(JSScript *script, StackFrame *prev, FrameRegs *regs,
                              const Value &thisv, JSObject &scopeChain, ExecuteType type)
 {
     /*
      * See encoding of ExecuteType. When GLOBAL isn't set, we are executing a
      * script in the context of another frame and the frame type is determined
      * by the context.
      */
-    flags_ = type | HAS_SCOPECHAIN | HAS_PREVPC;
+    flags_ = type | HAS_SCOPECHAIN | HAS_BLOCKCHAIN | HAS_PREVPC;
     if (!(flags_ & GLOBAL))
         flags_ |= (prev->flags_ & (FUNCTION | GLOBAL));
 
     Value *dstvp = (Value *)this - 2;
     dstvp[1] = thisv;
 
     if (isFunctionFrame()) {
         dstvp[0] = prev->calleev();
@@ -97,16 +97,17 @@ StackFrame::initExecuteFrame(JSScript *s
         args.script = (JSScript *)0xbad;
 #endif
     }
 
     scopeChain_ = &scopeChain;
     prev_ = prev;
     prevpc_ = regs ? regs->pc : (jsbytecode *)0xbad;
     prevInline_ = regs ? regs->inlined() : NULL;
+    blockChain_ = NULL;
 
 #ifdef DEBUG
     ncode_ = (void *)0xbad;
     Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
     hookData_ = (void *)0xbad;
     annotation_ = (void *)0xbad;
 #endif
 
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -340,20 +340,21 @@ class StackFrame
         /* Lazy frame initialization */
         HAS_CALL_OBJ       =     0x2000,  /* frame has a callobj reachable from scopeChain_ */
         HAS_ARGS_OBJ       =     0x4000,  /* frame has an argsobj in StackFrame::args */
         HAS_HOOK_DATA      =     0x8000,  /* frame has hookData_ set */
         HAS_ANNOTATION     =    0x10000,  /* frame has annotation_ set */
         HAS_RVAL           =    0x20000,  /* frame has rval_ set */
         HAS_SCOPECHAIN     =    0x40000,  /* frame has scopeChain_ set */
         HAS_PREVPC         =    0x80000,  /* frame has prevpc_ and prevInline_ set */
+        HAS_BLOCKCHAIN     =   0x100000,  /* frame has blockChain_ set */
 
         /* Method JIT state */
-        DOWN_FRAMES_EXPANDED = 0x100000,  /* inlining in down frames has been expanded */
-        LOWERED_CALL_APPLY   = 0x200000   /* Pushed by a lowered call/apply */
+        DOWN_FRAMES_EXPANDED = 0x400000,  /* inlining in down frames has been expanded */
+        LOWERED_CALL_APPLY   = 0x800000   /* Pushed by a lowered call/apply */
     };
 
   private:
     mutable uint32_t    flags_;         /* bits described by Flags */
     union {                             /* describes what code is executing in a */
         JSScript        *script;        /*   global frame */
         JSFunction      *fun;           /*   function frame, pre GetScopeChain */
     } exec;
@@ -363,16 +364,17 @@ class StackFrame
         JSScript        *script;        /* eval has no args, but needs a script */
     } args;
     mutable JSObject    *scopeChain_;   /* current scope chain */
     StackFrame          *prev_;         /* previous cx->regs->fp */
     void                *ncode_;        /* return address for method JIT */
 
     /* Lazily initialized */
     Value               rval_;          /* return value of the frame */
+    JSObject            *blockChain_;   /* innermost let block */
     jsbytecode          *prevpc_;       /* pc of previous frame*/
     JSInlinedSite       *prevInline_;   /* inlined site in previous frame */
     void                *hookData_;     /* closure returned by call hook */
     void                *annotation_;   /* perhaps remove with bug 546848 */
     JSRejoinState       rejoin_;        /* If rejoining into the interpreter
                                          * from JIT code, state at rejoin. */
 
     static void staticAsserts() {
@@ -835,16 +837,36 @@ class StackFrame
         JS_ASSERT_IF(ret, !isNonStrictEvalFrame());
         return ret;
     }
 
     inline CallObject &callObj() const;
     inline void setScopeChainNoCallObj(JSObject &obj);
     inline void setScopeChainWithOwnCallObj(CallObject &obj);
 
+    /* Block chain */
+
+    bool hasBlockChain() const {
+        return (flags_ & HAS_BLOCKCHAIN) && blockChain_;
+    }
+
+    JSObject *maybeBlockChain() {
+        return (flags_ & HAS_BLOCKCHAIN) ? blockChain_ : NULL;
+    }
+
+    JSObject &blockChain() const {
+        JS_ASSERT(hasBlockChain());
+        return *blockChain_;
+    }
+
+    void setBlockChain(JSObject *obj) {
+        flags_ |= HAS_BLOCKCHAIN;
+        blockChain_ = obj;
+    }
+
     /*
      * Prologue for function frames: make a call object for heavyweight
      * functions, and maintain type nesting invariants.
      */
     inline bool functionPrologue(JSContext *cx);
 
     /*
      * Epilogue for function frames: put any args or call object for the frame
--- a/js/src/vm/StackSpace.h
+++ b/js/src/vm/StackSpace.h
@@ -50,17 +50,17 @@ class FrameGuard;
 class DummyFrameGuard;
 class ExecuteFrameGuard;
 class GeneratorFrameGuard;
 
 /* Flags specified for a frame as it is constructed. */
 enum InitialFrameFlags {
     INITIAL_NONE           =          0,
     INITIAL_CONSTRUCT      =       0x80, /* == StackFrame::CONSTRUCTING, asserted in Stack.h */
-    INITIAL_LOWERED        =   0x200000  /* == StackFrame::LOWERED_CALL_APPLY, asserted in Stack.h */
+    INITIAL_LOWERED        =   0x800000  /* == StackFrame::LOWERED_CALL_APPLY, asserted in Stack.h */
 };
 
 enum ExecuteType {
     EXECUTE_GLOBAL         =        0x1, /* == StackFrame::GLOBAL */
     EXECUTE_DIRECT_EVAL    =        0x8, /* == StackFrame::EVAL */
     EXECUTE_INDIRECT_EVAL  =        0x9, /* == StackFrame::GLOBAL | EVAL */
     EXECUTE_DEBUG          =       0x18  /* == StackFrame::EVAL | DEBUGGER */
 };