Bug 927782 - Part 11: Optimize block scopes without aliased locals. r=luke
☠☠ backed out by 94cdaced90bf ☠ ☠
authorAndy Wingo <wingo@igalia.com>
Tue, 26 Nov 2013 12:07:02 +0100
changeset 173974 51d6617835d140affaf45ed9787d317388beb1ff
parent 173973 eed9795fa80ea7d960962db8cc7d228137cffe54
child 173975 f86d2d4cfadf457bef12afbedb8a8f82a5e69fca
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs927782
milestone28.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 927782 - Part 11: Optimize block scopes without aliased locals. r=luke
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineFrame-inl.h
js/src/jit/BaselineFrame.h
js/src/jit/IonBuilder.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jsanalyze.cpp
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/vm/Interpreter.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -136,25 +136,16 @@ EmitCheck(ExclusiveContext *cx, Bytecode
     jsbytecode dummy = 0;
     if (!bce->code().appendN(dummy, delta)) {
         js_ReportOutOfMemory(cx);
         return -1;
     }
     return offset;
 }
 
-static StaticBlockObject &
-LastBlockAdded(BytecodeEmitter *bce, jsbytecode *pc)
-{
-    DebugOnly<uint32_t> index = GET_UINT32_INDEX(pc);
-    JS_ASSERT(index < bce->objectList.length);
-    JS_ASSERT(index == bce->objectList.length - 1);
-    return bce->objectList.lastbox->object->as<StaticBlockObject>();
-}
-
 static void
 UpdateDepth(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t target)
 {
     jsbytecode *pc = bce->code(target);
     JSOp op = (JSOp) *pc;
     const JSCodeSpec *cs = &js_CodeSpec[op];
 
     if (cs->format & JOF_TMPSLOT_MASK) {
@@ -163,36 +154,18 @@ UpdateDepth(ExclusiveContext *cx, Byteco
          * Account for this in maxStackDepth separately from uses/defs here.
          */
         unsigned depth = (unsigned) bce->stackDepth +
                       ((cs->format & JOF_TMPSLOT_MASK) >> JOF_TMPSLOT_SHIFT);
         if (depth > bce->maxStackDepth)
             bce->maxStackDepth = depth;
     }
 
-    /*
-     * Specially handle any case in which StackUses or StackDefs would call
-     * NumBlockSlots, since that requires a well-formed script. This allows us
-     * to safely pass nullptr as the 'script' parameter to StackUses and
-     * StackDefs.
-     */
-    int nuses, ndefs;
-    if (op == JSOP_ENTERBLOCK) {
-        nuses = 0;
-        ndefs = LastBlockAdded(bce, pc).slotCount();
-    } else if (op == JSOP_ENTERLET0) {
-        nuses = ndefs = LastBlockAdded(bce, pc).slotCount();
-    } else if (op == JSOP_ENTERLET1) {
-        nuses = ndefs = LastBlockAdded(bce, pc).slotCount() + 1;
-    } else if (op == JSOP_ENTERLET2) {
-        nuses = ndefs = LastBlockAdded(bce, pc).slotCount() + 2;
-    } else {
-        nuses = StackUses(nullptr, pc);
-        ndefs = StackDefs(nullptr, pc);
-    }
+    int nuses = StackUses(nullptr, pc);
+    int ndefs = StackDefs(nullptr, pc);
 
     bce->stackDepth -= nuses;
     JS_ASSERT(bce->stackDepth >= 0);
     bce->stackDepth += ndefs;
     if ((unsigned)bce->stackDepth > bce->maxStackDepth)
         bce->maxStackDepth = bce->stackDepth;
 }
 
@@ -591,26 +564,29 @@ NonLocalExitScope::prepareForNonLocalJum
              */
             npops += 2;
             break;
 
           default:;
         }
 
         if (stmt->isBlockScope) {
-            FLUSH_POPS();
             JS_ASSERT(stmt->blockObj);
             StaticBlockObject &blockObj = *stmt->blockObj;
             uint32_t blockScopeIndex = stmt->blockScopeIndex;
             uint32_t scopeObjectIndex = bce->blockScopeList.findEnclosingScope(blockScopeIndex);
             if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
                 return false;
             if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset()))
                 return false;
-            EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObj.slotCount());
+            if (blockObj.needsClone()) {
+                if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0)
+                    return false;
+            }
+            npops += blockObj.slotCount();
         }
     }
 
     FLUSH_POPS();
     return true;
 
 #undef FLUSH_POPS
 }
@@ -746,44 +722,87 @@ ComputeAliasedSlots(ExclusiveContext *cx
     JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(blockObj));
 
     return true;
 }
 
 static bool
 EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmitter *bce);
 
+// ~ Block Scopes ~
+//
+// A block scope is a region of a script with an additional set of named
+// variables.  Those variables may be local and thus accessible directly from
+// the stack, or "aliased" and only accessible through the scope chain.
+//
+// A block scope may or may not have a corresponding link on the scope chain.
+// If no variable declared in the scope is "aliased", then no scope chain node
+// is allocated.
+//
+// To help debuggers, the bytecode emitter arranges to record the PC ranges
+// comprehended by a block scope, and ultimately attach them to the JSScript.
+// An element in the "block scope array" specifies the PC range, and links to a
+// StaticBlockObject in the object list of the script.  That block is linked to
+// the previous block in the scope, if any.  The static block chain at any
+// pre-retire PC can be retrieved using JSScript::getBlockScope(jsbytecode *pc).
+//
+// When PUSHBLOCKSCOPE is executed, it assumes that the block's locals are
+// already on the stack.  Initial values of "aliased" locals are copied from the
+// stack to the ClonedBlockObject, and no further access is made to the stack
+// slot.
+//
+// Likewise after leaving a POPBLOCKSCOPE, we will need to emit code to pop the
+// stack values.
+//
+// Finally, to assist the debugger, we also emit a DEBUGLEAVEBLOCK opcode before
+// POPBLOCKSCOPE in all cases -- even if the block has no aliased locals.  This
+// allows DebugScopes to invalidate any association between a debugger scope
+// object, which can proxy access to unaliased stack locals, and the actual live
+// frame.  In normal, non-debug mode, this opcode does not cause any baseline
+// code to be emitted.
+//
+// In this function "extraSlots" indicates the number of slots that are
+// "floating" on the stack above the scope's slots.  This will only be nonzero
+// in the case of for-let-in and for-let-of loops, where loop iterator state
+// floats above the block scopes.  It would be nice to fix this eventually so
+// that loop iterator state gets assigned to block-scoped fp-addressable
+// temporaries, instead of being addressable only via the sp.  This would also
+// make generators more efficient, as the loop state could be heap-allocated, so
+// that the value stack would likely be empty at yield points inside for-of /
+// for-in loops.
+//
+// Summary: Enter block scopes with EnterBlockScope.  It will emit
+// PUSHBLOCKSCOPE if needed.  Leave them with LeaveBlockScope, which will emit
+// DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE.  Pass EnterBlockScope a fresh
+// StmtInfoBCE object, and pass that same object to the corresponding
+// LeaveBlockScope.  Push locals before entering a scope, and pop them
+// afterwards.  Brush your teeth, and clean behind your ears!
+//
 static bool
 EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
                 unsigned extraSlots)
 {
     StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
 
     uint32_t scopeObjectIndex = bce->objectList.add(objbox);
     stmt->blockScopeIndex = bce->blockScopeList.length();
     if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset()))
         return false;
 
     int depth = bce->stackDepth - (blockObj.slotCount() + extraSlots);
     JS_ASSERT(depth >= 0);
     blockObj.setStackDepth(depth);
 
