Backed out 12 changesets (bug 927782) for SM rootanalysis orange.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 06 Dec 2013 15:03:19 -0500
changeset 174975 94cdaced90bf49679eae210824875570fb98df9c
parent 174974 6fd483b8cf6614b27a01bc51c67105777c3023c3
child 174976 d86f108365974d2866cc36e1de9410f74768ceac
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)
bugs927782
milestone28.0a1
backs outf86d2d4cfadf457bef12afbedb8a8f82a5e69fca
51d6617835d140affaf45ed9787d317388beb1ff
eed9795fa80ea7d960962db8cc7d228137cffe54
b971de7edfff874f74137a241e352ed87d6d14a3
5f086f95b3059ed80dfe45013081478e3ed5ee82
8c74b1f68590e7791ec4a13f2e12196caf053709
f1237f11edcd1fc743d843fa902d086c31523576
d6946bd743b38c99c82eb45cf9203423cda9467c
cbdd50c96b858458ea7b3bfd6b5335ddde65b4c9
fc7a979712fc7b52f35125e8c15a85ed33f18c6c
c8304ccf88e90c17beed5c26fbae1782704c3cd6
9d99e9ca7b325fb7a3cf9e5b4d3f970e4b2077da
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
Backed out 12 changesets (bug 927782) for SM rootanalysis orange. Backed out changeset f86d2d4cfadf (bug 927782) Backed out changeset 51d6617835d1 (bug 927782) Backed out changeset eed9795fa80e (bug 927782) Backed out changeset b971de7edfff (bug 927782) Backed out changeset 5f086f95b305 (bug 927782) Backed out changeset 8c74b1f68590 (bug 927782) Backed out changeset f1237f11edcd (bug 927782) Backed out changeset d6946bd743b3 (bug 927782) Backed out changeset cbdd50c96b85 (bug 927782) Backed out changeset fc7a979712fc (bug 927782) Backed out changeset c8304ccf88e9 (bug 927782) Backed out changeset 9d99e9ca7b32 (bug 927782)
dom/indexedDB/test/unit/test_cursor_cycle.js
dom/indexedDB/test/unit/test_cursor_mutation.js
dom/indexedDB/test/unit/test_index_object_cursors.js
dom/indexedDB/test/unit/test_index_update_delete.js
js/jsd/jsd_stak.cpp
js/public/OldDebugAPI.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FullParseHandler.h
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/SharedContext.h
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineFrame-inl.h
js/src/jit/BaselineFrame.cpp
js/src/jit/BaselineFrame.h
js/src/jit/BaselineFrameInfo.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonFrames.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jsanalyze.cpp
js/src/jsfun.h
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/jsscript.cpp
js/src/jsscript.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/OldDebugAPI.cpp
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/dom/indexedDB/test/unit/test_cursor_cycle.js
+++ b/dom/indexedDB/test/unit/test_cursor_cycle.js
@@ -17,16 +17,21 @@ function testSteps()
   let db = event.target.result;
   event.target.onsuccess = continueToNextStep;
 
   let objectStore = db.createObjectStore("foo", { keyPath: "ss" });
   objectStore.createIndex("name", "name", { unique: true });
   objectStore.add(Bob);
   yield undefined;
 
+  // This direct eval causes locals to be aliased, and thus allocated on
+  // the scope chain.  Comment it out (and the workarounds below) and
+  // the test passes.  Bug 943409.
+  eval('');
+
   db.transaction("foo", "readwrite").objectStore("foo")
     .index("name").openCursor().onsuccess = function(event) {
     event.target.transaction.oncomplete = continueToNextStep;
     let cursor = event.target.result;
     if (cursor) {
       let objectStore = event.target.transaction.objectStore("foo");
       objectStore.delete(Bob.ss)
                  .onsuccess = function(event) { cursor.continue(); };
--- a/dom/indexedDB/test/unit/test_cursor_mutation.js
+++ b/dom/indexedDB/test/unit/test_cursor_mutation.js
@@ -30,16 +30,21 @@ function testSteps()
   let event = yield undefined;
 
   let db = event.target.result;
   event.target.onsuccess = continueToNextStep;
 
   let objectStore = db.createObjectStore("foo", { keyPath: "ss" });
   objectStore.createIndex("name", "name", { unique: true });
 
+  // This direct eval causes locals to be aliased, and thus allocated on
+  // the scope chain.  Comment it out (and the workarounds below) and
+  // the test passes.  Bug 943409.
+  eval('');
+
   for (let i = 0; i < objectStoreData.length - 1; i++) {
     objectStore.add(objectStoreData[i]);
   }
   yield undefined;
 
   let count = 0;
 
   let sawAdded = false;
--- a/dom/indexedDB/test/unit/test_index_object_cursors.js
+++ b/dom/indexedDB/test/unit/test_index_object_cursors.js
@@ -29,16 +29,19 @@ function testSteps()
   request.onupgradeneeded = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = event.target.result;
   db.onerror = errorHandler;
 
   event.target.onsuccess = continueToNextStep;
 
+  // Bug 943409.
+  eval('');
+
   for (let objectStoreIndex in objectStoreData) {
     const objectStoreInfo = objectStoreData[objectStoreIndex];
     let objectStore = db.createObjectStore(objectStoreInfo.name,
                                            objectStoreInfo.options);
     for (let indexIndex in indexData) {
       const indexInfo = indexData[indexIndex];
       let index = objectStore.createIndex(indexInfo.name,
                                           indexInfo.keyPath,
--- a/dom/indexedDB/test/unit/test_index_update_delete.js
+++ b/dom/indexedDB/test/unit/test_index_update_delete.js
@@ -13,16 +13,19 @@ function testSteps()
   request.onupgradeneeded = grabEventAndContinueHandler;
   request.onsuccess = grabEventAndContinueHandler;
 
   let event = yield undefined;
 
   let db = event.target.result;
   db.onerror = errorHandler;
 
+  // Bug 943409.
+  eval('');
+
   for each (let autoIncrement in [false, true]) {
     let objectStore =
       db.createObjectStore(autoIncrement, { keyPath: "id",
                                             autoIncrement: autoIncrement });
 
     for (let i = 0; i < 10; i++) {
       objectStore.add({ id: i, index: i });
     }
--- a/js/jsd/jsd_stak.cpp
+++ b/js/jsd/jsd_stak.cpp
@@ -92,17 +92,17 @@ jsd_NewThreadState(JSDContext* jsdc, JSC
 
     JS_BeginRequest(jsdthreadstate->context);
 
     JSBrokenFrameIterator iter(cx);
     while(!iter.done())
     {
         JSAbstractFramePtr frame = iter.abstractFramePtr();
         JS::RootedScript script(cx, frame.script());
-        uintptr_t  pc = (uintptr_t)frame.pc();
+        uintptr_t  pc = (uintptr_t)iter.pc();
         JS::RootedValue dummyThis(cx);
 
         /*
          * don't construct a JSDStackFrame for dummy frames (those without a
          * |this| object, or native frames, if JSD_INCLUDE_NATIVE_FRAMES
          * isn't set.
          */
         if (frame.getThisValue(cx, &dummyThis))
--- a/js/public/OldDebugAPI.h
+++ b/js/public/OldDebugAPI.h
@@ -334,28 +334,26 @@ JS_PutPropertyDescArray(JSContext *cx, J
 
 /*
  * JSAbstractFramePtr is the public version of AbstractFramePtr, a pointer to a
  * StackFrame or baseline JIT frame.
  */
 class JS_PUBLIC_API(JSAbstractFramePtr)
 {
     uintptr_t ptr_;
-    jsbytecode *pc_;
 
   protected:
     JSAbstractFramePtr()
-      : ptr_(0), pc_(nullptr)
+      : ptr_(0)
     { }
 
   public:
-    JSAbstractFramePtr(void *raw, jsbytecode *pc);
+    explicit JSAbstractFramePtr(void *raw);
 
     uintptr_t raw() const { return ptr_; }
-    jsbytecode *pc() const { return pc_; }
 
     operator bool() const { return !!ptr_; }
 
     JSObject *scopeChain(JSContext *cx);
     JSObject *callObject(JSContext *cx);
 
     JSFunction *maybeFun();
     JSScript *script();
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -136,16 +136,24 @@ EmitCheck(ExclusiveContext *cx, Bytecode
     jsbytecode dummy = 0;
     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;
+}
+
 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) {
@@ -154,18 +162,36 @@ 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;
     }
 
-    int nuses = StackUses(nullptr, pc);
-    int ndefs = StackDefs(nullptr, pc);
+    /*
+     * 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();
+    } else if (op == JSOP_ENTERLET0) {
+        nuses = ndefs = CurrentBlock(bce->topStmt).slotCount();
+    } else if (op == JSOP_ENTERLET1) {
+        nuses = ndefs = CurrentBlock(bce->topStmt).slotCount() + 1;
+    } else if (op == JSOP_ENTERLET2) {
+        nuses = ndefs = CurrentBlock(bce->topStmt).slotCount() + 2;
+    } else {
+        nuses = StackUses(nullptr, pc);
+        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;
 }
 
@@ -491,48 +517,29 @@ FlushPops(ExclusiveContext *cx, Bytecode
 static bool
 PopIterator(ExclusiveContext *cx, BytecodeEmitter *bce)
 {
     if (Emit1(cx, bce, JSOP_ENDITER) < 0)
         return false;
     return true;
 }
 
-namespace {
-
-class NonLocalExitScope {
-    ExclusiveContext *cx;
-    BytecodeEmitter *bce;
-    const uint32_t savedScopeIndex;
-    const int savedDepth;
-
-    NonLocalExitScope(const NonLocalExitScope &) MOZ_DELETE;
-
-  public:
-    explicit NonLocalExitScope(ExclusiveContext *cx_, BytecodeEmitter *bce_)
-      : cx(cx_),
-        bce(bce_),
-        savedScopeIndex(bce->blockScopeList.length()),
-        savedDepth(bce->stackDepth) {}
-
-    ~NonLocalExitScope() {
-        for (uint32_t n = savedScopeIndex; n < bce->blockScopeList.length(); n++)
-            bce->blockScopeList.recordEnd(n, bce->offset());
-        bce->stackDepth = savedDepth;
-    }
-
-    bool prepareForNonLocalJump(StmtInfoBCE *toStmt);
-};
-
 /*
  * Emit additional bytecode(s) for non-local jumps.
  */
-bool
-NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt)
-{
+static bool
+EmitNonLocalJumpFixup(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *toStmt)
+{
+    /*
+     * The non-local jump fixup we emit will unbalance bce->stackDepth, because
+     * the fixup replicates balanced code such as JSOP_LEAVEWITH emitted at the
+     * end of a with statement, so we save bce->stackDepth here and restore it
+     * just before a successful return.
+     */
+    int depth = bce->stackDepth;
     int npops = 0;
 
 #define FLUSH_POPS() if (npops && !FlushPops(cx, bce, &npops)) return false
 
     for (StmtInfoBCE *stmt = bce->topStmt; stmt != toStmt; stmt = stmt->down) {
         switch (stmt->type) {
           case STMT_FINALLY:
             FLUSH_POPS();
@@ -564,47 +571,57 @@ NonLocalExitScope::prepareForNonLocalJum
              */
             npops += 2;
             break;
 
           default:;
         }
 
         if (stmt->isBlockScope) {
-            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;
-            if (blockObj.needsClone()) {
-                if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0)
+            FLUSH_POPS();
+            unsigned blockObjCount = stmt->blockObj->slotCount();
+            if (stmt->isForLetBlock) {
+                /*
+                 * For a for-let-in/of statement, pushing/popping the block is
+                 * interleaved with JSOP_(END)ITER. Just handle both together
+                 * here and skip over the enclosing STMT_FOR_IN_LOOP.
+                 */
+                unsigned popCount = blockObjCount;
+                stmt = stmt->down;
+                if (stmt == toStmt)
+                    break;
+                if (Emit1(cx, bce, JSOP_LEAVEFORLETIN) < 0)
                     return false;
+                if (stmt->type == STMT_FOR_OF_LOOP) {
+                    popCount += 2;
+                } else {
+                    JS_ASSERT(stmt->type == STMT_FOR_IN_LOOP);
+                    if (!PopIterator(cx, bce))
+                        return false;
+                }
+                EMIT_UINT16_IMM_OP(JSOP_POPN, popCount);
+            } else {
+                /* There is a Block object with locals on the stack to pop. */
+                EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObjCount);
             }
-            npops += blockObj.slotCount();
         }
     }
 
     FLUSH_POPS();
+    bce->stackDepth = depth;
     return true;
 
 #undef FLUSH_POPS
 }
 
-}  // anonymous namespace
-
 static ptrdiff_t
 EmitGoto(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *toStmt, ptrdiff_t *lastp,
          SrcNoteType noteType = SRC_NULL)
 {
-    NonLocalExitScope nle(cx, bce);
-
-    if (!nle.prepareForNonLocalJump(toStmt))
+    if (!EmitNonLocalJumpFixup(cx, bce, toStmt))
         return -1;
 
     if (noteType != SRC_NULL) {
         if (NewSrcNote(cx, bce, noteType) < 0)
             return -1;
     }
 
     return EmitBackPatchOp(cx, bce, lastp);
@@ -651,220 +668,57 @@ EnclosingStaticScope(BytecodeEmitter *bc
     if (!bce->sc->isFunctionBox()) {
         JS_ASSERT(!bce->parent);
         return nullptr;
     }
 
     return bce->sc->asFunctionBox()->function();
 }
 
-// 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
-
+// Push a block scope statement and link blockObj into bce->blockChain.
 static bool
-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);
-
-// ~ 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)
+PushBlockScopeBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
+                  ptrdiff_t top)
 {
     StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
 
-    uint32_t scopeObjectIndex = bce->objectList.add(objbox);
+    PushStatementBCE(bce, stmt, STMT_BLOCK, top);
+
+    unsigned 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);
-
-    if (!ComputeAliasedSlots(cx, bce, blockObj))
-        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;
 }
 
 // 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)
 {
     StmtInfoBCE *stmt = bce->topStmt;
     if (!stmt->isTrying() &&
         (!BackPatch(cx, bce, stmt->breaks, bce->code().end(), JSOP_GOTO) ||
          !BackPatch(cx, bce, stmt->continues, bce->code(stmt->update), JSOP_GOTO)))
     {
         return false;
     }
 
+    if (stmt->isBlockScope)
+        bce->blockScopeList.recordEnd(stmt->blockScopeIndex, bce->offset());
+
     FinishPopStatement(bce);
     return true;
 }
 
 static bool
-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
-
-    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());
-
-    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;
     JS_ASSERT(len == size_t(js_CodeSpec[op].length));
     ptrdiff_t offset = EmitCheck(cx, bce, len);
     if (offset < 0)
         return false;
 
@@ -1159,49 +1013,125 @@ BytecodeEmitter::isAliasedName(ParseNode
     if (dn->pn_cookie.level() != script->staticLevel)
         return true;
 
     switch (dn->kind()) {
       case Definition::LET:
         /*
          * There are two ways to alias a let variable: nested functions and
          * dynamic scope operations. (This is overly conservative since the
-         * bindingsAccessedDynamically flag, checked by allLocalsAliased, is
-         * function-wide.)
-         *
-         * In addition all locals in generators are marked as aliased, to ensure
-         * that they are allocated on scope chains instead of on the stack.  See
-         * the definition of SharedContext::allLocalsAliased.
+         * bindingsAccessedDynamically flag is function-wide.)
          */
-        return dn->isClosed() || sc->allLocalsAliased();
+        return dn->isClosed() || sc->bindingsAccessedDynamically();
       case Definition::ARG:
         /*
          * Consult the bindings, since they already record aliasing. We might
          * be tempted to use the same definition as VAR/CONST/LET, but there is
          * a problem caused by duplicate arguments: only the last argument with
          * a given name is aliased. This is necessary to avoid generating a
          * shape for the call object with with more than one name for a given
          * slot (which violates internal engine invariants). All this means that
-         * the '|| sc->allLocalsAliased()' disjunct is incorrect since it will
-         * mark both parameters in function(x,x) as aliased.
+         * the '|| sc->bindingsAccessedDynamically' disjunct is incorrect since
+         * it will mark both parameters in function(x,x) as aliased.
          */
         return script->formalIsAliased(pn->pn_cookie.slot());
       case Definition::VAR:
       case Definition::CONST:
-        JS_ASSERT_IF(sc->allLocalsAliased(), script->varIsAliased(pn->pn_cookie.slot()));
         return script->varIsAliased(pn->pn_cookie.slot());
       case Definition::PLACEHOLDER:
       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;
+}
+
+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->bindingsAccessedDynamically());
+            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));
+    }
+
+    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)
 {
     /*
@@ -2363,57 +2293,64 @@ 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.
      */
-    StaticBlockObject *blockObj = nullptr;
+    uint32_t blockObjCount = 0;
     if (pn2->isKind(PNK_LEXICALSCOPE)) {
-        blockObj = &pn2->pn_objbox->object->as<StaticBlockObject>();
-        for (uint32_t i = 0; i < blockObj->slotCount(); ++i) {
+        blockObjCount = pn2->pn_objbox->object->as<StaticBlockObject>().slotCount();
+        for (uint32_t i = 0; i < blockObjCount; ++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 (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 1))
+        if (!PushBlockScopeBCE(bce, &stmtInfo, pn2->pn_objbox, -1))
             return false;
         stmtInfo.type = STMT_SWITCH;
-        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);
+        if (!EmitEnterBlock(cx, bce, pn2, JSOP_ENTERLET1))
+            return false;
     }
 
     /* 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;
     }
@@ -2675,24 +2612,21 @@ 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))
-            return false;
-        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount());
-    } else {
-        if (!PopStatementBCE(cx, bce))
-            return false;
-    }
+    if (!PopStatementBCE(cx, bce))
+        return false;
+
+    if (pn->pn_right->isKind(PNK_LEXICALSCOPE))
+        EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObjCount);
 
     return true;
 }
 
 bool
 BytecodeEmitter::isRunOnceLambda()
 {
     // The run once lambda flags set by the parser are approximate, and we look
@@ -3196,18 +3130,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);
-        uint32_t slot = i;
-        if (!AdjustBlockSlot(cx, bce, &slot))
+        int slot = AdjustBlockSlot(cx, bce, i);
+        if (slot < 0)
             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;
@@ -3824,16 +3758,18 @@ class EmitLevelManager
     ~EmitLevelManager() { bce->emitLevel--; }
 };
 
 } /* anonymous namespace */
 
 static bool
 EmitCatch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
+    ptrdiff_t guardJump;
+
     /*
      * Morph STMT_BLOCK to STMT_CATCH, note the block entry code offset,
      * and save the block object atom.
      */
     StmtInfoBCE *stmt = bce->topStmt;
     JS_ASSERT(stmt->type == STMT_BLOCK && stmt->isBlockScope);
     stmt->type = STMT_CATCH;
 
@@ -3872,225 +3808,248 @@ EmitCatch(ExclusiveContext *cx, Bytecode
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
         break;
 
       default:
         JS_ASSERT(0);
     }
 
-    // If there is a guard expression, emit it and arrange to jump to the next
-    // catch block if the guard expression is false.
+    /* Emit the guard expression, if there is one. */
     if (pn->pn_kid2) {
         if (!EmitTree(cx, bce, pn->pn_kid2))
             return false;
 
-        // If the guard expression is false, fall through, pop the block scope,
-        // and jump to the next catch block.  Otherwise jump over that code and
-        // pop the dupped exception.
-        ptrdiff_t guardCheck = EmitJump(cx, bce, JSOP_IFNE, 0);
-        if (guardCheck < 0)
-            return false;
-
-        {
-            NonLocalExitScope nle(cx, bce);
-
-            // Move exception back to cx->exception to prepare for
-            // the next catch.
-            if (Emit1(cx, bce, JSOP_THROWING) < 0)
-                return false;
-
-            // Leave the scope for this catch block.
-            if (!nle.prepareForNonLocalJump(stmt))
-                return false;
-
-            // Jump to the next handler.  The jump target is backpatched by EmitTry.
-            ptrdiff_t guardJump = EmitJump(cx, bce, JSOP_GOTO, 0);
-            if (guardJump < 0)
-                return false;
-            stmt->guardJump() = guardJump;
-        }
-
-        // Back to normal control flow.
-        SetJumpOffsetAt(bce, guardCheck);
-
-        // Pop duplicated exception object as we no longer need it.
+        /* ifeq <next block> */
+        guardJump = EmitJump(cx, bce, JSOP_IFEQ, 0);
+        if (guardJump < 0)
+            return false;
+        stmt->guardJump() = guardJump;
+
+        /* Pop duplicated exception object as we no longer need it. */
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
     }
 
     /* Emit the catch body. */
     return EmitTree(cx, bce, pn->pn_kid3);
 }
 
-// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the
-// comment on EmitSwitch.
-//
+/*
+ * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
+ * the comment on EmitSwitch.
+ */
 MOZ_NEVER_INLINE static bool
 EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfoBCE stmtInfo(cx);
 
-    // Push stmtInfo to track jumps-over-catches and gosubs-to-finally
-    // for later fixup.
-    //
-    // When a finally block is active (STMT_FINALLY in our parse context),
-    // non-local jumps (including jumps-over-catches) result in a GOSUB
-    // being written into the bytecode stream and fixed-up later (c.f.
-    // EmitBackPatchOp and BackPatch).
-    //
+    /*
+     * Push stmtInfo to track jumps-over-catches and gosubs-to-finally
+     * for later fixup.
+     *
+     * When a finally block is active (STMT_FINALLY in our parse context),
+     * non-local jumps (including jumps-over-catches) result in a GOSUB
+     * being written into the bytecode stream and fixed-up later (c.f.
+     * EmitBackPatchOp and BackPatch).
+     */
     PushStatementBCE(bce, &stmtInfo, pn->pn_kid3 ? STMT_FINALLY : STMT_TRY, bce->offset());
 
-    // Since an exception can be thrown at any place inside the try block,
-    // we need to restore the stack and the scope chain before we transfer
-    // the control to the exception handler.
-    //
-    // For that we store in a try note associated with the catch or
-    // finally block the stack depth upon the try entry. The interpreter
-    // uses this depth to properly unwind the stack and the scope chain.
-    //
+    /*
+     * Since an exception can be thrown at any place inside the try block,
+     * we need to restore the stack and the scope chain before we transfer
+     * the control to the exception handler.
+     *
+     * For that we store in a try note associated with the catch or
+     * finally block the stack depth upon the try entry. The interpreter
+     * uses this depth to properly unwind the stack and the scope chain.
+     */
     int depth = bce->stackDepth;
 
-    // Record the try location, then emit the try block.
+    /* Mark try location for decompilation, then emit try block. */
     ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_TRY);
     if (noteIndex < 0 || Emit1(cx, bce, JSOP_TRY) < 0)
         return false;
     ptrdiff_t tryStart = bce->offset();
     if (!EmitTree(cx, bce, pn->pn_kid1))
         return false;
     JS_ASSERT(depth == bce->stackDepth);
 
-    // GOSUB to finally, if present.
+    /* GOSUB to finally, if present. */
     if (pn->pn_kid3) {
         if (EmitBackPatchOp(cx, bce, &stmtInfo.gosubs()) < 0)
             return false;
     }
 
-    // Source note points to the jump at the end of the try block.
+    /* Source note points to the jump at the end of the try block. */
     if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, bce->offset() - tryStart + JSOP_TRY_LENGTH))
         return false;
 
-    // Emit jump over catch and/or finally.
+    /* Emit (hidden) jump over catch and/or finally. */
     ptrdiff_t catchJump = -1;
     if (EmitBackPatchOp(cx, bce, &catchJump) < 0)
         return false;
 
     ptrdiff_t tryEnd = bce->offset();
 
-    // If this try has a catch block, emit it.
+    /* If this try has a catch block, emit it. */
+    ParseNode *lastCatch = nullptr;
     if (ParseNode *pn2 = pn->pn_kid2) {
-        // The emitted code for a catch block looks like:
-        //
-        // 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
-        //   goto <next catch block>
-        //   POST: pop
-        // < catch block contents >
-        // debugleaveblock
-        // [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.
-        //
+        unsigned 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
+         * exception
+         * [dup]                               only if catchguard
+         * setlocalpop <slot>                  or destructuring code
+         * [< catchguard code >]               if there's a catchguard
+         * [ifeq <offset to next catch block>]         " "
+         * [pop]                               only if catchguard
+         * < catch block contents >
+         * leaveblock
+         * goto <end of catch blocks>          non-local; finally applies
+         *
+         * If there's no catch block without a catchguard, the last
+         * <offset to 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) {
+            ptrdiff_t guardJump;
+
             JS_ASSERT(bce->stackDepth == depth);
-
-            // Emit the lexical scope and catch body.
+            guardJump = stmtInfo.guardJump();
+            if (guardJump != -1) {
+                /* Fix up and clean up previous catch block. */
+                SetJumpOffsetAt(bce, guardJump);
+
+                /*
+                 * Account for JSOP_ENTERBLOCK (whose block object count
+                 * is saved below) and pushed exception object that we
+                 * still have after the jumping from the previous guard.
+                 */
+                bce->stackDepth = depth + count + 1;
+
+                /*
+                 * Move exception back to cx->exception to prepare for
+                 * the next catch.
+                 */
+                if (Emit1(cx, bce, JSOP_THROWING) < 0)
+                    return false;
+                EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
+                JS_ASSERT(bce->stackDepth == depth);
+            }
+
+            /*
+             * Emit the lexical scope and catch body.  Save the catch's
+             * block object population via count, for use when targeting
+             * guardJump at the next catch (the guard mismatch case).
+             */
             JS_ASSERT(pn3->isKind(PNK_LEXICALSCOPE));
+            count = pn3->pn_objbox->object->as<StaticBlockObject>().slotCount();
             if (!EmitTree(cx, bce, pn3))
                 return false;
 
-            // gosub <finally>, if required.
+            /* gosub <finally>, if required */
             if (pn->pn_kid3) {
                 if (EmitBackPatchOp(cx, bce, &stmtInfo.gosubs()) < 0)
                     return false;
                 JS_ASSERT(bce->stackDepth == depth);
             }
 
-            // Jump over the remaining catch blocks.  This will get fixed
-            // up to jump to after catch/finally.
+            /*
+             * Jump over the remaining catch blocks.  This will get fixed
+             * up to jump to after catch/finally.
+             */
             if (EmitBackPatchOp(cx, bce, &catchJump) < 0)
                 return false;
 
-            // If this catch block had a guard clause, patch the guard jump to
-            // come here.
-            if (stmtInfo.guardJump() != -1) {
-                SetJumpOffsetAt(bce, stmtInfo.guardJump());
-                stmtInfo.guardJump() = -1;
-
-                // If this catch block is the last one, rethrow, delegating
-                // execution of any finally block to the exception handler.
-                if (!pn3->pn_next) {
-                    if (Emit1(cx, bce, JSOP_EXCEPTION) < 0)
-                        return false;
-                    if (Emit1(cx, bce, JSOP_THROW) < 0)
-                        return false;
-                }
-            }
+            /*
+             * Save a pointer to the last catch node to handle try-finally
+             * and try-catch(guard)-finally special cases.
+             */
+            lastCatch = pn3->expr();
         }
     }
 
+    /*
+     * Last catch guard jumps to the rethrow code sequence if none of the
+     * guards match. Target guardJump at the beginning of the rethrow
+     * sequence, just in case a guard expression throws and leaves the
+     * stack unbalanced.
+     */
+    if (lastCatch && lastCatch->pn_kid2) {
+        SetJumpOffsetAt(bce, stmtInfo.guardJump());
+
+        /* Sync the stack to take into account pushed exception. */
+        JS_ASSERT(bce->stackDepth == depth);
+        bce->stackDepth = depth + 1;
+
+        /*
+         * Rethrow the exception, delegating executing of finally if any
+         * to the exception handler.
+         */
+        if (Emit1(cx, bce, JSOP_THROW) < 0)
+            return false;
+    }
+
     JS_ASSERT(bce->stackDepth == depth);
 
-    // Emit the finally handler, if there is one.
-    ptrdiff_t finallyStart = 0;
+    /* Emit finally handler if any. */
+    ptrdiff_t finallyStart = 0;   /* to quell GCC uninitialized warnings */
     if (pn->pn_kid3) {
-        // Fix up the gosubs that might have been emitted before non-local
-        // jumps to the finally code.
+        /*
+         * Fix up the gosubs that might have been emitted before non-local
+         * jumps to the finally code.
+         */
         if (!BackPatch(cx, bce, stmtInfo.gosubs(), bce->code().end(), JSOP_GOSUB))
             return false;
 
         finallyStart = bce->offset();
 
-        // Indicate that we're emitting a subroutine body.
+        /* Indicate that we're emitting a subroutine body. */
         stmtInfo.type = STMT_SUBROUTINE;
         if (!UpdateSourceCoordNotes(cx, bce, pn->pn_kid3->pn_pos.begin))
             return false;
         if (Emit1(cx, bce, JSOP_FINALLY) < 0 ||
             !EmitTree(cx, bce, pn->pn_kid3) ||
             Emit1(cx, bce, JSOP_RETSUB) < 0)
         {
             return false;
         }
         JS_ASSERT(bce->stackDepth == depth);
     }
     if (!PopStatementBCE(cx, bce))
         return false;
 
-    // ReconstructPCStack needs a NOP here to mark the end of the last catch block.
+    /* ReconstructPCStack needs a NOP here to mark the end of the last catch block. */
     if (Emit1(cx, bce, JSOP_NOP) < 0)
         return false;
 
-    // Fix up the end-of-try/catch jumps to come here.
+    /* Fix up the end-of-try/catch jumps to come here. */
     if (!BackPatch(cx, bce, catchJump, bce->code().end(), JSOP_GOTO))
         return false;
 
-    // Add the try note last, to let post-order give us the right ordering
-    // (first to last for a given nesting level, inner to outer by level).
+    /*
+     * Add the try note last, to let post-order give us the right ordering
+     * (first to last for a given nesting level, inner to outer by level).
+     */
     if (pn->pn_kid2 && !bce->tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd))
         return false;
 
-    // If we've got a finally, mark try+catch region with additional
-    // trynote to catch exceptions (re)thrown from a catch block or
-    // for the try{}finally{} case.
+    /*
+     * If we've got a finally, mark try+catch region with additional
+     * trynote to catch exceptions (re)thrown from a catch block or
+     * for the try{}finally{} case.
+     */
     if (pn->pn_kid3 && !bce->tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart))
         return false;
 
     return true;
 }
 
 static bool
 EmitIf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
@@ -4188,23 +4147,21 @@ EmitIf(ExclusiveContext *cx, BytecodeEmi
  *  evaluate b        +1
  *  dup               +1
  *  destructure y
  *  pick 1
  *  dup               +1
  *  destructure z
  *  pick 1
  *  pop               -1
- *  pushblockscope (if needed)
+ *  enterlet0
  *  evaluate e        +1
- *  debugleaveblock
- *  popblockscope (if needed)
- *  popnv 3           -3
+ *  leaveblockexpr    -3
  *
- * Note that, since pushblockscope simply changes fp->scopeChain and does not
+ * Note that, since enterlet0 simply changes fp->blockChain 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
@@ -4226,64 +4183,62 @@ 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 (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, 0))
+    if (!PushBlockScopeBCE(bce, &stmtInfo, letBody->pn_objbox, bce->offset()))
+        return false;
+
+    DebugOnly<ptrdiff_t> bodyBegin = bce->offset();
+    if (!EmitEnterBlock(cx, bce, letBody, JSOP_ENTERLET0))
         return false;
 
     if (!EmitTree(cx, bce, letBody->pn_expr))
         return false;
 
-    if (!LeaveBlockScope(cx, bce))
-        return false;
-
     JSOp leaveOp = letBody->getOp();
-    JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV);
+    JS_ASSERT(leaveOp == JSOP_LEAVEBLOCK || leaveOp == JSOP_LEAVEBLOCKEXPR);
     EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount());
 
-    return true;
+    DebugOnly<ptrdiff_t> bodyEnd = bce->offset();
+    JS_ASSERT(bodyEnd > bodyBegin);
+
+    return PopStatementBCE(cx, bce);
 }
 
 /*
  * 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_POPN);
+    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();
-
-    for (size_t n = 0; n < slots; ++n) {
-        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
-            return false;
-    }
-
-    if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0))
+    if (!PushBlockScopeBCE(bce, &stmtInfo, objbox, bce->offset()))
+        return false;
+
+    if (!EmitEnterBlock(cx, bce, pn, JSOP_ENTERBLOCK))
         return false;
 
     if (!EmitTree(cx, bce, pn->pn_expr))
         return false;
 
-    if (!LeaveBlockScope(cx, bce))
-        return false;
-
-    EMIT_UINT16_IMM_OP(JSOP_POPN, slots);
-
-    return true;
+    EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, slots);
+
+    return PopStatementBCE(cx, bce);
 }
 
 static bool
 EmitWith(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfoBCE stmtInfo(cx);
     if (!EmitTree(cx, bce, pn->pn_left))
         return false;
@@ -4296,16 +4251,19 @@ EmitWith(ExclusiveContext *cx, BytecodeE
     if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0)
         return false;
     return PopStatementBCE(cx, bce);
 }
 
 static bool
 EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
+    StmtInfoBCE stmtInfo(cx);
+    PushStatementBCE(bce, &stmtInfo, STMT_FOR_OF_LOOP, top);
+
     ParseNode *forHead = pn->pn_left;
     ParseNode *forBody = pn->pn_right;
 
     ParseNode *pn1 = forHead->pn_kid1;
     bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
     JS_ASSERT_IF(letDecl, pn1->isLet());
 
     Rooted<StaticBlockObject*>
@@ -4350,22 +4308,22 @@ 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 (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 2))
-            return false;
-    }
-
-    StmtInfoBCE stmtInfo(cx);
-    PushStatementBCE(bce, &stmtInfo, STMT_FOR_OF_LOOP, top);
+        if (!PushBlockScopeBCE(bce, &letStmt, pn1->pn_objbox, bce->offset()))
+            return false;
+        letStmt.isForLetBlock = true;
+        if (!EmitEnterBlock(cx, bce, pn1, JSOP_ENTERLET2))
+            return false;
+    }
 
     // Jump down to the loop condition to minimize overhead assuming at least
     // one iteration, as the other loop forms do.  Annotate so IonMonkey can
     // find the loop-closing jump.
     int noteIndex = NewSrcNote(cx, bce, SRC_FOR_OF);
     if (noteIndex < 0)
         return false;
     ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0);
@@ -4443,61 +4401,62 @@ EmitForOf(ExclusiveContext *cx, Bytecode
     if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, beq - jmp))
         return false;
 
     // Fixup breaks and continues.
     if (!PopStatementBCE(cx, bce))
         return false;
 
     if (letDecl) {
-        if (!LeaveBlockScope(cx, bce))
+        if (!PopStatementBCE(cx, bce))
+            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);
 
     return true;
 }
 
 static bool
 EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
+    StmtInfoBCE stmtInfo(cx);
+    PushStatementBCE(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
+
     ParseNode *forHead = pn->pn_left;
     ParseNode *forBody = pn->pn_right;
 
     ParseNode *pn1 = forHead->pn_kid1;
     bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
     JS_ASSERT_IF(letDecl, pn1->isLet());
 
     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 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:
+         * 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:
          *
          *   push x N
          *   eval rhs
          *   iter
-         *   pushblockscope (if needed)
+         *   enterlet1
          *   goto
          *     ... loop body
          *   ifne
-         *   debugleaveblock
-         *   popblockscope (if needed)
+         *   leaveforinlet
          *   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;
         }
     }
 
     /*
@@ -4526,22 +4485,22 @@ 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 (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 1))
-            return false;
-    }
-
-    StmtInfoBCE stmtInfo(cx);
-    PushStatementBCE(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
+        if (!PushBlockScopeBCE(bce, &letStmt, pn1->pn_objbox, bce->offset()))
+            return false;
+        letStmt.isForLetBlock = true;
+        if (!EmitEnterBlock(cx, bce, pn1, JSOP_ENTERLET1))
+            return false;
+    }
 
     /* Annotate so IonMonkey can find the loop-closing jump. */
     int noteIndex = NewSrcNote(cx, bce, SRC_FOR_IN);
     if (noteIndex < 0)
         return false;
 
     /*
      * Jump down to the loop condition to minimize overhead assuming at
@@ -4596,30 +4555,34 @@ 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;
 
-    // Fix up breaks and continues.
+    /* Fixup breaks and continues before JSOP_ITER (and JSOP_LEAVEFORINLET). */
     if (!PopStatementBCE(cx, bce))
         return false;
 
+    if (letDecl) {
+        if (!PopStatementBCE(cx, bce))
+            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) {
-        if (!LeaveBlockScope(cx, bce))
-            return false;
+    if (letDecl)
         EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount);
-    }
 
     return true;
 }
 
 static bool
 EmitNormalFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
     StmtInfoBCE stmtInfo(cx);
@@ -5113,22 +5076,18 @@ EmitReturn(ExclusiveContext *cx, Bytecod
      *
      * In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an
      * extra JSOP_RETRVAL after the fixups.
      */
     ptrdiff_t top = bce->offset();
 
     if (Emit1(cx, bce, JSOP_RETURN) < 0)
         return false;
-
-    NonLocalExitScope nle(cx, bce);
-
-    if (!nle.prepareForNonLocalJump(nullptr))
-        return false;
-
+    if (!EmitNonLocalJumpFixup(cx, bce, nullptr))
+        return false;
     if (top + JSOP_RETURN_LENGTH != bce->offset()) {
         bce->code()[top] = JSOP_SETRVAL;
         if (Emit1(cx, bce, JSOP_RETRVAL) < 0)
             return false;
     }
 
     return true;
 }
@@ -6477,26 +6436,28 @@ 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;
-        uint32_t slot = bce->arrayCompDepth;
-        if (!AdjustBlockSlot(cx, bce, &slot))
+        slot = AdjustBlockSlot(cx, bce, bce->arrayCompDepth);
+        if (slot < 0)
             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())
@@ -6886,26 +6847,16 @@ CGObjectList::finish(ObjectArray *array)
     do {
         --cursor;
         JS_ASSERT(!*cursor);
         *cursor = objbox->object;
     } while ((objbox = objbox->emitLink) != nullptr);
     JS_ASSERT(cursor == array->vector);
 }
 
