Bug 927782 - Part 7: Refactor entering and leaving block scopes in BytecodeEmitter. r=luke
☠☠ backed out by 94cdaced90bf ☠ ☠
authorAndy Wingo <wingo@igalia.com>
Fri, 06 Dec 2013 18:25:55 +0100
changeset 174944 8c74b1f68590e7791ec4a13f2e12196caf053709
parent 174943 f1237f11edcd1fc743d843fa902d086c31523576
child 174945 5f086f95b3059ed80dfe45013081478e3ed5ee82
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [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 7: Refactor entering and leaving block scopes in BytecodeEmitter. r=luke
js/src/frontend/BytecodeEmitter.cpp
js/src/jit/BaselineBailouts.cpp
js/src/vm/Interpreter.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -137,21 +137,22 @@ EmitCheck(ExclusiveContext *cx, Bytecode
     if (!bce->code().appendN(dummy, delta)) {
         js_ReportOutOfMemory(cx);
         return -1;
     }
     return offset;
 }
 
 static StaticBlockObject &
-CurrentBlock(StmtInfoBCE *topStmt)
-{
-    JS_ASSERT(topStmt->type == STMT_BLOCK || topStmt->type == STMT_SWITCH);
-    JS_ASSERT(topStmt->blockObj->is<StaticBlockObject>());
-    return *topStmt->blockObj;
+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];
@@ -171,23 +172,23 @@ UpdateDepth(ExclusiveContext *cx, Byteco
      * 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 = CurrentBlock(bce->topStmt).slotCount();
+        ndefs = LastBlockAdded(bce, pc).slotCount();
     } else if (op == JSOP_ENTERLET0) {
-        nuses = ndefs = CurrentBlock(bce->topStmt).slotCount();
+        nuses = ndefs = LastBlockAdded(bce, pc).slotCount();
     } else if (op == JSOP_ENTERLET1) {
-        nuses = ndefs = CurrentBlock(bce->topStmt).slotCount() + 1;
+        nuses = ndefs = LastBlockAdded(bce, pc).slotCount() + 1;
     } else if (op == JSOP_ENTERLET2) {
-        nuses = ndefs = CurrentBlock(bce->topStmt).slotCount() + 2;
+        nuses = ndefs = LastBlockAdded(bce, pc).slotCount() + 2;
     } else {
         nuses = StackUses(nullptr, pc);
         ndefs = StackDefs(nullptr, pc);
     }
 
     bce->stackDepth -= nuses;
     JS_ASSERT(bce->stackDepth >= 0);
     bce->stackDepth += ndefs;
@@ -648,33 +649,122 @@ EnclosingStaticScope(BytecodeEmitter *bc
     if (!bce->sc->isFunctionBox()) {
         JS_ASSERT(!bce->parent);
         return nullptr;
     }
 
     return bce->sc->asFunctionBox()->function();
 }
 
-// Push a block scope statement and link blockObj into bce->blockChain.
+// In a stack frame, block-scoped locals follow hoisted var-bound locals.  If
+// the current compilation unit is a function, add the number of "fixed slots"
+// (var-bound locals) to the given block-scoped index, to arrive at its final
+// position in the call frame.
+//
+static bool
+AdjustBlockSlot(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t *slot)
+{
+    JS_ASSERT(*slot < bce->maxStackDepth);
+    if (bce->sc->isFunctionBox()) {
+        *slot += bce->script->bindings.numVars();
+        if ((unsigned) *slot >= SLOTNO_LIMIT) {
+            bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
+            return false;
+        }
+    }
+    return true;
+}
+
+#ifdef DEBUG
+static bool
+AllLocalsAliased(StaticBlockObject &obj)
+{
+    for (unsigned i = 0; i < obj.slotCount(); i++)
+        if (!obj.isAliased(i))
+            return false;
+    return true;
+}
+#endif
+
 static bool
-PushBlockScopeBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
-                  ptrdiff_t top)
+ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, StaticBlockObject &blockObj)
+{
+    uint32_t depthPlusFixed = blockObj.stackDepth();
+    if (!AdjustBlockSlot(cx, bce, &depthPlusFixed))
+        return false;
+
+    for (unsigned i = 0; i < blockObj.slotCount(); i++) {
+        Definition *dn = blockObj.maybeDefinitionParseNode(i);
+
+        /* Beware the empty destructuring dummy. */
+        if (!dn) {
+            blockObj.setAliased(i, bce->sc->allLocalsAliased());
+            continue;
+        }
+
+        JS_ASSERT(dn->isDefn());
+        JS_ASSERT(dn->frameSlot() + depthPlusFixed < JS_BIT(16));
+        if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(),
+                               uint16_t(dn->frameSlot() + depthPlusFixed)))
+            return false;
+
+#ifdef DEBUG
+        for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
+            JS_ASSERT(pnu->pn_lexdef == dn);
+            JS_ASSERT(!(pnu->pn_dflags & PND_BOUND));
+            JS_ASSERT(pnu->pn_cookie.isFree());
+        }
+#endif
+
+        blockObj.setAliased(i, bce->isAliasedName(dn));
+    }
+
+    JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(blockObj));
+
+    return true;
+}
+
+static bool
+EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmitter *bce);
+
+static bool
+EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
+                unsigned extraSlots)
 {
     StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
 
-    PushStatementBCE(bce, stmt, STMT_BLOCK, top);
-
-    unsigned scopeObjectIndex = bce->objectList.add(objbox);
+    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;
+
+    PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset());
     blockObj.initEnclosingStaticScope(EnclosingStaticScope(bce));
     FinishPushBlockScope(bce, stmt, blockObj);
 