-    JSOp op;
-    switch (extraSlots) {
-      case 0: op = JSOP_ENTERLET0; break;
-      case 1: op = JSOP_ENTERLET1; break;
-      case 2: op = JSOP_ENTERLET2; break;
-      default: MOZ_ASSUME_UNREACHABLE("unexpected extraSlots");
-    }
-
     if (!ComputeAliasedSlots(cx, bce, blockObj))
         return false;
 
-    if (!EmitInternedObjectOp(cx, scopeObjectIndex, op, bce))
-        return false;
+    if (blockObj.needsClone()) {
+        if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce))
+            return false;
+    }
 
     PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset());
     blockObj.initEnclosingStaticScope(EnclosingStaticScope(bce));
     FinishPushBlockScope(bce, stmt, blockObj);
 
     JS_ASSERT(stmt->isBlockScope);
 
     return true;
@@ -802,43 +821,45 @@ PopStatementBCE(ExclusiveContext *cx, By
         return false;
     }
 
     FinishPopStatement(bce);
     return true;
 }
 
 static bool
-LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op)
+LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce)
 {
     StmtInfoBCE *stmt = bce->topStmt;
     JS_ASSERT(stmt->isBlockScope);
     uint32_t blockScopeIndex = stmt->blockScopeIndex;
 
 #ifdef DEBUG
     JS_ASSERT(bce->blockScopeList.list[blockScopeIndex].length == 0);
     uint32_t blockObjIndex = bce->blockScopeList.list[blockScopeIndex].index;
     ObjectBox *blockObjBox = bce->objectList.find(blockObjIndex);
     StaticBlockObject *blockObj = &blockObjBox->object->as<StaticBlockObject>();
     JS_ASSERT(stmt->blockObj == blockObj);
     JS_ASSERT(blockObj == bce->blockChain);
 #endif
 
-    uint32_t slotCount = bce->blockChain->slotCount();
+    bool blockOnChain = bce->blockChain->needsClone();
 
     if (!PopStatementBCE(cx, bce))
         return false;
 
     if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
         return false;
 
     bce->blockScopeList.recordEnd(blockScopeIndex, bce->offset());
 
-    JS_ASSERT(op == JSOP_LEAVEBLOCK || op == JSOP_LEAVEBLOCKEXPR);
-    EMIT_UINT16_IMM_OP(op, slotCount);
+    if (blockOnChain) {
+        if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0)
+            return false;
+    }
 
     return true;
 }
 
 static bool
 EmitIndex32(ExclusiveContext *cx, JSOp op, uint32_t index, BytecodeEmitter *bce)
 {
     const size_t len = 1 + UINT32_INDEX_LEN;
@@ -2654,20 +2675,20 @@ EmitSwitch(ExclusiveContext *cx, Bytecod
         for (uint32_t i = 0; i < tableLength; i++) {
             pn3 = table[i];
             off = pn3 ? pn3->pn_offset - top : 0;
             SET_JUMP_OFFSET(pc, off);
             pc += JUMP_OFFSET_LEN;
         }
     }
 
-
     if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) {
-        if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
-            return false;
+        if (!LeaveBlockScope(cx, bce))
+            return false;
+        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount());
     } else {
         if (!PopStatementBCE(cx, bce))
             return false;
     }
 
     return true;
 }
 
@@ -3953,32 +3974,35 @@ EmitTry(ExclusiveContext *cx, BytecodeEm
         return false;
 
     ptrdiff_t tryEnd = bce->offset();
 
     // If this try has a catch block, emit it.
     if (ParseNode *pn2 = pn->pn_kid2) {
         // The emitted code for a catch block looks like:
         //
-        // enterblock
+        // undefined...                 as many as there are locals in the catch block
+        // [pushblockscope]             only if any local aliased
         // exception
         // if there is a catchguard:
         //   dup
         // setlocal 0; pop              assign or possibly destructure exception
         // if there is a catchguard:
         //   < catchguard code >
         //   ifne POST
+        //   debugleaveblock
+        //   [popblockscope]            only if any local aliased
+        //   popnv <num block locals>   leave exception on top
         //   throwing                   pop exception to cx->exception
-        //   debugleaveblock
-        //   leaveblock
         //   goto <next catch block>
         //   POST: pop
         // < catch block contents >
         // debugleaveblock
-        // leaveblock
+        // [popblockscope]              only if any local aliased
+        // popn <num block locals>
         // goto <end of catch blocks>   non-local; finally applies
         //
         // If there's no catch block without a catchguard, the last <next catch
         // block> points to rethrow code.  This code will [gosub] to the finally
         // code if appropriate, and is also used for the catch-all trynote for
         // capturing exceptions thrown from catch{} blocks.
         //
         for (ParseNode *pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
@@ -4164,21 +4188,23 @@ EmitIf(ExclusiveContext *cx, BytecodeEmi
  *  evaluate b        +1
  *  dup               +1
  *  destructure y
  *  pick 1
  *  dup               +1
  *  destructure z
  *  pick 1
  *  pop               -1
- *  enterlet0
+ *  pushblockscope (if needed)
  *  evaluate e        +1
- *  leaveblockexpr    -3
+ *  debugleaveblock
+ *  popblockscope (if needed)
+ *  popnv 3           -3
  *
- * Note that, since enterlet0 simply changes fp->blockChain and does not
+ * Note that, since pushblockscope simply changes fp->scopeChain and does not
  * otherwise touch the stack, evaluation of the let-var initializers must leave
  * the initial value in the let-var's future slot.
  */
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
  * the comment on EmitSwitch.
  */
 MOZ_NEVER_INLINE static bool
@@ -4206,31 +4232,35 @@ EmitLet(ExclusiveContext *cx, BytecodeEm
 
     StmtInfoBCE stmtInfo(cx);
     if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, 0))
         return false;
 
     if (!EmitTree(cx, bce, letBody->pn_expr))
         return false;
 
-    if (!LeaveBlockScope(cx, bce, letBody->getOp()))
-        return false;
+    if (!LeaveBlockScope(cx, bce))
+        return false;
+
+    JSOp leaveOp = letBody->getOp();
+    JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV);
+    EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount());
 
     return true;
 }
 
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
  * the comment on EmitSwitch.
  */
 MOZ_NEVER_INLINE static bool
 EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
-    JS_ASSERT(pn->getOp() == JSOP_LEAVEBLOCK);
+    JS_ASSERT(pn->getOp() == JSOP_POPN);
 
     StmtInfoBCE stmtInfo(cx);
     ObjectBox *objbox = pn->pn_objbox;
     StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
     size_t slots = blockObj.slotCount();
 
     for (size_t n = 0; n < slots; ++n) {
         if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
@@ -4238,18 +4268,20 @@ EmitLexicalScope(ExclusiveContext *cx, B
     }
 
     if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0))
         return false;
 
     if (!EmitTree(cx, bce, pn->pn_expr))
         return false;
 
-    if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
-        return false;
+    if (!LeaveBlockScope(cx, bce))
+        return false;
+
+    EMIT_UINT16_IMM_OP(JSOP_POPN, slots);
 
     return true;
 }
 
 static bool
 EmitWith(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfoBCE stmtInfo(cx);
@@ -4410,23 +4442,23 @@ EmitForOf(ExclusiveContext *cx, Bytecode
     // Let Ion know where the closing jump of this loop is.
     if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, beq - jmp))
         return false;
 
     // Fixup breaks and continues.
     if (!PopStatementBCE(cx, bce))
         return false;
 
-    // Pop result and iter.
-    EMIT_UINT16_IMM_OP(JSOP_POPN, 2);
-
     if (letDecl) {
-        if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
-            return false;
-    }
+        if (!LeaveBlockScope(cx, bce))
+            return false;
+    }
+
+    // Pop result, iter, and slots from the lexical block (if any).
+    EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2);
 
     return true;
 }
 
 static bool
 EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
     ParseNode *forHead = pn->pn_left;