-ObjectBox*
-CGObjectList::find(uint32_t index)
-{
-    JS_ASSERT(index < length);
-    ObjectBox *box = lastbox;
-    for (unsigned n = length - 1; n > index; n--)
-        box = box->emitLink;
-    return box;
-}
-
 bool
 CGTryNoteList::append(JSTryNoteKind kind, unsigned stackDepth, size_t start, size_t end)
 {
     JS_ASSERT(unsigned(uint16_t(stackDepth)) == stackDepth);
     JS_ASSERT(start <= end);
     JS_ASSERT(size_t(uint32_t(start)) == start);
     JS_ASSERT(size_t(uint32_t(end)) == end);
 
@@ -6934,40 +6885,16 @@ CGBlockScopeList::append(uint32_t scopeO
     mozilla::PodZero(&note);
 
     note.index = scopeObject;
     note.start = offset;
 
     return list.append(note);
 }
 
-uint32_t
-CGBlockScopeList::findEnclosingScope(uint32_t index)
-{
-    JS_ASSERT(index < length());
-    JS_ASSERT(list[index].index != BlockScopeNote::NoBlockScopeIndex);
-
-    uint32_t pos = list[index].start;
-    while (index--) {
-        JS_ASSERT(list[index].start <= pos);
-        if (list[index].length == 0) {
-            // We are looking for the nearest enclosing live scope.  If the
-            // scope contains POS, it should still be open, so its length should
-            // be zero.
-            return list[index].index;
-        } else {
-            // Conversely, if the length is not zero, it should not contain
-            // POS.
-            JS_ASSERT(list[index].start + list[index].length <= pos);
-        }
-    }
-
-    return BlockScopeNote::NoBlockScopeIndex;
-}
-
 void
 CGBlockScopeList::recordEnd(uint32_t index, uint32_t offset)
 {
     JS_ASSERT(index < length());
     JS_ASSERT(offset >= list[index].start);
     JS_ASSERT(list[index].length == 0);
 
     list[index].length = offset - list[index].start;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -41,34 +41,32 @@ struct CGObjectList {
     uint32_t            length;     /* number of emitted so far objects */
     ObjectBox           *lastbox;   /* last emitted object */
 
     CGObjectList() : length(0), lastbox(nullptr) {}
 
     unsigned add(ObjectBox *objbox);
     unsigned indexOf(JSObject *obj);
     void finish(ObjectArray *array);
-    ObjectBox* find(uint32_t index);
 };
 
 struct CGTryNoteList {
     Vector<JSTryNote> list;
     CGTryNoteList(ExclusiveContext *cx) : list(cx) {}
 
     bool append(JSTryNoteKind kind, unsigned stackDepth, size_t start, size_t end);
     size_t length() const { return list.length(); }
     void finish(TryNoteArray *array);
 };
 
 struct CGBlockScopeList {
     Vector<BlockScopeNote> list;
     CGBlockScopeList(ExclusiveContext *cx) : list(cx) {}
 
     bool append(uint32_t scopeObject, uint32_t offset);
-    uint32_t findEnclosingScope(uint32_t index);
     void recordEnd(uint32_t index, uint32_t offset);
     size_t length() const { return list.length(); }
     void finish(BlockScopeArray *array);
 };
 
 struct StmtInfoBCE;
 
 // Use zero inline elements because these go on the stack and affect how many
--- 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_POPN));
+    JS_ASSERT(block->isOp(JSOP_LEAVEBLOCK));
     if (leaveBlockExpr)
-        block->setOp(JSOP_POPNV);
+        block->setOp(JSOP_LEAVEBLOCKEXPR);
     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_POPN);
+    pn->setOp(JSOP_LEAVEBLOCK);
     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_POPN or JSOP_POPNV
+ * PNK_LEXICALSCOPE name    pn_op: JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR
  *                          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
@@ -273,17 +273,17 @@ AppendPackedBindings(const ParseContext<
 
         /*
          * Bindings::init does not check for duplicates so we must ensure that
          * only one binding with a given name is marked aliased. pc->decls
          * maintains the canonical definition for each name, so use that.
          */
         JS_ASSERT_IF(dn->isClosed(), pc->decls().lookupFirst(name) == dn);
         bool aliased = dn->isClosed() ||
-                       (pc->sc->allLocalsAliased() &&
+                       (pc->sc->bindingsAccessedDynamically() &&
                         pc->decls().lookupFirst(name) == dn);
 
         *dst = Binding(name, kind, aliased);
     }
 }
 
 template <typename ParseHandler>
 bool
@@ -3226,16 +3226,17 @@ 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;
 }
@@ -3595,17 +3596,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_POPN);
+            pn1->setOp(JSOP_LEAVEBLOCK);
             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);
@@ -3631,18 +3632,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_POPNV,
-                     pn->pn_expr->isOp(JSOP_POPN));
+        JS_ASSERT_IF(pn && pn->isKind(PNK_LET) && pn->pn_expr->getOp() != JSOP_LEAVEBLOCK,
+                     pn->isOp(JSOP_NOP));
     } else 
         pn = letDeclaration();
     return pn;
 }
 
 template <>
 SyntaxParseHandler::Node
 Parser<SyntaxParseHandler>::letStatement()
@@ -6712,31 +6713,26 @@ 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_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.
+         *   JSOP_ENTERBLOCK <o> ... JSOP_LEAVEBLOCK <n>
          *
          * 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. 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, 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.
          *
          * 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/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -195,18 +195,16 @@ class SharedContext
     bool hasExplicitUseStrict()        const { return anyCxFlags.hasExplicitUseStrict; }
     bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; }
     bool hasDebuggerStatement()        const { return anyCxFlags.hasDebuggerStatement; }
 
     void setExplicitUseStrict()           { anyCxFlags.hasExplicitUseStrict        = true; }
     void setBindingsAccessedDynamically() { anyCxFlags.bindingsAccessedDynamically = true; }
     void setHasDebuggerStatement()        { anyCxFlags.hasDebuggerStatement        = true; }
 
-    inline bool allLocalsAliased();
-
     // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors.
     bool needStrictChecks() {
         return strict || extraWarnings;
     }
 };
 
 class GlobalSharedContext : public SharedContext
 {
@@ -304,40 +302,27 @@ class FunctionBox : public ObjectBox, pu
         startColumn = tokenStream.getColumn();
     }
 
     bool isHeavyweight()
     {
         // Note: this should be kept in sync with JSFunction::isHeavyweight().
         return bindings.hasAnyAliasedBindings() ||
                hasExtensibleScope() ||
-               needsDeclEnvObject() ||
-               isGenerator();
+               needsDeclEnvObject();
     }
 };
 
 inline FunctionBox *
 SharedContext::asFunctionBox()
 {
     JS_ASSERT(isFunctionBox());
     return static_cast<FunctionBox*>(this);
 }
 
