Bug 535912 - Eliminate blockChain from JSStackFrame (r=cdleary)
authorBill McCloskey <wmccloskey@mozilla.com>
Wed, 29 Sep 2010 13:21:36 -0700
changeset 54855 42728286536210f9b6e3a6b09f18adfe11bb5666
parent 54854 98c134cf59ef7a02abe9a2957bfa2ac6637f7a54
child 54856 f0ec8c5ece85f2fdad78bdf5db77caa3640f5075
push idunknown
push userunknown
push dateunknown
reviewerscdleary
bugs535912
milestone2.0b7pre
Bug 535912 - Eliminate blockChain from JSStackFrame (r=cdleary)
js/src/Makefile.in
js/src/jsemit.cpp
js/src/jsemit.h
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsinterpinlines.h
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/jsopcodeinlines.h
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jstracer.cpp
js/src/jstracer.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -197,16 +197,17 @@ INSTALLED_HEADERS = \
 		jslock.h \
 		jslong.h \
 		jsmath.h \
 		jsobj.h \
 		jsobjinlines.h \
 		json.h \
 		jsopcode.tbl \
 		jsopcode.h \
+		jsopcodeinlines.h \
 		jsotypes.h \
 		jsparse.h \
 		jsproxy.h \
 		jsprf.h \
 		jsprobes.h \
 		jspropertycache.h \
 		jspropertycacheinlines.h \
 		jspropertytree.h \
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -84,16 +84,22 @@
 
 using namespace js;
 using namespace js::gc;
 
 static JSBool
 NewTryNote(JSContext *cx, JSCodeGenerator *cg, JSTryNoteKind kind,
            uintN stackDepth, size_t start, size_t end);
 
+static JSBool
+EmitIndexOp(JSContext *cx, JSOp op, uintN index, JSCodeGenerator *cg);
+
+static JSBool
+EmitLeaveBlock(JSContext *cx, JSCodeGenerator *cg, JSOp op, JSObjectBox *box);
+
 JSCodeGenerator::JSCodeGenerator(Parser *parser,
                                  JSArenaPool *cpool, JSArenaPool *npool,
                                  uintN lineno)
   : JSTreeContext(parser),
     codePool(cpool), notePool(npool),
     codeMark(JS_ARENA_MARK(cpool)), noteMark(JS_ARENA_MARK(npool)),
     stackDepth(0), maxStackDepth(0),
     ntrynotes(0), lastTryNode(NULL),
@@ -288,16 +294,34 @@ js_Emit3(JSContext *cx, JSCodeGenerator 
         next[2] = op2;
         CG_NEXT(cg) = next + 3;
         UpdateDepth(cx, cg, offset);
     }
     return offset;
 }
 
 ptrdiff_t
+js_Emit5(JSContext *cx, JSCodeGenerator *cg, JSOp op, uint16 op1, uint16 op2)
+{
+    ptrdiff_t offset = EmitCheck(cx, cg, op, 5);
+
+    if (offset >= 0) {
+        jsbytecode *next = CG_NEXT(cg);
+        next[0] = (jsbytecode)op;
+        next[1] = UINT16_HI(op1);
+        next[2] = UINT16_LO(op1);
+        next[3] = UINT16_HI(op2);
+        next[4] = UINT16_LO(op2);
+        CG_NEXT(cg) = next + 5;
+        UpdateDepth(cx, cg, offset);
+    }
+    return offset;
+}
+
+ptrdiff_t
 js_EmitN(JSContext *cx, JSCodeGenerator *cg, JSOp op, size_t extra)
 {
     ptrdiff_t length = 1 + (ptrdiff_t)extra;
     ptrdiff_t offset = EmitCheck(cx, cg, op, length);
 
     if (offset >= 0) {
         jsbytecode *next = CG_NEXT(cg);
         *next = (jsbytecode)op;
@@ -1332,38 +1356,38 @@ void
 js_PushStatement(JSTreeContext *tc, JSStmtInfo *stmt, JSStmtType 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->blockObj);
+    JS_ASSERT(!stmt->blockBox);
     stmt->down = tc->topStmt;
     tc->topStmt = stmt;
     if (STMT_LINKS_SCOPE(stmt)) {
         stmt->downScope = tc->topScopeStmt;
         tc->topScopeStmt = stmt;
     } else {
         stmt->downScope = NULL;
     }
 }
 
 void
-js_PushBlockScope(JSTreeContext *tc, JSStmtInfo *stmt, JSObject *blockObj,
+js_PushBlockScope(JSTreeContext *tc, JSStmtInfo *stmt, JSObjectBox *blockBox,
                   ptrdiff_t top)
 {
     js_PushStatement(tc, stmt, STMT_BLOCK, top);
     stmt->flags |= SIF_SCOPE;
-    blockObj->setParent(tc->blockChain);
+    blockBox->object->setParent(tc->blockChain());
     stmt->downScope = tc->topScopeStmt;
     tc->topScopeStmt = stmt;
-    tc->blockChain = blockObj;
-    stmt->blockObj = blockObj;
+    tc->blockChainBox = blockBox;
+    stmt->blockBox = blockBox;
 }
 
 /*
  * 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, JSCodeGenerator *cg, JSOp op, ptrdiff_t *lastp)
@@ -1468,34 +1492,42 @@ EmitNonLocalJumpFixup(JSContext *cx, JSC
              */
             npops += 2;
             break;
 
           default:;
         }
 
         if (stmt->flags & SIF_SCOPE) {
-            uintN i;
-
             /* There is a Block object with locals on the stack to pop. */
             FLUSH_POPS();
             if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
                 return JS_FALSE;
-            i = OBJ_BLOCK_COUNT(cx, stmt->blockObj);
-            EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, i);
+            if (!EmitLeaveBlock(cx, cg, JSOP_LEAVEBLOCK, stmt->blockBox))
+                return JS_FALSE;
         }
     }
 
     FLUSH_POPS();
     cg->stackDepth = depth;
     return JS_TRUE;
 
 #undef FLUSH_POPS
 }
 