@@ -4438,30 +4470,34 @@ EmitForIn(ExclusiveContext *cx, Bytecode
 
     Rooted<StaticBlockObject*>
         blockObj(cx, letDecl ? &pn1->pn_objbox->object->as<StaticBlockObject>() : nullptr);
     uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0;
 
     if (letDecl) {
         /*
          * The let's slot(s) will be under the iterator, but the block must not
-         * be entered (i.e. fp->blockChain set) until after evaluating the rhs.
-         * Thus, push to reserve space and enterblock after. The same argument
-         * applies when leaving the loop. Thus, a for-let-in loop looks like:
+         * be entered until after evaluating the rhs.  So, we reserve space for
+         * the block scope now, and only push the block onto the scope chain
+         * later.  Thus, a for-let-in loop looks like:
          *
          *   push x N
          *   eval rhs
          *   iter
-         *   enterlet1
+         *   pushblockscope (if needed)
          *   goto
          *     ... loop body
          *   ifne
-         *   leaveforinlet
+         *   debugleaveblock
+         *   popblockscope (if needed)
          *   enditer
          *   popn(N)
+         *
+         * Note that pushblockscope and popblockscope only get emitted if some
+         * of the variables in the block are captured.
          */
         for (uint32_t i = 0; i < blockObjCount; ++i) {
             if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
                 return false;
         }
     }
 
     /*
@@ -4570,18 +4606,19 @@ EmitForIn(ExclusiveContext *cx, Bytecode
         return false;
 
     if (!bce->tryNoteList.append(JSTRY_ITER, bce->stackDepth, top, bce->offset()))
         return false;
     if (Emit1(cx, bce, JSOP_ENDITER) < 0)
         return false;
 
     if (letDecl) {
-        if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
-            return false;
+        if (!LeaveBlockScope(cx, bce))
+            return false;
+        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount);
     }
 
     return true;
 }
 
 static bool
 EmitNormalFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -598,19 +598,19 @@ FullParseHandler::addCatchBlock(ParseNod
     catchList->append(letBlock);
     letBlock->pn_expr = catchpn;
     return true;
 }
 
 inline void
 FullParseHandler::setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr)
 {
-    JS_ASSERT(block->isOp(JSOP_LEAVEBLOCK));
+    JS_ASSERT(block->isOp(JSOP_POPN));
     if (leaveBlockExpr)
-        block->setOp(JSOP_LEAVEBLOCKEXPR);
+        block->setOp(JSOP_POPNV);
     block->pn_expr = kid;
 }
 
 inline void
 FullParseHandler::setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *defaultValue)
 {
     ParseNode *arg = funcpn->pn_body->last();
     arg->pn_dflags |= PND_DEFAULT;
@@ -632,17 +632,17 @@ FullParseHandler::newFunctionDefinition(
 
 inline ParseNode *
 FullParseHandler::newLexicalScope(ObjectBox *blockbox)
 {
     ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, this);
     if (!pn)
         return nullptr;
 
-    pn->setOp(JSOP_LEAVEBLOCK);
+    pn->setOp(JSOP_POPN);
     pn->pn_objbox = blockbox;
     pn->pn_cookie.makeFree();
     pn->pn_dflags = 0;
     return pn;
 }
 
 inline bool
 FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op)
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -402,17 +402,17 @@ enum ParseNodeKind
  * PNK_NAME     name        If pn_used, PNK_NAME uses the lexdef member instead
  *                          of the expr member it overlays
  * PNK_NUMBER   dval        pn_dval: double value of numeric literal
  * PNK_TRUE,    nullary     pn_op: JSOp bytecode
  * PNK_FALSE,
  * PNK_NULL,
  * PNK_THIS
  *
- * PNK_LEXICALSCOPE name    pn_op: JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR
+ * PNK_LEXICALSCOPE name    pn_op: JSOP_POPN or JSOP_POPNV
  *                          pn_objbox: block object in ObjectBox holder
  *                          pn_expr: block body
  * PNK_ARRAYCOMP    list    pn_count: 1
  *                          pn_head: list of 1 element, which is block
  *                          enclosing for loop(s) and optionally
  *                          if-guarded PNK_ARRAYPUSH
  * PNK_ARRAYPUSH    unary   pn_op: JSOP_ARRAYCOMP
  *                          pn_kid: array comprehension expression
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3226,17 +3226,16 @@ template <>
 ParseNode *
 Parser<FullParseHandler>::pushLetScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt)
 {
     JS_ASSERT(blockObj);
     ParseNode *pn = pushLexicalScope(blockObj, stmt);
     if (!pn)
         return null();
 
-    /* Tell codegen to emit JSOP_ENTERLETx (not JSOP_ENTERBLOCK). */
     pn->pn_dflags |= PND_LET;
 
     /* Populate the new scope with decls found in the head with updated blockid. */
     if (!ForEachLetDef(tokenStream, pc, blockObj, AddLetDecl(stmt->blockid)))
         return null();
 
     return pn;
 }
@@ -3596,17 +3595,17 @@ Parser<FullParseHandler>::letDeclaration
             JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
 #endif
 
             /* Create a new lexical scope node for these statements. */
             ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, &handler);
             if (!pn1)
                 return null();
 
-            pn1->setOp(JSOP_LEAVEBLOCK);
+            pn1->setOp(JSOP_POPN);
             pn1->pn_pos = pc->blockNode->pn_pos;
             pn1->pn_objbox = blockbox;
             pn1->pn_expr = pc->blockNode;
             pn1->pn_blockid = pc->blockNode->pn_blockid;
             pc->blockNode = pn1;
         }
 
         pn = variables(PNK_LET, nullptr, pc->blockChain, HoistVars);
@@ -3632,18 +3631,18 @@ Parser<FullParseHandler>::letStatement()
 {
     handler.disableSyntaxParser();
 
     /* Check for a let statement or let expression. */
     ParseNode *pn;
     if (tokenStream.peekToken() == TOK_LP) {
         pn = letBlock(LetStatement);
         JS_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI));
-        JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_LEAVEBLOCK,
-                     pn->isOp(JSOP_NOP));
+        JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_POPNV,
+                     pn->pn_expr->isOp(JSOP_POPN));
     } else 
         pn = letDeclaration();
     return pn;
 }
 
 template <>
 SyntaxParseHandler::Node
 Parser<SyntaxParseHandler>::letStatement()
@@ -6713,26 +6712,31 @@ Parser<ParseHandler>::arrayInitializer()
          *
          * where array is a nameless block-local variable. The "roughly"
          * means that an implementation may optimize away the array.push.
          * An array comprehension opens exactly one block scope, no matter
          * how many for heads it contains.
          *
          * Each let () {...} or for (let ...) ... compiles to:
          *
-         *   JSOP_ENTERBLOCK <o> ... JSOP_LEAVEBLOCK <n>
+         *   JSOP_PUSHN <N>            // Push space for block-scoped locals.
+         *   (JSOP_PUSHBLOCKSCOPE <O>) // If a local is aliased, push on scope
+         *                             // chain.
+         *   ...
+         *   JSOP_DEBUGLEAVEBLOCK      // Invalidate any DebugScope proxies.
+         *   JSOP_POPBLOCKSCOPE?       // Pop off scope chain, if needed.
+         *   JSOP_POPN <N>             // Pop space for block-scoped locals.
          *
          * where <o> is a literal object representing the block scope,
          * with <n> properties, naming each var declared in the block.
          *