-// In generators, we treat all locals as aliased so that they get stored on the
-// heap.  This way there is less information to copy off the stack when
-// suspending, and back on when resuming.  It also avoids the need to create and
-// invalidate DebugScope proxies for unaliased locals in a generator frame, as
-// the generator frame will be copied out to the heap and released only by GC.
-inline bool
-SharedContext::allLocalsAliased()
-{
-    return bindingsAccessedDynamically() || (isFunctionBox() && asFunctionBox()->isGenerator());
-}
-
-
 /*
  * NB: If you add a new type of statement that is a scope, add it between
  * STMT_WITH and STMT_CATCH, or you will break StmtInfoBase::linksScope. If you
  * add a non-looping statement type, add it before STMT_DO_LOOP or you will
  * break StmtInfoBase::isLoop().
  *
  * Also remember to keep the statementName array in BytecodeEmitter.cpp in
  * sync.
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -606,16 +606,20 @@ InitFromBailout(JSContext *cx, HandleScr
     // Do not need to initialize scratchValue or returnValue fields in BaselineFrame.
 
     blFrame->setFlags(flags);
 
     // initArgsObjUnchecked modifies the frame's flags, so call it after setFlags.
     if (argsObj)
         blFrame->initArgsObjUnchecked(*argsObj);
 
+    // Ion doesn't compile code with try/catch, so the block object will always be
+    // null.
+    blFrame->setBlockChainNull();
+
     if (fun) {
         // The unpacked thisv and arguments should overwrite the pushed args present
         // in the calling frame.
         Value thisv = iter.read();
         IonSpew(IonSpew_BaselineBailouts, "      Is function!");
         IonSpew(IonSpew_BaselineBailouts, "      thisv=%016llx", *((uint64_t *) &thisv));
 
         size_t thisvOffset = builder.framePushed() + IonJSFrameLayout::offsetOfThis();
@@ -656,16 +660,17 @@ 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/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -501,30 +501,29 @@ BaselineCompiler::emitStackCheck(bool ea
 
     if (!callVM(CheckOverRecursedWithExtraInfo, phase))
         return false;
 
     masm.bind(&skipCall);
     return true;
 }
 
-typedef bool (*DebugPrologueFn)(JSContext *, BaselineFrame *, jsbytecode *, bool *);
+typedef bool (*DebugPrologueFn)(JSContext *, BaselineFrame *, bool *);
 static const VMFunction DebugPrologueInfo = FunctionInfo<DebugPrologueFn>(jit::DebugPrologue);
 
 bool
 BaselineCompiler::emitDebugPrologue()
 {
     if (!debugMode_)
         return true;
 
     // Load pointer to BaselineFrame in R0.
     masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     prepareVMCall();
-    pushArg(ImmPtr(pc));
     pushArg(R0.scratchReg());
     if (!callVM(DebugPrologueInfo))
         return false;
 
     // If the stub returns |true|, we have to return the value stored in the
     // frame's return value slot.
     Label done;
     masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done);
@@ -854,25 +853,16 @@ 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);
 
@@ -2551,64 +2541,115 @@ bool
 BaselineCompiler::emit_JSOP_RETSUB()
 {
     frame.popRegsAndSync(2);
 
     ICRetSub_Fallback::Compiler stubCompiler(cx);
     return emitOpIC(stubCompiler.getStub(&stubSpace_));
 }
 
-typedef bool (*PushBlockScopeFn)(JSContext *, BaselineFrame *, Handle<StaticBlockObject *>);
-static const VMFunction PushBlockScopeInfo = FunctionInfo<PushBlockScopeFn>(jit::PushBlockScope);
+typedef bool (*EnterBlockFn)(JSContext *, BaselineFrame *, Handle<StaticBlockObject *>);
+static const VMFunction EnterBlockInfo = FunctionInfo<EnterBlockFn>(jit::EnterBlock);
 
 bool
-BaselineCompiler::emit_JSOP_PUSHBLOCKSCOPE()
+BaselineCompiler::emitEnterBlock()
 {
     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(PushBlockScopeInfo);
+    return callVM(EnterBlockInfo);
 }
 
-typedef bool (*PopBlockScopeFn)(JSContext *, BaselineFrame *);
-static const VMFunction PopBlockScopeInfo = FunctionInfo<PopBlockScopeFn>(jit::PopBlockScope);
+bool
+BaselineCompiler::emit_JSOP_ENTERBLOCK()
+{
+    return emitEnterBlock();
+}
+
+bool
+BaselineCompiler::emit_JSOP_ENTERLET0()
+{
+    return emitEnterBlock();
+}
 
 bool
-BaselineCompiler::emit_JSOP_POPBLOCKSCOPE()
+BaselineCompiler::emit_JSOP_ENTERLET1()
+{
+    return emitEnterBlock();
+}
+
+bool
+BaselineCompiler::emit_JSOP_ENTERLET2()
+{
+    return emitEnterBlock();
+}
+
+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(PopBlockScopeInfo);
+    return callVM(LeaveBlockInfo);
 }
 
-typedef bool (*DebugLeaveBlockFn)(JSContext *, BaselineFrame *, jsbytecode *);
-static const VMFunction DebugLeaveBlockInfo = FunctionInfo<DebugLeaveBlockFn>(jit::DebugLeaveBlock);
+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_DEBUGLEAVEBLOCK()
+BaselineCompiler::emit_JSOP_LEAVEBLOCKEXPR()
 {
-    if (!debugMode_)
-        return true;
-
-    prepareVMCall();
-    masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
-    pushArg(ImmPtr(pc));
-    pushArg(R0.scratchReg());
-
-    return callVM(DebugLeaveBlockInfo);
+    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()
@@ -2644,34 +2685,33 @@ BaselineCompiler::emit_JSOP_DEBUGGER()
     {
         masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
         masm.jump(&return_);
     }
     masm.bind(&done);
     return true;
 }
 
-typedef bool (*DebugEpilogueFn)(JSContext *, BaselineFrame *, jsbytecode *, bool);
+typedef bool (*DebugEpilogueFn)(JSContext *, BaselineFrame *, bool);
 static const VMFunction DebugEpilogueInfo = FunctionInfo<DebugEpilogueFn>(jit::DebugEpilogue);
 
 bool
 BaselineCompiler::emitReturn()
 {
     if (debugMode_) {
         // Move return value into the frame's rval slot.
         masm.storeValue(JSReturnOperand, frame.addressOfReturnValue());
         masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags());
 
         // Load BaselineFrame pointer in R0.
         frame.syncStack(0);
         masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
         prepareVMCall();
         pushArg(Imm32(1));
-        pushArg(ImmPtr(pc));
         pushArg(R0.scratchReg());
         if (!callVM(DebugEpilogueInfo))
             return false;
 
         masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand);
     }
 
     // Only emit the jump if this JSOP_RETRVAL is not the last instruction.
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -22,17 +22,16 @@ 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)                \
@@ -141,19 +140,23 @@ namespace jit {
     _(JSOP_TYPEOF)             \
     _(JSOP_TYPEOFEXPR)         \
     _(JSOP_SETCALL)            \
     _(JSOP_THROW)              \
     _(JSOP_TRY)                \
     _(JSOP_FINALLY)            \
     _(JSOP_GOSUB)              \
     _(JSOP_RETSUB)             \
-    _(JSOP_PUSHBLOCKSCOPE)     \
-    _(JSOP_POPBLOCKSCOPE)      \
-    _(JSOP_DEBUGLEAVEBLOCK)    \
+    _(JSOP_ENTERBLOCK)         \
+    _(JSOP_ENTERLET0)          \
+    _(JSOP_ENTERLET1)          \
+    _(JSOP_ENTERLET2)          \
+    _(JSOP_LEAVEBLOCK)         \
+    _(JSOP_LEAVEBLOCKEXPR)     \
+    _(JSOP_LEAVEFORLETIN)      \
     _(JSOP_EXCEPTION)          \
     _(JSOP_DEBUGGER)           \
     _(JSOP_ARGUMENTS)          \
     _(JSOP_RUNONCE)            \
     _(JSOP_REST)               \
     _(JSOP_TOID)               \
     _(JSOP_TABLESWITCH)        \
     _(JSOP_ITER)               \
@@ -246,16 +249,19 @@ 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,32 +31,44 @@ inline void
 BaselineFrame::popOffScopeChain()
 {
     scopeChain_ = &scopeChain_->as<ScopeObject>().enclosingScope();
 }
 
 inline bool
 BaselineFrame::pushBlock(JSContext *cx, Handle<StaticBlockObject *> block)
 {
-    JS_ASSERT(block->needsClone());
+    JS_ASSERT_IF(hasBlockChain(), blockChain() == *block->enclosingBlock());
 
-    ClonedBlockObject *clone = ClonedBlockObject::create(cx, block, this);
-    if (!clone)
-        return false;
-    pushOnScopeChain(*clone);
+    if (block->needsClone()) {
+        ClonedBlockObject *clone = ClonedBlockObject::create(cx, block, this);
+        if (!clone)
+            return false;
 
+        pushOnScopeChain(*clone);
+    }
+
+    setBlockChain(*block);
     return true;
 }
 
 inline void
 BaselineFrame::popBlock(JSContext *cx)
 {
-    JS_ASSERT(scopeChain_->is<ClonedBlockObject>());
+    JS_ASSERT(hasBlockChain());
+
+    if (cx->compartment()->debugMode())
+        DebugScopes::onPopBlock(cx, this);
 
-    popOffScopeChain();
+    if (blockChain_->needsClone()) {
+        JS_ASSERT(scopeChain_->as<ClonedBlockObject>().staticBlock() == *blockChain_);
+        popOffScopeChain();
+    }
+
+    setBlockChain(*blockChain_->enclosingBlock());
 }
 
 inline CallObject &
 BaselineFrame::callObj() const
 {
     JS_ASSERT(hasCallObj());
     JS_ASSERT(fun()->isHeavyweight());
 
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -108,16 +108,21 @@ BaselineFrame::initForOsr(StackFrame *fp
 {
     mozilla::PodZero(this);
 
     scopeChain_ = fp->scopeChain();
 
     if (fp->hasCallObjUnchecked())
         flags_ |= BaselineFrame::HAS_CALL_OBJ;
 
+    if (fp->hasBlockChain()) {
+        flags_ |= BaselineFrame::HAS_BLOCKCHAIN;
+        blockChain_ = &fp->blockChain();
+    }
+
     if (fp->isEvalFrame()) {
         flags_ |= BaselineFrame::EVAL;
         evalScript_ = fp->script();
     }
 
     if (fp->script()->needsArgsObj() && fp->hasArgsObj()) {
         flags_ |= BaselineFrame::HAS_ARGS_OBJ;
         argsObj_ = &fp->argsObj();
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -33,16 +33,19 @@ namespace jit {
 // itself.
 class BaselineFrame
 {
   public:
     enum Flags {
         // The frame has a valid return value. See also StackFrame::HAS_RVAL.
         HAS_RVAL         = 1 << 0,
 
+        // Frame has blockChain_ set.
+        HAS_BLOCKCHAIN   = 1 << 1,
+
         // A call object has been pushed on the scope chain.
         HAS_CALL_OBJ     = 1 << 2,
 
         // Frame has an arguments object, argsObj_.
         HAS_ARGS_OBJ     = 1 << 4,
 
         // See StackFrame::PREV_UP_TO_DATE.
         PREV_UP_TO_DATE  = 1 << 5,
@@ -64,23 +67,21 @@ class BaselineFrame
     // We need to split the Value into 2 fields of 32 bits, otherwise the C++
     // compiler may add some padding between the fields.
     uint32_t loScratchValue_;
     uint32_t hiScratchValue_;
     uint32_t loReturnValue_;        // If HAS_RVAL, the frame's return value.
     uint32_t hiReturnValue_;
     uint32_t frameSize_;
     JSObject *scopeChain_;          // Scope chain (always initialized).
+    StaticBlockObject *blockChain_; // If HAS_BLOCKCHAIN, the static block chain.
     JSScript *evalScript_;          // If isEvalFrame(), the current eval script.
     ArgumentsObject *argsObj_;      // If HAS_ARGS_OBJ, the arguments object.
     void *hookData_;                // If HAS_HOOK_DATA, debugger call hook data.
     uint32_t flags_;
-#if JS_BITS_PER_WORD == 32
-    uint32_t padding_;              // Pad to 8-byte alignment.
-#endif
 
   public:
     // Distance between the frame pointer and the frame header (return address).
     // This is the old frame pointer saved in the prologue.
     static const uint32_t FramePointerOffset = sizeof(void *);
 
     bool initForOsr(StackFrame *fp, uint32_t numStackValues);
 
@@ -166,17 +167,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(), i);
+        CheckLocalUnaliased(checkAliasing, script(), maybeBlockChain(), i);
 #endif
         return *valueSlot(i);
     }
 
     unsigned numActualArgs() const {
         return *(size_t *)(reinterpret_cast<const uint8_t *>(this) +
                              BaselineFrame::Size() +
                              offsetOfNumActualArgs());
@@ -206,16 +207,38 @@ class BaselineFrame
     void setReturnValue(const Value &v) {
         flags_ |= HAS_RVAL;
         *returnValue() = v;
     }
     inline Value *addressOfReturnValue() {
         return reinterpret_cast<Value *>(&loReturnValue_);
     }
 
+    bool hasBlockChain() const {
+        return (flags_ & HAS_BLOCKCHAIN) && blockChain_;
+    }
+    StaticBlockObject &blockChain() const {
+        JS_ASSERT(hasBlockChain());
+        return *blockChain_;
+    }
+    StaticBlockObject *maybeBlockChain() const {
+        return hasBlockChain() ? blockChain_ : nullptr;
+    }
+    void setBlockChain(StaticBlockObject &block) {
+        flags_ |= HAS_BLOCKCHAIN;
+        blockChain_ = &block;
+    }
+    void setBlockChainNull() {
+        JS_ASSERT(!hasBlockChain());
+        blockChain_ = nullptr;
+    }
+    StaticBlockObject **addressOfBlockChain() {
+        return &blockChain_;
+    }
+
     bool hasCallObj() const {
         return flags_ & HAS_CALL_OBJ;
     }
 
     inline CallObject &callObj() const;
 
     void setFlags(uint32_t flags) {
         flags_ = flags;
@@ -355,16 +378,19 @@ class BaselineFrame
         return -int(Size()) + offsetof(BaselineFrame, frameSize_);
     }
     static int reverseOffsetOfScratchValue() {
         return -int(Size()) + offsetof(BaselineFrame, loScratchValue_);
     }
     static int reverseOffsetOfScopeChain() {
         return -int(Size()) + offsetof(BaselineFrame, scopeChain_);
     }
+    static int reverseOffsetOfBlockChain() {
+        return -int(Size()) + offsetof(BaselineFrame, blockChain_);
+    }
     static int reverseOffsetOfArgsObj() {
         return -int(Size()) + offsetof(BaselineFrame, argsObj_);
     }
     static int reverseOffsetOfFlags() {
         return -int(Size()) + offsetof(BaselineFrame, flags_);
     }
     static int reverseOffsetOfEvalScript() {
         return -int(Size()) + offsetof(BaselineFrame, evalScript_);
--- a/js/src/jit/BaselineFrameInfo.h
+++ b/js/src/jit/BaselineFrameInfo.h
@@ -279,16 +279,19 @@ class FrameInfo
         return Address(BaselineFrameReg, BaselineFrame::offsetOfThis());
     }
     Address addressOfCallee() const {
         return Address(BaselineFrameReg, BaselineFrame::offsetOfCalleeToken());
     }
     Address addressOfScopeChain() const {
         return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScopeChain());
     }
+    Address addressOfBlockChain() const {
+        return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfBlockChain());
+    }
     Address addressOfFlags() const {
         return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags());
     }
     Address addressOfEvalScript() const {
         return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfEvalScript());
     }
     Address addressOfReturnValue() const {
         return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfReturnValue());
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1247,17 +1247,16 @@ 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:
@@ -1500,25 +1499,16 @@ 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/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -429,17 +429,17 @@ HandleExceptionBaseline(JSContext *cx, c
 
           case JSTRAP_CONTINUE:
           case JSTRAP_THROW:
             JS_ASSERT(cx->isExceptionPending());
             break;
 
           case JSTRAP_RETURN:
             JS_ASSERT(baselineFrame->hasReturnValue());
-            if (jit::DebugEpilogue(cx, baselineFrame, pc, true)) {
+            if (jit::DebugEpilogue(cx, baselineFrame, true)) {
                 rfe->kind = ResumeFromException::RESUME_FORCED_RETURN;
                 rfe->framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
                 rfe->stackPointer = reinterpret_cast<uint8_t *>(baselineFrame);
                 return;
             }
 
             // DebugEpilogue threw an exception. Propagate to the caller frame.
             *calledDebugEpilogue = true;
@@ -452,33 +452,32 @@ HandleExceptionBaseline(JSContext *cx, c
 
     if (!script->hasTrynotes())
         return;
 
     JSTryNote *tn = script->trynotes()->vector;
     JSTryNote *tnEnd = tn + script->trynotes()->length;
 
     uint32_t pcOffset = uint32_t(pc - script->main());
-    ScopeIter si(frame.baselineFrame(), pc, cx);
     for (; tn != tnEnd; ++tn) {
         if (pcOffset < tn->start)
             continue;
         if (pcOffset >= tn->start + tn->length)
             continue;
 
         // Skip if the try note's stack depth exceeds the frame's stack depth.
         // See the big comment in TryNoteIter::settle for more info.
         JS_ASSERT(frame.baselineFrame()->numValueSlots() >= script->nfixed);
         size_t stackDepth = frame.baselineFrame()->numValueSlots() - script->nfixed;
         if (tn->stackDepth > stackDepth)
             continue;
 
         // Unwind scope chain (pop block objects).
         if (cx->isExceptionPending())
-            UnwindScope(cx, si, tn->stackDepth);
+            UnwindScope(cx, frame.baselineFrame(), tn->stackDepth);
 
         // Compute base pointer and stack pointer.
         rfe->framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
         rfe->stackPointer = rfe->framePointer - BaselineFrame::Size() -
             (script->nfixed + tn->stackDepth) * sizeof(Value);
 
         switch (tn->kind) {
           case JSTRY_CATCH:
@@ -603,20 +602,17 @@ HandleException(ResumeFromException *rfe
             // to be.  Unset the flag here so that if we call DebugEpilogue below,
             // it doesn't try to pop the SPS frame again.
             iter.baselineFrame()->unsetPushedSPSFrame();
  
             if (cx->compartment()->debugMode() && !calledDebugEpilogue) {
                 // If DebugEpilogue returns |true|, we have to perform a forced
                 // return, e.g. return frame->returnValue() to the caller.
                 BaselineFrame *frame = iter.baselineFrame();
-                RootedScript script(cx);
-                jsbytecode *pc;
-                iter.baselineScriptAndPc(script.address(), &pc);
-                if (jit::DebugEpilogue(cx, frame, pc, false)) {
+                if (jit::DebugEpilogue(cx, frame, false)) {
                     JS_ASSERT(frame->hasReturnValue());
                     rfe->kind = ResumeFromException::RESUME_FORCED_RETURN;
                     rfe->framePointer = iter.fp() - BaselineFrame::FramePointerOffset;
                     rfe->stackPointer = reinterpret_cast<uint8_t *>(frame);
                     return;
                 }
             }
         }
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -690,52 +690,51 @@ GetIndexFromString(JSString *str)
     JSAtom *atom = &str->asAtom();
     if (!atom->isIndex(&index))
         return UINT32_MAX;
 
     return index;
 }
 
 bool
-DebugPrologue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn)
+DebugPrologue(JSContext *cx, BaselineFrame *frame, bool *mustReturn)
 {
     *mustReturn = false;
 
-    JSTrapStatus status = ScriptDebugPrologue(cx, frame, pc);
+    JSTrapStatus status = ScriptDebugPrologue(cx, frame);
     switch (status) {
       case JSTRAP_CONTINUE:
         return true;
 
       case JSTRAP_RETURN:
         // The script is going to return immediately, so we have to call the
         // debug epilogue handler as well.
         JS_ASSERT(frame->hasReturnValue());
         *mustReturn = true;
-        return jit::DebugEpilogue(cx, frame, pc, true);
+        return jit::DebugEpilogue(cx, frame, true);
 
       case JSTRAP_THROW:
       case JSTRAP_ERROR:
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid trap status");
     }
 }
 
 bool
-DebugEpilogue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool ok)
+DebugEpilogue(JSContext *cx, BaselineFrame *frame, bool ok)
 {
     // Unwind scope chain to stack depth 0.
-    ScopeIter si(frame, pc, cx);
-    UnwindScope(cx, si, 0);
+    UnwindScope(cx, frame, 0);
 
     // If ScriptDebugEpilogue returns |true| we have to return the frame's
     // return value. If it returns |false|, the debugger threw an exception.
     // In both cases we have to pop debug scopes.
-    ok = ScriptDebugEpilogue(cx, frame, pc, ok);
+    ok = ScriptDebugEpilogue(cx, frame, ok);
 
     if (frame->isNonEvalFunctionFrame()) {
         JS_ASSERT_IF(ok, frame->hasReturnValue());
         DebugScopes::onPopCall(frame, cx);
     } else if (frame->isStrictEvalFrame()) {
         JS_ASSERT_IF(frame->hasCallObj(), frame->scopeChain()->as<CallObject>().isForEval());
         DebugScopes::onPopStrictEvalScope(frame);
     }
@@ -844,17 +843,17 @@ HandleDebugTrap(JSContext *cx, BaselineF
         break;
 
       case JSTRAP_ERROR:
         return false;
 
       case JSTRAP_RETURN:
         *mustReturn = true;
         frame->setReturnValue(rval);
-        return jit::DebugEpilogue(cx, frame, pc, true);
+        return jit::DebugEpilogue(cx, frame, true);
 
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid trap status");
     }
@@ -882,51 +881,41 @@ OnDebuggerStatement(JSContext *cx, Basel
         return false;
 
       case JSTRAP_CONTINUE:
         return true;
 
       case JSTRAP_RETURN:
         frame->setReturnValue(rval);
         *mustReturn = true;
-        return jit::DebugEpilogue(cx, frame, pc, true);
+        return jit::DebugEpilogue(cx, frame, true);
 
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         return false;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Invalid trap status");
     }
 }
 
 bool
-PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block)
+EnterBlock(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block)
 {
     return frame->pushBlock(cx, block);
 }
 
 bool
-PopBlockScope(JSContext *cx, BaselineFrame *frame)
+LeaveBlock(JSContext *cx, BaselineFrame *frame)
 {
     frame->popBlock(cx);
     return true;
 }
 
 bool
-DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc)
-{
-    JS_ASSERT(cx->compartment()->debugMode());
-
-    DebugScopes::onPopBlock(cx, frame, pc);
-
-    return true;
-}
-
-bool
 InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame, uint32_t numStackValues)
 {
     return frame->initForOsr(interpFrame, numStackValues);
 }
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type,
                                 HandleObject owner, int32_t offset)
 {
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -633,33 +633,32 @@ bool FilterArgumentsOrEval(JSContext *cx
 
 #ifdef JSGC_GENERATIONAL
 void PostWriteBarrier(JSRuntime *rt, JSObject *obj);
 void PostGlobalWriteBarrier(JSRuntime *rt, JSObject *obj);
 #endif
 
 uint32_t GetIndexFromString(JSString *str);
 
-bool DebugPrologue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool *mustReturn);
-bool DebugEpilogue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool ok);
+bool DebugPrologue(JSContext *cx, BaselineFrame *frame, bool *mustReturn);
+bool DebugEpilogue(JSContext *cx, BaselineFrame *frame, bool ok);
 
 bool StrictEvalPrologue(JSContext *cx, BaselineFrame *frame);
 bool HeavyweightFunPrologue(JSContext *cx, BaselineFrame *frame);
 
 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 PushBlockScope(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block);
-bool PopBlockScope(JSContext *cx, BaselineFrame *frame);
-bool DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc);
+bool EnterBlock(JSContext *cx, BaselineFrame *frame, Handle<StaticBlockObject *> block);
+bool LeaveBlock(JSContext *cx, BaselineFrame *frame);
 
 bool InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame,
                              uint32_t numStackValues);
 
 JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject type,
                                 HandleObject owner, int32_t offset);
 
 } // namespace jit
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -248,16 +248,17 @@ 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/jsfun.h
+++ b/js/src/jsfun.h
@@ -95,18 +95,17 @@ class JSFunction : public JSObject
         JS_ASSERT(!isInterpretedLazy());
 
         if (isNative())
             return false;
 
         // Note: this should be kept in sync with FunctionBox::isHeavyweight().
         return nonLazyScript()->bindings.hasAnyAliasedBindings() ||
                nonLazyScript()->funHasExtensibleScope ||
-               nonLazyScript()->funNeedsDeclEnvObject ||
-               isGenerator();
+               nonLazyScript()->funNeedsDeclEnvObject;
     }
 
     /* A function can be classified as either native (C++) or interpreted (JS): */
     bool isInterpreted()            const { return flags & (INTERPRETED | INTERPRETED_LAZY); }
     bool isNative()                 const { return !isInterpreted(); }
 
     /* Possible attributes of a native function: */
     bool isNativeConstructor()      const { return flags & NATIVE_CTOR; }
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5669,18 +5669,19 @@ js_DumpStackFrame(JSContext *cx, StackFr
         fputc('\n', stderr);
 
         fprintf(stderr, "file %s line %u\n",
                 i.script()->filename(), (unsigned) i.script()->lineno);
 
         if (jsbytecode *pc = i.pc()) {
             fprintf(stderr, "  pc = %p\n", pc);
             fprintf(stderr, "  current op: %s\n", js_CodeName[*pc]);
-            MaybeDumpObject("blockChain", i.script()->getBlockScope(pc));
         }
+        if (!i.isJit())
+            MaybeDumpObject("blockChain", i.interpFrame()->maybeBlockChain());
         MaybeDumpValue("this", i.thisv());
         if (!i.isJit()) {
             fprintf(stderr, "  rval: ");
             dumpValue(i.interpFrame()->returnValue());
             fputc('\n', stderr);
         }
 
         fprintf(stderr, "  flags:");
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -112,45 +112,72 @@ 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_POPNV:
+      case JSOP_LEAVEBLOCK:
+        return GET_UINT16(pc);
+      case JSOP_LEAVEBLOCKEXPR:
         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];
-    JS_ASSERT (cs.ndefs >= 0);
-    return cs.ndefs;
+    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;
 }
 
 static const char * const countBaseNames[] = {
     "interp"
 };
 
 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT);
 
@@ -749,17 +776,17 @@ js_DisassembleAtPC(JSContext *cx, JSScri
                 }
                 Sprint(sp, "%02u ", SN_TYPE(sn));
             }
             else
                 sp->put("   ");
             if (parser.isReachable(next))
                 Sprint(sp, "%05u ", parser.stackDepthAtPC(next));
             else
-                Sprint(sp, "      ");
+                Sprint(sp, "      ", parser.stackDepthAtPC(next));
         }
         len = js_Disassemble1(cx, script, next, script->pcToOffset(next), lines, sp);
         if (!len)
             return false;
         next += len;
     }
     return true;
 }
@@ -1400,16 +1427,40 @@ js_QuoteString(ExclusiveContext *cx, JSS
     char *bytes = QuoteString(&sprinter, str, quote);
     if (!bytes)
         return nullptr;
     return js_NewStringCopyZ<CanGC>(cx, bytes);
 }
 
 /************************************************************************/
 
+static JSObject *
+GetBlockChainAtPC(JSContext *cx, JSScript *script, jsbytecode *pc)
+{
+    JS_ASSERT(script->containsPC(pc));
+    JS_ASSERT(pc >= script->main());
+
+    ptrdiff_t offset = pc - script->main();
+
+    if (!script->hasBlockScopes())
+        return nullptr;
+
+    BlockScopeArray *blockScopes = script->blockScopes();
+    JSObject *blockChain = nullptr;
+    for (uint32_t n = 0; n < blockScopes->length; n++) {
+        const BlockScopeNote *note = &blockScopes->vector[n];
+        if (note->start > offset)
+            break;
+        if (offset <= note->start + note->length)
+            blockChain = script->getObject(note->index);
+    }
+
+    return blockChain;
+}
+
 namespace {
 /*
  * The expression decompiler is invoked by error handling code to produce a
  * string representation of the erroring expression. As it's only a debugging
  * tool, it only supports basic expressions. For anything complicated, it simply
  * puts "(intermediate value)" into the error result.
  *
  * Here's the basic algorithm:
@@ -1659,27 +1710,34 @@ JSAtom *
 ExpressionDecompiler::loadAtom(jsbytecode *pc)
 {
     return script->getAtom(GET_UINT32_INDEX(pc));
 }
 
 JSAtom *
 ExpressionDecompiler::findLetVar(jsbytecode *pc, unsigned depth)
 {
-    for (JSObject *chain = script->getBlockScope(pc); chain; chain = chain->getParent()) {
-        StaticBlockObject &block = chain->as<StaticBlockObject>();
-        uint32_t blockDepth = block.stackDepth();
-        uint32_t blockCount = block.slotCount();
-        if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) {
-            for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
-                const Shape &shape = r.front();
-                if (shape.shortid() == int(depth - blockDepth))
-                    return JSID_TO_ATOM(shape.propid());
+    if (script->hasObjects()) {
+        JSObject *chain = GetBlockChainAtPC(cx, script, pc);
+        if (!chain)
+            return nullptr;
+        JS_ASSERT(chain->is<BlockObject>());
+        do {
+            BlockObject &block = chain->as<BlockObject>();
+            uint32_t blockDepth = block.stackDepth();
+            uint32_t blockCount = block.slotCount();
+            if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) {
+                for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
+                    const Shape &shape = r.front();
+                    if (shape.shortid() == int(depth - blockDepth))
+                        return JSID_TO_ATOM(shape.propid());
+                }
             }
-        }
+            chain = chain->getParent();
+        } while (chain && chain->is<BlockObject>());
     }
     return nullptr;
 }
 
 JSAtom *
 ExpressionDecompiler::getArg(unsigned slot)
 {
     JS_ASSERT(fun);
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -92,19 +92,17 @@ 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)
 
-/* Pop N values, preserving top value.  */
-OPDEF(JSOP_POPNV,     44, "popnv",      NULL,         3, -1,  1,  JOF_UINT16)
-
+OPDEF(JSOP_UNUSED44,  44, "unused44",   NULL,         1,  0,  0,  JOF_BYTE)
 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)