+static JSBool
+EmitBlockChain(JSContext *cx, JSCodeGenerator *cg)
+{
+    JSObjectBox *box = cg->blockChainBox;
+    if (box)
+        return EmitIndexOp(cx, JSOP_BLOCKCHAIN, box->index, cg);
+    else
+        return js_Emit1(cx, cg, JSOP_NULLBLOCKCHAIN) >= 0;
+}
+
 static ptrdiff_t
 EmitGoto(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
          ptrdiff_t *lastp, JSAtomListElement *label, JSSrcNoteType noteType)
 {
     intN index;
 
     if (!EmitNonLocalJumpFixup(cx, cg, toStmt))
         return -1;
@@ -1542,17 +1574,21 @@ js_PopStatement(JSTreeContext *tc)
 {
     JSStmtInfo *stmt;
 
     stmt = tc->topStmt;
     tc->topStmt = stmt->down;
     if (STMT_LINKS_SCOPE(stmt)) {
         tc->topScopeStmt = stmt->downScope;
         if (stmt->flags & SIF_SCOPE) {
-            tc->blockChain = stmt->blockObj->getParent();
+            if (stmt->downScope) {
+                tc->blockChainBox = stmt->downScope->blockBox;
+            } else {
+                tc->blockChainBox = NULL;
+            }
             JS_SCOPE_DEPTH_METERING(--tc->scopeDepth);
         }
     }
 }
 
 JSBool
 js_PopStatementCG(JSContext *cx, JSCodeGenerator *cg)
 {
@@ -1589,17 +1625,17 @@ js_LexicalLookup(JSTreeContext *tc, JSAt
     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->blockObj;
+        JSObject *obj = stmt->blockBox->object;
         JS_ASSERT(obj->isStaticBlock());
 
         const Shape *shape = obj->nativeLookup(ATOM_TO_JSID(atom));
         if (shape) {
             JS_ASSERT(shape->hasShortID());
 
             if (slotp) {
                 JS_ASSERT(obj->fslots[JSSLOT_BLOCK_DEPTH].isInt32());
@@ -1882,16 +1918,31 @@ EmitEnterBlock(JSContext *cx, JSParseNod
          */
         bool isClosed = cg->shouldNoteClosedName(dn);
         blockObj->setSlot(slot, BooleanValue(isClosed));
     }
 
     return true;
 }
 
+static JSBool
+EmitLeaveBlock(JSContext *cx, JSCodeGenerator *cg, JSOp op,
+               JSObjectBox *box)
+{
+    JSOp bigSuffix;
+    uintN count = OBJ_BLOCK_COUNT(cx, box->object);
+    
+    bigSuffix = EmitBigIndexPrefix(cx, cg, box->index);
+    if (bigSuffix == JSOP_FALSE)
+        return JS_FALSE;
+    if (js_Emit5(cx, cg, op, count, box->index) < 0)
+        return JS_FALSE;
+    return bigSuffix == JSOP_NOP || js_Emit1(cx, cg, bigSuffix) >= 0;
+}
+
 /*
  * When eval is called from a function, the eval code or function code it
  * compiles may reference upvars that live in the eval-calling function. The
  * eval-invoked compiler does not have explicit definitions for these upvars
  * and we do not attempt to create them a-priori (by inspecting the function's
  * args and vars) -- we could, but we'd take an avoidable penalty for each
  * function local not referenced by any upvar. Instead, we map such upvars
  * lazily, growing upvarMap.vector by powers of two.
@@ -3079,17 +3130,17 @@ EmitSwitch(JSContext *cx, JSCodeGenerato
     uint32 caseCount, tableLength;
     JSParseNode **table;
     int32_t i, low, high;
     JSAtomListElement *ale;
     intN noteIndex;
     size_t switchSize, tableSize;
     jsbytecode *pc, *savepc;
 #if JS_HAS_BLOCK_SCOPE
-    jsint count;
+    JSObjectBox *box;
 #endif
 
     /* 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;
 
@@ -3104,18 +3155,18 @@ EmitSwitch(JSContext *cx, JSCodeGenerato
     if (pn2->pn_type == TOK_LEXICALSCOPE) {
         /*
          * Push the body's block scope before discriminant code-gen for proper
          * static block scope linkage in case the discriminant contains a let
          * expression.  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.
          */
-        count = OBJ_BLOCK_COUNT(cx, pn2->pn_objbox->object);
-        js_PushBlockScope(cg, stmtInfo, pn2->pn_objbox->object, -1);
+        box = pn2->pn_objbox;
+        js_PushBlockScope(cg, stmtInfo, box, -1);
         stmtInfo->type = STMT_SWITCH;
 
         /* Emit JSOP_ENTERBLOCK before code to evaluate the discriminant. */
         if (!EmitEnterBlock(cx, pn2, cg))
             return JS_FALSE;
 
         /*
          * Pop the switch's statement info around discriminant code-gen.  Note
@@ -3123,17 +3174,17 @@ EmitSwitch(JSContext *cx, JSCodeGenerato
          * block scope object, which is necessary for correct block parenting
          * in the case where the discriminant contains a let expression.
          */
         cg->topStmt = stmtInfo->down;
         cg->topScopeStmt = stmtInfo->downScope;
     }
 #ifdef __GNUC__
     else {
-        count = 0;
+        box = NULL;
     }
 #endif
 #endif
 
     /*
      * Emit code for the discriminant first (or nearly first, in the case of a
      * switch whose body is a block scope).
      */
@@ -3145,16 +3196,17 @@ EmitSwitch(JSContext *cx, JSCodeGenerato
 #if !JS_HAS_BLOCK_SCOPE
     js_PushStatement(cg, stmtInfo, STMT_SWITCH, top);
 #else
     if (pn2->pn_type == TOK_LC) {
         js_PushStatement(cg, stmtInfo, STMT_SWITCH, top);
     } else {
         /* Re-push the switch's statement info record. */
         cg->topStmt = cg->topScopeStmt = stmtInfo;
+        cg->blockChainBox = stmtInfo->blockBox;
 
         /* Set the statement info record's idea of top. */
         stmtInfo->update = top;
 
         /* Advance pn2 to refer to the switch case list. */
         pn2 = pn2->expr();
     }
 #endif
@@ -3608,17 +3660,17 @@ EmitSwitch(JSContext *cx, JSCodeGenerato
 out:
     if (table)
         cx->free(table);
     if (ok) {
         ok = js_PopStatementCG(cx, cg);
 
 #if JS_HAS_BLOCK_SCOPE
         if (ok && pn->pn_right->pn_type == TOK_LEXICALSCOPE)
-            EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
+            ok = EmitLeaveBlock(cx, cg, JSOP_LEAVEBLOCK, box);
 #endif
     }
     return ok;
 
 bad:
     ok = JS_FALSE;
     goto out;
 }
@@ -4261,16 +4313,17 @@ EmitVariables(JSContext *cx, JSCodeGener
                 if (!js_EmitTree(cx, cg, pn3))
                     return JS_FALSE;
                 cg->flags |= oldflags & TCF_IN_FOR_INIT;
 
 #if JS_HAS_BLOCK_SCOPE
                 if (popScope) {
                     cg->topStmt = stmt;
                     cg->topScopeStmt = scopeStmt;
+                    cg->blockChainBox = scopeStmt->blockBox;
                 }
 #endif
             }
         }
 
         /*
          * The parser rewrites 'for (var x = i in o)' to hoist 'var x = i' --
          * likewise 'for (let x = i in o)' becomes 'i; for (let x in o)' using
@@ -4513,16 +4566,20 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
         /* Emit a bytecode pointing to the closure object in its immediate. */
         op = PN_OP(pn);
         if (op != JSOP_NOP) {
             if ((pn->pn_funbox->tcflags & TCF_GENEXP_LAMBDA) &&
                 js_NewSrcNote(cx, cg, SRC_GENEXP) < 0) {
                 return JS_FALSE;
             }
             EMIT_INDEX_OP(op, index);
+
+            /* Make blockChain determination quicker. */
+            if (EmitBlockChain(cx, cg) < 0)
+                return JS_FALSE;
             break;
         }
 
         /*
          * 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
@@ -4531,16 +4588,19 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
          * definitions can be scheduled before generating the rest of code.
          */
         if (!cg->inFunction()) {
             JS_ASSERT(!cg->topStmt);
             if (pn->pn_cookie.isFree()) {
                 CG_SWITCH_TO_PROLOG(cg);
                 op = FUN_FLAT_CLOSURE(fun) ? JSOP_DEFFUN_FC : JSOP_DEFFUN;
                 EMIT_INDEX_OP(op, index);
+                /* Make blockChain determination quicker. */
+                if (EmitBlockChain(cx, cg) < 0)
+                    return JS_FALSE;
                 CG_SWITCH_TO_MAIN(cg);
             }
 
             /* Emit NOP for the decompiler. */
             if (!EmitFunctionDefNop(cx, cg, index))
                 return JS_FALSE;
         } else {
 #ifdef DEBUG
@@ -4553,16 +4613,20 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
             op = FUN_FLAT_CLOSURE(fun) ? JSOP_DEFLOCALFUN_FC : JSOP_DEFLOCALFUN;
             if (pn->isClosed() &&
                 !cg->callsEval() &&
                 !cg->closedVars.append(pn->pn_cookie.slot())) {
                 return JS_FALSE;
             }
             if (!EmitSlotIndexOp(cx, op, slot, index, cg))
                 return JS_FALSE;
+
+            /* Make blockChain determination quicker. */
+            if (EmitBlockChain(cx, cg) < 0)
+                return JS_FALSE;
         }
         break;
       }
 
       case TOK_ARGSBODY:
       {
         JSParseNode *pnlast = pn->last();
         for (JSParseNode *pn2 = pn->pn_head; pn2 != pnlast; pn2 = pn2->pn_next) {
@@ -5260,18 +5324,19 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
             return JS_FALSE;
 
         tryEnd = CG_OFFSET(cg);
 
         /* If this try has a catch block, emit it. */
         pn2 = pn->pn_kid2;
         lastCatch = NULL;
         if (pn2) {
-            jsint count = 0;    /* previous catch block's population */
-
+            JSObjectBox *prevBox = NULL;
+            uintN count = 0;    /* previous catch block's population */
+            
             /*
              * The emitted code for a catch block looks like:
              *
              * [throwing]                          only if 2nd+ catch block
              * [leaveblock]                        only if 2nd+ catch block
              * enterblock                          with SRC_CATCH
              * exception
              * [dup]                               only if catchguard
@@ -5312,17 +5377,18 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
                      * start of the previous guarded catch.
                      */
                     if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
                         js_Emit1(cx, cg, JSOP_THROWING) < 0) {
                         return JS_FALSE;
                     }
                     if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
                         return JS_FALSE;
-                    EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
+                    if (!EmitLeaveBlock(cx, cg, JSOP_LEAVEBLOCK, prevBox))
+                        return JS_FALSE;
                     JS_ASSERT(cg->stackDepth == depth);
                 }
 
                 /*
                  * Annotate the JSOP_ENTERBLOCK that's about to be generated
                  * by the call to js_EmitTree immediately below.  Save this
                  * source note's index in stmtInfo for use by the TOK_CATCH:
                  * case, where the length of the catch guard is set as the
@@ -5335,16 +5401,17 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
 
                 /*
                  * 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->pn_type == TOK_LEXICALSCOPE);
                 count = OBJ_BLOCK_COUNT(cx, pn3->pn_objbox->object);
+                prevBox = pn3->pn_objbox;
                 if (!js_EmitTree(cx, cg, pn3))
                     return JS_FALSE;
 
                 /* gosub <finally>, if required */
                 if (pn->pn_kid3) {
                     jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH,
                                           &GOSUBS(stmtInfo));
                     if (jmp < 0)
@@ -5459,17 +5526,17 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
         /*
          * Morph STMT_BLOCK to STMT_CATCH, note the block entry code offset,
          * and save the block object atom.
          */
         stmt = cg->topStmt;
         JS_ASSERT(stmt->type == STMT_BLOCK && (stmt->flags & SIF_SCOPE));
         stmt->type = STMT_CATCH;
         catchStart = stmt->update;
-        blockObj = stmt->blockObj;
+        blockObj = stmt->blockBox->object;
 
         /* Go up one statement info record to the TRY or FINALLY record. */
         stmt = stmt->down;
         JS_ASSERT(stmt->type == STMT_TRY || stmt->type == STMT_FINALLY);
 
         /* Pick up the pending exception and bind it to the catch variable. */
         if (js_Emit1(cx, cg, JSOP_EXCEPTION) < 0)
             return JS_FALSE;
@@ -6443,28 +6510,30 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
         }
         cg->flags |= oldflags & TCF_IN_FOR_INIT;
         if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - off) < 0)
             return JS_FALSE;
 
         argc = pn->pn_count - 1;
         if (js_Emit3(cx, cg, PN_OP(pn), ARGC_HI(argc), ARGC_LO(argc)) < 0)
             return JS_FALSE;
-        if (PN_OP(pn) == JSOP_EVAL)
+        if (PN_OP(pn) == JSOP_EVAL) {
             EMIT_UINT16_IMM_OP(JSOP_LINENO, pn->pn_pos.begin.lineno);
+            if (EmitBlockChain(cx, cg) < 0)
+                return JS_FALSE;
+        }
         break;
       }
 
       case TOK_LEXICALSCOPE:
       {
         JSObjectBox *objbox;
-        uintN count;
 
         objbox = pn->pn_objbox;
-        js_PushBlockScope(cg, &stmtInfo, objbox->object, CG_OFFSET(cg));
+        js_PushBlockScope(cg, &stmtInfo, objbox, CG_OFFSET(cg));
 
         /*
          * 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.
@@ -6501,18 +6570,18 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
             if (noteIndex >= 0 &&
                 !js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
                                      CG_OFFSET(cg) - top)) {
                 return JS_FALSE;
             }
         }
 
         /* Emit the JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR opcode. */
-        count = OBJ_BLOCK_COUNT(cx, objbox->object);
-        EMIT_UINT16_IMM_OP(op, count);
+        if (!EmitLeaveBlock(cx, cg, op, objbox))
+            return JS_FALSE;
 
         ok = js_PopStatementCG(cx, cg);
         break;
       }
 
 #if JS_HAS_BLOCK_SCOPE
       case TOK_LET:
         /* Let statements have their variable declarations on the left. */