-         * Each var declaration in a let-block binds a name in <o> at
-         * compile time, and allocates a slot on the operand stack at
-         * runtime via JSOP_ENTERBLOCK. A block-local var is accessed by
-         * the JSOP_GETLOCAL and JSOP_SETLOCAL ops. These ops have an
-         * immediate operand, the local slot's stack index from fp->spbase.
+         * Each var declaration in a let-block binds a name in <o> at compile
+         * time. A block-local var is accessed by the JSOP_GETLOCAL and
+         * JSOP_SETLOCAL ops. These ops have an immediate operand, the local
+         * slot's stack index from fp->spbase.
          *
          * The array comprehension iteration step, array.push(i * j) in
          * the example above, is done by <i * j>; JSOP_ARRAYPUSH <array>,
          * where <array> is the index of array's stack slot.
          */
         if (index == 0 && !spread && tokenStream.matchToken(TOK_FOR) && missingTrailingComma) {
             if (!arrayInitializerComprehensionTail(literal))
                 return null();
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -856,16 +856,25 @@ BaselineCompiler::emit_JSOP_POP()
 bool
 BaselineCompiler::emit_JSOP_POPN()
 {
     frame.popn(GET_UINT16(pc));
     return true;
 }
 
 bool
+BaselineCompiler::emit_JSOP_POPNV()
+{
+    frame.popRegsAndSync(1);
+    frame.popn(GET_UINT16(pc));
+    frame.push(R0);
+    return true;
+}
+
+bool
 BaselineCompiler::emit_JSOP_DUP()
 {
     // Keep top stack value in R0, sync the rest so that we can use R1. We use
     // separate registers because every register can be used by at most one
     // StackValue.
     frame.popRegsAndSync(1);
     masm.moveValue(R0, R1);
 
@@ -2544,65 +2553,47 @@ bool
 BaselineCompiler::emit_JSOP_RETSUB()
 {
     frame.popRegsAndSync(2);
 
     ICRetSub_Fallback::Compiler stubCompiler(cx);
     return emitOpIC(stubCompiler.getStub(&stubSpace_));
 }
 
-typedef bool (*EnterBlockFn)(JSContext *, BaselineFrame *, Handle<StaticBlockObject *>);
-static const VMFunction EnterBlockInfo = FunctionInfo<EnterBlockFn>(jit::EnterBlock);
+typedef bool (*PushBlockScopeFn)(JSContext *, BaselineFrame *, Handle<StaticBlockObject *>);
+static const VMFunction PushBlockScopeInfo = FunctionInfo<PushBlockScopeFn>(jit::PushBlockScope);
 
 bool
-BaselineCompiler::emitEnterBlock()
+BaselineCompiler::emit_JSOP_PUSHBLOCKSCOPE()
 {
     StaticBlockObject &blockObj = script->getObject(pc)->as<StaticBlockObject>();
 
-    if (JSOp(*pc) == JSOP_ENTERBLOCK) {
-        for (size_t i = 0; i < blockObj.slotCount(); i++)
-            frame.push(UndefinedValue());
-
-        // Pushed values will be accessed using GETLOCAL and SETLOCAL, so ensure
-        // they are synced.
-        frame.syncStack(0);
-    }
-
     // Call a stub to push the block on the block chain.
     prepareVMCall();
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     pushArg(ImmGCPtr(&blockObj));
     pushArg(R0.scratchReg());
 
-    return callVM(EnterBlockInfo);
+    return callVM(PushBlockScopeInfo);
 }
 
-bool
-BaselineCompiler::emit_JSOP_ENTERBLOCK()
-{
-    return emitEnterBlock();
-}
+typedef bool (*PopBlockScopeFn)(JSContext *, BaselineFrame *);
+static const VMFunction PopBlockScopeInfo = FunctionInfo<PopBlockScopeFn>(jit::PopBlockScope);
 
 bool
-BaselineCompiler::emit_JSOP_ENTERLET0()
-{
-    return emitEnterBlock();
-}
-
-bool
-BaselineCompiler::emit_JSOP_ENTERLET1()
+BaselineCompiler::emit_JSOP_POPBLOCKSCOPE()
 {
-    return emitEnterBlock();
-}
-
-bool
-BaselineCompiler::emit_JSOP_ENTERLET2()
-{
-    return emitEnterBlock();
+    // Call a stub to pop the block from the block chain.
+    prepareVMCall();
+
+    masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
+    pushArg(R0.scratchReg());
+
+    return callVM(PopBlockScopeInfo);
 }
 
 typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *, jsbytecode *);
 static const VMFunction DebugLeaveBlockInfo = FunctionInfo<DebugLeaveBlockFn>(jit::DebugLeaveBlock);
 
 bool
 BaselineCompiler::emit_JSOP_DEBUGLEAVEBLOCK()
 {
@@ -2612,66 +2603,16 @@ BaselineCompiler::emit_JSOP_DEBUGLEAVEBL
     prepareVMCall();
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
     pushArg(ImmPtr(pc));
     pushArg(R0.scratchReg());
 
     return callVM(DebugLeaveBlockInfo);
 }
 
-typedef bool (*LeaveBlockFn)(JSContext *, BaselineFrame *);
-static const VMFunction LeaveBlockInfo = FunctionInfo<LeaveBlockFn>(jit::LeaveBlock);
-
-bool
-BaselineCompiler::emitLeaveBlock()
-{
-    // Call a stub to pop the block from the block chain.
-    prepareVMCall();
-
-    masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
-    pushArg(R0.scratchReg());
-
-    return callVM(LeaveBlockInfo);
-}
-
-bool
-BaselineCompiler::emit_JSOP_LEAVEBLOCK()
-{
-    if (!emitLeaveBlock())
-        return false;
-
-    // Pop slots pushed by JSOP_ENTERBLOCK.
-    frame.popn(GET_UINT16(pc));
-    return true;
-}
-
-bool
-BaselineCompiler::emit_JSOP_LEAVEBLOCKEXPR()
-{
-    if (!emitLeaveBlock())
-        return false;
-
-    // Pop slots pushed by JSOP_ENTERBLOCK, but leave the topmost value
-    // on the stack.
-    frame.popRegsAndSync(1);
-    frame.popn(GET_UINT16(pc));
-    frame.push(R0);
-    return true;
-}
-
-bool
-BaselineCompiler::emit_JSOP_LEAVEFORLETIN()
-{
-    if (!emitLeaveBlock())
-        return false;
-
-    // Another op will pop the slots (after the enditer).
-    return true;
-}
-
 typedef bool (*GetAndClearExceptionFn)(JSContext *, MutableHandleValue);
 static const VMFunction GetAndClearExceptionInfo =
     FunctionInfo<GetAndClearExceptionFn>(GetAndClearException);
 
 bool
 BaselineCompiler::emit_JSOP_EXCEPTION()
 {
     prepareVMCall();
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -22,16 +22,17 @@ namespace js {
 namespace jit {
 
 #define OPCODE_LIST(_)         \
     _(JSOP_NOP)                \
     _(JSOP_LABEL)              \
     _(JSOP_NOTEARG)            \
     _(JSOP_POP)                \
     _(JSOP_POPN)               \
+    _(JSOP_POPNV)              \
     _(JSOP_DUP)                \
     _(JSOP_DUP2)               \
     _(JSOP_SWAP)               \
     _(JSOP_PICK)               \
     _(JSOP_GOTO)               \
     _(JSOP_IFEQ)               \
     _(JSOP_IFNE)               \
     _(JSOP_AND)                \
@@ -140,23 +141,18 @@ namespace jit {
     _(JSOP_TYPEOF)             \
     _(JSOP_TYPEOFEXPR)         \
     _(JSOP_SETCALL)            \
     _(JSOP_THROW)              \
     _(JSOP_TRY)                \
     _(JSOP_FINALLY)            \
     _(JSOP_GOSUB)              \
     _(JSOP_RETSUB)             \
-    _(JSOP_ENTERBLOCK)         \
-    _(JSOP_ENTERLET0)          \
-    _(JSOP_ENTERLET1)          \
-    _(JSOP_ENTERLET2)          \
-    _(JSOP_LEAVEBLOCK)         \
-    _(JSOP_LEAVEBLOCKEXPR)     \
-    _(JSOP_LEAVEFORLETIN)      \
+    _(JSOP_PUSHBLOCKSCOPE)     \
+    _(JSOP_POPBLOCKSCOPE)      \
     _(JSOP_DEBUGLEAVEBLOCK)    \
     _(JSOP_EXCEPTION)          \
     _(JSOP_DEBUGGER)           \
     _(JSOP_ARGUMENTS)          \
     _(JSOP_RUNONCE)            \
     _(JSOP_REST)               \
     _(JSOP_TOID)               \
     _(JSOP_TABLESWITCH)        \
@@ -250,19 +246,16 @@ class BaselineCompiler : public Baseline
     bool emitAndOr(bool branchIfTrue);
     bool emitCall();
 
     bool emitInitPropGetterSetter();
     bool emitInitElemGetterSetter();
 
     bool emitFormalArgAccess(uint32_t arg, bool get);
 
-    bool emitEnterBlock();
-    bool emitLeaveBlock();
-
     bool addPCMappingEntry(bool addIndexEntry);
 
     void getScopeCoordinateObject(Register reg);
     Address getScopeCoordinateAddressFromObject(Register objReg, Register reg);
     Address getScopeCoordinateAddress(Register reg);
 };
 
 } // namespace jit
--- a/js/src/jit/BaselineFrame-inl.h
+++ b/js/src/jit/BaselineFrame-inl.h
@@ -31,41 +31,32 @@ inline void
 BaselineFrame::popOffScopeChain()
 {
     scopeChain_ = &scopeChain_->as<ScopeObject>().enclosingScope();
 }
 
 inline bool
 BaselineFrame::pushBlock(JSContext *cx, Handle<StaticBlockObject *> block)
 {
-    JS_ASSERT_IF(hasBlockChain(), blockChain() == *block->enclosingBlock());
+    JS_ASSERT(block->needsClone());
 
-    if (block->needsClone()) {
-        ClonedBlockObject *clone = ClonedBlockObject::create(cx, block, this);
-        if (!clone)
-            return false;
+    ClonedBlockObject *clone = ClonedBlockObject::create(cx, block, this);
+    if (!clone)
+        return false;
+    pushOnScopeChain(*clone);
 
-        pushOnScopeChain(*clone);
-    }
-
-    setBlockChain(*block);
     return true;
 }
 
 inline void
 BaselineFrame::popBlock(JSContext *cx)
 {
-    JS_ASSERT(hasBlockChain());
+    JS_ASSERT(scopeChain_->is<ClonedBlockObject>());
 
-    if (blockChain_->needsClone()) {
-        JS_ASSERT(scopeChain_->as<ClonedBlockObject>().staticBlock() == *blockChain_);
-        popOffScopeChain();
-    }
-
-    setBlockChain(*blockChain_->enclosingBlock());
+    popOffScopeChain();
 }
 
 inline CallObject &
 BaselineFrame::callObj() const
 {
     JS_ASSERT(hasCallObj());
     JS_ASSERT(fun()->isHeavyweight());
 
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -164,17 +164,17 @@ class BaselineFrame
         JS_ASSERT(i < numActualArgs());
         JS_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals());
         JS_ASSERT_IF(checkAliasing && i < numFormalArgs(), !script()->formalIsAliased(i));
         return argv()[i];
     }
 
     Value &unaliasedLocal(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const {
 #ifdef DEBUG
-        CheckLocalUnaliased(checkAliasing, script(), maybeBlockChain(), i);
+        CheckLocalUnaliased(checkAliasing, script(), i);
 #endif
         return *valueSlot(i);
     }
 
     unsigned numActualArgs() const {
         return *(size_t *)(reinterpret_cast<const uint8_t *>(this) +
                              BaselineFrame::Size() +
                              offsetOfNumActualArgs());
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1247,16 +1247,17 @@ IonBuilder::traverseBytecode()
         for (size_t i = 0; i < popped.length(); i++) {
             // Call instructions can discard PassArg instructions. Ignore them.
             if (popped[i]->isPassArg() && !popped[i]->hasUses())
                 continue;
 
             switch (op) {
               case JSOP_POP:
               case JSOP_POPN:
+              case JSOP_POPNV:
               case JSOP_DUP:
               case JSOP_DUP2:
               case JSOP_PICK:
               case JSOP_SWAP:
               case JSOP_SETARG:
               case JSOP_SETLOCAL:
               case JSOP_SETRVAL:
               case JSOP_VOID:
@@ -1499,16 +1500,25 @@ IonBuilder::inspectOpcode(JSOp op)
             return true;
         return maybeInsertResume();
 
       case JSOP_POPN:
         for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++)
             current->pop();
         return true;
 
+      case JSOP_POPNV:
+      {
+        MDefinition *mins = current->pop();
+        for (uint32_t i = 0, n = GET_UINT16(pc); i < n; i++)
+            current->pop();
+        current->push(mins);
+        return true;
+      }
+
       case JSOP_NEWINIT:
         if (GET_UINT8(pc) == JSProto_Array)
             return jsop_newarray(0);
         return jsop_newobject();
 
       case JSOP_NEWARRAY:
         return jsop_newarray(GET_UINT24(pc));
 
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -894,23 +894,23 @@ OnDebuggerStatement(JSContext *cx, Basel
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid trap status");
     }
 }
 
 bool
-EnterBlock(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block)
+PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block)
 {
     return frame->pushBlock(cx, block);
 }
 
 bool
-LeaveBlock(JSContext *cx, BaselineFrame *frame)
+PopBlockScope(JSContext *cx, BaselineFrame *frame)
 {
     frame->popBlock(cx);
     return true;
 }
 
 bool
 DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc)
 {
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -647,18 +647,18 @@ bool HeavyweightFunPrologue(JSContext *c
 bool NewArgumentsObject(JSContext *cx, BaselineFrame *frame, MutableHandleValue res);
 
 JSObject *InitRestParameter(JSContext *cx, uint32_t length, Value *rest, HandleObject templateObj,
                             HandleObject res);
 
 bool HandleDebugTrap(JSContext *cx, BaselineFrame *frame, uint8_t *retAddr, bool *mustReturn);
 bool OnDebuggerStatement(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn);
 
-bool EnterBlock(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block);
-bool LeaveBlock(JSContext *cx, BaselineFrame *frame);
+bool PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block);
+bool PopBlockScope(JSContext *cx, BaselineFrame *frame);
 bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc);
 
 bool InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame,
                              uint32_t numStackValues);
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type,
                                 HandleObject owner, int32_t offset);
 
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -248,17 +248,16 @@ ScriptAnalysis::analyzeBytecode(JSContex
           case JSOP_DEFCONST:
           case JSOP_SETCONST:
             usesScopeChain_ = true; // Requires access to VarObj via ScopeChain.
             canTrackVars = false;
             break;
 
           case JSOP_EVAL:
           case JSOP_SPREADEVAL:
-          case JSOP_ENTERLET2:
           case JSOP_ENTERWITH:
             canTrackVars = false;
             break;
 
           case JSOP_TABLESWITCH: {
             unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc);
             jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
             int32_t low = GET_JUMP_OFFSET(pc2);
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -112,72 +112,45 @@ js_GetVariableBytecodeLength(jsbytecode 
         unsigned ncases = unsigned(high - low + 1);
         return 1 + 3 * JUMP_OFFSET_LEN + ncases * JUMP_OFFSET_LEN;
       }
       default:
         MOZ_ASSUME_UNREACHABLE("Unexpected op");
     }
 }
 
-static uint32_t
-NumBlockSlots(JSScript *script, jsbytecode *pc)
-{
-    JS_ASSERT(*pc == JSOP_ENTERBLOCK ||
-              *pc == JSOP_ENTERLET0 || *pc == JSOP_ENTERLET1 || *pc == JSOP_ENTERLET2);
-    JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET0_LENGTH);
-    JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET1_LENGTH);
-    JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET2_LENGTH);
-
-    return script->getObject(GET_UINT32_INDEX(pc))->as<StaticBlockObject>().propertyCountForCompilation();
-}
-
 unsigned
 js::StackUses(JSScript *script, jsbytecode *pc)
 {
     JSOp op = (JSOp) *pc;
     const JSCodeSpec &cs = js_CodeSpec[op];
     if (cs.nuses >= 0)
         return cs.nuses;
 
     JS_ASSERT(js_CodeSpec[op].nuses == -1);
     switch (op) {
       case JSOP_POPN:
         return GET_UINT16(pc);
-      case JSOP_LEAVEBLOCK:
-        return GET_UINT16(pc);
-      case JSOP_LEAVEBLOCKEXPR:
+      case JSOP_POPNV:
         return GET_UINT16(pc) + 1;
-      case JSOP_ENTERLET0:
-        return NumBlockSlots(script, pc);
-      case JSOP_ENTERLET1:
-        return NumBlockSlots(script, pc) + 1;
-      case JSOP_ENTERLET2:
-        return NumBlockSlots(script, pc) + 2;
       default:
         /* stack: fun, this, [argc arguments] */
         JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
                   op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
         return 2 + GET_ARGC(pc);
     }
 }
 
 unsigned
 js::StackDefs(JSScript *script, jsbytecode *pc)
 {
     JSOp op = (JSOp) *pc;
     const JSCodeSpec &cs = js_CodeSpec[op];
-    if (cs.ndefs >= 0)
-        return cs.ndefs;
-
-    uint32_t n = NumBlockSlots(script, pc);
-    if (op == JSOP_ENTERLET1)
-        return n + 1;
-    if (op == JSOP_ENTERLET2)
-        return n + 2;
-    return n;
+    JS_ASSERT (cs.ndefs >= 0);
+    return cs.ndefs;
 }
 
 static const char * const countBaseNames[] = {
     "interp"
 };
 
 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT);
 
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -92,17 +92,19 @@ OPDEF(JSOP_VOID,      40, js_void_str,  
 
 /* spreadcall variant of JSOP_CALL */
 OPDEF(JSOP_SPREADCALL,41, "spreadcall", NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
 /* spreadcall variant of JSOP_NEW */
 OPDEF(JSOP_SPREADNEW, 42, "spreadnew",  NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
 /* spreadcall variant of JSOP_EVAL */
 OPDEF(JSOP_SPREADEVAL,43, "spreadeval", NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET)
 
-OPDEF(JSOP_UNUSED44,  44, "unused44",   NULL,         1,  0,  0,  JOF_BYTE)
+/* Pop N values, preserving top value.  */
+OPDEF(JSOP_POPNV,     44, "popnv",      NULL,         3, -1,  1,  JOF_UINT16)
+
 OPDEF(JSOP_UNUSED45,  45, "unused45",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED46,  46, "unused46",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED47,  47, "unused47",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED48,  48, "unused48",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED49,  49, "unused49",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED50,  50, "unused50",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED51,  51, "unused51",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED52,  52, "unused52",   NULL,         1,  0,  0,  JOF_BYTE)
@@ -215,19 +217,17 @@ OPDEF(JSOP_INITPROP_GETTER,  97, "initpr
 OPDEF(JSOP_INITPROP_SETTER,  98, "initprop_setter",   NULL, 5,  2,  1, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING)
 OPDEF(JSOP_INITELEM_GETTER,  99, "initelem_getter",   NULL, 1,  3,  1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING)
 OPDEF(JSOP_INITELEM_SETTER, 100, "initelem_setter",   NULL, 1,  3,  1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING)
 
 OPDEF(JSOP_UNUSED101,  101, "unused101",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED102,  102, "unused102",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED103,  103, "unused103",   NULL,         1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED104,  104, "unused104",   NULL,         1,  0,  0,  JOF_BYTE)
-
-/* Leave a for-let-in block leaving its storage pushed (to be popped after enditer). */
-OPDEF(JSOP_LEAVEFORLETIN, 105,"leaveforletin",NULL,   1,  0,  0,  JOF_BYTE)
+OPDEF(JSOP_UNUSED105,  105, "unused105",   NULL,         1,  0,  0,  JOF_BYTE)
 
 /* The argument is the offset to the next statement and is used by IonMonkey. */
 OPDEF(JSOP_LABEL,     106,"label",     NULL,          5,  0,  0,  JOF_JUMP)
 
 OPDEF(JSOP_UNUSED107, 107,"unused107",  NULL,         1,  0,  0,  JOF_BYTE)
 
 /* Like JSOP_FUNAPPLY but for f.call instead of f.apply. */
 OPDEF(JSOP_FUNCALL,   108,"funcall",    NULL,         3, -1,  1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET)
@@ -395,23 +395,19 @@ OPDEF(JSOP_UNUSED178,     178,"unused178
 OPDEF(JSOP_UNUSED179,     179,"unused179",  NULL,     1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED180,     180,"unused180",  NULL,     1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED181,     181,"unused181",  NULL,     1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED182,     182,"unused182",  NULL,     1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED183,     183,"unused183",  NULL,     1,  0,  0,  JOF_BYTE)
 
 OPDEF(JSOP_CALLPROP,      184,"callprop",   NULL,     5,  1,  1, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_TMPSLOT3)
 
-/* Enter a let block/expr whose slots are at the top of the stack. */
-OPDEF(JSOP_ENTERLET0,     185,"enterlet0",  NULL,     5, -1, -1,  JOF_OBJECT)
-
-/* Enter a let block/expr whose slots are 1 below the top of the stack. */
-OPDEF(JSOP_ENTERLET1,     186,"enterlet1",  NULL,     5, -1, -1,  JOF_OBJECT)
-/* Enter a let block/expr whose slots are 2 below the top of the stack. */
-OPDEF(JSOP_ENTERLET2,     187,"enterlet2",  NULL,     5, -1, -1,  JOF_OBJECT)
+OPDEF(JSOP_UNUSED185,     185,"unused185",  NULL,     1,  0,  0,  JOF_BYTE)
+OPDEF(JSOP_UNUSED186,     186,"unused186",  NULL,     1,  0,  0,  JOF_BYTE)
+OPDEF(JSOP_UNUSED187,     187,"unused187",  NULL,     1,  0,  0,  JOF_BYTE)
 
 /*
  * Opcode to hold 24-bit immediate integer operands.
  */
 OPDEF(JSOP_UINT24,        188,"uint24",     NULL,     4,  0,  1, JOF_UINT24)
 
 OPDEF(JSOP_UNUSED189,     189,"unused189",   NULL,    1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED190,     190,"unused190",   NULL,    1,  0,  0,  JOF_BYTE)
@@ -433,18 +429,18 @@ OPDEF(JSOP_UNUSED196,     196,"unused196
 /*
  * Specialized JSOP_TYPEOF to avoid reporting undefined for typeof(0, undef).
  */
 OPDEF(JSOP_TYPEOFEXPR,    197,"typeofexpr",  NULL,    1,  1,  1, JOF_BYTE|JOF_DETECTING)
 
 /*
  * Block-local scope support.
  */
-OPDEF(JSOP_ENTERBLOCK,    198,"enterblock",  NULL,    5,  0, -1,  JOF_OBJECT)
-OPDEF(JSOP_LEAVEBLOCK,    199,"leaveblock",  NULL,    3, -1,  0,  JOF_UINT16)
+OPDEF(JSOP_PUSHBLOCKSCOPE,198,"pushblockscope", NULL, 5,  0,  0,  JOF_OBJECT)
+OPDEF(JSOP_POPBLOCKSCOPE, 199,"popblockscope", NULL,  1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_DEBUGLEAVEBLOCK, 200,"debugleaveblock", NULL, 1,  0,  0,  JOF_BYTE)
 
 OPDEF(JSOP_UNUSED201,     201,"unused201",  NULL,     1,  0,  0,  JOF_BYTE)
 
 /*
  * Generator and array comprehension support.
  */
 OPDEF(JSOP_GENERATOR,     202,"generator",   NULL,    1,  0,  0,  JOF_BYTE)
@@ -456,22 +452,17 @@ OPDEF(JSOP_ARRAYPUSH,     204,"arraypush
  */
 OPDEF(JSOP_GETFUNNS,      205,"getfunns",   NULL,     1,  0,  1,  JOF_BYTE)
 
 /*
  * Variant of JSOP_ENUMELEM for destructuring const (const [a, b] = ...).
  */
 OPDEF(JSOP_ENUMCONSTELEM, 206,"enumconstelem",NULL,   1,  3,  0,  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,  3, -1,  1,  JOF_UINT16)
-
+OPDEF(JSOP_UNUSED207,     207, "unused207",    NULL,  1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED208,     208, "unused208",    NULL,  1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED209,     209, "unused209",    NULL,  1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED210,     210, "unused210",    NULL,  1,  0,  0,  JOF_BYTE)
 
 OPDEF(JSOP_CALLGNAME,     211, "callgname",    NULL,  5,  0,  1,  JOF_ATOM|JOF_NAME|JOF_TYPESET|JOF_GNAME)
 OPDEF(JSOP_CALLLOCAL,     212, "calllocal",    NULL,  3,  0,  1,  JOF_LOCAL|JOF_NAME)
 OPDEF(JSOP_CALLARG,       213, "callarg",      NULL,  3,  0,  1,  JOF_QARG |JOF_NAME)
 OPDEF(JSOP_BINDGNAME,     214, "bindgname",    NULL,  5,  0,  1,  JOF_ATOM|JOF_NAME|JOF_SET|JOF_GNAME)
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -849,18 +849,18 @@ js::UnwindScope(JSContext *cx, ScopeIter
 {
     for (; !si.done(); ++si) {
         switch (si.type()) {
           case ScopeIter::Block:
             if (si.staticBlock().stackDepth() < stackDepth)
                 return;
             if (cx->compartment()->debugMode())
                 DebugScopes::onPopBlock(cx, si);
-            JS_ASSERT(&si.staticBlock() == si.frame().maybeBlockChain());
-            si.frame().popBlock(cx);
+            if (si.staticBlock().needsClone())
+                si.frame().popBlock(cx);
             break;
           case ScopeIter::With:
             if (si.scope().as<WithObject>().stackDepth() < stackDepth)
                 return;
             si.frame().popWith(cx);
             break;
           case ScopeIter::Call:
           case ScopeIter::StrictEvalScope:
@@ -1584,29 +1584,29 @@ CASE(EnableInterruptsPseudoOpcode)
     /* Commence executing the actual opcode. */
     SANITY_CHECKS();
     DISPATCH_TO(op);
 }
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_UNUSED2)
-CASE(JSOP_UNUSED44)
 CASE(JSOP_UNUSED45)
 CASE(JSOP_UNUSED46)
 CASE(JSOP_UNUSED47)
 CASE(JSOP_UNUSED48)
 CASE(JSOP_UNUSED49)
 CASE(JSOP_UNUSED50)
 CASE(JSOP_UNUSED51)
 CASE(JSOP_UNUSED52)
 CASE(JSOP_UNUSED101)
 CASE(JSOP_UNUSED102)
 CASE(JSOP_UNUSED103)
 CASE(JSOP_UNUSED104)
+CASE(JSOP_UNUSED105)
 CASE(JSOP_UNUSED107)
 CASE(JSOP_UNUSED125)
 CASE(JSOP_UNUSED126)
 CASE(JSOP_UNUSED132)
 CASE(JSOP_UNUSED139)
 CASE(JSOP_UNUSED140)
 CASE(JSOP_UNUSED141)
 CASE(JSOP_UNUSED142)
@@ -1636,24 +1636,28 @@ CASE(JSOP_UNUSED175)
 CASE(JSOP_UNUSED176)
 CASE(JSOP_UNUSED177)
 CASE(JSOP_UNUSED178)
 CASE(JSOP_UNUSED179)
 CASE(JSOP_UNUSED180)
 CASE(JSOP_UNUSED181)
 CASE(JSOP_UNUSED182)
 CASE(JSOP_UNUSED183)
+CASE(JSOP_UNUSED185)
+CASE(JSOP_UNUSED186)
+CASE(JSOP_UNUSED187)
 CASE(JSOP_UNUSED189)
 CASE(JSOP_UNUSED190)
 CASE(JSOP_UNUSED191)
 CASE(JSOP_UNUSED192)
 CASE(JSOP_UNUSED194)
 CASE(JSOP_UNUSED196)
 CASE(JSOP_UNUSED201)
 CASE(JSOP_GETFUNNS)
+CASE(JSOP_UNUSED207)
 CASE(JSOP_UNUSED208)
 CASE(JSOP_UNUSED209)
 CASE(JSOP_UNUSED210)
 CASE(JSOP_UNUSED219)
 CASE(JSOP_UNUSED220)
 CASE(JSOP_UNUSED221)
 CASE(JSOP_UNUSED222)
 CASE(JSOP_UNUSED223)
@@ -1709,21 +1713,34 @@ END_CASE(JSOP_UNDEFINED)
 CASE(JSOP_POP)
     REGS.sp--;
 END_CASE(JSOP_POP)
 
 CASE(JSOP_POPN)
     JS_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth());
     REGS.sp -= GET_UINT16(REGS.pc);
 #ifdef DEBUG
-    if (StaticBlockObject *block = REGS.fp()->maybeBlockChain())
+    if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPN_LENGTH))
         JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount());
 #endif
 END_CASE(JSOP_POPN)
 
+CASE(JSOP_POPNV)
+{
+    JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth());
+    Value val = REGS.sp[-1];
+    REGS.sp -= GET_UINT16(REGS.pc);
+    REGS.sp[-1] = val;
+#ifdef DEBUG
+    if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPNV_LENGTH))
+        JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount());
+#endif
+}
+END_CASE(JSOP_POPNV)
+
 CASE(JSOP_SETRVAL)
     POP_RETURN_VALUE();
 END_CASE(JSOP_SETRVAL)
 
 CASE(JSOP_ENTERWITH)
 {
     RootedValue &val = rootValue0;
     val = REGS.sp[-1];
@@ -3331,62 +3348,47 @@ CASE(JSOP_DEBUGGER)
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         goto error;
       default:;
     }
 }
 END_CASE(JSOP_DEBUGGER)
 
-CASE(JSOP_ENTERBLOCK)
-CASE(JSOP_ENTERLET0)
-CASE(JSOP_ENTERLET1)
-CASE(JSOP_ENTERLET2)
+CASE(JSOP_PUSHBLOCKSCOPE)
 {
     StaticBlockObject &blockObj = script->getObject(REGS.pc)->as<StaticBlockObject>();
 
-    if (*REGS.pc == JSOP_ENTERBLOCK) {
-        JS_ASSERT(REGS.stackDepth() == blockObj.stackDepth());
-        JS_ASSERT(REGS.stackDepth() + blockObj.slotCount() <= script->nslots);
-        Value *vp = REGS.sp + blockObj.slotCount();
-        SetValueRangeToUndefined(REGS.sp, vp);
-        REGS.sp = vp;
-    }
-
-    /* Clone block iff there are any closed-over variables. */
+    JS_ASSERT(blockObj.needsClone());
+
+    // FIXME: "Aliased" slots don't need to be on the stack.
+    JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
+
+    // Clone block and push on scope chain.
     if (!REGS.fp()->pushBlock(cx, blockObj))
         goto error;
 }
-END_CASE(JSOP_ENTERBLOCK)
-
-CASE(JSOP_LEAVEBLOCK)
-CASE(JSOP_LEAVEFORLETIN)
-CASE(JSOP_LEAVEBLOCKEXPR)
+END_CASE(JSOP_PUSHBLOCKSCOPE)
+
+CASE(JSOP_POPBLOCKSCOPE)
 {
-    blockDepth = REGS.fp()->blockChain().stackDepth();
+#ifdef DEBUG
+    // Pop block from scope chain.
+    JS_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK);
+    StaticBlockObject *blockObj = script->getBlockScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH);
+    JS_ASSERT(blockObj && blockObj->needsClone());
+
+    // FIXME: "Aliased" slots don't need to be on the stack.
+    JS_ASSERT(REGS.stackDepth() >= blockObj->stackDepth() + blockObj->slotCount());
+#endif
 
     // Pop block from scope chain.
     REGS.fp()->popBlock(cx);
-
-    if (*REGS.pc == JSOP_LEAVEBLOCK) {
-        /* Pop the block's slots. */
-        REGS.sp -= GET_UINT16(REGS.pc);
-        JS_ASSERT(REGS.stackDepth() == blockDepth);
-    } else if (*REGS.pc == JSOP_LEAVEBLOCKEXPR) {
-        /* Pop the block's slots maintaining the topmost expr. */
-        Value *vp = &REGS.sp[-1];
-        REGS.sp -= GET_UINT16(REGS.pc);
-        JS_ASSERT(REGS.stackDepth() == blockDepth + 1);
-        REGS.sp[-1] = *vp;
-    } else {
-        /* Another op will pop; nothing to do here. */
-        ADVANCE_AND_DISPATCH(JSOP_LEAVEFORLETIN_LENGTH);
-    }
 }
-END_CASE(JSOP_LEAVEBLOCK)
+END_CASE(JSOP_POPBLOCKSCOPE)
 
 CASE(JSOP_DEBUGLEAVEBLOCK)
 {
     JS_ASSERT(script->getBlockScope(REGS.pc));
 
     // FIXME: This opcode should not be necessary.  The debugger shouldn't need
     // help from bytecode to do its job.  See bug 927782.
 
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -105,17 +105,17 @@ StackFrame::unaliasedVar(unsigned i, May
     JS_ASSERT(i < script()->nfixed);
     return slots()[i];
 }
 
 inline Value &
 StackFrame::unaliasedLocal(unsigned i, MaybeCheckAliasing checkAliasing)
 {
 #ifdef DEBUG
-    CheckLocalUnaliased(checkAliasing, script(), maybeBlockChain(), i);
+    CheckLocalUnaliased(checkAliasing, script(), i);
 #endif
     return slots()[i];
 }
 
 inline Value &
 StackFrame::unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT(i < numFormalArgs());
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -322,43 +322,33 @@ StackFrame::epilogue(JSContext *cx)
 
     if (isConstructing() && thisValue().isObject() && returnValue().isPrimitive())
         setReturnValue(ObjectValue(constructorThis()));
 }
 
 bool
 StackFrame::pushBlock(JSContext *cx, StaticBlockObject &block)
 {
-    JS_ASSERT_IF(hasBlockChain(), blockChain_ == block.enclosingBlock());
+    JS_ASSERT (block.needsClone());
 
-    if (block.needsClone()) {
-        Rooted<StaticBlockObject *> blockHandle(cx, &block);
-        ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this);
-        if (!clone)
-            return false;
+    Rooted<StaticBlockObject *> blockHandle(cx, &block);
+    ClonedBlockObject *clone = ClonedBlockObject::create(cx, blockHandle, this);
+    if (!clone)
+        return false;
 
-        pushOnScopeChain(*clone);
-
-        blockChain_ = blockHandle;
-    } else {
-        blockChain_ = &block;
-    }
+    pushOnScopeChain(*clone);
 
     return true;
 }
 
 void
 StackFrame::popBlock(JSContext *cx)
 {
-    if (blockChain_->needsClone()) {
-        JS_ASSERT(scopeChain_->as<ClonedBlockObject>().staticBlock() == *blockChain_);
-        popOffScopeChain();
-    }
-
-    blockChain_ = blockChain_->enclosingBlock();
+    JS_ASSERT(scopeChain_->is<ClonedBlockObject>());
+    popOffScopeChain();
 }
 
 void
 StackFrame::popWith(JSContext *cx)
 {
     if (JS_UNLIKELY(cx->compartment()->debugMode()))
         DebugScopes::onPopWith(this);
 
@@ -1249,33 +1239,27 @@ AbstractFramePtr::hasPushedSPSFrame() co
     return asBaselineFrame()->hasPushedSPSFrame();
 #else
     MOZ_ASSUME_UNREACHABLE("Invalid frame");
 #endif
 }
 
 #ifdef DEBUG
 void
-js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script,
-                        StaticBlockObject *maybeBlock, unsigned i)
+js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, unsigned i)
 {
     if (!checkAliasing)
         return;
 
     JS_ASSERT(i < script->nslots);
     if (i < script->nfixed) {
         JS_ASSERT(!script->varIsAliased(i));
     } else {
-        unsigned depth = i - script->nfixed;
-        for (StaticBlockObject *b = maybeBlock; b; b = b->enclosingBlock()) {
-            if (b->containsVarAtDepth(depth)) {
-                JS_ASSERT(!b->isAliased(depth - b->stackDepth()));
-                break;
-            }
-        }
+        // FIXME: The callers of this function do not easily have the PC of the
+        // current frame, and so they do not know the block scope.
     }
 }
 #endif
 
 jit::JitActivation::JitActivation(JSContext *cx, bool firstFrameIsConstructing, bool active)
   : Activation(cx, Jit),
     firstFrameIsConstructing_(firstFrameIsConstructing),
     active_(active)
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -70,18 +70,17 @@ struct ScopeCoordinate;
 // is a local var of js::Interpret.
 
 enum MaybeCheckAliasing { CHECK_ALIASING = true, DONT_CHECK_ALIASING = false };
 
 /*****************************************************************************/
 
 #ifdef DEBUG
 extern void
-CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script,
-                    StaticBlockObject *maybeBlock, unsigned i);
+CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, unsigned i);
 #endif
 
 namespace jit {
     class BaselineFrame;
 }
 
 /*
  * Pointer to either a ScriptFrameIter::Data, a StackFrame, or a baseline JIT
@@ -586,24 +585,18 @@ class StackFrame
     inline GlobalObject &global() const;
     inline CallObject &callObj() const;
     inline JSObject &varObj();
 
     inline void pushOnScopeChain(ScopeObject &scope);
     inline void popOffScopeChain();
 
     /*
-     * Block chain
-     *
-     * Entering/leaving a let (or exception) block may do 1 or 2 things: First,
-     * a static block object (created at compiled time and stored in the
-     * script) is pushed on StackFrame::blockChain. Second, if the static block
-     * may be cloned to hold the dynamic values if this is needed for dynamic
-     * scope access. A clone is created for a static block iff
-     * StaticBlockObject::needsClone.
+     * For blocks with aliased locals, these interfaces push and pop entries on
+     * the scope chain.
      */
 
     bool hasBlockChain() const {
         return blockChain_;
     }
 
     StaticBlockObject *maybeBlockChain() {
         return blockChain_;