@@ -217,17 +215,19 @@ 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)
-OPDEF(JSOP_UNUSED105,  105, "unused105",   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)
 
 /* 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,19 +395,23 @@ 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)
 
-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)
+/* 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)
 
 /*
  * 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)
@@ -429,20 +433,21 @@ 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_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_ENTERBLOCK,    198,"enterblock",  NULL,    5,  0, -1,  JOF_OBJECT)
+OPDEF(JSOP_LEAVEBLOCK,    199,"leaveblock",  NULL,    3, -1,  0,  JOF_UINT16)
 
+
+OPDEF(JSOP_UNUSED200,     200,"unused200",  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)
 OPDEF(JSOP_YIELD,         203,"yield",       NULL,    1,  1,  1,  JOF_BYTE)
 OPDEF(JSOP_ARRAYPUSH,     204,"arraypush",   NULL,    3,  1,  0,  JOF_LOCAL)
@@ -452,17 +457,22 @@ 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)
 
-OPDEF(JSOP_UNUSED207,     207, "unused207",    NULL,  1,  0,  0,  JOF_BYTE)
+/*
+ * 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_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/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2888,46 +2888,16 @@ LazyScript::markChildren(JSTracer *trc)
 
 void
 LazyScript::finalize(FreeOp *fop)
 {
     if (table_)
         fop->free_(table_);
 }
 
-StaticBlockObject *
-JSScript::getBlockScope(jsbytecode *pc)
-{
-    JS_ASSERT(containsPC(pc));
-
-    ptrdiff_t offset = pc - main();
-
-    if (offset < 0)
-        return nullptr;
-
-    if (!hasBlockScopes())
-        return nullptr;
-
-    BlockScopeArray *scopeArray = blockScopes();
-    JSObject *blockChain = nullptr;
-    for (uint32_t n = 0; n < scopeArray->length; n++) {
-        const BlockScopeNote *note = &scopeArray->vector[n];
-        if (note->start > offset)
-            break;
-        if (offset < note->start + note->length) {
-            if (note->index == BlockScopeNote::NoBlockScopeIndex)
-                blockChain = nullptr;
-            else
-                blockChain = getObject(note->index);
-        }
-    }
-
-    return blockChain ? &blockChain->as<StaticBlockObject>() : nullptr;
-}
-
 void
 JSScript::setArgumentsHasVarBinding()
 {
     argsHasVarBinding_ = true;
     needsArgsAnalysis_ = true;
 }
 
 void
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -38,17 +38,16 @@ namespace jit {
 # define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript *)0x1)
 
 class BreakpointSite;
 class BindingIter;
 class RegExpObject;
 struct SourceCompressionTask;
 class Shape;
 class WatchpointMap;
-class StaticBlockObject;
 
 namespace analyze {
     class ScriptAnalysis;
 }
 
 namespace frontend {
     class BytecodeEmitter;
 }
@@ -77,37 +76,19 @@ struct JSTryNote {
     uint16_t        stackDepth; /* stack depth upon exception handler entry */
     uint32_t        start;      /* start of the try statement or loop
                                    relative to script->main */
     uint32_t        length;     /* length of the try statement or loop */
 };
 
 namespace js {
 
-// A block scope has a range in bytecode: it is entered at some offset, and left
-// at some later offset.  Scopes can be nested.  Given an offset, the
-// BlockScopeNote containing that offset whose with the highest start value
-// indicates the block scope.  The block scope list is sorted by increasing
-// start value.
-//
-// It is possible to leave a scope nonlocally, for example via a "break"
-// statement, so there may be short bytecode ranges in a block scope in which we
-// are popping the block chain in preparation for a goto.  These exits are also
-// nested with respect to outer scopes.  The scopes in these exits are indicated
-// by the "index" field, just like any other block.  If a nonlocal exit pops the
-// last block scope, the index will be NoBlockScopeIndex.
-//
 struct BlockScopeNote {
-    static const uint32_t NoBlockScopeIndex = UINT32_MAX;
-
-    uint32_t        index;      // Index of StaticScopeObject in the object
-                                // array, or NoBlockScopeIndex if there is no
-                                // block scope in this range.
-    uint32_t        start;      // Bytecode offset at which this scope starts,
-                                // from script->main().
+    uint32_t        index;      // Index of StaticScopeObject in the object array.
+    uint32_t        start;      // Bytecode offset at which this scope starts.
     uint32_t        length;     // Bytecode length of scope.
     uint32_t        padding;    // Pad to 64-bit boundary.
 };
 
 struct ConstArray {
     js::HeapValue   *vector;    /* array of indexed constant values */
     uint32_t        length;
 };
@@ -1100,18 +1081,16 @@ class JSScript : public js::gc::Barriere
     inline js::RegExpObject *getRegExp(size_t index);
 
     const js::Value &getConst(size_t index) {
         js::ConstArray *arr = consts();
         JS_ASSERT(index < arr->length);
         return arr->vector[index];
     }
 
-    js::StaticBlockObject *getBlockScope(jsbytecode *pc);
-
     /*
      * The isEmpty method tells whether this script has code that computes any
      * result (not return value, result AKA normal completion value) other than
      * JSVAL_VOID, or any other effects.
      */
     bool isEmpty() const {
         if (length() > 3)
             return false;
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1507,17 +1507,17 @@ TrapHandler(JSContext *cx, JSScript *, j
 {
     RootedString str(cx, JSVAL_TO_STRING(closure));
     RootedValue rval(cx, *rvalArg);
 
     ScriptFrameIter iter(cx);
     JS_ASSERT(!iter.done());
 
     /* Debug-mode currently disables Ion compilation. */
-    JSAbstractFramePtr frame(iter.abstractFramePtr().raw(), iter.pc());
+    JSAbstractFramePtr frame(Jsvalify(iter.abstractFramePtr()));
     RootedScript script(cx, iter.script());
 
     size_t length;
     const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
     if (!chars)
         return JSTRAP_ERROR;
 
     if (!frame.evaluateUCInStackFrame(cx, chars, length,
@@ -2585,17 +2585,17 @@ EvalInFrame(JSContext *cx, unsigned argc
         ac.construct(cx, DefaultObjectForContextOrNull(cx));
     }
 
     size_t length;
     const jschar *chars = JS_GetStringCharsAndLength(cx, str, &length);
     if (!chars)
         return false;
 
-    JSAbstractFramePtr frame(fi.abstractFramePtr().raw(), fi.pc());
+    JSAbstractFramePtr frame(Jsvalify(fi.abstractFramePtr()));
     RootedScript fpscript(cx, frame.script());
     bool ok = !!frame.evaluateUCInStackFrame(cx, chars, length,
                                              fpscript->filename(),
                                              JS_PCToLineNumber(cx, fpscript,
                                                                fi.pc()),
                                              MutableHandleValue::fromMarkedLocation(vp));
     return ok;
 }
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3982,22 +3982,22 @@ DebuggerFrame_getType(JSContext *cx, uns
                           ? cx->names().global
                           : cx->names().call);
     return true;
 }
 
 static bool
 DebuggerFrame_getEnvironment(JSContext *cx, unsigned argc, Value *vp)
 {
-    THIS_FRAME_OWNER_ITER(cx, argc, vp, "get environment", args, thisobj, _, iter, dbg);
+    THIS_FRAME_OWNER(cx, argc, vp, "get environment", args, thisobj, frame, dbg);
 
     Rooted<Env*> env(cx);
     {
-        AutoCompartment ac(cx, iter.abstractFramePtr().scopeChain());
-        env = GetDebugScopeForFrame(cx, iter.abstractFramePtr(), iter.pc());
+        AutoCompartment ac(cx, frame.scopeChain());
+        env = GetDebugScopeForFrame(cx, frame);
         if (!env)
             return false;
     }
 
     return dbg->wrapEnvironment(cx, env, args.rval());
 }
 
 static bool
@@ -4433,17 +4433,17 @@ DebuggerGenericEval(JSContext *cx, const
 
     RootedValue thisv(cx);
     Rooted<Env *> env(cx);
     if (iter) {
         /* ExecuteInEnv requires 'fp' to have a computed 'this" value. */
         if (!iter->computeThis(cx))
             return false;
         thisv = iter->thisv();
-        env = GetDebugScopeForFrame(cx, iter->abstractFramePtr(), iter->pc());
+        env = GetDebugScopeForFrame(cx, iter->abstractFramePtr());
         if (!env)
             return false;
     } else {
         /*
          * Use the global as 'this'. If the global is an inner object, it
          * should have a thisObject hook that returns the appropriate outer
          * object.
          */
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -840,58 +840,44 @@ EnterWith(JSContext *cx, AbstractFramePt
         return false;
 
     frame.pushOnScopeChain(*withobj);
     return true;
 }
 
 /* Unwind block and scope chains to match the given depth. */
 void
-js::UnwindScope(JSContext *cx, ScopeIter &si, uint32_t stackDepth)
+js::UnwindScope(JSContext *cx, AbstractFramePtr frame, uint32_t stackDepth)
 {
-    for (; !si.done(); ++si) {
+    JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
+    JS_ASSERT_IF(frame.isStackFrame(), stackDepth <= cx->interpreterRegs().stackDepth());
+
+    for (ScopeIter si(frame, cx); !si.done(); ++si) {
         switch (si.type()) {
           case ScopeIter::Block:
             if (si.staticBlock().stackDepth() < stackDepth)
                 return;
-            if (cx->compartment()->debugMode())
-                DebugScopes::onPopBlock(cx, si);
-            if (si.staticBlock().needsClone())
-                si.frame().popBlock(cx);
+            frame.popBlock(cx);
             break;
           case ScopeIter::With:
             if (si.scope().as<WithObject>().stackDepth() < stackDepth)
                 return;
-            si.frame().popWith(cx);
+            frame.popWith(cx);
             break;
           case ScopeIter::Call:
           case ScopeIter::StrictEvalScope:
             break;
         }
     }
 }
 
-static void
-ForcedReturn(JSContext *cx, ScopeIter &si, FrameRegs &regs)
-{
-    UnwindScope(cx, si, 0);
-    regs.setToEndOfScript();
-}
-
-static void
-ForcedReturn(JSContext *cx, FrameRegs &regs)
-{
-    ScopeIter si(regs.fp(), regs.pc, cx);
-    ForcedReturn(cx, si, regs);
-}
-
 void
 js::UnwindForUncatchableException(JSContext *cx, const FrameRegs &regs)
 {
-    /* c.f. the regular (catchable) TryNoteIter loop in HandleError. */
+    /* c.f. the regular (catchable) TryNoteIter loop in Interpret. */
     for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
         JSTryNote *tn = *tni;
         if (tn->kind == JSTRY_ITER) {
             Value *sp = regs.spForStackDepth(tn->stackDepth);
             UnwindIteratorForUncatchableException(cx, &sp[-1].toObject());
         }
     }
 }
@@ -950,117 +936,16 @@ TryNoteIter::settle()
          * depth exceeding the current one and this condition is what we use to
          * filter them out.
          */
         if (tn->stackDepth <= regs.stackDepth())
             break;
     }
 }
 
-enum HandleErrorContinuation
-{
-    SuccessfulReturnContinuation,
-    ErrorReturnContinuation,
-    CatchContinuation,
-    FinallyContinuation
-};
-
-static HandleErrorContinuation
-HandleError(JSContext *cx, FrameRegs &regs)
-{
-    JS_ASSERT(regs.fp()->script()->containsPC(regs.pc));
-
-    ScopeIter si(regs.fp(), regs.pc, cx);
-    bool ok = false;
-
-  again:
-    if (cx->isExceptionPending()) {
-        /* Call debugger throw hooks. */
-        if (JS_UNLIKELY(cx->compartment()->debugMode())) {
-            JSTrapStatus status = DebugExceptionUnwind(cx, regs.fp(), regs.pc);
-            switch (status) {
-              case JSTRAP_ERROR:
-                goto again;
-
-              case JSTRAP_CONTINUE:
-              case JSTRAP_THROW:
-                break;
-
-              case JSTRAP_RETURN:
-                ForcedReturn(cx, si, regs);
-                return SuccessfulReturnContinuation;
-
-              default:
-                MOZ_ASSUME_UNREACHABLE("Invalid trap status");
-            }
-        }
-
-        for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
-            JSTryNote *tn = *tni;
-
-            UnwindScope(cx, si, tn->stackDepth);
-
-            /*
-             * Set pc to the first bytecode after the the try note to point
-             * to the beginning of catch or finally or to [enditer] closing
-             * the for-in loop.
-             */
-            regs.pc = regs.fp()->script()->main() + tn->start + tn->length;
-            regs.sp = regs.spForStackDepth(tn->stackDepth);
-
-            switch (tn->kind) {
-              case JSTRY_CATCH:
-                /* Catch cannot intercept the closing of a generator. */
-                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.
-                 */
-                return CatchContinuation;
-
-              case JSTRY_FINALLY:
-                return FinallyContinuation;
-
-              case JSTRY_ITER: {
-                /* This is similar to JSOP_ENDITER in the interpreter loop. */
-                JS_ASSERT(JSOp(*regs.pc) == JSOP_ENDITER);
-                RootedObject obj(cx, &regs.sp[-1].toObject());
-                bool ok = UnwindIteratorForException(cx, obj);
-                regs.sp -= 1;
-                if (!ok)
-                    goto again;
-                break;
-              }
-
-              case JSTRY_LOOP:
-                break;
-            }
-        }
-
-        /*
-         * Propagate the exception or error to the caller unless the exception
-         * is an asynchronous return from a generator.
-         */
-        if (JS_UNLIKELY(cx->isExceptionPending() &&
-                        cx->getPendingException().isMagic(JS_GENERATOR_CLOSING))) {
-            cx->clearPendingException();
-            ok = true;
-            regs.fp()->clearReturnValue();
-        }
-    } else {
-        UnwindForUncatchableException(cx, regs);
-    }
-
-    ForcedReturn(cx, si, regs);
-    return ok ? SuccessfulReturnContinuation : ErrorReturnContinuation;
-}
-
 #define REGS                     (activation.regs())
 #define PUSH_COPY(v)             do { *REGS.sp++ = (v); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
 #define PUSH_COPY_SKIP_CHECK(v)  *REGS.sp++ = (v)
 #define PUSH_NULL()              REGS.sp++->setNull()
 #define PUSH_UNDEFINED()         REGS.sp++->setUndefined()
 #define PUSH_BOOLEAN(b)          REGS.sp++->setBoolean(b)
 #define PUSH_DOUBLE(d)           REGS.sp++->setDouble(d)
 #define PUSH_INT32(i)            REGS.sp++->setInt32(i)
@@ -1481,34 +1366,35 @@ Interpret(JSContext *cx, RunState &state
 
     if (!activation.entryFrame()->isGeneratorFrame()) {
         if (!activation.entryFrame()->prologue(cx))
             goto error;
     } else {
         probes::EnterScript(cx, script, script->function(), activation.entryFrame());
     }
     if (JS_UNLIKELY(cx->compartment()->debugMode())) {
-        JSTrapStatus status = ScriptDebugPrologue(cx, activation.entryFrame(), REGS.pc);
+        JSTrapStatus status = ScriptDebugPrologue(cx, activation.entryFrame());
         switch (status) {
           case JSTRAP_CONTINUE:
             break;
           case JSTRAP_RETURN:
-            ForcedReturn(cx, REGS);
-            goto successful_return_continuation;
+            interpReturnOK = true;
+            goto forced_return;
           case JSTRAP_THROW:
           case JSTRAP_ERROR:
             goto error;
           default:
             MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status");
         }
     }
 
     if (cx->runtime()->profilingScripts || cx->runtime()->debugHooks.interruptHook)
         activation.enableInterruptsUnconditionally();
 
+  enterInterpreterLoop:
     // Enter the interpreter loop starting at the current pc.
     ADVANCE_AND_DISPATCH(0);
 
 INTERPRETER_LOOP() {
 
 CASE(EnableInterruptsPseudoOpcode)
 {
     bool moreInterrupts = false;
@@ -1538,18 +1424,18 @@ CASE(EnableInterruptsPseudoOpcode)
                 status = Debugger::onSingleStep(cx, &rval);
             switch (status) {
               case JSTRAP_ERROR:
                 goto error;
               case JSTRAP_CONTINUE:
                 break;
               case JSTRAP_RETURN:
                 REGS.fp()->setReturnValue(rval);
-                ForcedReturn(cx, REGS);
-                goto successful_return_continuation;
+                interpReturnOK = true;
+                goto forced_return;
               case JSTRAP_THROW:
                 cx->setPendingException(rval);
                 goto error;
               default:;
             }
             moreInterrupts = true;
         }
 
@@ -1559,18 +1445,18 @@ CASE(EnableInterruptsPseudoOpcode)
         if (script->hasBreakpointsAt(REGS.pc)) {
             RootedValue rval(cx);
             JSTrapStatus status = Debugger::onTrap(cx, &rval);
             switch (status) {
               case JSTRAP_ERROR:
                 goto error;
               case JSTRAP_RETURN:
                 REGS.fp()->setReturnValue(rval);
-                ForcedReturn(cx, REGS);
-                goto successful_return_continuation;
+                interpReturnOK = true;
+                goto forced_return;
               case JSTRAP_THROW:
                 cx->setPendingException(rval);
                 goto error;
               default:
                 break;
             }
             JS_ASSERT(status == JSTRAP_CONTINUE);
             JS_ASSERT(rval.isInt32() && rval.toInt32() == op);
@@ -1584,29 +1470,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,28 +1522,25 @@ 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_UNUSED200)
 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)
@@ -1713,34 +1596,21 @@ 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 = script->getBlockScope(REGS.pc + JSOP_POPN_LENGTH))
+    if (StaticBlockObject *block = REGS.fp()->maybeBlockChain())
         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];
@@ -1774,28 +1644,26 @@ CASE(JSOP_RETURN)
 CASE(JSOP_RETRVAL)
 {
     /*
      * When the inlined frame exits with an exception or an error, ok will be
      * false after the inline_return label.
      */
     CHECK_BRANCH();
 
-  successful_return_continuation:
     interpReturnOK = true;
-  return_continuation:
     if (activation.entryFrame() != REGS.fp())
   inline_return:
     {
 #if JS_TRACE_LOGGING
         TraceLogging::defaultLogger()->log(TraceLogging::SCRIPT_STOP);
 #endif
 
         if (JS_UNLIKELY(cx->compartment()->debugMode()))
-            interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), REGS.pc, interpReturnOK);
+            interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), interpReturnOK);
 
         if (!REGS.fp()->isYielding())
             REGS.fp()->epilogue(cx);
         else
             probes::ExitScript(cx, script, script->function(), REGS.fp()->hasPushedSPSFrame());
 
 #if defined(JS_ION)
   jit_return_pop_frame:
@@ -1818,16 +1686,17 @@ CASE(JSOP_RETRVAL)
         }
 
         /* Increment pc so that |sp - fp->slots == ReconstructStackDepth(pc)|. */
         REGS.pc += JSOP_CALL_LENGTH;
         goto error;
     } else {
         JS_ASSERT(REGS.stackDepth() == 0);
     }
+    interpReturnOK = true;
     goto exit;
 }
 
 CASE(JSOP_DEFAULT)
     REGS.sp--;
     /* FALL THROUGH */
 CASE(JSOP_GOTO)
 {
@@ -2693,22 +2562,22 @@ CASE(JSOP_FUNCALL)
 #if JS_TRACE_LOGGING
     TraceLogging::defaultLogger()->log(TraceLogging::SCRIPT_START, script);
     TraceLogging::defaultLogger()->log(TraceLogging::INFO_ENGINE_INTERPRETER);
 #endif
 
     if (!REGS.fp()->prologue(cx))
         goto error;
     if (JS_UNLIKELY(cx->compartment()->debugMode())) {
-        switch (ScriptDebugPrologue(cx, REGS.fp(), REGS.pc)) {
+        switch (ScriptDebugPrologue(cx, REGS.fp())) {
           case JSTRAP_CONTINUE:
             break;
           case JSTRAP_RETURN:
-            ForcedReturn(cx, REGS);
-            goto successful_return_continuation;
+            interpReturnOK = true;
+            goto forced_return;
           case JSTRAP_THROW:
           case JSTRAP_ERROR:
             goto error;
           default:
             MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status");
         }
     }
 
@@ -3338,69 +3207,71 @@ CASE(JSOP_DEBUGGER)
         st = Debugger::onDebuggerStatement(cx, &rval);
     switch (st) {
       case JSTRAP_ERROR:
         goto error;
       case JSTRAP_CONTINUE:
         break;
       case JSTRAP_RETURN:
         REGS.fp()->setReturnValue(rval);
-        ForcedReturn(cx, REGS);
-        goto successful_return_continuation;
+        interpReturnOK = true;
+        goto forced_return;
       case JSTRAP_THROW:
         cx->setPendingException(rval);
         goto error;
       default:;
     }
 }
 END_CASE(JSOP_DEBUGGER)
 
-CASE(JSOP_PUSHBLOCKSCOPE)
+CASE(JSOP_ENTERBLOCK)
+CASE(JSOP_ENTERLET0)
+CASE(JSOP_ENTERLET1)
+CASE(JSOP_ENTERLET2)
 {
     StaticBlockObject &blockObj = script->getObject(REGS.pc)->as<StaticBlockObject>();
 
-    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.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. */
     if (!REGS.fp()->pushBlock(cx, blockObj))
         goto error;
 }
-END_CASE(JSOP_PUSHBLOCKSCOPE)
-
-CASE(JSOP_POPBLOCKSCOPE)
+END_CASE(JSOP_ENTERBLOCK)
+
+CASE(JSOP_LEAVEBLOCK)
+CASE(JSOP_LEAVEFORLETIN)
+CASE(JSOP_LEAVEBLOCKEXPR)
 {
-#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.
+    blockDepth = REGS.fp()->blockChain().stackDepth();
+
     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_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.
-
-    if (JS_UNLIKELY(cx->compartment()->debugMode()))
-        DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc);
-}
-END_CASE(JSOP_DEBUGLEAVEBLOCK)
+END_CASE(JSOP_LEAVEBLOCK)
 
 CASE(JSOP_GENERATOR)
 {
     JS_ASSERT(!cx->isExceptionPending());
     REGS.fp()->initGeneratorFrame();
     REGS.pc += JSOP_GENERATOR_LENGTH;
     JSObject *obj = js_NewGenerator(cx, REGS);
     if (!obj)
@@ -3450,42 +3321,128 @@ DEFAULT()
     goto error;
 }
 
 } /* interpreter loop */
 
     MOZ_ASSUME_UNREACHABLE("Interpreter loop exited via fallthrough");
 
   error:
-    switch (HandleError(cx, REGS)) {
-      case SuccessfulReturnContinuation:
-        goto successful_return_continuation;
-
-      case ErrorReturnContinuation:
+    JS_ASSERT(script->containsPC(REGS.pc));
+
+    if (cx->isExceptionPending()) {
+        /* Call debugger throw hooks. */
+        if (JS_UNLIKELY(cx->compartment()->debugMode())) {
+            JSTrapStatus status = DebugExceptionUnwind(cx, REGS.fp(), REGS.pc);
+            switch (status) {
+              case JSTRAP_ERROR:
+                goto error;
+
+              case JSTRAP_CONTINUE:
+              case JSTRAP_THROW:
+                break;
+
+              case JSTRAP_RETURN:
+                interpReturnOK = true;
+                goto forced_return;
+
+              default:
+                MOZ_ASSUME_UNREACHABLE("Invalid trap status");
+            }
+        }
+
+        for (TryNoteIter tni(cx, REGS); !tni.done(); ++tni) {
+            JSTryNote *tn = *tni;
+
+            UnwindScope(cx, REGS.fp(), tn->stackDepth);
+
+            /*
+             * Set pc to the first bytecode after the the try note to point
+             * 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)))
+                    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.
+                 */
+                goto enterInterpreterLoop;
+
+              case JSTRY_FINALLY:
+                /*
+                 * Push (true, exception) pair for finally to indicate that
+                 * [retsub] should rethrow the exception.
+                 */
+                PUSH_BOOLEAN(true);
+                PUSH_COPY(cx->getPendingException());
+                cx->clearPendingException();
+
+                /*
+                 * Leave the scope via a plain goto (and not via
+                 * ADVANCE_AND_DISPATCH, which may be implemented with indirect
+                 * goto) so that the TryNoteIter goes out of scope properly.
+                 */
+                goto enterInterpreterLoop;
+
+              case JSTRY_ITER: {
+                /* This is similar to JSOP_ENDITER in the interpreter loop. */
+                JS_ASSERT(JSOp(*REGS.pc) == JSOP_ENDITER);
+                RootedObject &obj = rootObject0;
+                obj = &REGS.sp[-1].toObject();
+                bool ok = UnwindIteratorForException(cx, obj);
+                REGS.sp -= 1;
+                if (!ok)
+                    goto error;
+                break;
+              }
+
+              case JSTRY_LOOP:
+                break;
+           }
+        }
+
+        /*
+         * Propagate the exception or error to the caller unless the exception
+         * is an asynchronous return from a generator.
+         */
         interpReturnOK = false;
-        goto return_continuation;
-
-      case CatchContinuation:
-        ADVANCE_AND_DISPATCH(0);
-
-      case FinallyContinuation:
-        /*
-         * Push (true, exception) pair for finally to indicate that [retsub]
-         * should rethrow the exception.
-         */
-        PUSH_BOOLEAN(true);
-        PUSH_COPY(cx->getPendingException());
-        cx->clearPendingException();
-        ADVANCE_AND_DISPATCH(0);
+        if (JS_UNLIKELY(cx->isExceptionPending() &&
+                        cx->getPendingException().isMagic(JS_GENERATOR_CLOSING))) {
+            cx->clearPendingException();
+            interpReturnOK = true;
+            REGS.fp()->clearReturnValue();
+        }
+    } else {
+        UnwindForUncatchableException(cx, REGS);
+        interpReturnOK = false;
     }
-    MOZ_ASSUME_UNREACHABLE("Invalid HandleError continuation");
+
+  forced_return:
+    UnwindScope(cx, REGS.fp(), 0);
+    REGS.setToEndOfScript();
+
+    if (activation.entryFrame() != REGS.fp())
+        goto inline_return;
 
   exit:
     if (JS_UNLIKELY(cx->compartment()->debugMode()))
-        interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), REGS.pc, interpReturnOK);
+        interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), interpReturnOK);
     if (!REGS.fp()->isYielding())
         REGS.fp()->epilogue(cx);
     else
         probes::ExitScript(cx, script, script->function(), REGS.fp()->hasPushedSPSFrame());
 
     gc::MaybeVerifyBarriers(cx, true);
 
 #if JS_TRACE_LOGGING
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -13,18 +13,16 @@
 
 #include "jsiter.h"
 #include "jspubtd.h"
 
 #include "vm/Stack.h"
 
 namespace js {
 
-class ScopeIter;
-
 /*
  * Announce to the debugger that the thread has entered a new JavaScript frame,
  * |frame|. Call whatever hooks have been registered to observe new frames, and
  * return a JSTrapStatus code indication how execution should proceed:
  *
  * - JSTRAP_CONTINUE: Continue execution normally.
  *
  * - JSTRAP_THROW: Throw an exception. ScriptDebugPrologue has set |cx|'s
@@ -33,17 +31,17 @@ class ScopeIter;
  * - JSTRAP_ERROR: Terminate execution (as is done when a script is terminated
  *   for running too long). ScriptDebugPrologue has cleared |cx|'s pending
  *   exception.
  *
  * - JSTRAP_RETURN: Return from the new frame immediately. ScriptDebugPrologue
  *   has set |frame|'s return value appropriately.
  */
 extern JSTrapStatus
-ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
+ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame);
 
 /*
  * Announce to the debugger that the thread has exited a JavaScript frame, |frame|.
  * If |ok| is true, the frame is returning normally; if |ok| is false, the frame
  * is throwing an exception or terminating.
  *
  * Call whatever hooks have been registered to observe frame exits. Change cx's
  * current exception and |frame|'s return value to reflect the changes in behavior
@@ -51,17 +49,17 @@ ScriptDebugPrologue(JSContext *cx, Abstr
  *
  * This function may be called twice for the same outgoing frame; only the
  * first call has any effect. (Permitting double calls simplifies some
  * cases where an onPop handler's resumption value changes a return to a
  * throw, or vice versa: we can redirect to a complete copy of the
  * alternative path, containing its own call to ScriptDebugEpilogue.)
  */
 extern bool
-ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc, bool ok);
+ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, bool ok);
 
 /*
  * Announce to the debugger that an exception has been thrown and propagated
  * to |frame|. Call whatever hooks have been registered to observe this and
  * return a JSTrapStatus code indication how execution should proceed:
  *
  * - JSTRAP_CONTINUE: Continue throwing the current exception.
  *
@@ -315,17 +313,17 @@ TypeOfObject(JSObject *obj);
 extern JSType
 TypeOfValue(const Value &v);
 
 extern bool
 HasInstance(JSContext *cx, HandleObject obj, HandleValue v, bool *bp);
 
 /* Unwind block and scope chains to match the given depth. */
 extern void
-UnwindScope(JSContext *cx, ScopeIter &si, uint32_t stackDepth);
+UnwindScope(JSContext *cx, AbstractFramePtr frame, uint32_t stackDepth);
 
 /*
  * Unwind for an uncatchable exception. This means not running finalizers, etc;
  * just preserving the basic engine stack invariants.
  */
 extern void
 UnwindForUncatchableException(JSContext *cx, const FrameRegs &regs);
 
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -63,29 +63,28 @@ static bool
 IsTopFrameConstructing(JSContext *cx, AbstractFramePtr frame)
 {
     ScriptFrameIter iter(cx);
     JS_ASSERT(iter.abstractFramePtr() == frame);
     return iter.isConstructing();
 }
 
 JSTrapStatus
-js::ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
+js::ScriptDebugPrologue(JSContext *cx, AbstractFramePtr frame)
 {
     JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
 
     if (!frame.script()->selfHosted) {
-        JSAbstractFramePtr jsframe(frame.raw(), pc);
         if (frame.isFramePushedByExecute()) {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.executeHook)
-                frame.setHookData(hook(cx, jsframe, IsTopFrameConstructing(cx, frame),
+                frame.setHookData(hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame),
                                        true, 0, cx->runtime()->debugHooks.executeHookData));
         } else {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.callHook)
-                frame.setHookData(hook(cx, jsframe, IsTopFrameConstructing(cx, frame),
+                frame.setHookData(hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame),
                                        true, 0, cx->runtime()->debugHooks.callHookData));
         }
     }
 
     RootedValue rval(cx);
     JSTrapStatus status = Debugger::onEnterFrame(cx, frame, &rval);
     switch (status) {
       case JSTRAP_CONTINUE:
@@ -101,31 +100,30 @@ js::ScriptDebugPrologue(JSContext *cx, A
         break;
       default:
         MOZ_ASSUME_UNREACHABLE("bad Debugger::onEnterFrame JSTrapStatus value");
     }
     return status;
 }
 
 bool
-js::ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc, bool okArg)
+js::ScriptDebugEpilogue(JSContext *cx, AbstractFramePtr frame, bool okArg)
 {
     JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
 
     bool ok = okArg;
 
     // We don't add hook data for self-hosted scripts, so we don't need to check for them, here.
     if (void *hookData = frame.maybeHookData()) {
-        JSAbstractFramePtr jsframe(frame.raw(), pc);
         if (frame.isFramePushedByExecute()) {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.executeHook)
-                hook(cx, jsframe, IsTopFrameConstructing(cx, frame), false, &ok, hookData);
+                hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame), false, &ok, hookData);
         } else {
             if (JSInterpreterHook hook = cx->runtime()->debugHooks.callHook)
-                hook(cx, jsframe, IsTopFrameConstructing(cx, frame), false, &ok, hookData);
+                hook(cx, Jsvalify(frame), IsTopFrameConstructing(cx, frame), false, &ok, hookData);
         }
     }
 
     return Debugger::onLeaveFrame(cx, frame, ok);
 }
 
 JSTrapStatus
 js::DebugExceptionUnwind(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
@@ -1225,37 +1223,37 @@ JS::FormatStackDump(JSContext *cx, char 
     }
 
     if (!num)
         buf = JS_sprintf_append(buf, "JavaScript stack is empty\n");
 
     return buf;
 }
 