+    JS_ASSERT(stmt->isBlockScope);
+
     return true;
 }
 
 // Patches |breaks| and |continues| unless the top statement info record
 // represents a try-catch-finally suite. May fail if a jump offset overflows.
 static bool
 PopStatementBCE(ExclusiveContext *cx, BytecodeEmitter *bce)
 {
@@ -689,16 +779,45 @@ PopStatementBCE(ExclusiveContext *cx, By
     if (stmt->isBlockScope)
         bce->blockScopeList.recordEnd(stmt->blockScopeIndex, bce->offset());
 
     FinishPopStatement(bce);
     return true;
 }
 
 static bool
+LeaveBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op)
+{
+#ifdef DEBUG
+    StmtInfoBCE *stmt = bce->topStmt;
+    uint32_t blockScopeIndex = stmt->blockScopeIndex;
+    JS_ASSERT(stmt->isBlockScope);
+    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();
+
+    if (!PopStatementBCE(cx, bce))
+        return false;
+
+    if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
+        return false;
+
+    JS_ASSERT(op == JSOP_LEAVEBLOCK || op == JSOP_LEAVEBLOCKEXPR);
+    EMIT_UINT16_IMM_OP(op, slotCount);
+
+    return true;
+}
+
+static bool
 EmitIndex32(ExclusiveContext *cx, JSOp op, uint32_t index, BytecodeEmitter *bce)
 {
     const size_t len = 1 + UINT32_INDEX_LEN;
     JS_ASSERT(len == size_t(js_CodeSpec[op].length));
     ptrdiff_t offset = EmitCheck(cx, bce, len);
     if (offset < 0)
         return false;
 
@@ -1026,111 +1145,16 @@ BytecodeEmitter::isAliasedName(ParseNode
       case Definition::NAMED_LAMBDA:
       case Definition::MISSING:
         MOZ_ASSUME_UNREACHABLE("unexpected dn->kind");
     }
     return false;
 }
 
 /*
- * Adjust the slot for a block local to account for the number of variables
- * that share the same index space with locals. Due to the incremental code
- * generation for top-level script, we do the adjustment via code patching in
- * js::frontend::CompileScript; see comments there.
- *
- * The function returns -1 on failures.
- */
-static int
-AdjustBlockSlot(ExclusiveContext *cx, BytecodeEmitter *bce, int slot)
-{
-    JS_ASSERT((unsigned) slot < bce->maxStackDepth);
-    if (bce->sc->isFunctionBox()) {
-        slot += bce->script->bindings.numVars();
-        if ((unsigned) slot >= SLOTNO_LIMIT) {
-            bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
-            slot = -1;
-        }
-    }
-    return slot;
-}
-
-#ifdef DEBUG
-static bool
-AllLocalsAliased(StaticBlockObject &obj)
-{
-    for (unsigned i = 0; i < obj.slotCount(); i++)
-        if (!obj.isAliased(i))
-            return false;
-    return true;
-}
-#endif
-
-static bool
-EmitEnterBlock(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSOp op)
-{
-    JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
-    StmtInfoBCE *stmt = bce->topStmt;
-    JS_ASSERT(stmt->type == STMT_BLOCK || stmt->type == STMT_SWITCH);
-    JS_ASSERT(stmt->isBlockScope);
-    JS_ASSERT(stmt->blockScopeIndex == bce->blockScopeList.length() - 1);
-    JS_ASSERT(bce->blockScopeList.list[stmt->blockScopeIndex].length == 0);
-    uint32_t scopeObjectIndex = bce->blockScopeList.list[stmt->blockScopeIndex].index;
-    JS_ASSERT(scopeObjectIndex == bce->objectList.length - 1);
-    JS_ASSERT(pn->pn_objbox == bce->objectList.lastbox);
-    if (!EmitInternedObjectOp(cx, scopeObjectIndex, op, bce))
-        return false;
-
-    Rooted<StaticBlockObject*> blockObj(cx, &pn->pn_objbox->object->as<StaticBlockObject>());
-
-    int extraSlots = (op == JSOP_ENTERLET1)
-                     ? 1
-                     : (op == JSOP_ENTERLET2)
-                     ? 2
-                     : 0;
-    int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots);
-    JS_ASSERT(depth >= 0);
-
-    blockObj->setStackDepth(depth);
-
-    int depthPlusFixed = AdjustBlockSlot(cx, bce, depth);
-    if (depthPlusFixed < 0)
-        return false;
-
-    for (unsigned i = 0; i < blockObj->slotCount(); i++) {
-        Definition *dn = blockObj->maybeDefinitionParseNode(i);
-
-        /* Beware the empty destructuring dummy. */
-        if (!dn) {
-            blockObj->setAliased(i, bce->sc->allLocalsAliased());
-            continue;
-        }
-
-        JS_ASSERT(dn->isDefn());
-        JS_ASSERT(unsigned(dn->frameSlot() + depthPlusFixed) < JS_BIT(16));
-        if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(),
-                               uint16_t(dn->frameSlot() + depthPlusFixed)))
-            return false;
-
-#ifdef DEBUG
-        for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
-            JS_ASSERT(pnu->pn_lexdef == dn);
-            JS_ASSERT(!(pnu->pn_dflags & PND_BOUND));
-            JS_ASSERT(pnu->pn_cookie.isFree());
-        }
-#endif
-
-        blockObj->setAliased(i, bce->isAliasedName(dn));
-    }
-
-    JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(*blockObj));
-
-    return true;
-}
-
-/*
  * Try to convert a *NAME op with a free name to a more specialized GNAME,
  * INTRINSIC or ALIASEDVAR op, which optimize accesses on that name.
  * Return true if a conversion was made.
  */
 static bool
 TryConvertFreeName(BytecodeEmitter *bce, ParseNode *pn)
 {
     /*
@@ -2292,64 +2316,57 @@ EmitSwitch(ExclusiveContext *cx, Bytecod
     JSOp switchOp;
     bool hasDefault;
     ptrdiff_t top, off, defaultOffset;
     ParseNode *pn2, *pn3, *pn4;
     int32_t low, high;
     int noteIndex;
     size_t switchSize;
     jsbytecode *pc;
-    StmtInfoBCE stmtInfo(cx);
 
     /* Try for most optimal, fall back if not dense ints. */
     switchOp = JSOP_TABLESWITCH;
     hasDefault = false;
     defaultOffset = -1;
 
     pn2 = pn->pn_right;
+    JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST));
+
     /*
      * If there are hoisted let declarations, their stack slots go under the
      * discriminant's value so push their slots now and enter the block later.
      */