@@ -7446,17 +7515,18 @@ js_FinishTakingTryNotes(JSCodeGenerator 
  * the pre-compilation prototype, a pigeon-hole problem for instanceof tests.
  */
 uintN
 JSCGObjectList::index(JSObjectBox *objbox)
 {
     JS_ASSERT(!objbox->emitLink);
     objbox->emitLink = lastbox;
     lastbox = objbox;
-    return length++;
+    objbox->index = length++;
+    return objbox->index;
 }
 
 void
 JSCGObjectList::finish(JSObjectArray *array)
 {
     JSObject **cursor;
     JSObjectBox *objbox;
 
--- a/js/src/jsemit.h
+++ b/js/src/jsemit.h
@@ -125,17 +125,17 @@ struct JSStmtInfo {
     uint16          type;           /* statement type */
     uint16          flags;          /* flags, see below */
     uint32          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 */
-        JSObject    *blockObj;      /* block scope object */
+        JSObjectBox *blockBox;      /* block scope object */
     };
     JSStmtInfo      *down;          /* info for enclosing statement */
     JSStmtInfo      *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 */
@@ -271,17 +271,17 @@ struct JSStmtInfo {
                                  TCF_STRICT_MODE_CODE)
 
 struct JSTreeContext {              /* tree context for semantic checks */
     uint32          flags;          /* statement state flags, see above */
     uint32          bodyid;         /* block number of program/function body */
     uint32          blockidGen;     /* preincremented block number generator */
     JSStmtInfo      *topStmt;       /* top of statement info stack */
     JSStmtInfo      *topScopeStmt;  /* top lexical scope statement */
-    JSObject        *blockChain;    /* compile time block scope chain (NB: one
+    JSObjectBox     *blockChainBox; /* compile time block scope chain (NB: one
                                        deeper than the topScopeStmt/downScope
                                        chain when in head of let block/expr) */
     JSParseNode     *blockNode;     /* parse node for a block with let declarations
                                        (block with its own lexical scope)  */
     JSAtomList      decls;          /* function, const, and var declarations */
     js::Parser      *parser;        /* ptr to common parsing and lexing data */
 
     union {
@@ -303,17 +303,17 @@ struct JSTreeContext {              /* t
 
 #ifdef JS_SCOPE_DEPTH_METER
     uint16          scopeDepth;     /* current lexical scope chain depth */
     uint16          maxScopeDepth;  /* maximum lexical scope chain depth */
 #endif
 
     JSTreeContext(js::Parser *prs)
       : flags(0), bodyid(0), blockidGen(0),
-        topStmt(NULL), topScopeStmt(NULL), blockChain(NULL), blockNode(NULL),
+        topStmt(NULL), topScopeStmt(NULL), blockChainBox(NULL), blockNode(NULL),
         parser(prs), scopeChain(NULL), parent(prs->tc), staticLevel(0),
         funbox(NULL), functionList(NULL), innermostWith(NULL), sharpSlotBase(-1)
     {
         prs->tc = this;
         JS_SCOPE_DEPTH_METERING(scopeDepth = maxScopeDepth = 0);
     }
 
     /*
@@ -328,16 +328,20 @@ struct JSTreeContext {              /* t
                                                           ->context
                                                           ->runtime
                                                           ->lexicalScopeDepthStats,
                                                         maxScopeDepth));
     }
 
     uintN blockid() { return topStmt ? topStmt->blockid : bodyid; }
 
+    JSObject *blockChain() {
+        return blockChainBox ? blockChainBox->object : NULL;
+    }
+    
     bool atTopLevel() { return !topStmt || (topStmt->flags & SIF_BODY_BLOCK); }
 
     /* Test whether we're in a statement of given type. */
     bool inStatement(JSStmtType type);
 
     bool inStrictMode() const {
         return flags & TCF_STRICT_MODE_CODE;
     }
@@ -641,16 +645,23 @@ js_Emit2(JSContext *cx, JSCodeGenerator 
 /*
  * Emit three bytecodes, an opcode with two bytes of immediate operands.
  */
 extern ptrdiff_t
 js_Emit3(JSContext *cx, JSCodeGenerator *cg, JSOp op, jsbytecode op1,
          jsbytecode op2);
 
 /*
+ * Emit five bytecodes, an opcode with two 16-bit immediates.
+ */
+extern ptrdiff_t
+js_Emit5(JSContext *cx, JSCodeGenerator *cg, JSOp op, uint16 op1,
+         uint16 op2);
+
+/*
  * Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
  */
 extern ptrdiff_t
 js_EmitN(JSContext *cx, JSCodeGenerator *cg, JSOp op, size_t extra);
 
 /*
  * Unsafe macro to call js_SetJumpOffset and return false if it does.
  */
@@ -683,17 +694,17 @@ js_PushStatement(JSTreeContext *tc, JSSt
                  ptrdiff_t top);
 
 /*
  * Push a block scope statement and link blockObj into tc->blockChain. To pop
  * this statement info record, use js_PopStatement as usual, or if appropriate
  * (if generating code), js_PopStatementCG.
  */
 extern void
-js_PushBlockScope(JSTreeContext *tc, JSStmtInfo *stmt, JSObject *blockObj,
+js_PushBlockScope(JSTreeContext *tc, JSStmtInfo *stmt, JSObjectBox *blockBox,
                   ptrdiff_t top);
 
 /*
  * Pop tc->topStmt. If the top JSStmtInfo struct is not stack-allocated, it
  * is up to the caller to free it.
  */
 extern void
 js_PopStatement(JSTreeContext *tc);
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -62,16 +62,17 @@
 #include "jsinterp.h"
 #include "jsiter.h"
 #include "jslock.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jspropertycache.h"
 #include "jsscan.h"
+#include "jsemit.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jsstaticcheck.h"
 #include "jstracer.h"
 #include "jslibmath.h"
 #include "jsvector.h"
 #include "methodjit/MethodJIT.h"
@@ -81,16 +82,17 @@
 #include "jscntxtinlines.h"
 #include "jsinterpinlines.h"
 #include "jsobjinlines.h"
 #include "jsprobes.h"
 #include "jspropertycacheinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
 #include "jsstrinlines.h"
+#include "jsopcodeinlines.h"
 
 #if JS_HAS_XML_SUPPORT
 #include "jsxml.h"
 #endif
 
 #include "jsautooplen.h"
 
 #if defined(JS_METHODJIT) && defined(JS_MONOIC)
@@ -155,16 +157,100 @@ JSStackFrame::pc(JSContext *cx, JSStackF
     return callIC.pc;
 #else
     JS_NOT_REACHED("Unknown PC for frame");
     return NULL;
 #endif
 }
 
 /*
+ * 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, JSStackFrame *fp)
+{
+    if (!fp->isScriptFrame())
+        return NULL;
+
+    JSScript *script = fp->script();
+    jsbytecode *start = script->main;
+    /* Assume that imacros don't affect blockChain */
+    jsbytecode *pc = fp->hasImacropc() ? fp->imacropc() : fp->pc(cx);
+    
+    JS_ASSERT(pc >= start && pc < script->code + script->length);
+    
+    ptrdiff_t oplen;
+    JSObject *blockChain = NULL;
+    for (jsbytecode *p = start; p < pc; p += oplen) {
+        JSOp op = js_GetOpcode(cx, script, p);
+        const JSCodeSpec *cs = &js_CodeSpec[op];
+        oplen = cs->length;
+        if (oplen < 0)
+            oplen = js_GetVariableBytecodeLength(p);
+
+        if (op == JSOP_ENTERBLOCK) { 
+            blockChain = script->getObject(GET_INDEX(p));
+        } else if (op == JSOP_LEAVEBLOCK || op == JSOP_LEAVEBLOCKEXPR) {
+            /*
+             * Some LEAVEBLOCK instructions are due to early exits via
+             * return/break/etc. from block-scoped loops and functions.
+             * We should ignore these instructions, since they don't really
+             * signal the end of the block.
+             */
+            jssrcnote *sn = js_GetSrcNote(script, p);
+            if (!(sn && SN_TYPE(sn) == SRC_HIDDEN))
+                blockChain = blockChain->getParent();
+        }
+    }
+
+    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, JSStackFrame *fp, JSOp op, size_t oplen)
+{
+    /* Assume that we're in a script frame. */
+    jsbytecode *pc = fp->pc(cx);
+
+    /* The fast path. */
+    if (pc[oplen] == JSOP_NULLBLOCKCHAIN) {
+        JS_ASSERT(js_GetOpcode(cx, fp->script(), pc) == op);
+        return NULL;
+    }
+
+    JSScript *script = fp->script();
+
+    JS_ASSERT(js_GetOpcode(cx, script, pc) == op);
+    
+    JSObject *blockChain;
+    JSOp opNext = js_GetOpcode(cx, script, pc + oplen);
+    if (opNext == JSOP_BLOCKCHAIN) {
+        blockChain = script->getObject(GET_INDEX(pc + oplen));
+    } else if (opNext == JSOP_NULLBLOCKCHAIN) {
+        blockChain = NULL;
+    } else {
+        blockChain = NULL; /* appease gcc */
+        JS_NOT_REACHED("invalid opcode for fast block chain access");
+    }
+
+    return blockChain;
+}
+
+/*
  * We can't determine in advance which local variables can live on the stack and
  * be freed when their dynamic scope ends, and which will be closed over and
  * need to live in the heap.  So we place variables on the stack initially, note
  * when they are closed over, and copy those that are out to the heap when we
  * leave their dynamic scope.
  *
  * The bytecode compiler produces a tree of block objects accompanying each
  * JSScript representing those lexical blocks in the script that have let-bound
@@ -184,20 +270,20 @@ JSStackFrame::pc(JSContext *cx, JSStackF
  * a closure, we clone the missing blocks from blockChain (which is always
  * current), place them at the head of scopeChain, and use that for the
  * closure's scope chain.  If we never close over a lexical block, we never
  * place a mutable clone of it on scopeChain.
  *
  * This lazy cloning is implemented in js_GetScopeChain, which is also used in
  * some other cases --- entering 'with' blocks, for example.
  */
-JSObject *
-js_GetScopeChain(JSContext *cx, JSStackFrame *fp)
-{
-    JSObject *sharedBlock = fp->maybeBlockChain();
+static JSObject *
+js_GetScopeChainFull(JSContext *cx, JSStackFrame *fp, JSObject *blockChain)
+{
+    JSObject *sharedBlock = blockChain;
 
     if (!sharedBlock) {
         /*
          * Don't force a call object for a lightweight function call, but do
          * insist that there is a call object for a heavyweight function call.
          */
         JS_ASSERT_IF(fp->isFunctionFrame() && fp->fun()->isHeavyweight(),
                      fp->hasCallObj());
@@ -305,16 +391,28 @@ js_GetScopeChain(JSContext *cx, JSStackF
                  limitClone->getPrivate() == js_FloatingFrameIfGenerator(cx, fp),
                  sharedBlock);
 
     /* Place our newly cloned blocks at the head of the scope chain.  */
     fp->setScopeChainNoCallObj(*innermostNewChild);
     return innermostNewChild;
 }
 
+JSObject *
+js_GetScopeChain(JSContext *cx, JSStackFrame *fp)
+{
+    return js_GetScopeChainFull(cx, fp, js_GetBlockChain(cx, fp));
+}
+
+JSObject *
+js_GetScopeChainFast(JSContext *cx, JSStackFrame *fp, JSOp op, size_t oplen)
+{
+    return js_GetScopeChainFull(cx, fp, js_GetBlockChainFast(cx, fp, op, oplen));
+}
+
 JSBool
 js_GetPrimitiveThis(JSContext *cx, Value *vp, Class *clasp, const Value **vpp)
 {
     const Value *p = &vp[1];
     if (p->isObjectOrNull()) {
         JSObject *obj = ComputeThisFromVp(cx, vp);
         if (!InstanceOf(cx, obj, clasp, vp + 2))
             return JS_FALSE;
@@ -1217,30 +1315,22 @@ js_IsActiveWithOrBlock(JSContext *cx, JS
 
 /*
  * Unwind block and scope chains to match the given depth. The function sets
  * fp->sp on return to stackDepth.
  */
 JS_REQUIRES_STACK JSBool
 js_UnwindScope(JSContext *cx, jsint stackDepth, JSBool normalUnwind)
 {
-    JSObject *obj;
     Class *clasp;
 
     JS_ASSERT(stackDepth >= 0);
     JS_ASSERT(cx->fp()->base() + stackDepth <= cx->regs->sp);
 
     JSStackFrame *fp = cx->fp();
-    for (obj = fp->maybeBlockChain(); obj; obj = obj->getParent()) {
-        JS_ASSERT(obj->isStaticBlock());
-        if (OBJ_BLOCK_DEPTH(cx, obj) < stackDepth)
-            break;
-    }
-    fp->setBlockChain(obj);
-
     for (;;) {
         clasp = js_IsActiveWithOrBlock(cx, &fp->scopeChain(), stackDepth);
         if (!clasp)
             break;
         if (clasp == &js_BlockClass) {
             /* Don't fail until after we've updated all stacks. */
             normalUnwind &= js_PutBlockObject(cx, normalUnwind);
         } else {
@@ -2113,17 +2203,17 @@ Interpret(JSContext *cx, JSStackFrame *e
                     GET_INDEX(regs.pc + PCOFF) < js_common_atom_count         \
                   : (size_t)(atoms - script->atomMap.vector) <                \
                     (size_t)(script->atomMap.length -                         \
                              GET_INDEX(regs.pc + PCOFF)));                    \
         atom = atoms[GET_INDEX(regs.pc + PCOFF)];                             \
     JS_END_MACRO
 
 #define GET_FULL_INDEX(PCOFF)                                                 \
-    (atoms - script->atomMap.vector + GET_INDEX(regs.pc + PCOFF))
+    (atoms - script->atomMap.vector + GET_INDEX(regs.pc + (PCOFF)))
 
 #define LOAD_OBJECT(PCOFF, obj)                                               \
     (obj = script->getObject(GET_FULL_INDEX(PCOFF)))
 
 #define LOAD_FUNCTION(PCOFF)                                                  \
     (fun = script->getFunction(GET_FULL_INDEX(PCOFF)))
 
 #define LOAD_DOUBLE(PCOFF, dbl)                                               \
@@ -2452,40 +2542,44 @@ Interpret(JSContext *cx, JSStackFrame *e
 ADD_EMPTY_CASE(JSOP_NOP)
 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_UNUSED180)
+ADD_EMPTY_CASE(JSOP_NULLBLOCKCHAIN)
 END_EMPTY_CASES
 
 BEGIN_CASE(JSOP_TRACE)
     LEAVE_ON_SAFE_POINT();
 END_CASE(JSOP_TRACE)
 
 /* 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_PUSH)
     PUSH_UNDEFINED();
 END_CASE(JSOP_PUSH)
 
 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 = regs.fp->maybeBlockChain();
+    JSObject *obj = js_GetBlockChain(cx, regs.fp);
     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->getParent()) {
         Class *clasp = obj->getClass();
         if (clasp != &js_BlockClass && clasp != &js_WithClass)
             continue;
         if (obj->getPrivate() != js_FloatingFrameIfGenerator(cx, regs.fp))
@@ -2557,17 +2651,16 @@ BEGIN_CASE(JSOP_STOP)
         DO_OP();
     }
 #endif
 
     interpReturnOK = true;
     if (entryFrame != regs.fp)
   inline_return:
     {
-        JS_ASSERT(!regs.fp->hasBlockChain());
         JS_ASSERT(!js_IsActiveWithOrBlock(cx, &regs.fp->scopeChain(), 0));
         if (JS_UNLIKELY(regs.fp->hasHookData())) {
             if (JSInterpreterHook hook = cx->debugHooks->callHook) {
                 hook(cx, regs.fp, JS_FALSE, &interpReturnOK, regs.fp->hookData());
                 CHECK_INTERRUPT_HANDLER();
             }
         }
 
@@ -5211,27 +5304,19 @@ 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_FLAT_CLOSURE(fun));
 
-        /*
-         * Inline js_GetScopeChain a bit to optimize for the case of a
-         * top-level function.
-         */
-        if (!regs.fp->hasBlockChain()) {
-            obj2 = &regs.fp->scopeChain();
-        } else {
-            obj2 = js_GetScopeChain(cx, regs.fp);
-            if (!obj2)
-                goto error;
-        }
+        obj2 = js_GetScopeChainFast(cx, regs.fp, JSOP_DEFFUN, JSOP_DEFFUN_LENGTH);
+        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
      * compilation over a number of executions.  Examples include XUL scripts
      * and event handlers shared among Firefox or other Mozilla app chrome
@@ -5358,17 +5443,18 @@ BEGIN_CASE(JSOP_DEFLOCALFUN)
     JS_ASSERT(!FUN_FLAT_CLOSURE(fun));
     JSObject *obj = FUN_OBJECT(fun);
 
     if (FUN_NULL_CLOSURE(fun)) {
         obj = CloneFunctionObject(cx, fun, &regs.fp->scopeChain());
         if (!obj)
             goto error;
     } else {
-        JSObject *parent = js_GetScopeChain(cx, regs.fp);
+        JSObject *parent = js_GetScopeChainFast(cx, regs.fp, JSOP_DEFLOCALFUN,
+                                                JSOP_DEFLOCALFUN_LENGTH);
         if (!parent)
             goto error;
 
         if (obj->getParent() != parent) {
 #ifdef JS_TRACER
             if (TRACE_RECORDER(cx))
                 AbortRecording(cx, "DEFLOCALFUN for closure");
 #endif
@@ -5424,50 +5510,38 @@ BEGIN_CASE(JSOP_LAMBDA)
 
     /* do-while(0) so we can break instead of using a goto. */
     do {
         JSObject *parent;
         if (FUN_NULL_CLOSURE(fun)) {
             parent = &regs.fp->scopeChain();
 
             if (obj->getParent() == parent) {
-                jsbytecode *pc2 = regs.pc + JSOP_LAMBDA_LENGTH;
+                jsbytecode *pc2 = js_AdvanceOverBlockchain(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.
                  *
                  * WARNING: code in TraceRecorder::record_JSOP_LAMBDA must
                  * match the optimization cases in the following code that
                  * break from the outer do-while(0).
                  */
                 if (op2 == JSOP_INITMETHOD) {
-#ifdef DEBUG
-                    const Value &lref = regs.sp[-1];
-                    JS_ASSERT(lref.isObject());
-                    JSObject *obj2 = &lref.toObject();
-                    JS_ASSERT(obj2->getClass() == &js_ObjectClass);
-#endif
-
-                    fun->setMethodAtom(script->getAtom(GET_FULL_INDEX(JSOP_LAMBDA_LENGTH)));
+                    fun->setMethodAtom(script->getAtom(GET_FULL_INDEX(pc2 - regs.pc)));
                     JS_FUNCTION_METER(cx, joinedinitmethod);
                     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()) {
-                        fun->setMethodAtom(script->getAtom(GET_FULL_INDEX(JSOP_LAMBDA_LENGTH)));
+                        fun->setMethodAtom(script->getAtom(GET_FULL_INDEX(pc2 - regs.pc)));
                         JS_FUNCTION_METER(cx, joinedsetmethod);
                         break;
                     }
                 } else if (fun->joinable()) {
                     if (op2 == JSOP_CALL) {
                         /*
                          * Array.prototype.sort and String.prototype.replace are
                          * optimized as if they are special form. We know that they
@@ -5522,17 +5596,17 @@ BEGIN_CASE(JSOP_LAMBDA)
                     h.add(p, fun, 1);
                 } else {
                     JS_ASSERT(p->key == fun);
                     ++p->value;
                 }
             }
 #endif
         } else {
-            parent = js_GetScopeChain(cx, regs.fp);
+            parent = js_GetScopeChainFast(cx, regs.fp, JSOP_LAMBDA, JSOP_LAMBDA_LENGTH);
             if (!parent)
                 goto error;
         }
 
         obj = CloneFunctionObject(cx, fun, parent);
         if (!obj)
             goto error;
     } while (0);
@@ -6383,18 +6457,16 @@ 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->getParent());
-
     /*
      * 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();
@@ -6405,45 +6477,41 @@ 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->getParent()) != 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(regs.fp->blockChain()->isStaticBlock());
-    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, regs.fp->blockChain());
-
+    JS_ASSERT(blockChain->isStaticBlock());
+    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, 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() == regs.fp->blockChain()) {
+    if (obj.getProto() == blockChain) {
         JS_ASSERT(obj.isClonedBlock());
         if (!js_PutBlockObject(cx, JS_TRUE))
             goto error;
     }
 
-    /* Pop the block chain, too.  */
-    regs.fp->setBlockChain(regs.fp->blockChain()->getParent());
-
     /* 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;
@@ -6765,17 +6833,16 @@ END_CASE(JSOP_ARRAYPUSH)
     cx->setCurrentRegs(prevContextRegs);
 
 #ifdef JS_TRACER
     JS_ASSERT_IF(interpReturnOK && (interpFlags & JSINTERP_RECORD), !TRACE_RECORDER(cx));
     if (TRACE_RECORDER(cx))
         AbortRecording(cx, "recording out of Interpret");
 #endif
 
-    JS_ASSERT_IF(!regs.fp->isGeneratorFrame(), !regs.fp->hasBlockChain());
     JS_ASSERT_IF(!regs.fp->isGeneratorFrame(), !js_IsActiveWithOrBlock(cx, &regs.fp->scopeChain(), 0));
 
     --cx->interpLevel;
 
     return interpReturnOK;
 
   atom_not_defined:
     {
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -132,19 +132,16 @@ struct JSStackFrame
 
     /* Lazily initialized */
     js::Value           rval_;          /* return value of the frame */
     jsbytecode          *prevpc_;       /* pc of previous frame*/
     jsbytecode          *imacropc_;     /* pc of macro caller */
     void                *hookData_;     /* closure returned by call hook */
     void                *annotation_;   /* perhaps remove with bug 546848 */
 
-    /* TODO: remove */
-    JSObject            *blockChain_;   /* bug 540675 */
-
 #if JS_BITS_PER_WORD == 32
     void                *padding;
 #endif
 
     friend class js::StackSpace;
     friend class js::FrameRegsIter;
     friend struct JSContext;
 
@@ -507,35 +504,16 @@ struct JSStackFrame
     }
 
     inline JSObject &callObj() const;
     inline JSObject *maybeCallObj() const;
     inline void setScopeChainNoCallObj(JSObject &obj);
     inline void setScopeChainAndCallObj(JSObject &obj);
     inline void clearCallObj();
 
-    /* Block chain */
-
-    bool hasBlockChain() const {
-        return blockChain_ != NULL;
-    }
-
-    JSObject* blockChain() const {
-        JS_ASSERT(hasBlockChain());
-        return blockChain_;
-    }
-
-    JSObject* maybeBlockChain() const {
-        return blockChain_;
-    }
-
-    void setBlockChain(JSObject *obj) {
-        blockChain_ = obj;
-    }
-
     /*
      * Imacropc
      *
      * A frame's IMacro pc is the bytecode address when an imacro started
      * executing (guaranteed non-null). An imacro does not push a frame, so
      * when the imacro finishes, the frame's IMacro pc becomes the current pc.
      */
 
@@ -771,20 +749,16 @@ struct JSStackFrame
     static size_t offsetOfReturnValue() {
         return offsetof(JSStackFrame, rval_);
     }
 
     static ptrdiff_t offsetOfncode() {
         return offsetof(JSStackFrame, ncode_);
     }
 
-    static size_t offsetOfBlockChain() {
-        return offsetof(JSStackFrame, blockChain_);
-    }
-
     static ptrdiff_t offsetOfCallee(JSFunction *fun) {
         JS_ASSERT(fun != NULL);
         return -(fun->nargs + 2) * sizeof(js::Value);
     }
 
     static ptrdiff_t offsetOfThis(JSFunction *fun) {
         return fun == NULL
                ? -1 * ptrdiff_t(sizeof(js::Value))
@@ -817,26 +791,35 @@ struct JSStackFrame
 
 namespace js {
 
 static const size_t VALUES_PER_STACK_FRAME = sizeof(JSStackFrame) / sizeof(Value);
 
 } /* namespace js */
 
 
+extern JSObject *
+js_GetBlockChain(JSContext *cx, JSStackFrame *fp);
+
+extern JSObject *
+js_GetBlockChainFast(JSContext *cx, JSStackFrame *fp, JSOp op, size_t oplen);
+
 /*
  * 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 *
 js_GetScopeChain(JSContext *cx, JSStackFrame *fp);
 
+extern JSObject *
+js_GetScopeChainFast(JSContext *cx, JSStackFrame *fp, JSOp op, size_t oplen);
+
 /*
  * Given a context and a vector of [callee, this, args...] for a function that
  * was specified with a JSFUN_THISP_PRIMITIVE flag, get the primitive value of
  * |this| into *thisvp. In doing so, if |this| is an object, insist it is an
  * instance of clasp and extract its private slot value to return via *thisvp.
  *
  * NB: this function loads and uses *vp before storing *thisvp, so the two may
  * alias the same Value.
--- a/js/src/jsinterpinlines.h
+++ b/js/src/jsinterpinlines.h
@@ -53,17 +53,16 @@ JSStackFrame::initCallFrame(JSContext *c
     flags_ = JSFRAME_FUNCTION | JSFRAME_HAS_PREVPC | flagsArg;
     exec.fun = fun;
     args.nactual = nactual;  /* only need to write if over/under-flow */
     scopeChain_ = callee.getParent();
     /* prevpc_, prev_ initialized by push*Frame */
     JS_ASSERT(!hasImacropc());
     JS_ASSERT(!hasHookData());
     rval_.setUndefined();
-    blockChain_ = NULL;
     JS_ASSERT(annotation() == NULL);
 
     JS_ASSERT(!hasCallObj());
 }
 
 inline void
 JSStackFrame::initCallFrameCallerHalf(JSContext *cx, JSObject &scopeChain,
                                       uint32 nactual, uint32 flagsArg)
@@ -101,17 +100,16 @@ JSStackFrame::initCallFrameEarlyPrologue
 /*
  * The "late prologue" refers to the members that are stored after having
  * checked for stack overflow and formal/actual arg mismatch.
  */
 inline void
 JSStackFrame::initCallFrameLatePrologue()
 {
     rval_.setUndefined();
-    blockChain_ = NULL;
 
     SetValueRangeToUndefined(slots(), script()->nfixed);
 }
 
 inline void
 JSStackFrame::initEvalFrame(JSScript *script, JSStackFrame *prev,
                             jsbytecode *prevpc, uint32 flagsArg)
 {
@@ -142,17 +140,16 @@ JSStackFrame::initEvalFrame(JSScript *sc
     }
     scopeChain_ = &prev->scopeChain();
     JS_ASSERT_IF(isFunctionFrame(), &callObj() == &prev->callObj());
 
     setPrev(prev, prevpc);
     JS_ASSERT(!hasImacropc());
     JS_ASSERT(!hasHookData());
     rval_.setUndefined();
-    blockChain_ = NULL;
     setAnnotation(prev->annotation());
 }
 
 inline void
 JSStackFrame::initGlobalFrame(JSScript *script, JSObject &chain, uint32 flagsArg)
 {
     JS_ASSERT((flagsArg & ~(JSFRAME_EVAL | JSFRAME_DEBUGGER)) == 0);
 
@@ -166,17 +163,16 @@ JSStackFrame::initGlobalFrame(JSScript *
     exec.script = script;
     args.script = (JSScript *)0xbad;
     scopeChain_ = &chain;
 
     prev_ = NULL;
     JS_ASSERT(!hasImacropc());
     JS_ASSERT(!hasHookData());
     rval_.setUndefined();
-    blockChain_ = NULL;
     JS_ASSERT(annotation() == NULL);
 }
 
 inline void
 JSStackFrame::initDummyFrame(JSContext *cx, JSObject &chain)
 {
     js::PodZero(this);
     flags_ = JSFRAME_DUMMY | JSFRAME_HAS_PREVPC;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1075,17 +1075,24 @@ obj_eval(JSContext *cx, uintN argc, Valu
     /* From here on, control must exit through label out with ok set. */
     MUST_FLOW_THROUGH("out");
     uintN staticLevel = caller->script()->staticLevel + 1;
 
     /*
      * Bring fp->scopeChain up to date. We're either going to use
      * it (direct call) or save it and restore it (indirect call).
      */
-    JSObject *callerScopeChain = js_GetScopeChain(cx, caller);
+    JSObject *callerScopeChain;
+
+    if (callerPC && *callerPC == JSOP_EVAL)
+        callerScopeChain = js_GetScopeChainFast(cx, caller, JSOP_EVAL,
+                                                JSOP_EVAL_LENGTH + JSOP_LINENO_LENGTH);
+    else
+        callerScopeChain = js_GetScopeChain(cx, caller);
+
     if (!callerScopeChain)
         return JS_FALSE;
 
     JSObject *scopeobj = NULL;
 
 #if JS_HAS_EVAL_THIS_SCOPE
     /*
      * If we see an indirect call, then run eval in the global scope. We do
@@ -6672,18 +6679,16 @@ js_DumpStackFrame(JSContext *cx, JSStack
             fprintf(stderr, " eval");
         if (fp->isYielding())
             fprintf(stderr, " yielding");
         if (fp->isGeneratorFrame())
             fprintf(stderr, " generator");
         fputc('\n', stderr);
 
         fprintf(stderr, "  scopeChain: (JSObject *) %p\n", (void *) &fp->scopeChain());
-        if (fp->hasBlockChain())
-            fprintf(stderr, "  blockChain: (JSObject *) %p\n", (void *) fp->blockChain());
 
         fputc('\n', stderr);
     }
 }
 
 #ifdef DEBUG
 bool
 IsSaneThisObject(JSObject &obj)
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -418,16 +418,17 @@ js_Disassemble1(JSContext *cx, JSScript 
         if (!bytes)
             return 0;
         fprintf(fp, " %s", bytes);
         break;
 
       case JOF_UINT16PAIR:
         i = (jsint)GET_UINT16(pc);
         fprintf(fp, " %d", i);
+        pc += UINT16_LEN;
         /* FALL THROUGH */
 
       case JOF_UINT16:
         i = (jsint)GET_UINT16(pc);
         goto print_int;
 
       case JOF_TABLESWITCH:
       case JOF_TABLESWITCHX:
@@ -4104,16 +4105,23 @@ Decompile(SprintStack *ss, jsbytecode *p
                         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 if (*pc == JSOP_NULLBLOCKCHAIN) {
+                        pc += JSOP_NULLBLOCKCHAIN_LENGTH;
+                    } else {
+                        JS_NOT_REACHED("should see block chain operation");
+                    }
                     LOCAL_ASSERT(*pc == JSOP_NULL);
                     pc += JSOP_NULL_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
@@ -483,17 +483,17 @@ OPDEF(JSOP_CALLXMLNAME,   197, "callxmln
  * Specialized JSOP_TYPEOF to avoid reporting undefined for typeof(0, undef).
  */
 OPDEF(JSOP_TYPEOFEXPR,    198,"typeofexpr",  NULL,    1,  1,  1, 15,  JOF_BYTE|JOF_DETECTING)
 
 /*
  * Block-local scope support.
  */
 OPDEF(JSOP_ENTERBLOCK,    199,"enterblock",  NULL,    3,  0, -1,  0,  JOF_OBJECT)
-OPDEF(JSOP_LEAVEBLOCK,    200,"leaveblock",  NULL,    3, -1,  0,  0,  JOF_UINT16)
+OPDEF(JSOP_LEAVEBLOCK,    200,"leaveblock",  NULL,    5, -1,  0,  0,  JOF_UINT16)
 
 /* Jump to target if top of stack value is of primitive type. */
 OPDEF(JSOP_IFPRIMTOP,     201,"ifprimtop",   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,       202,"primtop",     NULL,    2,  1,  1,  0,  JOF_INT8)
 
 /*
@@ -512,17 +512,17 @@ OPDEF(JSOP_GETFUNNS,      206,"getfunns"
  * Variant of JSOP_ENUMELEM for destructuring const (const [a, b] = ...).
  */
 OPDEF(JSOP_ENUMCONSTELEM, 207,"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,208,"leaveblockexpr",NULL,  3, -1,  1,  3,  JOF_UINT16)
+OPDEF(JSOP_LEAVEBLOCKEXPR,208,"leaveblockexpr",NULL,  5, -1,  1,  3,  JOF_UINT16)
 
 /*
  * Optimize common JSOP_{THIS,GET{ARG,LOCAL}} -> JSOP_GETPROP cliches.
  */
 OPDEF(JSOP_GETTHISPROP,   209,"getthisprop",   NULL,  3,  0,  1, 18,  JOF_ATOM|JOF_VARPROP)
 OPDEF(JSOP_GETARGPROP,    210,"getargprop",    NULL,  5,  0,  1, 18,  JOF_SLOTATOM|JOF_VARPROP)
 OPDEF(JSOP_GETLOCALPROP,  211,"getlocalprop",  NULL,  5,  0,  1, 18,  JOF_SLOTATOM|JOF_VARPROP)
 
@@ -606,8 +606,17 @@ OPDEF(JSOP_GETGLOBAL,     239,"getglobal
 OPDEF(JSOP_SETGLOBAL,     240,"setglobal",     NULL,  3,  1,  1,  3,  JOF_GLOBAL|JOF_NAME|JOF_SET|JOF_DETECTING)
 OPDEF(JSOP_INCGLOBAL,     241,"incglobal",     NULL,  3,  0,  1, 15,  JOF_GLOBAL|JOF_NAME|JOF_INC|JOF_TMPSLOT2)
 OPDEF(JSOP_DECGLOBAL,     242,"decglobal",     NULL,  3,  0,  1, 15,  JOF_GLOBAL|JOF_NAME|JOF_DEC|JOF_TMPSLOT2)
 OPDEF(JSOP_GLOBALINC,     243,"globalinc",     NULL,  3,  0,  1, 15,  JOF_GLOBAL|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT2)
 OPDEF(JSOP_GLOBALDEC,     244,"globaldec",     NULL,  3,  0,  1, 15,  JOF_GLOBAL|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT2)
 OPDEF(JSOP_CALLGLOBAL,    245,"callglobal",    NULL,  3,  0,  2, 19,  JOF_GLOBAL|JOF_NAME|JOF_CALLOP)
 OPDEF(JSOP_FORGLOBAL,     246,"forglobal",     NULL,  3,  1,  1, 19,  JOF_GLOBAL|JOF_NAME|JOF_FOR|JOF_TMPSLOT)
 
+/*
+ * 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,    247,"blockchain",    NULL,  3,  0,  0,  0, JOF_OBJECT)
+OPDEF(JSOP_NULLBLOCKCHAIN,248,"nullblockchain",NULL,  1,  0,  0,  0, JOF_BYTE)
new file mode 100644
--- /dev/null
+++ b/js/src/jsopcodeinlines.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Communicator client code, released
+ * March 31, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "jsautooplen.h"
+
+JS_ALWAYS_INLINE jsbytecode *
+js_AdvanceOverBlockchain(jsbytecode *pc)
+{
+    if (*pc == JSOP_NULLBLOCKCHAIN)
+        return pc + JSOP_NULLBLOCKCHAIN_LENGTH;
+    else if (*pc == JSOP_BLOCKCHAIN)
+        return pc + JSOP_BLOCKCHAIN_LENGTH;
+    else
+        return pc;
+}
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -3358,17 +3358,17 @@ BindLet(JSContext *cx, BindData *data, J
      * successor standard to ES3.1 that specifies 'let'.
      */
     JS_ASSERT(!tc->atTopLevel());
 
     pn = data->pn;
     if (!CheckStrictBinding(cx, tc, atom, pn))
         return false;
 
-    blockObj = tc->blockChain;
+    blockObj = tc->blockChain();
     ale = tc->decls.lookup(atom);
     if (ale && ALE_DEFN(ale)->pn_blockid == tc->blockid()) {
         const char *name = js_AtomToPrintableString(cx, atom);
         if (name) {
             ReportCompileErrorNumber(cx, TS(tc->parser), pn,
                                      JSREPORT_ERROR, JSMSG_REDECLARED_VAR,
                                      (ale && ALE_DEFN(ale)->isConst())
                                      ? js_const_str
@@ -3423,17 +3423,17 @@ BindLet(JSContext *cx, BindData *data, J
 }
 
 static void
 PopStatement(JSTreeContext *tc)
 {
     JSStmtInfo *stmt = tc->topStmt;
 
     if (stmt->flags & SIF_SCOPE) {
-        JSObject *obj = stmt->blockObj;
+        JSObject *obj = stmt->blockBox->object;
         JS_ASSERT(!obj->isClonedBlock());
 
         for (Shape::Range r = obj->lastProperty()->all(); !r.empty(); r.popFront()) {
             JSAtom *atom = JSID_TO_ATOM(r.front().id);
 
             /* Beware the empty destructuring dummy. */
             if (atom == tc->parser->context->runtime->atomState.emptyAtom)
                 continue;
@@ -4210,18 +4210,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) {
-        ok = !!js_DefineNativeProperty(cx, tc->blockChain,
+        OBJ_BLOCK_COUNT(cx, tc->blockChain()) == 0) {
+        ok = !!js_DefineNativeProperty(cx, tc->blockChain(),
                                        ATOM_TO_JSID(cx->runtime->atomState.emptyAtom),
                                        UndefinedValue(), NULL, NULL,
                                        JSPROP_ENUMERATE | JSPROP_PERMANENT,
                                        Shape::HAS_SHORTID, 0, NULL);
         if (!ok)
             goto out;
     }
 
@@ -4575,17 +4575,17 @@ PushLexicalScope(JSContext *cx, TokenStr
     obj = js_NewBlockObject(cx);
     if (!obj)
         return NULL;
 
     blockbox = tc->parser->newObjectBox(obj);
     if (!blockbox)
         return NULL;
 
-    js_PushBlockScope(tc, stmt, obj, -1);
+    js_PushBlockScope(tc, stmt, blockbox, -1);
     pn->pn_type = TOK_LEXICALSCOPE;
     pn->pn_op = 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;
@@ -5706,18 +5706,18 @@ Parser::statement()
         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->blockChain == stmt->blockObj);
-            obj = tc->blockChain;
+            JS_ASSERT(tc->blockChainBox == stmt->blockBox);
+            obj = tc->blockChain();
         } 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.
                  */
                 tokenStream.mungeCurrentToken(TOK_VAR, JSOP_DEFVAR);
 
@@ -5757,19 +5757,19 @@ Parser::statement()
              * block.
              */
             stmt->flags |= SIF_SCOPE;
             stmt->downScope = tc->topScopeStmt;
             tc->topScopeStmt = stmt;
             JS_SCOPE_DEPTH_METERING(++tc->scopeDepth > tc->maxScopeDepth &&
                                     (tc->maxScopeDepth = tc->scopeDepth));
 
-            obj->setParent(tc->blockChain);
-            tc->blockChain = obj;
-            stmt->blockObj = obj;
+            obj->setParent(tc->blockChain());
+            tc->blockChainBox = blockbox;
+            stmt->blockBox = blockbox;
 
 #ifdef DEBUG
             pn1 = tc->blockNode;
             JS_ASSERT(!pn1 || pn1->pn_type != TOK_LEXICALSCOPE);
 #endif
 
             /* Create a new lexical scope node for these statements. */
             pn1 = LexicalScopeNode::create(tc);
@@ -6007,17 +6007,17 @@ Parser::variables(bool inLetHead)
     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->blockChain == scopeStmt->blockObj);
+        JS_ASSERT(tc->blockChainBox == scopeStmt->blockBox);
         data.binder = BindLet;
         data.let.overflow = JSMSG_TOO_MANY_LOCALS;
     } else {
         data.binder = BindVarOrConst;
     }
 
     do {
         tt = tokenStream.getToken();
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -910,16 +910,17 @@ JSParseNode::setFunArg()
         pn_lexdef->pn_dflags |= PND_FUNARG;
     pn_dflags |= PND_FUNARG;
 }
 
 struct JSObjectBox {
     JSObjectBox         *traceLink;
     JSObjectBox         *emitLink;
     JSObject            *object;
+    uintN               index;
 };
 
 #define JSFB_LEVEL_BITS 14
 
 struct JSFunctionBox : public JSObjectBox
 {
     JSParseNode         *node;
     JSFunctionBox       *siblings;
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -81,16 +81,17 @@
 #include "jscntxtinlines.h"
 #include "jsfuninlines.h"
 #include "jsinterpinlines.h"
 #include "jspropertycacheinlines.h"
 #include "jsobjinlines.h"
 #include "jsscopeinlines.h"
 #include "jsscriptinlines.h"
 #include "jscntxtinlines.h"
+#include "jsopcodeinlines.h"
 
 #include "jsautooplen.h"        // generated headers last
 #include "imacros.c.out"
 
 #if defined(NANOJIT_ARM) && defined(__GNUC__) && defined(AVMPLUS_LINUX)
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/types.h>
@@ -4349,19 +4350,16 @@ TraceRecorder::snapshot(ExitType exitTyp
     exit->from = fragment;
     exit->calldepth = callDepth;
     exit->numGlobalSlots = ngslots;
     exit->numStackSlots = stackSlots;
     exit->numStackSlotsBelowCurrentFrame = cx->fp()->isFunctionFrame() ?
                                            nativeStackOffset(&cx->fp()->calleeValue()) / sizeof(double) :
                                            0;
     exit->exitType = exitType;
-    exit->block = fp->maybeBlockChain();
-    if (fp->hasBlockChain())
-        tree->gcthings.addUnique(ObjectValue(*fp->blockChain()));
     exit->pc = pc;
     exit->imacpc = fp->maybeImacropc();
     exit->sp_adj = (stackSlots * sizeof(double)) - tree->nativeStackBase;
     exit->rp_adj = exit->calldepth * sizeof(FrameInfo*);
     exit->lookupFlags = js_InferFlags(cx, 0);
     memcpy(exit->fullTypeMap(), typemap, typemap_size);
 
 #if defined JS_JIT_SPEW
@@ -5729,17 +5727,16 @@ SynthesizeFrame(JSContext* cx, const Fra
     JSScript* newscript = newfun->script();
 
     /* Fill in the prev-frame's sp. */
     JSFrameRegs *regs = cx->regs;
     regs->sp = fp->slots() + fi.spdist;
     regs->pc = fi.pc;
     if (fi.imacpc)
         fp->setImacropc(fi.imacpc);
-    fp->setBlockChain(fi.block);
 
     /* Set argc/flags then mimic JSOP_CALL. */
     uintN argc = fi.get_argc();
     uint32 flags = fi.is_constructing ()
                    ? JSFRAME_CONSTRUCTING | JSFRAME_CONSTRUCTING
                    : 0;
 
 
@@ -6925,24 +6922,20 @@ LeaveTree(TraceMonitor *tm, TracerState&
                           fp->script()->filename, js_FramePCToLineNumber(cx, fp),
                           FramePCOffset(cx, fp));
 #endif
     }
 
     /*
      * Adjust sp and pc relative to the tree we exited from (not the tree we
      * entered into).  These are our final values for sp and pc since
-     * SynthesizeFrame has already taken care of all frames in between. But
-     * first we recover fp->blockChain, which comes from the side exit
-     * struct.
+     * SynthesizeFrame has already taken care of all frames in between.
      */
     JSStackFrame* const fp = cx->fp();
 
-    fp->setBlockChain(innermost->block);
-
     /*
      * If we are not exiting from an inlined frame, the state->sp is spbase.
      * Otherwise spbase is whatever slots frames around us consume.
      */
     cx->regs->pc = innermost->pc;
     if (innermost->imacpc)
         fp->setImacropc(innermost->imacpc);
     else
@@ -13331,19 +13324,16 @@ TraceRecorder::interpretedFunctionCall(V
     JSValueType* typemap = (JSValueType*)(fi + 1);
 
     DetermineTypesVisitor detVisitor(*this, typemap);
     VisitStackSlots(detVisitor, cx, 0);
 
     JS_ASSERT(argc < FrameInfo::CONSTRUCTING_FLAG);
 
     tree->gcthings.addUnique(fval);
-    fi->block = fp->maybeBlockChain();
-    if (fp->hasBlockChain())
-        tree->gcthings.addUnique(ObjectValue(*fp->blockChain()));
     fi->pc = cx->regs->pc;
     fi->imacpc = fp->maybeImacropc();
     fi->spdist = cx->regs->sp - fp->slots();
     fi->set_argc(uint16(argc), constructing);
     fi->callerHeight = stackSlots - (2 + argc);
     fi->callerArgc = fp->isGlobalFrame() || fp->isEvalFrame() ? 0 : fp->numActualArgs();
 
     if (callDepth >= tree->maxCallDepth)
@@ -14920,16 +14910,28 @@ TraceRecorder::record_JSOP_EXCEPTION()
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_LINENO()
 {
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
+TraceRecorder::record_JSOP_BLOCKCHAIN()
+{
+    return ARECORD_CONTINUE;
+}
+
+JS_REQUIRES_STACK AbortableRecordingStatus
+TraceRecorder::record_JSOP_NULLBLOCKCHAIN()
+{
+    return ARECORD_CONTINUE;
+}
+
+JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_CONDSWITCH()
 {
     return ARECORD_CONTINUE;
 }
 
 JS_REQUIRES_STACK AbortableRecordingStatus
 TraceRecorder::record_JSOP_CASE()
 {
@@ -15019,17 +15021,17 @@ TraceRecorder::record_JSOP_LAMBDA()
      * See jsinterp.cpp, the JSOP_LAMBDA null closure case. The JSOP_SETMETHOD and
      * JSOP_INITMETHOD logic governing the early ARECORD_CONTINUE returns below
      * must agree with the corresponding break-from-do-while(0) logic there.
      */
     if (FUN_NULL_CLOSURE(fun)) {
         if (FUN_OBJECT(fun)->getParent() != globalObj)
             RETURN_STOP_A("Null closure function object parent must be global object");
 
-        jsbytecode *pc2 = cx->regs->pc + JSOP_LAMBDA_LENGTH;
+        jsbytecode *pc2 = js_AdvanceOverBlockchain(cx->regs->pc + JSOP_LAMBDA_LENGTH);
         JSOp op2 = JSOp(*pc2);
 
         if (op2 == JSOP_INITMETHOD) {
             stack(0, INS_CONSTOBJ(FUN_OBJECT(fun)));
             return ARECORD_CONTINUE;
         }
 
         if (op2 == JSOP_SETMETHOD) {
@@ -15083,17 +15085,17 @@ TraceRecorder::record_JSOP_LAMBDA()
         CHECK_STATUS_A(getClassPrototype(JSProto_Function, proto_ins));
 
         LIns* args[] = { INS_CONSTOBJ(globalObj), proto_ins, INS_CONSTFUN(fun), cx_ins };
         LIns* x = lir->insCall(&js_NewNullClosure_ci, args);
         stack(0, x);
         return ARECORD_CONTINUE;
     }
 
-    if (cx->fp()->hasBlockChain())
+    if (js_GetBlockChainFast(cx, cx->fp(), JSOP_LAMBDA, JSOP_LAMBDA_LENGTH))
         RETURN_STOP_A("Unable to trace creating lambda in let");
 
     LIns *proto_ins;
     CHECK_STATUS_A(getClassPrototype(JSProto_Function, proto_ins));
     LIns* scopeChain_ins = scopeChain();
     JS_ASSERT(scopeChain_ins);
     LIns* args[] = { proto_ins, scopeChain_ins, INS_CONSTPTR(fun), cx_ins };
     LIns* call_ins = lir->insCall(&js_CloneFunctionObject_ci, args);
@@ -15109,17 +15111,17 @@ JS_REQUIRES_STACK AbortableRecordingStat
 TraceRecorder::record_JSOP_LAMBDA_FC()
 {
     JSFunction* fun;
     fun = cx->fp()->script()->getFunction(getFullIndex());
 
     if (FUN_OBJECT(fun)->getParent() != globalObj)
         return ARECORD_STOP;
 
-    if (cx->fp()->hasBlockChain())
+    if (js_GetBlockChainFast(cx, cx->fp(), JSOP_LAMBDA_FC, JSOP_LAMBDA_FC_LENGTH))
         RETURN_STOP_A("Unable to trace creating lambda in let");
 
     LIns* args[] = {
         scopeChain(),
         INS_CONSTFUN(fun),
         cx_ins
     };
     LIns* closure_ins = lir->insCall(&js_AllocFlatClosure_ci, args);
--- a/js/src/jstracer.h
+++ b/js/src/jstracer.h
@@ -396,17 +396,16 @@ enum ExitType {
     #undef MAKE_EXIT_CODE
     TOTAL_EXIT_TYPES
 };
 
 struct FrameInfo;
 
 struct VMSideExit : public nanojit::SideExit
 {
-    JSObject* block;
     jsbytecode* pc;
     jsbytecode* imacpc;
     intptr_t sp_adj;
     intptr_t rp_adj;
     int32_t calldepth;
     uint32 numGlobalSlots;
     uint32 numStackSlots;
     uint32 numStackSlotsBelowCurrentFrame;
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -49,16 +49,17 @@
 #include "Retcon.h"
 #include "assembler/jit/ExecutableAllocator.h"
 #include "assembler/assembler/LinkBuffer.h"
 #include "FrameState-inl.h"
 #include "jsscriptinlines.h"
 #include "InlineFrameAssembler.h"
 #include "jscompartment.h"
 #include "jsobjinlines.h"
+#include "jsopcodeinlines.h"
 
 #include "jsautooplen.h"
 
 using namespace js;
 using namespace js::mjit;
 #if defined JS_POLYIC
 using namespace js::mjit::ic;
 #endif
@@ -242,17 +243,16 @@ mjit::Compiler::generatePrologue()
         {
             stubcc.linkExitDirect(stackCheck, stubcc.masm.label());
             stubcc.call(stubs::HitStackQuota);
             stubcc.crossJump(stubcc.masm.jump(), masm.label());
         }
 
         /* Fill in the members that initCallFrameLatePrologue does. */
         masm.storeValue(UndefinedValue(), Address(JSFrameReg, JSStackFrame::offsetOfReturnValue()));
-        masm.storePtr(ImmPtr(NULL), Address(JSFrameReg, JSStackFrame::offsetOfBlockChain()));
 
         /* Set cx->fp */
         masm.loadPtr(FrameAddress(offsetof(VMFrame, cx)), Registers::ReturnReg);
 
         /* Set locals to undefined. */
         for (uint32 i = 0; i < script->nfixed; i++) {
             Address local(JSFrameReg, sizeof(JSStackFrame) + i * sizeof(Value));
             masm.storeValue(UndefinedValue(), local);
@@ -1340,16 +1340,22 @@ mjit::Compiler::generateMethod()
             frame.freeReg(reg);
             frame.push(excn);
           }
           END_CASE(JSOP_EXCEPTION)
 
           BEGIN_CASE(JSOP_LINENO)
           END_CASE(JSOP_LINENO)
 
+          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_DEFFUN)
           {
             uint32 index = fullAtomIndex(PC);
             JSFunction *inner = script->getFunction(index);
@@ -1393,17 +1399,19 @@ mjit::Compiler::generateMethod()
 
           BEGIN_CASE(JSOP_LAMBDA)
           {
             JSFunction *fun = script->getFunction(fullAtomIndex(PC));
 
             JSObjStubFun stub = stubs::Lambda;
             uint32 uses = 0;
 
-            JSOp next = JSOp(PC[JSOP_LAMBDA_LENGTH]);
+            jsbytecode *pc2 = js_AdvanceOverBlockchain(PC + JSOP_LAMBDA_LENGTH);
+            JSOp next = JSOp(*pc2);
+            
             if (next == JSOP_INITMETHOD) {
                 stub = stubs::LambdaForInit;
             } else if (next == JSOP_SETMETHOD) {
                 stub = stubs::LambdaForSet;
                 uses = 1;
             } else if (fun->joinable()) {
                 if (next == JSOP_CALL) {
                     stub = stubs::LambdaJoinableForCall;
@@ -1411,17 +1419,24 @@ mjit::Compiler::generateMethod()
                 } else if (next == JSOP_NULL) {
                     stub = stubs::LambdaJoinableForNull;
                 }
             }
 
             prepareStubCall(Uses(uses));
             masm.move(ImmPtr(fun), Registers::ArgReg1);
 
-            stubCall(stub);
+            if (stub == stubs::Lambda) {
+                stubCall(stub);
+            } else {
+                jsbytecode *savedPC = PC;
+                PC = pc2;
+                stubCall(stub);
+                PC = savedPC;
+            }
 
             frame.takeReg(Registers::ReturnReg);
             frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg);
           }
           END_CASE(JSOP_LAMBDA)
 
           BEGIN_CASE(JSOP_TRY)
             frame.syncAndForgetEverything();
@@ -4269,13 +4284,15 @@ 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 n = js_GetVariableStackUses(JSOP_LEAVEBLOCK, PC);
+    JSObject *obj = script->getObject(fullAtomIndex(PC + UINT16_LEN));
     prepareStubCall(Uses(n));
+    masm.move(ImmPtr(obj), Registers::ArgReg1);
     stubCall(stubs::LeaveBlock);
     frame.leaveBlock(n);
 }
 
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -180,17 +180,16 @@ top:
 static bool
 InlineReturn(VMFrame &f, JSBool ok, JSBool popFrame)
 {
     JSContext *cx = f.cx;
     JSStackFrame *fp = f.regs.fp;
 
     JS_ASSERT(f.fp() != f.entryFp);
 
-    JS_ASSERT(!fp->hasBlockChain());
     JS_ASSERT(!js_IsActiveWithOrBlock(cx, &fp->scopeChain(), 0));
 
     // Marker for debug support.
     if (JS_UNLIKELY(fp->hasHookData())) {
         JSInterpreterHook hook;
         JSBool status;
 
         hook = cx->debugHooks->callHook;
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -865,27 +865,19 @@ 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_FLAT_CLOSURE(fun));
 
-        /*
-         * Inline js_GetScopeChain a bit to optimize for the case of a
-         * top-level function.
-         */
-        if (!fp->hasBlockChain()) {
-            obj2 = &fp->scopeChain();
-        } else {
-            obj2 = js_GetScopeChain(cx, fp);
-            if (!obj2)
-                THROW();
-        }
+        obj2 = js_GetScopeChainFast(cx, fp, JSOP_DEFFUN, JSOP_DEFFUN_LENGTH);
+        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
      * compilation over a number of executions.  Examples include XUL scripts
      * and event handlers shared among Firefox or other Mozilla app chrome
@@ -1521,17 +1513,18 @@ stubs::DefLocalFun(VMFrame &f, JSFunctio
     JS_ASSERT(!FUN_FLAT_CLOSURE(fun));
     JSObject *obj = FUN_OBJECT(fun);
 
     if (FUN_NULL_CLOSURE(fun)) {
         obj = CloneFunctionObject(f.cx, fun, &f.fp()->scopeChain());
         if (!obj)
             THROWV(NULL);
     } else {
-        JSObject *parent = js_GetScopeChain(f.cx, f.fp());
+        JSObject *parent = js_GetScopeChainFast(f.cx, f.fp(), JSOP_DEFLOCALFUN,
+                                                JSOP_DEFLOCALFUN_LENGTH);
         if (!parent)
             THROWV(NULL);
 
         if (obj->getParent() != parent) {
             obj = CloneFunctionObject(f.cx, fun, parent);
             if (!obj)
                 THROWV(NULL);
         }
@@ -1570,30 +1563,30 @@ stubs::RegExp(VMFrame &f, JSObject *rege
     return obj;
 }
 
 JSObject * JS_FASTCALL
 stubs::LambdaForInit(VMFrame &f, JSFunction *fun)
 {
     JSObject *obj = FUN_OBJECT(fun);
     if (FUN_NULL_CLOSURE(fun) && obj->getParent() == &f.fp()->scopeChain()) {
-        fun->setMethodAtom(f.fp()->script()->getAtom(GET_SLOTNO(f.regs.pc + JSOP_LAMBDA_LENGTH)));
+        fun->setMethodAtom(f.fp()->script()->getAtom(GET_SLOTNO(f.regs.pc)));
         return obj;
     }
     return Lambda(f, fun);
 }
 
 JSObject * JS_FASTCALL
 stubs::LambdaForSet(VMFrame &f, JSFunction *fun)
 {
     JSObject *obj = FUN_OBJECT(fun);
     if (FUN_NULL_CLOSURE(fun) && obj->getParent() == &f.fp()->scopeChain()) {
         const Value &lref = f.regs.sp[-1];
         if (lref.isObject() && lref.toObject().canHaveMethodBarrier()) {
-            fun->setMethodAtom(f.fp()->script()->getAtom(GET_SLOTNO(f.regs.pc + JSOP_LAMBDA_LENGTH)));
+            fun->setMethodAtom(f.fp()->script()->getAtom(GET_SLOTNO(f.regs.pc)));
             return obj;
         }
     }
     return Lambda(f, fun);
 }
 
 JSObject * JS_FASTCALL
 stubs::LambdaJoinableForCall(VMFrame &f, JSFunction *fun)
@@ -1602,17 +1595,17 @@ stubs::LambdaJoinableForCall(VMFrame &f,
     if (FUN_NULL_CLOSURE(fun) && obj->getParent() == &f.fp()->scopeChain()) {
         /*
          * 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
          * object for identity/mutation reasons.
          */
-        int iargc = GET_ARGC(f.regs.pc + JSOP_LAMBDA_LENGTH);
+        int iargc = GET_ARGC(f.regs.pc);
 
         /*
          * Note that we have not yet pushed obj 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)];
         JSObject *callee;
@@ -1632,17 +1625,17 @@ stubs::LambdaJoinableForCall(VMFrame &f,
     return Lambda(f, fun);
 }
 
 JSObject * JS_FASTCALL
 stubs::LambdaJoinableForNull(VMFrame &f, JSFunction *fun)
 {
     JSObject *obj = FUN_OBJECT(fun);
     if (FUN_NULL_CLOSURE(fun) && obj->getParent() == &f.fp()->scopeChain()) {
-        jsbytecode *pc2 = f.regs.pc + JSOP_LAMBDA_LENGTH + JSOP_NULL_LENGTH;
+        jsbytecode *pc2 = f.regs.pc + JSOP_NULL_LENGTH;
         JSOp op2 = JSOp(*pc2);
 
         if (op2 == JSOP_CALL && GET_ARGC(pc2) == 0)
             return obj;
     }
     return Lambda(f, fun);
 }
 
@@ -1650,17 +1643,17 @@ JSObject * JS_FASTCALL
 stubs::Lambda(VMFrame &f, JSFunction *fun)
 {
     JSObject *obj = FUN_OBJECT(fun);
 
     JSObject *parent;
     if (FUN_NULL_CLOSURE(fun)) {
         parent = &f.fp()->scopeChain();
     } else {
-        parent = js_GetScopeChain(f.cx, f.fp());
+        parent = js_GetScopeChainFast(f.cx, f.fp(), JSOP_LAMBDA, JSOP_LAMBDA_LENGTH);
         if (!parent)
             THROWV(NULL);
     }
 
     obj = CloneFunctionObject(f.cx, fun, parent);
     if (!obj)
         THROWV(NULL);
 
@@ -2452,29 +2445,30 @@ stubs::ArgCnt(VMFrame &f)
     if (!js_GetArgsProperty(cx, fp, id, &f.regs.sp[-1]))
         THROW();
 }
 
 void JS_FASTCALL
 stubs::EnterBlock(VMFrame &f, JSObject *obj)
 {
     JSFrameRegs &regs = f.regs;
+#ifdef DEBUG
     JSStackFrame *fp = f.fp();
+#endif
 
     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->getParent());
 
     /*
      * 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.
      */
@@ -2486,46 +2480,41 @@ 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->getParent()) != youngestProto)
             JS_ASSERT(parent);
     }
 #endif
-
-    fp->setBlockChain(obj);
 }
 
 void JS_FASTCALL
-stubs::LeaveBlock(VMFrame &f)
+stubs::LeaveBlock(VMFrame &f, JSObject *blockChain)
 {
     JSContext *cx = f.cx;
     JSStackFrame *fp = f.fp();
 
 #ifdef DEBUG
-    JS_ASSERT(fp->blockChain()->getClass() == &js_BlockClass);
-    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, fp->blockChain());
+    JS_ASSERT(blockChain->isStaticBlock());
+    uintN blockDepth = OBJ_BLOCK_DEPTH(cx, 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() == fp->blockChain()) {
+    if (obj->getProto() == blockChain) {
         JS_ASSERT(obj->getClass() == &js_BlockClass);
         if (!js_PutBlockObject(cx, JS_TRUE))
             THROW();
     }
-
-    /* Pop the block chain, too.  */
-    fp->setBlockChain(fp->blockChain()->getParent());
 }
 
 void * JS_FASTCALL
 stubs::LookupSwitch(VMFrame &f, jsbytecode *pc)
 {
     jsbytecode *jpc = pc;
     JSScript *script = f.fp()->script();
 
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -159,17 +159,17 @@ JSObject * JS_FASTCALL Lambda(VMFrame &f
 JSObject * JS_FASTCALL LambdaForInit(VMFrame &f, JSFunction *fun);
 JSObject * JS_FASTCALL LambdaForSet(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 ArgSub(VMFrame &f, uint32 n);
 void JS_FASTCALL EnterBlock(VMFrame &f, JSObject *obj);
-void JS_FASTCALL LeaveBlock(VMFrame &f);
+void JS_FASTCALL LeaveBlock(VMFrame &f, JSObject *blockChain);
 
 void JS_FASTCALL VpInc(VMFrame &f, Value *vp);
 void JS_FASTCALL VpDec(VMFrame &f, Value *vp);
 void JS_FASTCALL DecVp(VMFrame &f, Value *vp);
 void JS_FASTCALL IncVp(VMFrame &f, Value *vp);
 void JS_FASTCALL LocalInc(VMFrame &f, uint32 slot);
 void JS_FASTCALL LocalDec(VMFrame &f, uint32 slot);
 void JS_FASTCALL IncLocal(VMFrame &f, uint32 slot);