-JSAbstractFramePtr::JSAbstractFramePtr(void *raw, jsbytecode *pc)
-  : ptr_(uintptr_t(raw)), pc_(pc)
+JSAbstractFramePtr::JSAbstractFramePtr(void *raw)
+  : ptr_(uintptr_t(raw))
 { }
 
 JSObject *
 JSAbstractFramePtr::scopeChain(JSContext *cx)
 {
-    AbstractFramePtr frame(*this);
+    AbstractFramePtr frame = Valueify(*this);
     RootedObject scopeChain(cx, frame.scopeChain());
     AutoCompartment ac(cx, scopeChain);
-    return GetDebugScopeForFrame(cx, frame, pc());
+    return GetDebugScopeForFrame(cx, frame);
 }
 
 JSObject *
 JSAbstractFramePtr::callObject(JSContext *cx)
 {
-    AbstractFramePtr frame(*this);
+    AbstractFramePtr frame = Valueify(*this);
     if (!frame.isFunctionFrame())
         return nullptr;
 
-    JSObject *o = GetDebugScopeForFrame(cx, frame, pc());
+    JSObject *o = GetDebugScopeForFrame(cx, frame);
 
     /*
      * Given that fp is a function frame and GetDebugScopeForFrame always fills
      * in missing scopes, we can expect to find fp's CallObject on 'o'. Note:
      *  - GetDebugScopeForFrame wraps every ScopeObject (missing or not) with
      *    a DebugScopeObject proxy.
      *  - If fp is an eval-in-function, then fp has no callobj of its own and
      *    JS_GetFrameCallObject will return the innermost function's callobj.
@@ -1267,45 +1265,45 @@ JSAbstractFramePtr::callObject(JSContext
         o = o->enclosingScope();
     }
     return nullptr;
 }
 
 JSFunction *
 JSAbstractFramePtr::maybeFun()
 {
-    AbstractFramePtr frame(*this);
+    AbstractFramePtr frame = Valueify(*this);
     return frame.maybeFun();
 }
 
 JSScript *
 JSAbstractFramePtr::script()
 {
-    AbstractFramePtr frame(*this);
+    AbstractFramePtr frame = Valueify(*this);
     return frame.script();
 }
 
 bool
 JSAbstractFramePtr::getThisValue(JSContext *cx, MutableHandleValue thisv)
 {
-    AbstractFramePtr frame(*this);
+    AbstractFramePtr frame = Valueify(*this);
 
     RootedObject scopeChain(cx, frame.scopeChain());
     js::AutoCompartment ac(cx, scopeChain);
     if (!ComputeThis(cx, frame))
         return false;
 
     thisv.set(frame.thisValue());
     return true;
 }
 
 bool
 JSAbstractFramePtr::isDebuggerFrame()
 {
-    AbstractFramePtr frame(*this);
+    AbstractFramePtr frame = Valueify(*this);
     return frame.isDebuggerFrame();
 }
 
 bool
 JSAbstractFramePtr::evaluateInStackFrame(JSContext *cx,
                                          const char *bytes, unsigned length,
                                          const char *filename, unsigned lineno,
                                          MutableHandleValue rval)
@@ -1337,17 +1335,17 @@ JSAbstractFramePtr::evaluateUCInStackFra
     if (!CheckDebugMode(cx))
         return false;
 
     RootedObject scope(cx, scopeChain(cx));
     Rooted<Env*> env(cx, scope);
     if (!env)
         return false;
 
-    AbstractFramePtr frame(*this);
+    AbstractFramePtr frame = Valueify(*this);
     if (!ComputeThis(cx, frame))
         return false;
     RootedValue thisv(cx, frame.thisValue());
 
     js::AutoCompartment ac(cx, env);
     return EvaluateInEnv(cx, env, thisv, frame, StableCharPtr(chars, length), length,
                          filename, lineno, rval);
 }
@@ -1379,17 +1377,17 @@ JSBrokenFrameIterator::operator++()
     *data = iter.data_;
     return *this;
 }
 
 JSAbstractFramePtr
 JSBrokenFrameIterator::abstractFramePtr() const
 {
     NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
-    return JSAbstractFramePtr(iter.abstractFramePtr().raw(), iter.pc());
+    return Jsvalify(iter.abstractFramePtr());
 }
 
 jsbytecode *
 JSBrokenFrameIterator::pc() const
 {
     NonBuiltinScriptFrameIter iter(*(ScriptFrameIter::Data *)data_);
     return iter.pc();
 }
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -869,65 +869,64 @@ ScopeIter::ScopeIter(JSObject &enclosing
     frame_(NullFramePtr()),
     cur_(cx, &enclosingScope),
     block_(cx, nullptr),
     type_(Type(-1))
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
+ScopeIter::ScopeIter(AbstractFramePtr frame, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(frame),
     cur_(cx, frame.scopeChain()),
-    block_(cx, frame.script()->getBlockScope(pc))
+    block_(cx, frame.maybeBlockChain())
 {
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(const ScopeIterKey &key, JSContext *cx
+ScopeIter::ScopeIter(const ScopeIter &si, AbstractFramePtr frame, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-  : cx(cx),
-    frame_(key.frame()),
-    cur_(cx, key.cur()),
-    block_(cx, key.block()),
-    type_(key.type()),
-    hasScopeObject_(key.hasScopeObject())
+  : cx(si.cx),
+    frame_(frame),
+    cur_(cx, si.cur_),
+    block_(cx, si.block_),
+    type_(si.type_),
+    hasScopeObject_(si.hasScopeObject_)
 {
-    assertSameCompartment(cx, key.frame());
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, ScopeObject &scope, JSContext *cx
+ScopeIter::ScopeIter(AbstractFramePtr frame, ScopeObject &scope, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(frame),
     cur_(cx, &scope),
     block_(cx)
 {
     /*
      * Find the appropriate static block for this iterator, given 'scope'. We
      * know that 'scope' is a (non-optimized) scope on fp's scope chain. We do
      * not, however, know whether fp->maybeScopeChain() encloses 'scope'. E.g.:
      *
      *   let (x = 1) {
      *     g = function() { eval('debugger') };
      *     let (y = 1) g();
      *   }
      *
-     * g will have x's block in its enclosing scope but not y's. However, at the
-     * debugger statement, both the x's and y's blocks will be on the block
-     * chain. Fortunately, we can compare scope object stack depths to determine
-     * the block (if any) that encloses 'scope'.
+     * g will have x's block in its enclosing scope but not y's. However, at
+     * the debugger statement, both the x's and y's blocks will be on
+     * fp->blockChain. Fortunately, we can compare scope object stack depths to
+     * determine the block (if any) that encloses 'scope'.
      */
     if (cur_->is<NestedScopeObject>()) {
-        block_ = frame.script()->getBlockScope(pc);
+        block_ = frame.maybeBlockChain();
         while (block_) {
             if (block_->stackDepth() <= cur_->as<NestedScopeObject>().stackDepth())
                 break;
             block_ = block_->enclosingBlock();
         }
         JS_ASSERT_IF(cur_->is<ClonedBlockObject>(),
                      cur_->as<ClonedBlockObject>().staticBlock() == *block_);
     } else {
@@ -1092,17 +1091,18 @@ class DebugScopeProxy : public BaseProxy
     enum Action { SET, GET };
 
     /*
      * This function handles access to unaliased locals/formals. Since they are
      * unaliased, the values of these variables are not stored in the slots of
      * the normal Call/BlockObject scope objects and thus must be recovered
      * from somewhere else:
      *  + if the invocation for which the scope was created is still executing,
-     *    there is a StackFrame live on the stack holding the values;
+     *    there is a StackFrame (either live on the stack or floating in a
+     *    generator object) holding the values;
      *  + if the invocation for which the scope was created finished executing:
      *     - and there was a DebugScopeObject associated with scope, then the
      *       DebugScopes::onPop(Call|Block) handler copied out the unaliased
      *       variables:
      *        . for block scopes, the unaliased values were copied directly
      *          into the block object, since there is a slot allocated for every
      *          block binding, regardless of whether it is aliased;
      *        . for function scopes, a dense array is created in onPopCall to hold
@@ -1112,17 +1112,17 @@ class DebugScopeProxy : public BaseProxy
      *
      * handleUnaliasedAccess returns 'true' if the access was unaliased and
      * completed by handleUnaliasedAccess.
      */
     bool handleUnaliasedAccess(JSContext *cx, Handle<DebugScopeObject*> debugScope, Handle<ScopeObject*> scope,
                                jsid id, Action action, MutableHandleValue vp)
     {
         JS_ASSERT(&debugScope->scope() == scope);
-        ScopeIterKey* maybeLiveScope = DebugScopes::hasLiveScope(*scope);
+        AbstractFramePtr maybeframe = DebugScopes::hasLiveFrame(*scope);
 
         /* Handle unaliased formals, vars, and consts at function scope. */
         if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
             CallObject &callobj = scope->as<CallObject>();
             RootedScript script(cx, callobj.callee().nonLazyScript());
             if (!script->ensureHasTypes(cx))
                 return false;
 
@@ -1133,50 +1133,48 @@ class DebugScopeProxy : public BaseProxy
             if (!bi)
                 return false;
 
             if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) {
                 unsigned i = bi.frameIndex();
                 if (script->varIsAliased(i))
                     return false;
 
-                if (maybeLiveScope) {
-                    AbstractFramePtr frame = maybeLiveScope->frame();
+                if (maybeframe) {
                     if (action == GET)
-                        vp.set(frame.unaliasedVar(i));
+                        vp.set(maybeframe.unaliasedVar(i));
                     else
-                        frame.unaliasedVar(i) = vp;
+                        maybeframe.unaliasedVar(i) = vp;
                 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
                     if (action == GET)
                         vp.set(snapshot->getDenseElement(bindings.numArgs() + i));
                     else
                         snapshot->setDenseElement(bindings.numArgs() + i, vp);
                 } else {
                     /* The unaliased value has been lost to the debugger. */
                     if (action == GET)
                         vp.set(UndefinedValue());
                 }
             } else {
                 JS_ASSERT(bi->kind() == ARGUMENT);
                 unsigned i = bi.frameIndex();
                 if (script->formalIsAliased(i))
                     return false;
 
-                if (maybeLiveScope) {
-                    AbstractFramePtr frame = maybeLiveScope->frame();
-                    if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
+                if (maybeframe) {
+                    if (script->argsObjAliasesFormals() && maybeframe.hasArgsObj()) {
                         if (action == GET)
-                            vp.set(frame.argsObj().arg(i));
+                            vp.set(maybeframe.argsObj().arg(i));
                         else
-                            frame.argsObj().setArg(i, vp);
+                            maybeframe.argsObj().setArg(i, vp);
                     } else {
                         if (action == GET)
-                            vp.set(frame.unaliasedFormal(i, DONT_CHECK_ALIASING));
+                            vp.set(maybeframe.unaliasedFormal(i, DONT_CHECK_ALIASING));
                         else
-                            frame.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp;
+                            maybeframe.unaliasedFormal(i, DONT_CHECK_ALIASING) = vp;
                     }
                 } else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
                     if (action == GET)
                         vp.set(snapshot->getDenseElement(i));
                     else
                         snapshot->setDenseElement(i, vp);
                 } else {
                     /* The unaliased value has been lost to the debugger. */
@@ -1197,24 +1195,23 @@ class DebugScopeProxy : public BaseProxy
             Shape *shape = block->lastProperty()->search(cx, id);
             if (!shape)
                 return false;
 
             unsigned i = shape->shortid();
             if (block->staticBlock().isAliased(i))
                 return false;
 
-            if (maybeLiveScope) {
-                AbstractFramePtr frame = maybeLiveScope->frame();
-                JSScript *script = frame.script();
+            if (maybeframe) {
+                JSScript *script = maybeframe.script();
                 unsigned local = block->slotToLocalIndex(script->bindings, shape->slot());
                 if (action == GET)
-                    vp.set(frame.unaliasedLocal(local));
+                    vp.set(maybeframe.unaliasedLocal(local));
                 else
-                    frame.unaliasedLocal(local) = vp;
+                    maybeframe.unaliasedLocal(local) = vp;
                 JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script));
             } else {
                 if (action == GET)
                     vp.set(block->var(i, DONT_CHECK_ALIASING));
                 else
                     block->setVar(i, vp, DONT_CHECK_ALIASING);
             }
 
@@ -1261,24 +1258,24 @@ class DebugScopeProxy : public BaseProxy
         *maybeArgsObj = nullptr;
 
         if (!isArguments(cx, id) || !isFunctionScope(scope))
             return true;
 
         if (scope.as<CallObject>().callee().nonLazyScript()->needsArgsObj())
             return true;
 
-        ScopeIterKey *maybeScope = DebugScopes::hasLiveScope(scope);
-        if (!maybeScope) {
+        AbstractFramePtr maybeframe = DebugScopes::hasLiveFrame(scope);
+        if (!maybeframe) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
                                  "Debugger scope");
             return false;
         }
 
-        *maybeArgsObj = ArgumentsObject::createUnexpected(cx, maybeScope->frame());
+        *maybeArgsObj = ArgumentsObject::createUnexpected(cx, maybeframe);
         return true;
     }
 
   public:
     static int family;
     static DebugScopeProxy singleton;
 
     DebugScopeProxy() : BaseProxyHandler(&family) {}
@@ -1585,35 +1582,50 @@ DebugScopes::mark(JSTracer *trc)
 {
     proxiedScopes.trace(trc);
 }
 
 void
 DebugScopes::sweep(JSRuntime *rt)
 {
     /*
-     * missingScopes points to debug scopes weakly so that debug scopes can be
-     * released more eagerly.
+     * Note: missingScopes points to debug scopes weakly not just so that debug
+     * scopes can be released more eagerly, but, more importantly, to avoid
+     * creating an uncollectable cycle with suspended generator frames.
      */
     for (MissingScopeMap::Enum e(missingScopes); !e.empty(); e.popFront()) {
         if (IsObjectAboutToBeFinalized(e.front().value().unsafeGet()))
             e.removeFront();
     }
 
     for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) {
         ScopeObject *scope = e.front().key();
+        AbstractFramePtr frame = e.front().value();
 
         /*
          * Scopes can be finalized when a debugger-synthesized ScopeObject is
          * no longer reachable via its DebugScopeObject.
          */
         if (IsObjectAboutToBeFinalized(&scope)) {
             e.removeFront();
             continue;
         }
+
+        /*
+         * As explained in onGeneratorFrameChange, liveScopes includes
+         * suspended generator frames. Since a generator can be finalized while
+         * its scope is live, we must explicitly detect finalized generators.
+         */
+        if (JSGenerator *gen = frame.maybeSuspendedGenerator(rt)) {
+            JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN);
+            if (IsObjectAboutToBeFinalized(&gen->obj)) {
+                e.removeFront();
+                continue;
+            }
+        }
     }
 }
 
 /*
  * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode
  * (in particular, JS_GetFrameScopeChain does not require debug mode). Since
  * DebugScopes::onPop* are only called in debug mode, this means we cannot
  * use any of the maps in DebugScopes. This will produce debug scope chains
@@ -1694,33 +1706,32 @@ DebugScopes::hasDebugScope(JSContext *cx
     return nullptr;
 }
 
 bool
 DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope)
 {
     JS_ASSERT(!si.hasScopeObject());
     JS_ASSERT(cx->compartment() == debugScope.compartment());
-    JS_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator());
 
     if (!CanUseDebugScopeMaps(cx))
         return true;
 
     DebugScopes *scopes = ensureCompartmentData(cx);
     if (!scopes)
         return false;
 
     JS_ASSERT(!scopes->missingScopes.has(si));
     if (!scopes->missingScopes.put(si, &debugScope)) {
         js_ReportOutOfMemory(cx);
         return false;
     }
 
     JS_ASSERT(!scopes->liveScopes.has(&debugScope.scope()));
-    if (!scopes->liveScopes.put(&debugScope.scope(), si)) {
+    if (!scopes->liveScopes.put(&debugScope.scope(), si.frame())) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope());
 
     return true;
 }
 
@@ -1744,17 +1755,17 @@ DebugScopes::onPopCall(AbstractFramePtr 
         if (!frame.hasCallObj())
             return;
 
         CallObject &callobj = frame.scopeChain()->as<CallObject>();
         scopes->liveScopes.remove(&callobj);
         if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj))
             debugScope = &p->value()->as<DebugScopeObject>();
     } else {
-        ScopeIter si(frame, frame.script()->main(), cx);
+        ScopeIter si(frame, cx);
         if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) {
             debugScope = p->value();
             scopes->liveScopes.remove(&debugScope->scope().as<CallObject>());
             scopes->missingScopes.remove(p);
         }
     }
 
     /*
@@ -1799,45 +1810,34 @@ DebugScopes::onPopCall(AbstractFramePtr 
             return;
         }
 
         debugScope->initSnapshot(*snapshot);
     }
 }
 
 void
-DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
+DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame)
 {
     assertSameCompartment(cx, frame);
 
     DebugScopes *scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return;
 
-    ScopeIter si(frame, pc, cx);
-    onPopBlock(cx, si);
-}
-
-void
-DebugScopes::onPopBlock(JSContext *cx, const ScopeIter &si)
-{
-    DebugScopes *scopes = cx->compartment()->debugScopes;
-    if (!scopes)
-        return;
-
-    JS_ASSERT(si.type() == ScopeIter::Block);
-
-    if (si.staticBlock().needsClone()) {
-        ClonedBlockObject &clone = si.scope().as<ClonedBlockObject>();
-        clone.copyUnaliasedValues(si.frame());
+    StaticBlockObject &staticBlock = *frame.maybeBlockChain();
+    if (staticBlock.needsClone()) {
+        ClonedBlockObject &clone = frame.scopeChain()->as<ClonedBlockObject>();
+        clone.copyUnaliasedValues(frame);
         scopes->liveScopes.remove(&clone);
     } else {
+        ScopeIter si(frame, cx);
         if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) {
             ClonedBlockObject &clone = p->value()->scope().as<ClonedBlockObject>();
-            clone.copyUnaliasedValues(si.frame());
+            clone.copyUnaliasedValues(frame);
             scopes->liveScopes.remove(&clone);
             scopes->missingScopes.remove(p);
         }
     }
 }
 
 void
 DebugScopes::onPopWith(AbstractFramePtr frame)
@@ -1858,16 +1858,53 @@ DebugScopes::onPopStrictEvalScope(Abstra
      * The StackFrame may be observed before the prologue has created the
      * CallObject. See ScopeIter::settle.
      */
     if (frame.hasCallObj())
         scopes->liveScopes.remove(&frame.scopeChain()->as<CallObject>());
 }
 
 void
+DebugScopes::onGeneratorFrameChange(AbstractFramePtr from, AbstractFramePtr to, JSContext *cx)
+{
+    for (ScopeIter toIter(to, cx); !toIter.done(); ++toIter) {
+        DebugScopes *scopes = ensureCompartmentData(cx);
+        if (!scopes)
+            return;
+
+        if (toIter.hasScopeObject()) {
+            /*
+             * Not only must we correctly replace mappings [scope -> from] with
+             * mappings [scope -> to], but we must add [scope -> to] if it
+             * doesn't already exist so that if we need to proxy a generator's
+             * scope while it is suspended, we can find its frame (which would
+             * otherwise not be found by AllFramesIter).
+             */
+            JS_ASSERT(toIter.scope().compartment() == cx->compartment());
+            LiveScopeMap::AddPtr livePtr = scopes->liveScopes.lookupForAdd(&toIter.scope());
+            if (livePtr) {
+                livePtr->value() = to;
+            } else {
+                scopes->liveScopes.add(livePtr, &toIter.scope(), to);  // OOM here?
+                liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &toIter.scope());
+            }
+        } else {
+            ScopeIter si(toIter, from, cx);
+            JS_ASSERT(si.frame().scopeChain()->compartment() == cx->compartment());
+            if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(si)) {
+                DebugScopeObject &debugScope = *p->value();
+                scopes->liveScopes.lookup(&debugScope.scope())->value() = to;
+                scopes->missingScopes.remove(p);
+                scopes->missingScopes.put(toIter, &debugScope);  // OOM here?
+            }
+        }
+    }
+}
+
+void
 DebugScopes::onCompartmentLeaveDebugMode(JSCompartment *c)
 {
     DebugScopes *scopes = c->debugScopes;
     if (scopes) {
         scopes->proxiedScopes.clear();
         scopes->missingScopes.clear();
         scopes->liveScopes.clear();
     }
@@ -1896,51 +1933,64 @@ DebugScopes::updateLiveScopes(JSContext 
          */
         if (i.isIon())
             continue;
 
         AbstractFramePtr frame = i.abstractFramePtr();
         if (frame.scopeChain()->compartment() != cx->compartment())
             continue;
 
-        if (frame.isFunctionFrame() && frame.callee()->isGenerator())
-            continue;
-
-        for (ScopeIter si(frame, i.pc(), cx); !si.done(); ++si) {
+        for (ScopeIter si(frame, cx); !si.done(); ++si) {
             if (si.hasScopeObject()) {
                 JS_ASSERT(si.scope().compartment() == cx->compartment());
                 DebugScopes *scopes = ensureCompartmentData(cx);
                 if (!scopes)
                     return false;
-                if (!scopes->liveScopes.put(&si.scope(), si))
+                if (!scopes->liveScopes.put(&si.scope(), frame))
                     return false;
                 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope());
             }
         }
 
         if (frame.prevUpToDate())
             return true;
         JS_ASSERT(frame.scopeChain()->compartment()->debugMode());
         frame.setPrevUpToDate();
     }
 
     return true;
 }
 
-ScopeIterKey*
-DebugScopes::hasLiveScope(ScopeObject &scope)
+AbstractFramePtr
+DebugScopes::hasLiveFrame(ScopeObject &scope)
 {
     DebugScopes *scopes = scope.compartment()->debugScopes;
     if (!scopes)
-        return nullptr;
+        return NullFramePtr();
+
+    if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope)) {
+        AbstractFramePtr frame = p->value();
 
-    if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope))
-        return &p->value();
+        /*
+         * Since liveScopes is effectively a weak pointer, we need a read
+         * barrier. The scenario where this is necessary is:
+         *  1. GC starts, a suspended generator is not live
+         *  2. hasLiveFrame returns a StackFrame* to the (soon to be dead)
+         *     suspended generator
+         *  3. stack frame values (which will neve be marked) are read from the
+         *     StackFrame
+         *  4. GC completes, live objects may now point to values that weren't
+         *     marked and thus may point to swept GC things
+         */
+        if (JSGenerator *gen = frame.maybeSuspendedGenerator(scope.compartment()->runtimeFromMainThread()))
+            JSObject::readBarrier(gen->obj);
 
-    return nullptr;
+        return frame;
+    }
+    return NullFramePtr();
 }
 
 /*****************************************************************************/
 
 static JSObject *
 GetDebugScope(JSContext *cx, const ScopeIter &si);
 
 static DebugScopeObject *
@@ -1991,36 +2041,32 @@ GetDebugScopeForMissing(JSContext *cx, c
      *
      * Note: to preserve scopeChain depth invariants, these lazily-reified
      * scopes must not be put on the frame's scope chain; instead, they are
      * maintained via DebugScopes hooks.
      */
     DebugScopeObject *debugScope = nullptr;
     switch (si.type()) {
       case ScopeIter::Call: {
-        // Generators should always reify their scopes.
-        JS_ASSERT(!si.frame().callee()->isGenerator());
         Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.frame()));
         if (!callobj)
             return nullptr;
 
         if (callobj->enclosingScope().is<DeclEnvObject>()) {
             JS_ASSERT(CallObjectLambdaName(callobj->callee()));
             DeclEnvObject &declenv = callobj->enclosingScope().as<DeclEnvObject>();
             enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug);
             if (!enclosingDebug)
                 return nullptr;
         }
 
         debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug);
         break;
       }
       case ScopeIter::Block: {
-        // Generators should always reify their scopes.
-        JS_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator());
         Rooted<StaticBlockObject *> staticBlock(cx, &si.staticBlock());
         ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.frame());
         if (!block)
             return nullptr;
 
         debugScope = DebugScopeObject::create(cx, *block, enclosingDebug);
         break;
       }
@@ -2051,18 +2097,18 @@ GetDebugScope(JSContext *cx, JSObject &o
         JSObject *o = &obj;
         while ((o = o->enclosingScope()))
             JS_ASSERT(!o->is<ScopeObject>());
 #endif
         return &obj;
     }
 
     Rooted<ScopeObject*> scope(cx, &obj.as<ScopeObject>());
-    if (ScopeIterKey *maybeLiveScope = DebugScopes::hasLiveScope(*scope)) {
-        ScopeIter si(*maybeLiveScope, cx);
+    if (AbstractFramePtr frame = DebugScopes::hasLiveFrame(*scope)) {
+        ScopeIter si(frame, *scope, cx);
         return GetDebugScope(cx, si);
     }
     ScopeIter si(scope->enclosingScope(), cx);
     return GetDebugScopeForScope(cx, scope, si);
 }
 
 static JSObject *
 GetDebugScope(JSContext *cx, const ScopeIter &si)
@@ -2087,22 +2133,22 @@ js::GetDebugScopeForFunction(JSContext *
     assertSameCompartment(cx, fun);
     JS_ASSERT(cx->compartment()->debugMode());
     if (!DebugScopes::updateLiveScopes(cx))
         return nullptr;
     return GetDebugScope(cx, *fun->environment());
 }
 
 JSObject *
-js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
+js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame)
 {
     assertSameCompartment(cx, frame);
     if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx))
         return nullptr;
-    ScopeIter si(frame, pc, cx);
+    ScopeIter si(frame, cx);
     return GetDebugScope(cx, si);
 }
 
 #ifdef DEBUG
 
 typedef HashSet<PropertyName *> PropertyNameSet;
 
 static bool
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -529,35 +529,39 @@ class ScopeIter
     /* ScopeIter does not have value semantics. */
     ScopeIter(const ScopeIter &si) MOZ_DELETE;
 
     ScopeIter(JSContext *cx) MOZ_DELETE;
 
   public:
 
     /* Constructing from a copy of an existing ScopeIter. */