-    uint32_t blockObjCount = 0;
+    StaticBlockObject *blockObj = nullptr;
     if (pn2->isKind(PNK_LEXICALSCOPE)) {
-        blockObjCount = pn2->pn_objbox->object->as<StaticBlockObject>().slotCount();
-        for (uint32_t i = 0; i < blockObjCount; ++i) {
+        blockObj = &pn2->pn_objbox->object->as<StaticBlockObject>();
+        for (uint32_t i = 0; i < blockObj->slotCount(); ++i) {
             if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
                 return false;
         }
     }
 
     /* Push the discriminant. */
     if (!EmitTree(cx, bce, pn->pn_left))
         return false;
 
+    StmtInfoBCE stmtInfo(cx);
     if (pn2->isKind(PNK_LEXICALSCOPE)) {
-        if (!PushBlockScopeBCE(bce, &stmtInfo, pn2->pn_objbox, -1))
+        if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 1))
             return false;
         stmtInfo.type = STMT_SWITCH;
-        if (!EmitEnterBlock(cx, bce, pn2, JSOP_ENTERLET1))
-            return false;
+        stmtInfo.update = top = bce->offset();
+        /* Advance pn2 to refer to the switch case list. */
+        pn2 = pn2->expr();
+    } else {
+        JS_ASSERT(pn2->isKind(PNK_STATEMENTLIST));
+        top = bce->offset();
+        PushStatementBCE(bce, &stmtInfo, STMT_SWITCH, top);
     }
 
     /* Switch bytecodes run from here till end of final case. */
-    top = bce->offset();
-    if (pn2->isKind(PNK_STATEMENTLIST)) {
-        PushStatementBCE(bce, &stmtInfo, STMT_SWITCH, top);
-    } else {
-        /*
-         * Set the statement info record's idea of top. Reset top too, since
-         * repushBlock emits code.
-         */
-        stmtInfo.update = top = bce->offset();
-
-        /* Advance pn2 to refer to the switch case list. */
-        pn2 = pn2->expr();
-    }
-
     uint32_t caseCount = pn2->pn_count;
     uint32_t tableLength = 0;
     ScopedJSFreePtr<ParseNode*> table(nullptr);
 
     if (caseCount > JS_BIT(16)) {
         bce->parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES);
         return false;
     }
@@ -2611,23 +2628,23 @@ 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 (!PopStatementBCE(cx, bce))
-        return false;
 
     if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) {
-        if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
-            return false;
-        EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObjCount);
+        if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
+            return false;
+    } else {
+        if (!PopStatementBCE(cx, bce))
+            return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::isRunOnceLambda()
 {
@@ -3132,18 +3149,18 @@ EmitGroupAssignment(ExclusiveContext *cx
             return false;
         ++limit;
     }
 
     i = depth;
     for (pn = lhs->pn_head; pn; pn = pn->pn_next, ++i) {
         /* MaybeEmitGroupAssignment requires lhs->pn_count <= rhs->pn_count. */
         JS_ASSERT(i < limit);
-        int slot = AdjustBlockSlot(cx, bce, i);
-        if (slot < 0)
+        uint32_t slot = i;
+        if (!AdjustBlockSlot(cx, bce, &slot))
             return false;
 
         if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce))
             return false;
 
         if (pn->isKind(PNK_ELISION)) {
             if (Emit1(cx, bce, JSOP_POP) < 0)
                 return false;
@@ -4166,66 +4183,58 @@ EmitLet(ExclusiveContext *cx, BytecodeEm
     uint32_t alreadyPushed = unsigned(bce->stackDepth - letHeadDepth);
     uint32_t blockObjCount = blockObj->slotCount();
     for (uint32_t i = alreadyPushed; i < blockObjCount; ++i) {
         if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
             return false;
     }
 
     StmtInfoBCE stmtInfo(cx);
-    if (!PushBlockScopeBCE(bce, &stmtInfo, letBody->pn_objbox, bce->offset()))
-        return false;
-
-    DebugOnly<ptrdiff_t> bodyBegin = bce->offset();
-    if (!EmitEnterBlock(cx, bce, letBody, JSOP_ENTERLET0))
+    if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, 0))
         return false;
 
     if (!EmitTree(cx, bce, letBody->pn_expr))
         return false;
 
-    if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
-        return false;
-    JSOp leaveOp = letBody->getOp();
-    JS_ASSERT(leaveOp == JSOP_LEAVEBLOCK || leaveOp == JSOP_LEAVEBLOCKEXPR);
-    EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount());
-
-    DebugOnly<ptrdiff_t> bodyEnd = bce->offset();
-    JS_ASSERT(bodyEnd > bodyBegin);
-
-    return PopStatementBCE(cx, bce);
+    if (!LeaveBlockScope(cx, bce, letBody->getOp()))
+        return false;
+
+    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);
 
     StmtInfoBCE stmtInfo(cx);
     ObjectBox *objbox = pn->pn_objbox;
     StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
     size_t slots = blockObj.slotCount();
-    if (!PushBlockScopeBCE(bce, &stmtInfo, objbox, bce->offset()))
-        return false;
-
-    if (!EmitEnterBlock(cx, bce, pn, JSOP_ENTERBLOCK))
+
+    for (size_t n = 0; n < slots; ++n) {
+        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+            return false;
+    }
+
+    if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0))
         return false;
 
     if (!EmitTree(cx, bce, pn->pn_expr))
         return false;
 
-    if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
-        return false;
-    EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, slots);
-
-    return PopStatementBCE(cx, bce);
+    if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
+        return false;
+
+    return true;
 }
 
 static bool
 EmitWith(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfoBCE stmtInfo(cx);
     if (!EmitTree(cx, bce, pn->pn_left))
         return false;
@@ -4292,19 +4301,17 @@ EmitForOf(ExclusiveContext *cx, Bytecode
 
     // Push a dummy result so that we properly enter iteration midstream.
     if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)                    // ITER RESULT
         return false;
 
     // Enter the block before the loop body, after evaluating the obj.
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!PushBlockScopeBCE(bce, &letStmt, pn1->pn_objbox, bce->offset()))
-            return false;
-        if (!EmitEnterBlock(cx, bce, pn1, JSOP_ENTERLET2))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 2))
             return false;
     }
 
     StmtInfoBCE stmtInfo(cx);
     PushStatementBCE(bce, &stmtInfo, STMT_FOR_OF_LOOP, top);
 
     // Jump down to the loop condition to minimize overhead assuming at least
     // one iteration, as the other loop forms do.  Annotate so IonMonkey can
@@ -4386,27 +4393,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 (!PopStatementBCE(cx, bce))
-            return false;
-        if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
-            return false;
-        if (Emit1(cx, bce, JSOP_LEAVEFORLETIN) < 0)
-            return false;
-    }
-
-    // Pop result, iter, and slots from the lexical block (if any).
-    EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2);
+        if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
+            return false;
+    }
 
     return true;
 }
 
 static bool
 EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
     ParseNode *forHead = pn->pn_left;
@@ -4470,19 +4473,17 @@ EmitForIn(ExclusiveContext *cx, Bytecode
      */
     JS_ASSERT(pn->isOp(JSOP_ITER));
     if (Emit2(cx, bce, JSOP_ITER, (uint8_t) pn->pn_iflags) < 0)
         return false;
 
     /* Enter the block before the loop body, after evaluating the obj. */
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!PushBlockScopeBCE(bce, &letStmt, pn1->pn_objbox, bce->offset()))
-            return false;
-        if (!EmitEnterBlock(cx, bce, pn1, JSOP_ENTERLET1))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 1))
             return false;
     }
 
     StmtInfoBCE stmtInfo(cx);
     PushStatementBCE(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
 
     /* Annotate so IonMonkey can find the loop-closing jump. */
     int noteIndex = NewSrcNote(cx, bce, SRC_FOR_IN);
@@ -4542,36 +4543,29 @@ EmitForIn(ExclusiveContext *cx, Bytecode
     ptrdiff_t beq = EmitJump(cx, bce, JSOP_IFNE, top - bce->offset());
     if (beq < 0)
         return false;
 
     /* Set the srcnote offset so we can find the closing jump. */
     if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, beq - jmp))
         return false;
 
-    /* Fixup breaks and continues before JSOP_ITER (and JSOP_LEAVEFORINLET). */
+    // Fix up breaks and continues.
     if (!PopStatementBCE(cx, bce))
         return false;
 
-    if (letDecl) {
-        if (!PopStatementBCE(cx, bce))
-            return false;
-        if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
-            return false;
-        if (Emit1(cx, bce, JSOP_LEAVEFORLETIN) < 0)
-            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)
-        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount);
+    if (letDecl) {
+        if (!LeaveBlockScope(cx, bce, JSOP_LEAVEBLOCK))
+            return false;
+    }
 
     return true;
 }
 
 static bool
 EmitNormalFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
     StmtInfoBCE stmtInfo(cx);
@@ -6425,28 +6419,26 @@ frontend::EmitTree(ExclusiveContext *cx,
 
       case PNK_IMPORT:
       case PNK_EXPORT:
        // TODO: Implement emitter support for modules
        bce->reportError(nullptr, JSMSG_MODULES_NOT_IMPLEMENTED);
        return false;
 
       case PNK_ARRAYPUSH: {
-        int slot;
-
         /*
          * The array object's stack index is in bce->arrayCompDepth. See below
          * under the array initialiser code generator for array comprehension
          * special casing. Note that the array object is a pure stack value,
          * unaliased by blocks, so we can EmitUnaliasedVarOp.
          */
         if (!EmitTree(cx, bce, pn->pn_kid))
             return false;
-        slot = AdjustBlockSlot(cx, bce, bce->arrayCompDepth);
-        if (slot < 0)
+        uint32_t slot = bce->arrayCompDepth;
+        if (!AdjustBlockSlot(cx, bce, &slot))
             return false;
         if (!EmitUnaliasedVarOp(cx, pn->getOp(), slot, bce))
             return false;
         break;
       }
 
       case PNK_ARRAY:
         if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -660,17 +660,16 @@ InitFromBailout(JSContext *cx, HandleScr
     }
 
     // Get the pc. If we are handling an exception, resume at the pc of the
     // catch or finally block.
     jsbytecode *pc = excInfo ? excInfo->resumePC : script->offsetToPC(iter.pcOffset());
     bool resumeAfter = excInfo ? false : iter.resumeAfter();
 
     JSOp op = JSOp(*pc);
-    JS_ASSERT_IF(excInfo, op == JSOP_ENTERBLOCK);
 
     // Fixup inlined JSOP_FUNCALL, JSOP_FUNAPPLY, and accessors on the caller side.
     // On the caller side this must represent like the function wasn't inlined.
     uint32_t pushedSlots = 0;
     AutoValueVector savedCallerArgs(cx);
     bool needToSaveArgs = op == JSOP_FUNAPPLY || IsGetPropPC(pc) || IsSetPropPC(pc);
     if (iter.moreFrames() && (op == JSOP_FUNCALL || needToSaveArgs))
     {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3372,20 +3372,18 @@ DEFAULT()
              * to the beginning of catch or finally or to [enditer] closing
              * the for-in loop.
              */
             REGS.pc = (script)->main() + tn->start + tn->length;
             REGS.sp = REGS.spForStackDepth(tn->stackDepth);
 
             switch (tn->kind) {
               case JSTRY_CATCH:
-                JS_ASSERT(*REGS.pc == JSOP_ENTERBLOCK || *REGS.pc == JSOP_EXCEPTION);
-
                 /* Catch cannot intercept the closing of a generator. */
-                  if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
+                if (JS_UNLIKELY(cx->getPendingException().isMagic(JS_GENERATOR_CLOSING)))
                     break;
 
                 /*
                  * Don't clear exceptions to save cx->exception from GC
                  * until it is pushed to the stack via [exception] in the
                  * catch block.
                  *
                  * Also, see the comment below about the use of goto here.