-    ScopeIter(const ScopeIter &si, JSContext *cx
-              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+    explicit ScopeIter(const ScopeIter &si, JSContext *cx
+                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     /* Constructing from StackFrame places ScopeIter on the innermost scope. */
-    ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
-              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+    explicit ScopeIter(AbstractFramePtr frame, JSContext *cx
+                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     /*
      * Without a StackFrame, the resulting ScopeIter is done() with
      * enclosingScope() as given.
      */
-    ScopeIter(JSObject &enclosingScope, JSContext *cx
+    explicit ScopeIter(JSObject &enclosingScope, JSContext *cx
+                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+
+    /*
+     * For the special case of generators, copy the given ScopeIter, with 'fp'
+     * as the StackFrame instead of si.fp(). Not for general use.
+     */
+    ScopeIter(const ScopeIter &si, AbstractFramePtr frame, JSContext *cx
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     /* Like ScopeIter(StackFrame *) except start at 'scope'. */
-    ScopeIter(AbstractFramePtr frame, jsbytecode *pc, ScopeObject &scope, JSContext *cx
-              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-
-    ScopeIter(const ScopeIterKey &key, JSContext *cx
+    ScopeIter(AbstractFramePtr frame, ScopeObject &scope, JSContext *cx
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
     bool done() const { return !frame_; }
 
     /* If done(): */
 
     JSObject &enclosingScope() const { JS_ASSERT(done()); return *cur_; }
 
@@ -576,30 +580,25 @@ class ScopeIter
 };
 
 class ScopeIterKey
 {
     AbstractFramePtr frame_;
     JSObject *cur_;
     StaticBlockObject *block_;
     ScopeIter::Type type_;
-    bool hasScopeObject_;
 
   public:
     ScopeIterKey() : frame_(NullFramePtr()), cur_(nullptr), block_(nullptr), type_() {}
     ScopeIterKey(const ScopeIter &si)
-      : frame_(si.frame_), cur_(si.cur_), block_(si.block_), type_(si.type_),
-        hasScopeObject_(si.hasScopeObject_)
+      : frame_(si.frame_), cur_(si.cur_), block_(si.block_), type_(si.type_)
     {}
 
     AbstractFramePtr frame() const { return frame_; }
-    JSObject *cur() const { return cur_; }
-    StaticBlockObject *block() const { return block_; }
     ScopeIter::Type type() const { return type_; }
-    bool hasScopeObject() const { return hasScopeObject_; }
 
     /* For use as hash policy */
     typedef ScopeIterKey Lookup;
     static HashNumber hash(ScopeIterKey si);
     static bool match(ScopeIterKey si1, ScopeIterKey si2);
 };
 
 /*****************************************************************************/
@@ -628,17 +627,17 @@ class ScopeIterKey
  * always produced for the same underlying scope (optimized or not!). This is
  * maintained by some bookkeeping information stored in DebugScopes.
  */
 
 extern JSObject *
 GetDebugScopeForFunction(JSContext *cx, HandleFunction fun);
 
 extern JSObject *
-GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
+GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame);
 
 /* Provides debugger access to a scope. */
 class DebugScopeObject : public ProxyObject
 {
     /*
      * The enclosing scope on the dynamic scope chain. This slot is analogous
      * to the SCOPE_CHAIN_SLOT of a ScopeObject.
      */
@@ -686,17 +685,17 @@ class DebugScopes
     /*
      * The map from scope objects of live frames to the live frame. This map
      * updated lazily whenever the debugger needs the information. In between
      * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop*
      * removes scopes as they are popped). Thus, two consecutive debugger lazy
      * updates of liveScopes need only fill in the new scopes.
      */
     typedef HashMap<ScopeObject *,
-                    ScopeIterKey,
+                    AbstractFramePtr,
                     DefaultHasher<ScopeObject *>,
                     RuntimeAllocPolicy> LiveScopeMap;
     LiveScopeMap liveScopes;
     static JS_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map,
                                                             ScopeObject *key);
 
   public:
     DebugScopes(JSContext *c);
@@ -713,25 +712,27 @@ class DebugScopes
 
     static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope);
     static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope);
 
     static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si);
     static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope);
 
     static bool updateLiveScopes(JSContext *cx);
-    static ScopeIterKey *hasLiveScope(ScopeObject &scope);
+    static AbstractFramePtr hasLiveFrame(ScopeObject &scope);
 
-    // In debug-mode, these must be called whenever exiting a scope that might
-    // have stack-allocated locals.
+    /*
+     * In debug-mode, these must be called whenever exiting a call/block or
+     * when activating/yielding a generator.
+     */
     static void onPopCall(AbstractFramePtr frame, JSContext *cx);
-    static void onPopBlock(JSContext *cx, const ScopeIter &si);
-    static void onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
+    static void onPopBlock(JSContext *cx, AbstractFramePtr frame);
     static void onPopWith(AbstractFramePtr frame);
     static void onPopStrictEvalScope(AbstractFramePtr frame);
+    static void onGeneratorFrameChange(AbstractFramePtr from, AbstractFramePtr to, JSContext *cx);
     static void onCompartmentLeaveDebugMode(JSCompartment *c);
 };
 
 }  /* namespace js */
 
 template<>
 inline bool
 JSObject::is<js::NestedScopeObject>() const
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -72,24 +72,26 @@ StackFrame::compartment() const
 inline void
 StackFrame::initCallFrame(JSContext *cx, StackFrame *prev, jsbytecode *prevpc, Value *prevsp, JSFunction &callee,
                           JSScript *script, Value *argv, uint32_t nactual, StackFrame::Flags flagsArg)
 {
     JS_ASSERT((flagsArg & ~CONSTRUCTING) == 0);
     JS_ASSERT(callee.nonLazyScript() == script);
 
     /* Initialize stack frame members. */
-    flags_ = FUNCTION | HAS_SCOPECHAIN | flagsArg;
+    flags_ = FUNCTION | HAS_SCOPECHAIN | HAS_BLOCKCHAIN | flagsArg;
     argv_ = argv;
     exec.fun = &callee;
     u.nactual = nactual;
     scopeChain_ = callee.environment();
     prev_ = prev;
     prevpc_ = prevpc;
     prevsp_ = prevsp;
+    blockChain_= nullptr;
+    JS_ASSERT(!hasBlockChain());
     JS_ASSERT(!hasHookData());
 
     initVarsToUndefined();
 }
 
 inline void
 StackFrame::initVarsToUndefined()
 {
@@ -103,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(), i);
+    CheckLocalUnaliased(checkAliasing, script(), maybeBlockChain(), i);
 #endif
     return slots()[i];
 }
 
 inline Value &
 StackFrame::unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT(i < numFormalArgs());
@@ -509,16 +511,35 @@ AbstractFramePtr::unaliasedActual(unsign
         return asStackFrame()->unaliasedActual(i, checkAliasing);
 #ifdef JS_ION
     return asBaselineFrame()->unaliasedActual(i, checkAliasing);
 #else
     MOZ_ASSUME_UNREACHABLE("Invalid frame");
 #endif
 }
 
+inline JSGenerator *
+AbstractFramePtr::maybeSuspendedGenerator(JSRuntime *rt) const
+{
+    if (isStackFrame())
+        return asStackFrame()->maybeSuspendedGenerator(rt);
+    return nullptr;
+}
+
+inline StaticBlockObject *
+AbstractFramePtr::maybeBlockChain() const
+{
+    if (isStackFrame())
+        return asStackFrame()->maybeBlockChain();
+#ifdef JS_ION
+    return asBaselineFrame()->maybeBlockChain();
+#else
+    MOZ_ASSUME_UNREACHABLE("Invalid frame");
+#endif
+}
 inline bool
 AbstractFramePtr::hasCallObj() const
 {
     if (isStackFrame())
         return asStackFrame()->hasCallObj();
 #ifdef JS_ION
     return asBaselineFrame()->hasCallObj();
 #else
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -32,17 +32,17 @@ void
 StackFrame::initExecuteFrame(JSContext *cx, JSScript *script, AbstractFramePtr evalInFramePrev,
                              const Value &thisv, JSObject &scopeChain, ExecuteType type)
 {
     /*
      * See encoding of ExecuteType. When GLOBAL isn't set, we are executing a
      * script in the context of another frame and the frame type is determined
      * by the context.
      */
-    flags_ = type | HAS_SCOPECHAIN;
+    flags_ = type | HAS_SCOPECHAIN | HAS_BLOCKCHAIN;
 
     JSObject *callee = nullptr;
     if (!(flags_ & (GLOBAL))) {
         if (evalInFramePrev) {
             JS_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame());
             if (evalInFramePrev.isFunctionFrame()) {
                 callee = evalInFramePrev.callee();
                 flags_ |= FUNCTION;
@@ -76,16 +76,17 @@ StackFrame::initExecuteFrame(JSContext *
         u.evalScript = (JSScript *)0xbad;
 #endif
     }
 
     scopeChain_ = &scopeChain;
     prev_ = nullptr;
     prevpc_ = nullptr;
     prevsp_ = nullptr;
+    blockChain_ = nullptr;
 
     JS_ASSERT_IF(evalInFramePrev, isDebuggerFrame());
     evalInFramePrev_ = evalInFramePrev;
 
 #ifdef DEBUG
     Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
     hookData_ = (void *)0xbad;
 #endif
@@ -117,16 +118,19 @@ StackFrame::copyFrameAndValues(JSContext
 
     srcend = othersp;
     dst = slots();
     for (const Value *src = otherfp->slots(); src < srcend; src++, dst++) {
         *dst = *src;
         if (doPostBarrier)
             HeapValue::writeBarrierPost(*dst, dst);
     }
+
+    if (JS_UNLIKELY(cx->compartment()->debugMode()))
+        DebugScopes::onGeneratorFrameChange(otherfp, this, cx);
 }
 
 /* Note: explicit instantiation for js_NewGenerator located in jsiter.cpp. */
 template
 void StackFrame::copyFrameAndValues<StackFrame::NoPostBarrier>(
                                     JSContext *, Value *, StackFrame *, const Value *, Value *);
 template
 void StackFrame::copyFrameAndValues<StackFrame::DoPostBarrier>(
@@ -146,16 +150,37 @@ StackFrame::writeBarrierPost()
             JSScript::writeBarrierPost(u.evalScript, (void *)&u.evalScript);
     } else {
         JSScript::writeBarrierPost(exec.script, (void *)&exec.script);
     }
     if (hasReturnValue())
         HeapValue::writeBarrierPost(rval_, &rval_);
 }
 
+JSGenerator *
+StackFrame::maybeSuspendedGenerator(JSRuntime *rt)
+{
+    /*
+     * A suspended generator's frame is embedded inside the JSGenerator object
+     * and is not currently running.
+     */
+    if (!isGeneratorFrame() || !isSuspended())
+        return nullptr;
+
+    /*
+     * Once we know we have a suspended generator frame, there is a static
+     * offset from the frame's snapshot to beginning of the JSGenerator.
+     */
+    char *vp = reinterpret_cast<char *>(generatorArgsSnapshotBegin());
+    char *p = vp - offsetof(JSGenerator, stackSnapshot);
+    JSGenerator *gen = reinterpret_cast<JSGenerator *>(p);
+    JS_ASSERT(gen->fp == this);
+    return gen;
+}
+
 bool
 StackFrame::copyRawFrameSlots(AutoValueVector *vec)
 {
     if (!vec->resize(numFormalArgs() + script()->nfixed))
         return false;
     PodCopy(vec->begin(), argv(), numFormalArgs());
     PodCopy(vec->begin() + numFormalArgs(), slots(), script()->nfixed);
     return true;
@@ -267,16 +292,17 @@ StackFrame::prologue(JSContext *cx)
     probes::EnterScript(cx, script, script->function(), this);
     return true;
 }
 
 void
 StackFrame::epilogue(JSContext *cx)
 {
     JS_ASSERT(!isYielding());
+    JS_ASSERT(!hasBlockChain());
 
     RootedScript script(cx, this->script());
     probes::ExitScript(cx, script, script->function(), hasPushedSPSFrame());
 
     if (isEvalFrame()) {
         if (isStrictEvalFrame()) {
             JS_ASSERT_IF(hasCallObj(), scopeChain()->as<CallObject>().isForEval());
             if (JS_UNLIKELY(cx->compartment()->debugMode()))
@@ -320,33 +346,49 @@ StackFrame::epilogue(JSContext *cx)
 
     if (isConstructing() && thisValue().isObject() && returnValue().isPrimitive())
         setReturnValue(ObjectValue(constructorThis()));
 }
 
 bool
 StackFrame::pushBlock(JSContext *cx, StaticBlockObject &block)
 {
-    JS_ASSERT (block.needsClone());
+    JS_ASSERT_IF(hasBlockChain(), blockChain_ == block.enclosingBlock());
+
+    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);
 
-    pushOnScopeChain(*clone);
+        blockChain_ = blockHandle;
+    } else {
+        blockChain_ = &block;
+    }
 
+    flags_ |= HAS_BLOCKCHAIN;
     return true;
 }
 
 void
 StackFrame::popBlock(JSContext *cx)
 {
-    JS_ASSERT(scopeChain_->is<ClonedBlockObject>());
-    popOffScopeChain();
+    JS_ASSERT(hasBlockChain());
+
+    if (JS_UNLIKELY(cx->compartment()->debugMode()))
+        DebugScopes::onPopBlock(cx, this);
+
+    if (blockChain_->needsClone()) {
+        JS_ASSERT(scopeChain_->as<ClonedBlockObject>().staticBlock() == *blockChain_);
+        popOffScopeChain();
+    }
+
+    blockChain_ = blockChain_->enclosingBlock();
 }
 
 void
 StackFrame::popWith(JSContext *cx)
 {
     if (JS_UNLIKELY(cx->compartment()->debugMode()))
         DebugScopes::onPopWith(this);
 
@@ -1237,27 +1279,33 @@ AbstractFramePtr::hasPushedSPSFrame() co
     return asBaselineFrame()->hasPushedSPSFrame();
 #else
     MOZ_ASSUME_UNREACHABLE("Invalid frame");
 #endif
 }
 
 #ifdef DEBUG
 void
-js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, unsigned i)
+js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script,
+                        StaticBlockObject *maybeBlock, unsigned i)
 {
     if (!checkAliasing)
         return;
 
     JS_ASSERT(i < script->nslots);
     if (i < script->nfixed) {
         JS_ASSERT(!script->varIsAliased(i));
     } else {
-        // 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.
+        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;
+            }
+        }
     }
 }
 #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,17 +70,18 @@ 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, unsigned i);
+CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script,
+                    StaticBlockObject *maybeBlock, unsigned i);
 #endif
 
 namespace jit {
     class BaselineFrame;
 }
 
 /*
  * Pointer to either a ScriptFrameIter::Data, a StackFrame, or a baseline JIT
@@ -166,23 +167,26 @@ class AbstractFramePtr
 
     void *raw() const { return reinterpret_cast<void *>(ptr_); }
 
     bool operator ==(const AbstractFramePtr &other) const { return ptr_ == other.ptr_; }
     bool operator !=(const AbstractFramePtr &other) const { return ptr_ != other.ptr_; }
 
     operator bool() const { return !!ptr_; }
 
+    inline JSGenerator *maybeSuspendedGenerator(JSRuntime *rt) const;
+
     inline JSObject *scopeChain() const;
     inline CallObject &callObj() const;
     inline bool initFunctionScopeObjects(JSContext *cx);
     inline void pushOnScopeChain(ScopeObject &scope);
 
     inline JSCompartment *compartment() const;
 
+    inline StaticBlockObject *maybeBlockChain() const;
     inline bool hasCallObj() const;
     inline bool isGeneratorFrame() const;
     inline bool isYielding() const;
     inline bool isFunctionFrame() const;
     inline bool isGlobalFrame() const;
     inline bool isEvalFrame() const;
     inline bool isFramePushedByExecute() const;
     inline bool isDebuggerFrame() const;
@@ -302,16 +306,17 @@ class StackFrame
         /* Function prologue state */
         HAS_CALL_OBJ       =      0x100,  /* CallObject created for heavyweight fun */
         HAS_ARGS_OBJ       =      0x200,  /* ArgumentsObject created for needsArgsObj script */
 
         /* Lazy frame initialization */
         HAS_HOOK_DATA      =      0x400,  /* frame has hookData_ set */
         HAS_RVAL           =      0x800,  /* frame has rval_ set */
         HAS_SCOPECHAIN     =     0x1000,  /* frame has scopeChain_ set */
+        HAS_BLOCKCHAIN     =     0x2000,  /* frame has blockChain_ set */
 
         /* Debugger state */
         PREV_UP_TO_DATE    =     0x4000,  /* see DebugScopes::updateLiveScopes */
 
         /* Used in tracking calls and profiling (see vm/SPSProfiler.cpp) */
         HAS_PUSHED_SPS_FRAME =   0x8000,  /* SPS was notified of enty */
 
         /*
@@ -331,16 +336,17 @@ class StackFrame
         JSFunction      *fun;           /*   function frame, pre GetScopeChain */
     } exec;
     union {                             /* describes the arguments of a function */
         unsigned        nactual;        /*   for non-eval frames */
         JSScript        *evalScript;    /*   the script of an eval-in-function */
     } u;
     mutable JSObject    *scopeChain_;   /* if HAS_SCOPECHAIN, current scope chain */
     Value               rval_;          /* if HAS_RVAL, return value of the frame */
+    StaticBlockObject   *blockChain_;   /* if HAS_BLOCKCHAIN, innermost let block */
     ArgumentsObject     *argsObj_;      /* if HAS_ARGS_OBJ, the call's arguments object */
 
     /*
      * Previous frame and its pc and sp. Always nullptr for
      * InterpreterActivation's entry frame, always non-nullptr for inline
      * frames.
      */
     StackFrame          *prev_;
@@ -583,20 +589,39 @@ class StackFrame
     inline GlobalObject &global() const;
     inline CallObject &callObj() const;
     inline JSObject &varObj();
 
     inline void pushOnScopeChain(ScopeObject &scope);
     inline void popOffScopeChain();
 
     /*
-     * For blocks with aliased locals, these interfaces push and pop entries on
-     * the scope chain.
+     * 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.
      */
 
+    bool hasBlockChain() const {
+        return (flags_ & HAS_BLOCKCHAIN) && blockChain_;
+    }
+
+    StaticBlockObject *maybeBlockChain() {
+        return (flags_ & HAS_BLOCKCHAIN) ? blockChain_ : nullptr;
+    }
+
+    StaticBlockObject &blockChain() const {
+        JS_ASSERT(hasBlockChain());
+        return *blockChain_;
+    }
+
     bool pushBlock(JSContext *cx, StaticBlockObject &block);
     void popBlock(JSContext *cx);
 
     /*
      * With
      *
      * Entering/leaving a |with| block pushes/pops an object on the scope chain.
      * Pushing uses pushOnScopeChain, popping should use popWith.
@@ -834,16 +859,18 @@ class StackFrame
     enum TriggerPostBarriers {
         DoPostBarrier = true,
         NoPostBarrier = false
     };
     template <TriggerPostBarriers doPostBarrier>
     void copyFrameAndValues(JSContext *cx, Value *vp, StackFrame *otherfp,
                             const Value *othervp, Value *othersp);
 
+    JSGenerator *maybeSuspendedGenerator(JSRuntime *rt);
+
     /*
      * js::Execute pushes both global and function frames (since eval() in a
      * function pushes a frame with isFunctionFrame() && isEvalFrame()). Most
      * code should not care where a frame was pushed, but if it is necessary to
      * pick out frames pushed by js::Execute, this is the right query:
      */
 
     bool isFramePushedByExecute() const {
@@ -968,16 +995,19 @@ InitialFrameFlagsFromConstructing(bool b
 }
 
 static inline bool
 InitialFrameFlagsAreConstructing(InitialFrameFlags initial)
 {
     return !!(initial & INITIAL_CONSTRUCT);
 }
 
+inline AbstractFramePtr Valueify(JSAbstractFramePtr frame) { return AbstractFramePtr(frame); }
+static inline JSAbstractFramePtr Jsvalify(AbstractFramePtr frame)   { return JSAbstractFramePtr(frame.raw()); }
+
 /*****************************************************************************/
 
 class FrameRegs
 {
   public:
     Value *sp;
     jsbytecode *pc;
   private: