Bug 962599 - Store let-bound variables in the fixed part of stack frames r=luke r=jandem
☠☠ backed out by f14720c7f56b ☠ ☠
authorAndy Wingo <wingo@igalia.com>
Wed, 12 Feb 2014 18:46:24 +0100
changeset 185724 c80de8d196af3d691fcc0ae79b2961b4e501651c
parent 185723 bc69628e2ad1b32e198876bf53a42493c0275889
child 185725 3abe5c6f633ca04712ad2e32f98a4e25922388b6
push idunknown
push userunknown
push dateunknown
reviewersluke, jandem
bugs962599
milestone30.0a1
Bug 962599 - Store let-bound variables in the fixed part of stack frames r=luke r=jandem
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SyntaxParseHandler.h
js/src/jit-test/tests/basic/testBug579647.js
js/src/jit/AsmJS.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineFrame.h
js/src/jit/BaselineFrameInfo.h
js/src/jit/CompileInfo.h
js/src/jit/IonBuilder.cpp
js/src/jsanalyze.cpp
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/tests/js1_8_1/regress/regress-420399.js
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -263,22 +263,16 @@ frontend::CompileScript(ExclusiveContext
 
     bool savedCallerFun = options.compileAndGo &&
                           evalCaller && evalCaller->functionOrCallerFunction();
     Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun,
                                                   options, staticLevel, sourceObject, 0, length));
     if (!script)
         return nullptr;
 
-    // Global/eval script bindings are always empty (all names are added to the
-    // scope dynamically via JSOP_DEFFUN/VAR).
-    InternalHandle<Bindings*> bindings(script, &script->bindings);
-    if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr))
-        return nullptr;
-
     // We can specialize a bit for the given scope chain if that scope chain is the global object.
     JSObject *globalScope =
         scopeChain && scopeChain == &scopeChain->global() ? (JSObject*) scopeChain : nullptr;
     JS_ASSERT_IF(globalScope, globalScope->isNative());
     JS_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass()));
 
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
@@ -288,17 +282,18 @@ frontend::CompileScript(ExclusiveContext
         return nullptr;
 
     // Syntax parsing may cause us to restart processing of top level
     // statements in the script. Use Maybe<> so that the parse context can be
     // reset when this occurs.
     Maybe<ParseContext<FullParseHandler> > pc;
 
     pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr, &globalsc,
-                 (Directives *) nullptr, staticLevel, /* bodyid = */ 0);
+                 (Directives *) nullptr, staticLevel, /* bodyid = */ 0,
+                 /* blockScopeDepth = */ 0);
     if (!pc.ref().init(parser.tokenStream))
         return nullptr;
 
     /* If this is a direct call to eval, inherit the caller's strictness.  */
     if (evalCaller && evalCaller->strict())
         globalsc.strict = true;
 
     if (options.compileAndGo) {
@@ -355,28 +350,35 @@ frontend::CompileScript(ExclusiveContext
 
                 // Destroying the parse context will destroy its free
                 // variables, so check if any deoptimization is needed.
                 if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref()))
                     return nullptr;
 
                 pc.destroy();
                 pc.construct(&parser, (GenericParseContext *) nullptr, (ParseNode *) nullptr,
-                             &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0);
+                             &globalsc, (Directives *) nullptr, staticLevel, /* bodyid = */ 0,
+                             script->bindings.numBlockScoped());
                 if (!pc.ref().init(parser.tokenStream))
                     return nullptr;
                 JS_ASSERT(parser.pc == pc.addr());
+
                 pn = parser.statement();
             }
             if (!pn) {
                 JS_ASSERT(!parser.hadAbortedSyntaxParse());
                 return nullptr;
             }
         }
 
+        // Accumulate the maximum block scope depth, so that EmitTree can assert
+        // when emitting JSOP_GETLOCAL that the local is indeed within the fixed
+        // part of the stack frame.
+        script->bindings.updateNumBlockScoped(pc.ref().blockScopeDepth);
+
         if (canHaveDirectives) {
             if (!parser.maybeParseDirective(/* stmtList = */ nullptr, pn, &canHaveDirectives))
                 return nullptr;
         }
 
         if (!FoldConstants(cx, &pn, &parser))
             return nullptr;
 
@@ -409,16 +411,25 @@ frontend::CompileScript(ExclusiveContext
 
     /*
      * Nowadays the threaded interpreter needs a last return instruction, so we
      * do have to emit that here.
      */
     if (Emit1(cx, &bce, JSOP_RETRVAL) < 0)
         return nullptr;
 
+    // Global/eval script bindings are always empty (all names are added to the
+    // scope dynamically via JSOP_DEFFUN/VAR).  They may have block-scoped
+    // locals, however, which are allocated to the fixed part of the stack
+    // frame.
+    InternalHandle<Bindings*> bindings(script, &script->bindings);
+    if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr,
+                                            pc.ref().blockScopeDepth))
+        return nullptr;
+
     if (!JSScript::fullyInitFromEmitter(cx, script, &bce))
         return nullptr;
 
     bce.tellDebuggerAboutCompiledScript(cx);
 
     if (sct && !extraSct && !sct->complete())
         return nullptr;
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -252,16 +252,44 @@ EmitJump(ExclusiveContext *cx, BytecodeE
 }
 
 static ptrdiff_t
 EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc)
 {
     return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc));
 }
 
+// Dup the var in operand stack slot "slot".  The first item on the operand
+// stack is one slot past the last fixed slot.  The last (most recent) item is
+// slot bce->stackDepth - 1.
+//
+// The instruction that is written (JSOP_DUPAT) switches the depth around so
+// that it is addressed from the sp instead of from the fp.  This is useful when
+// you don't know the size of the fixed stack segment (nfixed), as is the case
+// when compiling scripts (because each statement is parsed and compiled
+// separately, but they all together form one script with one fixed stack
+// frame).
+static bool
+EmitDupAt(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned slot)
+{
+    JS_ASSERT(slot < unsigned(bce->stackDepth));
+    // The slot's position on the operand stack, measured from the top.
+    unsigned slotFromTop = bce->stackDepth - 1 - slot;
+    if (slotFromTop >= JS_BIT(24)) {
+        bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
+        return false;
+    }
+    ptrdiff_t off = EmitN(cx, bce, JSOP_DUPAT, 3);
+    if (off < 0)
+        return false;
+    jsbytecode *pc = bce->code(off);
+    SET_UINT24(pc, slotFromTop);
+    return true;
+}
+
 /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */
 const char js_with_statement_str[] = "with statement";
 const char js_finally_block_str[]  = "finally block";
 const char js_script_str[]         = "script";
 
 static const char * const statementName[] = {
     "label statement",       /* LABEL */
     "if statement",          /* IF */
@@ -578,17 +606,16 @@ NonLocalExitScope::prepareForNonLocalJum
             if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
                 return false;
             if (!popScopeForNonLocalExit(stmt->blockScopeIndex))
                 return false;
             if (blockObj.needsClone()) {
                 if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0)
                     return false;
             }
-            npops += blockObj.slotCount();
         }
     }
 
     FLUSH_POPS();
     return true;
 
 #undef FLUSH_POPS
 }
@@ -653,65 +680,42 @@ 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 (*slot >= StaticBlockObject::VAR_INDEX_LIMIT) {
-            bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
-            return false;
-        }
-    }
-    return true;
-}
-
 #ifdef DEBUG
 static bool
 AllLocalsAliased(StaticBlockObject &obj)
 {
     for (unsigned i = 0; i < obj.slotCount(); i++)
         if (!obj.isAliased(i))
             return false;
     return true;
 }
 #endif
 
 static bool
 ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<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());
         if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(),
-                               dn->frameSlot() + depthPlusFixed))
+                               blockObj->varToLocalIndex(dn->frameSlot())))
         {
             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));
@@ -725,27 +729,102 @@ ComputeAliasedSlots(ExclusiveContext *cx
     JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(*blockObj));
 
     return true;
 }
 
 static bool
 EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmitter *bce);
 
+// In a function, block-scoped locals go after the vars, and form part of the
+// fixed part of a stack frame.  Outside a function, there are no fixed vars,
+// but block-scoped locals still form part of the fixed part of a stack frame
+// and are thus addressable via GETLOCAL and friends.
+static void
+ComputeLocalOffset(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<StaticBlockObject *> blockObj)
+{
+    unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0;
+    unsigned localOffset = nfixedvars;
+
+    if (bce->staticScope) {
+        Rooted<NestedScopeObject *> outer(cx, bce->staticScope);
+        for (; outer; outer = outer->enclosingNestedScope()) {
+            if (outer->is<StaticBlockObject>()) {
+                StaticBlockObject &outerBlock = outer->as<StaticBlockObject>();
+                localOffset = outerBlock.localOffset() + outerBlock.slotCount();
+                break;
+            }
+        }
+    }
+
+    JS_ASSERT(localOffset + blockObj->slotCount()
+              <= nfixedvars + bce->script->bindings.numBlockScoped());
+
+    blockObj->setLocalOffset(localOffset);
+}
+
+// ~ Nested Scopes ~
+//
+// A nested scope is a region of a compilation unit (function, script, or eval
+// code) with an additional node on the scope chain.  This node may either be a
+// "with" object or a "block" object.  "With" objects represent "with" scopes.
+// Block objects represent lexical scopes, and contain named block-scoped
+// bindings, for example "let" bindings or the exception in a catch block.
+// Those variables may be local and thus accessible directly from the stack, or
+// "aliased" (accessed by name from nested functions, or dynamically via nested
+// "eval" or "with") and only accessible through the scope chain.
+//
+// All nested scopes are present on the "static scope chain".  A nested scope
+// that is a "with" scope will be present on the scope chain at run-time as
+// well.  A block scope may or may not have a corresponding link on the run-time
+// scope chain; if no variable declared in the block 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 nested scope, and ultimately attach them to the JSScript.
+// An element in the "block scope array" specifies the PC range, and links to a
+// NestedScopeObject in the object list of the script.  That scope object is
+// linked to the previous link in the static scope chain, if any.  The static
+// scope chain at any pre-retire PC can be retrieved using
+// JSScript::getStaticScope(jsbytecode *pc).
+//
+// Block scopes store their locals in the fixed part of a stack frame, after the
+// "fixed var" bindings.  A fixed var binding is a "var" or legacy "const"
+// binding that occurs in a function (as opposed to a script or in eval code).
+// Only functions have fixed var bindings.
+//
+// To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a
+// block scope, 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.
+//
+// Enter a nested scope with EnterNestedScope.  It will emit
+// PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of
+// the scope.  Leave a nested scope with LeaveNestedScope, which, for blocks,
+// will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE.  (For "with" scopes it
+// emits LEAVEWITH, of course.)  Pass EnterNestedScope a fresh StmtInfoBCE
+// object, and pass that same object to the corresponding LeaveNestedScope.  If
+// the statement is a block scope, pass STMT_BLOCK as stmtType; otherwise for
+// with scopes pass STMT_WITH.
+//
 static bool
 EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
                  StmtType stmtType)
 {
     Rooted<NestedScopeObject *> scopeObj(cx, &objbox->object->as<NestedScopeObject>());
     uint32_t scopeObjectIndex = bce->objectList.add(objbox);
 
     switch (stmtType) {
       case STMT_BLOCK: {
         Rooted<StaticBlockObject *> blockObj(cx, &scopeObj->as<StaticBlockObject>());
 
+        ComputeLocalOffset(cx, bce, blockObj);
+
         if (!ComputeAliasedSlots(cx, bce, blockObj))
             return false;
 
         if (blockObj->needsClone()) {
             if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce))
                 return false;
         }
         break;
@@ -755,18 +834,17 @@ EnterNestedScope(ExclusiveContext *cx, B
         if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_ENTERWITH, bce))
             return false;
         break;
       default:
         MOZ_ASSUME_UNREACHABLE();
     }
 
     uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
-    if (bce->staticScope) {
-        StmtInfoBCE *stmt = bce->topScopeStmt;
+    if (StmtInfoBCE *stmt = bce->topScopeStmt) {
         for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {}
         parent = stmt->blockScopeIndex;
     }
 
     stmt->blockScopeIndex = bce->blockScopeList.length();
     if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent))
         return false;
 
@@ -774,81 +852,16 @@ EnterNestedScope(ExclusiveContext *cx, B
     scopeObj->initEnclosingNestedScope(EnclosingStaticScope(bce));
     FinishPushNestedScope(bce, stmt, *scopeObj);
     JS_ASSERT(stmt->isNestedScope);
     stmt->isBlockScope = (stmtType == STMT_BLOCK);
 
     return true;
 }
 
-// ~ 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::getStaticScope(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 LeaveNestedScope, which will emit
-// DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE.  Pass EnterBlockScope a fresh
-// StmtInfoBCE object, and pass that same object to the corresponding
-// LeaveNestedScope.  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)
-{
-    Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>());
-
-    // FIXME: Once bug 962599 lands, we won't care about the stack depth, so we
-    // won't have extraSlots and thus invocations of EnterBlockScope can become
-    // invocations of EnterNestedScope.
-    int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots);
-    JS_ASSERT(depth >= 0);
-    blockObj->setStackDepth(depth);
-
-    return EnterNestedScope(cx, bce, stmt, objbox, STMT_BLOCK);
-}
-
 // 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) ||
@@ -1135,27 +1148,27 @@ EmitAliasedVarOp(ExclusiveContext *cx, J
     } else {
         JS_ASSERT(IsLocalOp(pn->getOp()) || pn->isKind(PNK_FUNCTION));
         uint32_t local = pn->pn_cookie.slot();
         if (local < bceOfDef->script->bindings.numVars()) {
             if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
                 return false;
             JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
         } else {
-            uint32_t depth = local - bceOfDef->script->bindings.numVars();
+            JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals());
             JS_ASSERT(bceOfDef->staticScope->is<StaticBlockObject>());
             Rooted<StaticBlockObject*> b(cx, &bceOfDef->staticScope->as<StaticBlockObject>());
-            while (!b->containsVarAtDepth(depth)) {
+            while (local < b->localOffset()) {
                 if (b->needsClone())
                     skippedScopes++;
                 b = &b->enclosingNestedScope()->as<StaticBlockObject>();
             }
             if (!AssignHops(bce, pn, skippedScopes, &sc))
                 return false;
-            sc.setSlot(b->localIndexToSlot(bceOfDef->script->bindings, local));
+            sc.setSlot(b->localIndexToSlot(local));
         }
     }
 
     return EmitAliasedVarOp(cx, op, sc, bce);
 }
 
 static bool
 EmitVarOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce)
@@ -2410,16 +2423,65 @@ EmitNumberOp(ExclusiveContext *cx, doubl
 }
 
 static inline void
 SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off)
 {
     SET_JUMP_OFFSET(bce->code(off), bce->offset() - off);
 }
 
+static bool
+PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n)
+{
+    for (unsigned i = 0; i < n; ++i) {
+        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+            return false;
+    }
+    return true;
+}
+
+static bool
+InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce,
+                                     Handle<StaticBlockObject *> blockObj)
+{
+    for (unsigned i = blockObj->slotCount(); i > 0; --i) {
+        if (blockObj->isAliased(i - 1)) {
+            ScopeCoordinate sc;
+            sc.setHops(0);
+            sc.setSlot(BlockObject::RESERVED_SLOTS + i - 1);
+            if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce))
+                return false;
+        } else {
+            if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, blockObj->varToLocalIndex(i - 1), bce))
+                return false;
+        }
+        if (Emit1(cx, bce, JSOP_POP) < 0)
+            return false;
+    }
+    return true;
+}
+
+static bool
+EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmtInfo,
+                ObjectBox *objbox, unsigned alreadyPushed = 0)
+{
+    // Initial values for block-scoped locals.
+    Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>());
+    if (!PushUndefinedValues(cx, bce, blockObj->slotCount() - alreadyPushed))
+        return false;
+
+    if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK))
+        return false;
+
+    if (!InitializeBlockScopedLocalsFromStack(cx, bce, blockObj))
+        return false;
+
+    return true;
+}
+
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
  * LLVM is deciding to inline this function which uses a lot of stack space
  * into EmitTree which is recursive and uses relatively little stack space.
  */
 MOZ_NEVER_INLINE static bool
 EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
@@ -2435,37 +2497,25 @@ EmitSwitch(ExclusiveContext *cx, Bytecod
     /* 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.
-     */
-    Rooted<StaticBlockObject *> blockObj(cx, nullptr);
-    if (pn2->isKind(PNK_LEXICALSCOPE)) {
-        blockObj = &pn2->pn_objbox->object->as<StaticBlockObject>();
-        for (uint32_t i = 0; i < blockObj->slotCount(); ++i) {
-            if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
-                return false;
-        }
-    }
-
     /* Push the discriminant. */
     if (!EmitTree(cx, bce, pn->pn_left))
         return false;
 
     StmtInfoBCE stmtInfo(cx);
     if (pn2->isKind(PNK_LEXICALSCOPE)) {
-        if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 1))
-            return false;
+        if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 0))
+            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);
@@ -2741,17 +2791,16 @@ EmitSwitch(ExclusiveContext *cx, Bytecod
             SET_JUMP_OFFSET(pc, off);
             pc += JUMP_OFFSET_LEN;
         }
     }
 
     if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) {
         if (!LeaveNestedScope(cx, bce, &stmtInfo))
             return false;
-        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObj->slotCount());
     } else {
         if (!PopStatementBCE(cx, bce))
             return false;
     }
 
     return true;
 }
 
@@ -3272,21 +3321,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))
-            return false;
-
-        if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce))
+
+        if (!EmitDupAt(cx, bce, i))
             return false;
 
         if (pn->isKind(PNK_ELISION)) {
             if (Emit1(cx, bce, JSOP_POP) < 0)
                 return false;
         } else {
             if (!EmitDestructuringLHS(cx, bce, pn, InitializeVars))
                 return false;
@@ -4031,35 +4077,32 @@ EmitTry(ExclusiveContext *cx, BytecodeEm
         return false;
 
     ptrdiff_t tryEnd = bce->offset();
 
     // If this try has a catch block, emit it.
     if (ParseNode *pn2 = pn->pn_kid2) {
         // The emitted code for a catch block looks like:
         //
-        // 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.
         //
         for (ParseNode *pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
@@ -4245,21 +4288,23 @@ EmitIf(ExclusiveContext *cx, BytecodeEmi
  *  evaluate b        +1
  *  dup               +1
  *  destructure y
  *  pick 1
  *  dup               +1
  *  destructure z
  *  pick 1
  *  pop               -1
+ *  setlocal 2        -1
+ *  setlocal 1        -1
+ *  setlocal 0        -1
  *  pushblockscope (if needed)
  *  evaluate e        +1
  *  debugleaveblock
  *  popblockscope (if needed)
- *  popnv 3           -3
  *
  * Note that, since pushblockscope simply changes fp->scopeChain and does not
  * otherwise touch the stack, evaluation of the let-var initializers must leave
  * the initial value in the let-var's future slot.
  */
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
  * the comment on EmitSwitch.
@@ -4267,79 +4312,56 @@ EmitIf(ExclusiveContext *cx, BytecodeEmi
 MOZ_NEVER_INLINE static bool
 EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet)
 {
     JS_ASSERT(pnLet->isArity(PN_BINARY));
     ParseNode *varList = pnLet->pn_left;
     JS_ASSERT(varList->isArity(PN_LIST));
     ParseNode *letBody = pnLet->pn_right;
     JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE));
-    Rooted<StaticBlockObject*> blockObj(cx, &letBody->pn_objbox->object->as<StaticBlockObject>());
 
     int letHeadDepth = bce->stackDepth;
 
     if (!EmitVariables(cx, bce, varList, PushInitialValues, true))
         return false;
 
     /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */
     uint32_t alreadyPushed = 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 (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed))
         return false;
 
     if (!EmitTree(cx, bce, letBody->pn_expr))
         return false;
 
     if (!LeaveNestedScope(cx, bce, &stmtInfo))
         return false;
 
-    JSOp leaveOp = letBody->getOp();
-    JS_ASSERT(leaveOp == JSOP_POPN || leaveOp == JSOP_POPNV);
-    EMIT_UINT16_IMM_OP(leaveOp, blockObj->slotCount());
-
     return true;
 }
 
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
  * the comment on EmitSwitch.
  */
 MOZ_NEVER_INLINE static bool
 EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
-    JS_ASSERT(pn->getOp() == JSOP_POPN);
 
     StmtInfoBCE stmtInfo(cx);
-    ObjectBox *objbox = pn->pn_objbox;
-    StaticBlockObject &blockObj = objbox->object->as<StaticBlockObject>();
-    size_t slots = blockObj.slotCount();
-
-    for (size_t n = 0; n < slots; ++n) {
-        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
-            return false;
-    }
-
-    if (!EnterBlockScope(cx, bce, &stmtInfo, objbox, 0))
+    if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0))
         return false;
 
     if (!EmitTree(cx, bce, pn->pn_expr))
         return false;
 
     if (!LeaveNestedScope(cx, bce, &stmtInfo))
         return false;
 
-    EMIT_UINT16_IMM_OP(JSOP_POPN, slots);
-
     return true;
 }
 
 static bool
 EmitWith(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     StmtInfoBCE stmtInfo(cx);
     if (!EmitTree(cx, bce, pn->pn_left))
@@ -4358,39 +4380,30 @@ EmitForOf(ExclusiveContext *cx, Bytecode
 {
     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;
-
-    // For-of loops run with two values on the stack: the iterator and the
-    // current result object.  If the loop also has a lexical block, those
-    // lexicals are deeper on the stack than the iterator.
-    for (uint32_t i = 0; i < blockObjCount; ++i) {
-        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
-            return false;
-    }
-
     // If the left part is 'var x', emit code to define x if necessary using a
     // prolog opcode, but do not emit a pop.
     if (pn1) {
         ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
         JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
         bce->emittingForInit = true;
         if (!EmitVariables(cx, bce, decl, DefineVars))
             return false;
         bce->emittingForInit = false;
     }
 
+    // For-of loops run with two values on the stack: the iterator and the
+    // current result object.
+
     // Compile the object expression to the right of 'of'.
     if (!EmitTree(cx, bce, forHead->pn_kid3))
         return false;
 
     // Convert iterable to iterator.
     if (Emit1(cx, bce, JSOP_DUP) < 0)                          // OBJ OBJ
         return false;
     if (!EmitAtomOp(cx, cx->names().std_iterator, JSOP_CALLPROP, bce)) // OBJ @@ITERATOR
@@ -4403,17 +4416,17 @@ EmitForOf(ExclusiveContext *cx, Bytecode
 
     // Push a dummy result so that we properly enter iteration midstream.
     if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)                    // ITER RESULT
         return false;
 
     // Enter the block before the loop body, after evaluating the obj.
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 2))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
             return false;
     }
 
     StmtInfoBCE stmtInfo(cx);
     PushStatementBCE(bce, &stmtInfo, STMT_FOR_OF_LOOP, top);
 
     // Jump down to the loop condition to minimize overhead assuming at least
     // one iteration, as the other loop forms do.  Annotate so IonMonkey can
@@ -4496,64 +4509,32 @@ EmitForOf(ExclusiveContext *cx, Bytecode
     if (!PopStatementBCE(cx, bce))
         return false;
 
     if (letDecl) {
         if (!LeaveNestedScope(cx, bce, &letStmt))
             return false;
     }
 
-    // Pop result, iter, and slots from the lexical block (if any).
-    EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2);
+    // Pop the result and the iter.
+    EMIT_UINT16_IMM_OP(JSOP_POPN, 2);
 
     return true;
 }
 
 static bool
 EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t 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:
-         *
-         *   push x N
-         *   eval rhs
-         *   iter
-         *   pushblockscope (if needed)
-         *   goto
-         *     ... loop body
-         *   ifne
-         *   debugleaveblock
-         *   popblockscope (if needed)
-         *   enditer
-         *   popn(N)
-         *
-         * Note that pushblockscope and popblockscope only get emitted if some
-         * of the variables in the block are captured.
-         */
-        for (uint32_t i = 0; i < blockObjCount; ++i) {
-            if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
-                return false;
-        }
-    }
-
     /*
      * If the left part is 'var x', emit code to define x if necessary
      * using a prolog opcode, but do not emit a pop. If the left part was
      * originally 'var x = i', the parser will have rewritten it; see
      * Parser::forStatement. 'for (let x = i in o)' is mercifully banned.
      */
     if (pn1) {
         ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
@@ -4575,17 +4556,17 @@ EmitForIn(ExclusiveContext *cx, Bytecode
      */
     JS_ASSERT(pn->isOp(JSOP_ITER));
     if (Emit2(cx, bce, JSOP_ITER, (uint8_t) pn->pn_iflags) < 0)
         return false;
 
     /* Enter the block before the loop body, after evaluating the obj. */
     StmtInfoBCE letStmt(cx);
     if (letDecl) {
-        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 1))
+        if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
             return false;
     }
 
     StmtInfoBCE stmtInfo(cx);
     PushStatementBCE(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
 
     /* Annotate so IonMonkey can find the loop-closing jump. */
     int noteIndex = NewSrcNote(cx, bce, SRC_FOR_IN);
@@ -4657,17 +4638,16 @@ EmitForIn(ExclusiveContext *cx, Bytecode
     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 (!LeaveNestedScope(cx, bce, &letStmt))
             return false;
-        EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount);
     }
 
     return true;
 }
 
 static bool
 EmitNormalFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
@@ -4949,17 +4929,18 @@ EmitFunc(ExclusiveContext *cx, BytecodeE
         if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin))
             return false;
         bce->switchToMain();
     } else {
 #ifdef DEBUG
         BindingIter bi(bce->script);
         while (bi->name() != fun->atom())
             bi++;
-        JS_ASSERT(bi->kind() == VARIABLE || bi->kind() == CONSTANT || bi->kind() == ARGUMENT);
+        JS_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT ||
+                  bi->kind() == Binding::ARGUMENT);
         JS_ASSERT(bi.frameIndex() < JS_BIT(20));
 #endif
         pn->pn_index = index;
         if (!EmitIndexOp(cx, JSOP_LAMBDA, index, bce))
             return false;
         JS_ASSERT(pn->getOp() == JSOP_GETLOCAL || pn->getOp() == JSOP_GETARG);
         JSOp setOp = pn->getOp() == JSOP_GETLOCAL ? JSOP_SETLOCAL : JSOP_SETARG;
         if (!EmitVarOp(cx, pn, setOp, bce))
@@ -6518,20 +6499,17 @@ frontend::EmitTree(ExclusiveContext *cx,
         /*
          * 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))
-            return false;
-        if (!EmitUnaliasedVarOp(cx, JSOP_GETLOCAL, slot, bce))
+        if (!EmitDupAt(cx, bce, bce->arrayCompDepth))
             return false;
         if (Emit1(cx, bce, JSOP_ARRAYPUSH) < 0)
             return false;
         break;
       }
 
       case PNK_ARRAY:
         if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -416,31 +416,32 @@ class FullParseHandler
 
     ParseNode *newPropertyByValue(ParseNode *lhs, ParseNode *index, uint32_t end) {
         return new_<PropertyByValue>(lhs, index, lhs->pn_pos.begin, end);
     }
 
     inline bool addCatchBlock(ParseNode *catchList, ParseNode *letBlock,
                               ParseNode *catchName, ParseNode *catchGuard, ParseNode *catchBody);
 
-    inline void setLeaveBlockResult(ParseNode *block, ParseNode *kid, bool leaveBlockExpr);
-
     inline void setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *pn);
     inline ParseNode *newFunctionDefinition();
     void setFunctionBody(ParseNode *pn, ParseNode *kid) {
         pn->pn_body = kid;
     }
     void setFunctionBox(ParseNode *pn, FunctionBox *funbox) {
         JS_ASSERT(pn->isKind(PNK_FUNCTION));
         pn->pn_funbox = funbox;
     }
     void addFunctionArgument(ParseNode *pn, ParseNode *argpn) {
         pn->pn_body->append(argpn);
     }
+
     inline ParseNode *newLexicalScope(ObjectBox *blockbox);
+    inline void setLexicalScopeBody(ParseNode *block, ParseNode *body);
+
     bool isOperationWithoutParens(ParseNode *pn, ParseNodeKind kind) {
         return pn->isKind(kind) && !pn->isInParens();
     }
 
     inline bool finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op);
 
     void setBeginPosition(ParseNode *pn, ParseNode *oth) {
         setBeginPosition(pn, oth->pn_pos.begin);
@@ -593,25 +594,16 @@ FullParseHandler::addCatchBlock(ParseNod
         return false;
 
     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));
-    if (leaveBlockExpr)
-        block->setOp(JSOP_POPNV);
-    block->pn_expr = kid;
-}
-
-inline void
 FullParseHandler::setLastFunctionArgumentDefault(ParseNode *funcpn, ParseNode *defaultValue)
 {
     ParseNode *arg = funcpn->pn_body->last();
     arg->pn_dflags |= PND_DEFAULT;
     arg->pn_expr = defaultValue;
 }
 
 inline ParseNode *
@@ -629,23 +621,28 @@ FullParseHandler::newFunctionDefinition(
 
 inline ParseNode *
 FullParseHandler::newLexicalScope(ObjectBox *blockbox)
 {
     ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, this);
     if (!pn)
         return nullptr;
 
-    pn->setOp(JSOP_POPN);
     pn->pn_objbox = blockbox;
     pn->pn_cookie.makeFree();
     pn->pn_dflags = 0;
     return pn;
 }
 
+inline void
+FullParseHandler::setLexicalScopeBody(ParseNode *block, ParseNode *kid)
+{
+    block->pn_expr = kid;
+}
+
 inline bool
 FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JSOp op)
 {
     if (pn->isUsed()) {
         pn = makeAssignment(pn, init);
         if (!pn)
             return false;
     } else {
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -399,18 +399,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
- *                          pn_objbox: block object in ObjectBox holder
+ * PNK_LEXICALSCOPE name    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
  * PNK_NOP          nullary
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -267,26 +267,26 @@ ParseContext<ParseHandler>::popLetDecl(J
 template <typename ParseHandler>
 static void
 AppendPackedBindings(const ParseContext<ParseHandler> *pc, const DeclVector &vec, Binding *dst)
 {
     for (size_t i = 0; i < vec.length(); ++i, ++dst) {
         Definition *dn = vec[i];
         PropertyName *name = dn->name();
 
-        BindingKind kind;
+        Binding::Kind kind;
         switch (dn->kind()) {
           case Definition::VAR:
-            kind = VARIABLE;
+            kind = Binding::VARIABLE;
             break;
           case Definition::CONST:
-            kind = CONSTANT;
+            kind = Binding::CONSTANT;
             break;
           case Definition::ARG:
-            kind = ARGUMENT;
+            kind = Binding::ARGUMENT;
             break;
           default:
             MOZ_ASSUME_UNREACHABLE("unexpected dn->kind");
         }
 
         /*
          * Bindings::init does not check for duplicates so we must ensure that
          * only one binding with a given name is marked aliased. pc->decls
@@ -324,17 +324,17 @@ ParseContext<ParseHandler>::generateFunc
         js_ReportOutOfMemory(cx);
         return false;
     }
 
     AppendPackedBindings(this, args_, packedBindings);
     AppendPackedBindings(this, vars_, packedBindings + args_.length());
 
     return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(),
-                                              packedBindings);
+                                              packedBindings, blockScopeDepth);
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
                                    unsigned errorNumber, va_list args)
 {
     bool result = false;
@@ -610,17 +610,18 @@ Parser<ParseHandler>::parse(JSObject *ch
      * - run for any reason on another thread if this thread is suspended on
      *   an object lock before it finishes generating bytecode into a script
      *   protected from the GC by a root or a stack frame reference.
      */
     Directives directives(options().strictOption);
     GlobalSharedContext globalsc(context, chain, directives, options().extraWarningsOption);
     ParseContext<ParseHandler> globalpc(this, /* parent = */ nullptr, ParseHandler::null(),
                                         &globalsc, /* newDirectives = */ nullptr,
-                                        /* staticLevel = */ 0, /* bodyid = */ 0);
+                                        /* staticLevel = */ 0, /* bodyid = */ 0,
+                                        /* blockScopeDepth = */ 0);
     if (!globalpc.init(tokenStream))
         return null();
 
     Node pn = statements();
     if (pn) {
         if (!tokenStream.matchToken(TOK_EOF)) {
             report(ParseError, false, null(), JSMSG_SYNTAX_ERROR);
             return null();
@@ -872,17 +873,18 @@ Parser<FullParseHandler>::standaloneFunc
     FunctionBox *funbox = newFunctionBox(fn, fun, /* outerpc = */ nullptr, inheritedDirectives,
                                          generatorKind);
     if (!funbox)
         return null();
     funbox->length = fun->nargs() - fun->hasRest();
     handler.setFunctionBox(fn, funbox);
 
     ParseContext<FullParseHandler> funpc(this, pc, fn, funbox, newDirectives,
-                                         /* staticLevel = */ 0, /* bodyid = */ 0);
+                                         /* staticLevel = */ 0, /* bodyid = */ 0,
+                                         /* blockScopeDepth = */ 0);
     if (!funpc.init(tokenStream))
         return null();
 
     for (unsigned i = 0; i < formals.length(); i++) {
         if (!defineArg(fn, formals[i]))
             return null();
     }
 
@@ -2120,17 +2122,18 @@ Parser<FullParseHandler>::functionArgsAn
         {
             // Move the syntax parser to the current position in the stream.
             TokenStream::Position position(keepAtoms);
             tokenStream.tell(&position);
             parser->tokenStream.seek(position, tokenStream);
 
             ParseContext<SyntaxParseHandler> funpc(parser, outerpc, SyntaxParseHandler::null(), funbox,
                                                    newDirectives, outerpc->staticLevel + 1,
-                                                   outerpc->blockidGen);
+                                                   outerpc->blockidGen,
+                                                   /* blockScopeDepth = */ 0);
             if (!funpc.init(tokenStream))
                 return false;
 
             if (!parser->functionArgsAndBodyGeneric(SyntaxParseHandler::NodeGeneric,
                                                     fun, type, kind, newDirectives))
             {
                 if (parser->hadAbortedSyntaxParse()) {
                     // Try again with a full parse.
@@ -2155,17 +2158,18 @@ Parser<FullParseHandler>::functionArgsAn
 
         pn->pn_blockid = outerpc->blockid();
         PropagateTransitiveParseFlags(funbox, outerpc->sc);
         return true;
     } while (false);
 
     // Continue doing a full parse for this inner function.
     ParseContext<FullParseHandler> funpc(this, pc, pn, funbox, newDirectives,
-                                         outerpc->staticLevel + 1, outerpc->blockidGen);
+                                         outerpc->staticLevel + 1, outerpc->blockidGen,
+                                         /* blockScopeDepth = */ 0);
     if (!funpc.init(tokenStream))
         return false;
 
     if (!functionArgsAndBodyGeneric(pn, fun, type, kind, newDirectives))
         return false;
 
     if (!leaveFunction(pn, outerpc, kind))
         return false;
@@ -2194,17 +2198,18 @@ Parser<SyntaxParseHandler>::functionArgs
 
     // Create box for fun->object early to protect against last-ditch GC.
     FunctionBox *funbox = newFunctionBox(pn, fun, pc, inheritedDirectives, generatorKind);
     if (!funbox)
         return false;
 
     // Initialize early for possible flags mutation via destructuringExpr.
     ParseContext<SyntaxParseHandler> funpc(this, pc, handler.null(), funbox, newDirectives,
-                                           outerpc->staticLevel + 1, outerpc->blockidGen);
+                                           outerpc->staticLevel + 1, outerpc->blockidGen,
+                                           /* blockScopeDepth = */ 0);
     if (!funpc.init(tokenStream))
         return false;
 
     if (!functionArgsAndBodyGeneric(pn, fun, type, kind, newDirectives))
         return false;
 
     if (!leaveFunction(pn, outerpc, kind))
         return false;
@@ -2229,17 +2234,18 @@ Parser<FullParseHandler>::standaloneLazy
     FunctionBox *funbox = newFunctionBox(pn, fun, /* outerpc = */ nullptr, directives,
                                          generatorKind);
     if (!funbox)
         return null();
     funbox->length = fun->nargs() - fun->hasRest();
 
     Directives newDirectives = directives;
     ParseContext<FullParseHandler> funpc(this, /* parent = */ nullptr, pn, funbox,
-                                         &newDirectives, staticLevel, /* bodyid = */ 0);
+                                         &newDirectives, staticLevel, /* bodyid = */ 0,
+                                         /* blockScopeDepth = */ 0);
     if (!funpc.init(tokenStream))
         return null();
 
     if (!functionArgsAndBodyGeneric(pn, fun, Normal, Statement, &newDirectives)) {
         JS_ASSERT(directives == newDirectives);
         return null();
     }
 
@@ -2683,17 +2689,17 @@ Parser<FullParseHandler>::bindLet(BindDa
     ParseNode *pn = data->pn;
     if (!parser->checkStrictBinding(name, pn))
         return false;
 
     ExclusiveContext *cx = parser->context;
 
     Rooted<StaticBlockObject *> blockObj(cx, data->let.blockObj);
     unsigned index = blockObj->slotCount();
-    if (index >= StaticBlockObject::VAR_INDEX_LIMIT) {
+    if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) {
         parser->report(ParseError, false, pn, data->let.overflow);
         return false;
     }
 
     /*
      * Assign block-local index to pn->pn_cookie right away, encoding it as an
      * upvar cookie whose skip tells the current static level. The emitter will
      * adjust the node's slot based on its stack depth model -- and, for global
@@ -2764,23 +2770,51 @@ struct PopLetDecl {
     bool operator()(TokenStream &, ParseContext<ParseHandler> *pc, HandleStaticBlockObject,
                     const Shape &, JSAtom *atom)
     {
         pc->popLetDecl(atom);
         return true;
     }
 };
 
+// We compute the maximum block scope depth, in slots, of a compilation unit at
+// parse-time.  Each nested statement has a field indicating the maximum block
+// scope depth that is nested inside it.  When we leave a nested statement, we
+// add the number of slots in the statement to the nested depth, and use that to
+// update the maximum block scope depth of the outer statement or parse
+// context.  In the end, pc->blockScopeDepth will indicate the number of slots
+// to reserve in the fixed part of a stack frame.
+//
+template <typename ParseHandler>
+static void
+AccumulateBlockScopeDepth(ParseContext<ParseHandler> *pc)
+{
+    uint32_t innerDepth = pc->topStmt->innerBlockScopeDepth;
+    StmtInfoPC *outer = pc->topStmt->down;
+
+    if (pc->topStmt->isBlockScope)
+        innerDepth += pc->topStmt->staticScope->template as<StaticBlockObject>().slotCount();
+
+    if (outer) {
+        if (outer->innerBlockScopeDepth < innerDepth)
+            outer->innerBlockScopeDepth = innerDepth;
+    } else {
+        if (pc->blockScopeDepth < innerDepth)
+            pc->blockScopeDepth = innerDepth;
+    }
+}
+
 template <typename ParseHandler>
 static void
 PopStatementPC(TokenStream &ts, ParseContext<ParseHandler> *pc)
 {
     RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope);
     JS_ASSERT(!!scopeObj == pc->topStmt->isNestedScope);
 
+    AccumulateBlockScopeDepth(pc);
     FinishPopStatement(pc);
 
     if (scopeObj) {
         if (scopeObj->is<StaticBlockObject>()) {
             RootedStaticBlockObject blockObj(ts.context(), &scopeObj->as<StaticBlockObject>());
             JS_ASSERT(!blockObj->inDictionaryMode());
             ForEachLetDef(ts, pc, blockObj, PopLetDecl<ParseHandler>());
         }
@@ -2818,17 +2852,17 @@ LexicalLookup(ContextT *ct, HandleAtom a
             continue;
 
         StaticBlockObject &blockObj = stmt->staticBlock();
         Shape *shape = blockObj.nativeLookup(ct->sc->context, id);
         if (shape) {
             JS_ASSERT(shape->hasShortID());
 
             if (slotp)
-                *slotp = blockObj.stackDepth() + shape->shortid();
+                *slotp = shape->shortid();
             return stmt;
         }
     }
 
     if (slotp)
         *slotp = -1;
     return stmt;
 }
@@ -3329,17 +3363,17 @@ Parser<ParseHandler>::letBlock(LetContex
             return null();
         MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_LET);
     } else {
         JS_ASSERT(letContext == LetExpresion);
         expr = assignExpr();
         if (!expr)
             return null();
     }
-    handler.setLeaveBlockResult(block, expr, letContext != LetStatement);
+    handler.setLexicalScopeBody(block, expr);
     PopStatementPC(tokenStream, pc);
 
     handler.setEndPosition(pnlet, pos().end);
 
     if (needExprStmt) {
         if (!MatchOrInsertSemicolon(tokenStream))
             return null();
         return handler.newExprStatement(pnlet, pos().end);
@@ -3607,17 +3641,16 @@ 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->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->staticScope->as<StaticBlockObject>(), HoistVars);
@@ -3643,18 +3676,16 @@ 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));
     } else {
         pn = letDeclaration();
     }
     return pn;
 }
 
 template <>
 SyntaxParseHandler::Node
@@ -5997,30 +6028,56 @@ CompExprTransplanter::transplant(ParseNo
 
       case PN_NULLARY:
         /* Nothing. */
         break;
     }
     return true;
 }
 
+// Parsing JS1.7-style comprehensions is terrible: we parse the head expression
+// as if it's part of a comma expression, then when we see the "for" we
+// transplant the parsed expression into the inside of a constructed
+// for-of/for-in/for-each tail.  Transplanting an already-parsed expression is
+// tricky, but the CompExprTransplanter handles most of that.
+//
+// The one remaining thing to patch up is the block scope depth.  We need to
+// compute the maximum block scope depth of a function, so we know how much
+// space to reserve in the fixed part of a stack frame.  Normally this is done
+// whenever we leave a statement, via AccumulateBlockScopeDepth.  However if the
+// head has a let expression, we need to re-assign that depth to the tail of the
+// comprehension.
+//
+// Thing is, we don't actually know what that depth is, because the only
+// information we keep is the maximum nested depth within a statement, so we
+// just conservatively propagate the maximum nested depth from the top statement
+// to the comprehension tail.
+//
+template <typename ParseHandler>
+static unsigned
+ComprehensionHeadBlockScopeDepth(ParseContext<ParseHandler> *pc)
+{
+    return pc->topStmt ? pc->topStmt->innerBlockScopeDepth : pc->blockScopeDepth;
+}
+
 /*
  * Starting from a |for| keyword after the first array initialiser element or
  * an expression in an open parenthesis, parse the tail of the comprehension
  * or generator expression signified by this |for| keyword in context.
  *
  * Return null on failure, else return the top-most parse node for the array
  * comprehension or generator expression, with a unary node as the body of the
  * (possibly nested) for-loop, initialized by |kind, op, kid|.
  */
 template <>
 ParseNode *
 Parser<FullParseHandler>::comprehensionTail(ParseNode *kid, unsigned blockid, bool isGenexp,
                                             ParseContext<FullParseHandler> *outerpc,
-                                            ParseNodeKind kind, JSOp op)
+                                            ParseNodeKind kind, JSOp op,
+                                            unsigned innerBlockScopeDepth)
 {
     /*
      * If we saw any inner functions while processing the generator expression
      * then they may have upvars referring to the let vars in this generator
      * which were not correctly processed. Bail out and start over without
      * allowing lazy parsing.
      */
     if (handler.syntaxParser) {
@@ -6235,16 +6292,17 @@ Parser<FullParseHandler>::comprehensionT
 
     pn2 = UnaryNode::create(kind, &handler);
     if (!pn2)
         return null();
     pn2->setOp(op);
     pn2->pn_kid = kid;
     *pnp = pn2;
 
+    pc->topStmt->innerBlockScopeDepth += innerBlockScopeDepth;
     PopStatementPC(tokenStream, pc);
     return pn;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::arrayInitializerComprehensionTail(ParseNode *pn)
 {
@@ -6258,17 +6316,18 @@ Parser<FullParseHandler>::arrayInitializ
      */
     ParseNode *pnexp = pn->last();
     JS_ASSERT(pn->pn_count == 1);
     pn->pn_count = 0;
     pn->pn_tail = &pn->pn_head;
     *pn->pn_tail = nullptr;
 
     ParseNode *pntop = comprehensionTail(pnexp, pn->pn_blockid, false, nullptr,
-                                         PNK_ARRAYPUSH, JSOP_ARRAYPUSH);
+                                         PNK_ARRAYPUSH, JSOP_ARRAYPUSH,
+                                         ComprehensionHeadBlockScopeDepth(pc));
     if (!pntop)
         return false;
     pn->append(pntop);
     return true;
 }
 
 template <>
 bool
@@ -6328,17 +6387,18 @@ Parser<FullParseHandler>::generatorExpr(
         Directives directives(/* strict = */ outerpc->sc->strict);
         FunctionBox *genFunbox = newFunctionBox(genfn, fun, outerpc, directives,
                                                 LegacyGenerator);
         if (!genFunbox)
             return null();
 
         ParseContext<FullParseHandler> genpc(this, outerpc, genfn, genFunbox,
                                              /* newDirectives = */ nullptr,
-                                             outerpc->staticLevel + 1, outerpc->blockidGen);
+                                             outerpc->staticLevel + 1, outerpc->blockidGen,
+                                             /* blockScopeDepth = */ 0);
         if (!genpc.init(tokenStream))
             return null();
 
         /*
          * We assume conservatively that any deoptimization flags in pc->sc
          * come from the kid. So we propagate these flags into genfn. For code
          * simplicity we also do not detect if the flags were only set in the
          * kid and could be removed from pc->sc.
@@ -6346,17 +6406,19 @@ Parser<FullParseHandler>::generatorExpr(
         genFunbox->anyCxFlags = outerpc->sc->anyCxFlags;
         if (outerpc->sc->isFunctionBox())
             genFunbox->funCxFlags = outerpc->sc->asFunctionBox()->funCxFlags;
 
         JS_ASSERT(genFunbox->isLegacyGenerator());
         genFunbox->inGenexpLambda = true;
         genfn->pn_blockid = genpc.bodyid;
 
-        ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc);
+        ParseNode *body = comprehensionTail(pn, outerpc->blockid(), true, outerpc,
+                                            PNK_SEMI, JSOP_NOP,
+                                            ComprehensionHeadBlockScopeDepth(outerpc));
         if (!body)
             return null();
         JS_ASSERT(!genfn->pn_body);
         genfn->pn_body = body;
         genfn->pn_pos.begin = body->pn_pos.begin = kid->pn_pos.begin;
         genfn->pn_pos.end = body->pn_pos.end = pos().end;
 
         if (!leaveFunction(genfn, outerpc))
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -23,18 +23,19 @@
 namespace js {
 namespace frontend {
 
 struct StmtInfoPC : public StmtInfoBase {
     StmtInfoPC      *down;          /* info for enclosing statement */
     StmtInfoPC      *downScope;     /* next enclosing lexical scope */
 
     uint32_t        blockid;        /* for simplified dominance computation */
+    uint32_t        innerBlockScopeDepth; /* maximum depth of nested block scopes, in slots */
 
-    StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx) {}
+    StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx), innerBlockScopeDepth(0) {}
 };
 
 typedef HashSet<JSAtom *> FuncStmtSet;
 class SharedContext;
 
 typedef Vector<Definition *, 16> DeclVector;
 
 struct GenericParseContext
@@ -113,16 +114,17 @@ struct ParseContext : public GenericPars
     // An ES6 generator is marked as a "star generator" before its body is parsed.
     GeneratorKind generatorKind() const {
         return sc->isFunctionBox() ? sc->asFunctionBox()->generatorKind() : NotGenerator;
     }
     bool isGenerator() const { return generatorKind() != NotGenerator; }
     bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; }
     bool isStarGenerator() const { return generatorKind() == StarGenerator; }
 
+    uint32_t        blockScopeDepth; /* maximum depth of nested block scopes, in slots */
     Node            blockNode;      /* parse node for a block with let declarations
                                        (block with its own lexical scope)  */
   private:
     AtomDecls<ParseHandler> decls_; /* function, const, and var declarations */
     DeclVector      args_;          /* argument definitions */
     DeclVector      vars_;          /* var/const definitions */
 
   public:
@@ -130,21 +132,16 @@ struct ParseContext : public GenericPars
         return decls_;
     }
 
     uint32_t numArgs() const {
         JS_ASSERT(sc->isFunctionBox());
         return args_.length();
     }
 
-    uint32_t numVars() const {
-        JS_ASSERT(sc->isFunctionBox());
-        return vars_.length();
-    }
-
     /*
      * This function adds a definition to the lexical scope represented by this
      * ParseContext.
      *
      * Pre-conditions:
      *  + The caller must have already taken care of name collisions:
      *    - For non-let definitions, this means 'name' isn't in 'decls'.
      *    - For let definitions, this means 'name' isn't already a name in the
@@ -238,26 +235,27 @@ struct ParseContext : public GenericPars
     // The comments atop CheckDestructuring explain the distinction between
     // assignment-like and declaration-like destructuring patterns, and why
     // they need to be treated differently.
     bool            inDeclDestructuring:1;
 
     ParseContext(Parser<ParseHandler> *prs, GenericParseContext *parent,
                  Node maybeFunction, SharedContext *sc,
                  Directives *newDirectives,
-                 unsigned staticLevel, uint32_t bodyid)
+                 unsigned staticLevel, uint32_t bodyid, uint32_t blockScopeDepth)
       : GenericParseContext(parent, sc),
         bodyid(0),           // initialized in init()
         blockidGen(bodyid),  // used to set |bodyid| and subsequently incremented in init()
         topStmt(nullptr),
         topScopeStmt(nullptr),
         staticScope(prs->context),
         maybeFunction(maybeFunction),
         staticLevel(staticLevel),
         lastYieldOffset(NoYieldOffset),
+        blockScopeDepth(blockScopeDepth),
         blockNode(ParseHandler::null()),
         decls_(prs->context, prs->alloc),
         args_(prs->context),
         vars_(prs->context),
         parserPC(&prs->pc),
         oldpc(prs->pc),
         lexdeps(prs->context),
         funcStmts(nullptr),
@@ -542,17 +540,18 @@ class Parser : private AutoGCRooter, pub
                              GeneratorKind generatorKind,
                              Directives inheritedDirectives, Directives *newDirectives);
 
     Node unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin);
 
     Node condition();
     Node comprehensionTail(Node kid, unsigned blockid, bool isGenexp,
                            ParseContext<ParseHandler> *outerpc,
-                           ParseNodeKind kind = PNK_SEMI, JSOp op = JSOP_NOP);
+                           ParseNodeKind kind, JSOp op,
+                           unsigned innerBlockScopeDepth);
     bool arrayInitializerComprehensionTail(Node pn);
     Node generatorExpr(Node kid);
     bool argumentList(Node listNode, bool *isSpread);
     Node letBlock(LetContext letContext);
     Node destructuringExpr(BindData<ParseHandler> *data, TokenKind tt);
 
     Node identifierName();
 
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -152,24 +152,25 @@ class SyntaxParseHandler
         return NodeGetProp;
     }
 
     Node newPropertyByValue(Node pn, Node kid, uint32_t end) { return NodeLValue; }
 
     bool addCatchBlock(Node catchList, Node letBlock,
                        Node catchName, Node catchGuard, Node catchBody) { return true; }
 
-    void setLeaveBlockResult(Node block, Node kid, bool leaveBlockExpr) {}
-
     void setLastFunctionArgumentDefault(Node funcpn, Node pn) {}
     Node newFunctionDefinition() { return NodeGeneric; }
     void setFunctionBody(Node pn, Node kid) {}
     void setFunctionBox(Node pn, FunctionBox *funbox) {}
     void addFunctionArgument(Node pn, Node argpn) {}
+
     Node newLexicalScope(ObjectBox *blockbox) { return NodeGeneric; }
+    void setLexicalScopeBody(Node block, Node body) {}
+
     bool isOperationWithoutParens(Node pn, ParseNodeKind kind) {
         // It is OK to return false here, callers should only use this method
         // for reporting strict option warnings and parsing code which the
         // syntax parser does not handle.
         return false;
     }
 
     bool finishInitializerAssignment(Node pn, Node init, JSOp op) { return true; }
--- a/js/src/jit-test/tests/basic/testBug579647.js
+++ b/js/src/jit-test/tests/basic/testBug579647.js
@@ -1,13 +1,13 @@
-expected = "TypeError: NaN is not a function";
+expected = "TypeError: a is not a function";
 actual = "";
 
 try {
     a = ""
     for each(x in [0, 0, 0, 0]) {
         a %= x
     } ( let (a=-a) a)()
 } catch (e) {
     actual = '' + e;
 }
 
-assertEq(expected, actual);
+assertEq(actual, expected);
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -5264,17 +5264,18 @@ ParseFunction(ModuleCompiler &m, ParseNo
 
     Directives directives(outerpc);
     FunctionBox *funbox = m.parser().newFunctionBox(fn, fun, outerpc, directives, NotGenerator);
     if (!funbox)
         return false;
 
     Directives newDirectives = directives;
     AsmJSParseContext funpc(&m.parser(), outerpc, fn, funbox, &newDirectives,
-                            outerpc->staticLevel + 1, outerpc->blockidGen);
+                            outerpc->staticLevel + 1, outerpc->blockidGen,
+                            /* blockScopeDepth = */ 0);
     if (!funpc.init(m.parser().tokenStream))
         return false;
 
     if (!m.parser().functionArgsAndBodyGeneric(fn, fun, Normal, Statement, &newDirectives))
         return false;
 
     if (tokenStream.hadError() || directives != newDirectives)
         return false;
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -849,20 +849,26 @@ BaselineCompiler::emit_JSOP_POP()
 bool
 BaselineCompiler::emit_JSOP_POPN()
 {
     frame.popn(GET_UINT16(pc));
     return true;
 }
 
 bool
-BaselineCompiler::emit_JSOP_POPNV()
+BaselineCompiler::emit_JSOP_DUPAT()
 {
-    frame.popRegsAndSync(1);
-    frame.popn(GET_UINT16(pc));
+    frame.syncStack(0);
+
+    // DUPAT takes a value on the stack and re-pushes it on top.  It's like
+    // GETLOCAL but it addresses from the top of the stack instead of from the
+    // stack frame.
+
+    int depth = -(GET_UINT24(pc) + 1);
+    masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0);
     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
@@ -2285,27 +2291,17 @@ bool
 BaselineCompiler::emit_JSOP_INITELEM_SETTER()
 {
     return emitInitElemGetterSetter();
 }
 
 bool
 BaselineCompiler::emit_JSOP_GETLOCAL()
 {
-    uint32_t local = GET_LOCALNO(pc);
-
-    if (local >= frame.nlocals()) {
-        // Destructuring assignments may use GETLOCAL to access stack values.
-        frame.syncStack(0);
-        masm.loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)), R0);
-        frame.push(R0);
-        return true;
-    }
-
-    frame.pushLocal(local);
+    frame.pushLocal(GET_LOCALNO(pc));
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_CALLLOCAL()
 {
     return emit_JSOP_GETLOCAL();
 }
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -21,17 +21,17 @@
 namespace js {
 namespace jit {
 
 #define OPCODE_LIST(_)         \
     _(JSOP_NOP)                \
     _(JSOP_LABEL)              \
     _(JSOP_POP)                \
     _(JSOP_POPN)               \
-    _(JSOP_POPNV)              \
+    _(JSOP_DUPAT)              \
     _(JSOP_ENTERWITH)          \
     _(JSOP_LEAVEWITH)          \
     _(JSOP_DUP)                \
     _(JSOP_DUP2)               \
     _(JSOP_SWAP)               \
     _(JSOP_PICK)               \
     _(JSOP_GOTO)               \
     _(JSOP_IFEQ)               \
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -147,18 +147,18 @@ class BaselineFrame
         return size / sizeof(Value);
     }
     Value *valueSlot(size_t slot) const {
         JS_ASSERT(slot < numValueSlots());
         return (Value *)this - (slot + 1);
     }
 
     Value &unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const {
+        JS_ASSERT(i < script()->nfixedvars());
         JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i));
-        JS_ASSERT(i < script()->nfixed());
         return *valueSlot(i);
     }
 
     Value &unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) const {
         JS_ASSERT(i < numFormalArgs());
         JS_ASSERT_IF(checkAliasing, !script()->argsObjAliasesFormals() &&
                                     !script()->formalIsAliased(i));
         return argv()[i];
--- a/js/src/jit/BaselineFrameInfo.h
+++ b/js/src/jit/BaselineFrameInfo.h
@@ -238,16 +238,17 @@ class FrameInfo
         StackValue *sv = rawPush();
         sv->setConstant(val);
     }
     inline void push(const ValueOperand &val, JSValueType knownType=JSVAL_TYPE_UNKNOWN) {
         StackValue *sv = rawPush();
         sv->setRegister(val, knownType);
     }
     inline void pushLocal(uint32_t local) {
+        JS_ASSERT(local < nlocals());
         StackValue *sv = rawPush();
         sv->setLocalSlot(local);
     }
     inline void pushArg(uint32_t arg) {
         StackValue *sv = rawPush();
         sv->setArgSlot(arg);
     }
     inline void pushThis() {
@@ -255,25 +256,17 @@ class FrameInfo
         sv->setThis();
     }
     inline void pushScratchValue() {
         masm.pushValue(addressOfScratchValue());
         StackValue *sv = rawPush();
         sv->setStack();
     }
     inline Address addressOfLocal(size_t local) const {
-#ifdef DEBUG
-        if (local >= nlocals()) {
-            // GETLOCAL and SETLOCAL can be used to access stack values. This is
-            // fine, as long as they are synced.
-            size_t slot = local - nlocals();
-            JS_ASSERT(slot < stackDepth());
-            JS_ASSERT(stack[slot].kind() == StackValue::Stack);
-        }
-#endif
+        JS_ASSERT(local < nlocals());
         return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local));
     }
     Address addressOfArg(size_t arg) const {
         JS_ASSERT(arg < nargs());
         return Address(BaselineFrameReg, BaselineFrame::offsetOfArg(arg));
     }
     Address addressOfThis() const {
         return Address(BaselineFrameReg, BaselineFrame::offsetOfThis());
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_CompileInfo_h
 #define jit_CompileInfo_h
 
 #include "jsfun.h"
 
 #include "jit/Registers.h"
+#include "vm/ScopeObject.h"
 
 namespace js {
 namespace jit {
 
 inline unsigned
 StartArgSlot(JSScript *script)
 {
     // Reserved slots:
@@ -55,30 +56,34 @@ class CompileInfo
         // function to ensure that we do not try to embed a nursery pointer in
         // jit-code. Precisely because it can flow in from anywhere, it's not
         // guaranteed to be non-lazy. Hence, don't access its script!
         if (fun_) {
             fun_ = fun_->nonLazyScript()->functionNonDelazifying();
             JS_ASSERT(fun_->isTenured());
         }
 
+        osrStaticScope_ = osrPc ? script->getStaticScope(osrPc) : nullptr;
+
         nimplicit_ = StartArgSlot(script)                   /* scope chain and argument obj */
                    + (fun ? 1 : 0);                         /* this */
         nargs_ = fun ? fun->nargs() : 0;
+        nfixedvars_ = script->nfixedvars();
         nlocals_ = script->nfixed();
         nstack_ = script->nslots() - script->nfixed();
         nslots_ = nimplicit_ + nargs_ + nlocals_ + nstack_;
     }
 
     CompileInfo(unsigned nlocals, ExecutionMode executionMode)
-      : script_(nullptr), fun_(nullptr), osrPc_(nullptr), constructing_(false),
-        executionMode_(executionMode), scriptNeedsArgsObj_(false)
+      : script_(nullptr), fun_(nullptr), osrPc_(nullptr), osrStaticScope_(nullptr),
+        constructing_(false), executionMode_(executionMode), scriptNeedsArgsObj_(false)
     {
         nimplicit_ = 0;
         nargs_ = 0;
+        nfixedvars_ = 0;
         nlocals_ = nlocals;
         nstack_ = 1;  /* For FunctionCompiler::pushPhiInput/popPhiOutput */
         nslots_ = nlocals_ + nstack_;
     }
 
     JSScript *script() const {
         return script_;
     }
@@ -86,16 +91,19 @@ class CompileInfo
         return fun_;
     }
     bool constructing() const {
         return constructing_;
     }
     jsbytecode *osrPc() {
         return osrPc_;
     }
+    NestedScopeObject *osrStaticScope() const {
+        return osrStaticScope_;
+    }
 
     bool hasOsrAt(jsbytecode *pc) {
         JS_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY);
         return pc == osrPc();
     }
 
     jsbytecode *startPC() const {
         return script_->code();
@@ -150,17 +158,23 @@ class CompileInfo
     // maybe argumentsobject and this value.
     unsigned nimplicit() const {
         return nimplicit_;
     }
     // Number of arguments (without counting this value).
     unsigned nargs() const {
         return nargs_;
     }
-    // Number of slots needed for local variables.
+    // Number of slots needed for "fixed vars".  Note that this is only non-zero
+    // for function code.
+    unsigned nfixedvars() const {
+        return nfixedvars_;
+    }
+    // Number of slots needed for all local variables.  This includes "fixed
+    // vars" (see above) and also block-scoped locals.
     unsigned nlocals() const {
         return nlocals_;
     }
     unsigned ninvoke() const {
         return nslots_ - nstack_;
     }
 
     uint32_t scopeChainSlot() const {
@@ -218,38 +232,57 @@ class CompileInfo
         return CountArgSlots(script(), funMaybeLazy());
     }
 
     uint32_t totalSlots() const {
         JS_ASSERT(script() && funMaybeLazy());
         return nimplicit() + nargs() + nlocals();
     }
 
-    bool isSlotAliased(uint32_t index) const {
+    bool isSlotAliased(uint32_t index, NestedScopeObject *staticScope) const {
         if (funMaybeLazy() && index == thisSlot())
             return false;
 
         uint32_t arg = index - firstArgSlot();
-        if (arg < nargs()) {
-            if (script()->formalIsAliased(arg))
-                return true;
-            return false;
-        }
+        if (arg < nargs())
+            return script()->formalIsAliased(arg);
+
+        uint32_t local = index - firstLocalSlot();
+        if (local < nlocals()) {
+            // First, check if this local is a var.
+            if (local < nfixedvars())
+                return script()->varIsAliased(local);
 
-        uint32_t var = index - firstLocalSlot();
-        if (var < nlocals()) {
-            if (script()->varIsAliased(var))
-                return true;
+            // Otherwise, it might be part of a block scope.
+            for (; staticScope; staticScope = staticScope->enclosingNestedScope()) {
+                if (!staticScope->is<StaticBlockObject>())
+                    continue;
+                StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>();
+                if (blockObj.localOffset() < local) {
+                    if (local - blockObj.localOffset() < blockObj.slotCount())
+                        return blockObj.isAliased(local - blockObj.localOffset());
+                    return false;
+                }
+            }
+
+            // In this static scope, this var is dead.
             return false;
         }
 
         JS_ASSERT(index >= firstStackSlot());
         return false;
     }
 
+    bool isSlotAliasedAtEntry(uint32_t index) const {
+        return isSlotAliased(index, nullptr);
+    }
+    bool isSlotAliasedAtOsr(uint32_t index) const {
+        return isSlotAliased(index, osrStaticScope());
+    }
+
     bool hasArguments() const {
         return script()->argumentsHasVarBinding();
     }
     bool argumentsAliasesFormals() const {
         return script()->argumentsAliasesFormals();
     }
     bool needsArgsObj() const {
         return scriptNeedsArgsObj_;
@@ -264,22 +297,24 @@ class CompileInfo
 
     bool isParallelExecution() const {
         return executionMode_ == ParallelExecution;
     }
 
   private:
     unsigned nimplicit_;
     unsigned nargs_;
+    unsigned nfixedvars_;
     unsigned nlocals_;
     unsigned nstack_;
     unsigned nslots_;
     JSScript *script_;
     JSFunction *fun_;
     jsbytecode *osrPc_;
+    NestedScopeObject *osrStaticScope_;
     bool constructing_;
     ExecutionMode executionMode_;
 
     // Whether a script needs an arguments object is unstable over compilation
     // since the arguments optimization could be marked as failed on the main
     // thread, so cache a value here and use it throughout for consistency.
     bool scriptNeedsArgsObj_;
 };
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -85,22 +85,28 @@ jit::NewBaselineFrameInspector(TempAlloc
                 inspector->argTypes.infallibleAppend(types::GetValueType(frame->argsObj().arg(i)));
             else
                 inspector->argTypes.infallibleAppend(types::Type::UndefinedType());
         }
     }
 
     if (!inspector->varTypes.reserve(frame->script()->nfixed()))
         return nullptr;
-    for (size_t i = 0; i < frame->script()->nfixed(); i++) {
+    for (size_t i = 0; i < frame->script()->nfixedvars(); i++) {
         if (script->varIsAliased(i))
             inspector->varTypes.infallibleAppend(types::Type::UndefinedType());
         else
             inspector->varTypes.infallibleAppend(types::GetValueType(frame->unaliasedVar(i)));
     }
+    for (size_t i = frame->script()->nfixedvars(); i < frame->script()->nfixed(); i++) {
+        // FIXME: If this slot corresponds to a scope that is active at this PC,
+        // and the slot is unaliased, we should initialize the type from the
+        // slot value, as above.
+        inspector->varTypes.infallibleAppend(types::Type::UndefinedType());
+    }
 
     return inspector;
 }
 
 IonBuilder::IonBuilder(JSContext *analysisContext, CompileCompartment *comp,
                        const JitCompileOptions &options, TempAllocator *temp,
                        MIRGraph *graph, types::CompilerConstraintList *constraints,
                        BaselineInspector *inspector, CompileInfo *info,
@@ -1148,21 +1154,20 @@ IonBuilder::maybeAddOsrTypeBarriers()
     static const size_t OSR_PHI_POSITION = 1;
     JS_ASSERT(preheader->getPredecessor(OSR_PHI_POSITION) == osrBlock);
 
     MPhiIterator headerPhi = header->phisBegin();
     while (headerPhi != header->phisEnd() && headerPhi->slot() < info().startArgSlot())
         headerPhi++;
 
     for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++, headerPhi++) {
-
         // Aliased slots are never accessed, since they need to go through
         // the callobject. The typebarriers are added there and can be
-        // discared here.
-        if (info().isSlotAliased(i))
+        // discarded here.
+        if (info().isSlotAliasedAtOsr(i))
             continue;
 
         MInstruction *def = osrBlock->getSlot(i)->toInstruction();
 
         JS_ASSERT(headerPhi->slot() == i);
         MPhi *preheaderPhi = preheader->getSlot(i)->toPhi();
 
         MIRType type = headerPhi->type();
@@ -1297,17 +1302,17 @@ IonBuilder::traverseBytecode()
             lockOpcodeCount = 0;
         }
 
 #ifdef DEBUG
         for (size_t i = 0; i < popped.length(); i++) {
             switch (op) {
               case JSOP_POP:
               case JSOP_POPN:
-              case JSOP_POPNV:
+              case JSOP_DUPAT:
               case JSOP_DUP:
               case JSOP_DUP2:
               case JSOP_PICK:
               case JSOP_SWAP:
               case JSOP_SETARG:
               case JSOP_SETLOCAL:
               case JSOP_SETRVAL:
               case JSOP_VOID:
@@ -1547,24 +1552,19 @@ 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_DUPAT:
+        current->pushSlot(current->stackDepth() - 1 - GET_UINT24(pc));
+        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));
@@ -5832,21 +5832,21 @@ IonBuilder::newOsrPreheader(MBasicBlock 
 
     // Treat the OSR values as having the same type as the existing values
     // coming in to the loop. These will be fixed up with appropriate
     // unboxing and type barriers in finishLoop, once the possible types
     // at the loop header are known.
     for (uint32_t i = info().startArgSlot(); i < osrBlock->stackDepth(); i++) {
         MDefinition *existing = current->getSlot(i);
         MDefinition *def = osrBlock->getSlot(i);
-        JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliased(i), def->type() == MIRType_Value);
+        JS_ASSERT_IF(!needsArgsObj || !info().isSlotAliasedAtOsr(i), def->type() == MIRType_Value);
 
         // Aliased slots are never accessed, since they need to go through
         // the callobject. No need to type them here.
-        if (info().isSlotAliased(i))
+        if (info().isSlotAliasedAtOsr(i))
             continue;
 
         def->setResultType(existing->type());
         def->setResultTypeSet(existing->resultTypeSet());
     }
 
     // Finish the osrBlock.
     osrBlock->end(MGoto::New(alloc(), preheader));
@@ -5876,17 +5876,17 @@ IonBuilder::newPendingLoopHeader(MBasicB
         // in which case this may avoid restarts of loop analysis or bailouts
         // during the OSR itself.
 
         // Unbox the MOsrValue if it is known to be unboxable.
         for (uint32_t i = info().startArgSlot(); i < block->stackDepth(); i++) {
 
             // The value of aliased args and slots are in the callobject. So we can't
             // the value from the baseline frame.
-            if (info().isSlotAliased(i))
+            if (info().isSlotAliasedAtOsr(i))
                 continue;
 
             // Don't bother with expression stack values. The stack should be
             // empty except for let variables (not Ion-compiled) or iterators.
             if (i >= info().firstStackSlot())
                 continue;
 
             MPhi *phi = block->getSlot(i)->toPhi();
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -761,17 +761,17 @@ ScriptAnalysis::analyzeBytecode(JSContex
 
     PodZero(escapedSlots, numSlots);
 
     bool allVarsAliased = script_->compartment()->debugMode();
     bool allArgsAliased = allVarsAliased || script_->argumentsHasVarBinding();
 
     RootedScript script(cx, script_);
     for (BindingIter bi(script); bi; bi++) {
-        if (bi->kind() == ARGUMENT)
+        if (bi->kind() == Binding::ARGUMENT)
             escapedSlots[ArgSlot(bi.frameIndex())] = allArgsAliased || bi->aliased();
         else
             escapedSlots[LocalSlot(script_, bi.frameIndex())] = allVarsAliased || bi->aliased();
     }
 
     canTrackVars = true;
 
     /*
@@ -923,42 +923,21 @@ ScriptAnalysis::analyzeBytecode(JSContex
                             return false;
                         getCode(catchOffset).exceptionEntry = true;
                     }
                 }
             }
             break;
           }
 
-          case JSOP_GETLOCAL: {
-            /*
-             * Watch for uses of variables not known to be defined, and mark
-             * them as having possible uses before definitions.  Ignore GETLOCAL
-             * followed by a POP, these are generated for, e.g. 'var x;'
-             */
-            jsbytecode *next = pc + JSOP_GETLOCAL_LENGTH;
-            if (JSOp(*next) != JSOP_POP || jumpTarget(next)) {
-                uint32_t local = GET_LOCALNO(pc);
-                if (local >= script_->nfixed()) {
-                    localsAliasStack_ = true;
-                    break;
-                }
-            }
+          case JSOP_GETLOCAL:
+          case JSOP_CALLLOCAL:
+          case JSOP_SETLOCAL:
+            JS_ASSERT(GET_LOCALNO(pc) < script_->nfixed());
             break;
-          }
-
-          case JSOP_CALLLOCAL:
-          case JSOP_SETLOCAL: {
-            uint32_t local = GET_LOCALNO(pc);
-            if (local >= script_->nfixed()) {
-                localsAliasStack_ = true;
-                break;
-            }
-            break;
-          }
 
           case JSOP_PUSHBLOCKSCOPE:
             localsAliasStack_ = true;
             break;
 
           default:
             break;
         }
@@ -1817,16 +1796,23 @@ ScriptAnalysis::analyzeSSA(JSContext *cx
             stack[stackDepth - 1].v = stack[stackDepth - 2].v = code->poppedValues[0];
             break;
 
           case JSOP_DUP2:
             stack[stackDepth - 1].v = stack[stackDepth - 3].v = code->poppedValues[0];
             stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1];
             break;
 
+          case JSOP_DUPAT: {
+            unsigned pickedDepth = GET_UINT24 (pc);
+            JS_ASSERT(pickedDepth < stackDepth - 1);
+            stack[stackDepth - 1].v = stack[stackDepth - 2 - pickedDepth].v;
+            break;
+          }
+
           case JSOP_SWAP:
             /* Swap is like pick 1. */
           case JSOP_PICK: {
             unsigned pickedDepth = (op == JSOP_SWAP ? 1 : pc[1]);
             stack[stackDepth - 1].v = code->poppedValues[pickedDepth];
             for (unsigned i = 0; i < pickedDepth; i++)
                 stack[stackDepth - 2 - i].v = code->poppedValues[i];
             break;
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -112,18 +112,16 @@ js::StackUses(JSScript *script, jsbyteco
     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:
-        return GET_UINT16(pc) + 1;
       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);
     }
 }
 
@@ -463,16 +461,26 @@ BytecodeParser::simulateOp(JSOp op, uint
       case JSOP_DUP2:
         JS_ASSERT(ndefs == 4);
         if (offsetStack) {
             offsetStack[stackDepth + 2] = offsetStack[stackDepth];
             offsetStack[stackDepth + 3] = offsetStack[stackDepth + 1];
         }
         break;
 
+      case JSOP_DUPAT: {
+        JS_ASSERT(ndefs == 1);
+        jsbytecode *pc = script_->offsetToPC(offset);
+        unsigned n = GET_UINT24(pc);
+        JS_ASSERT(n < stackDepth);
+        if (offsetStack)
+            offsetStack[stackDepth] = offsetStack[stackDepth - 1 - n];
+        break;
+      }
+
       case JSOP_SWAP:
         JS_ASSERT(ndefs == 2);
         if (offsetStack) {
             uint32_t tmp = offsetStack[stackDepth + 1];
             offsetStack[stackDepth + 1] = offsetStack[stackDepth];
             offsetStack[stackDepth] = tmp;
         }
         break;
@@ -827,19 +835,19 @@ ToDisassemblySource(JSContext *cx, Handl
         if (!source)
             return false;
         bytes->initBytes(source);
         return true;
     }
 
     if (!JSVAL_IS_PRIMITIVE(v)) {
         JSObject *obj = JSVAL_TO_OBJECT(v);
-        if (obj->is<BlockObject>()) {
+        if (obj->is<StaticBlockObject>()) {
             char *source = JS_sprintf_append(nullptr, "depth %d {",
-                                             obj->as<BlockObject>().stackDepth());
+                                             obj->as<StaticBlockObject>().localOffset());
             if (!source)
                 return false;
 
             Shape::Range<CanGC> r(cx, obj->lastProperty());
 
             while (!r.empty()) {
                 Rooted<Shape*> shape(cx, &r.front());
                 JSAtom *atom = JSID_IS_INT(shape->propid())
@@ -1020,17 +1028,18 @@ js_Disassemble1(JSContext *cx, HandleScr
       {
         int i;
 
       case JOF_UINT16:
         i = (int)GET_UINT16(pc);
         goto print_int;
 
       case JOF_UINT24:
-        JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY);
+        JS_ASSERT(op == JSOP_UINT24 || op == JSOP_NEWARRAY || op == JSOP_INITELEM_ARRAY ||
+                  op == JSOP_DUPAT);
         i = (int)GET_UINT24(pc);
         goto print_int;
 
       case JOF_UINT8:
         i = GET_UINT8(pc);
         goto print_int;
 
       case JOF_INT8:
@@ -1431,19 +1440,18 @@ struct ExpressionDecompiler
           localNames(nullptr),
           parser(cx, script),
           sprinter(cx)
     {}
     ~ExpressionDecompiler();
     bool init();
     bool decompilePCForStackOperand(jsbytecode *pc, int i);
     bool decompilePC(jsbytecode *pc);
-    JSAtom *getVar(uint32_t slot);
+    JSAtom *getFixed(uint32_t slot, jsbytecode *pc);
     JSAtom *getArg(unsigned slot);
-    JSAtom *findLetVar(jsbytecode *pc, unsigned depth);
     JSAtom *loadAtom(jsbytecode *pc);
     bool quote(JSString *s, uint32_t quote);
     bool write(const char *s);
     bool write(JSString *str);
     bool getOutput(char **out);
 };
 
 bool
@@ -1499,28 +1507,19 @@ ExpressionDecompiler::decompilePC(jsbyte
       case JSOP_CALLARG: {
         unsigned slot = GET_ARGNO(pc);
         JSAtom *atom = getArg(slot);
         return write(atom);
       }
       case JSOP_GETLOCAL:
       case JSOP_CALLLOCAL: {
         uint32_t i = GET_LOCALNO(pc);
-        JSAtom *atom;
-        if (i >= script->nfixed()) {
-            i -= script->nfixed();
-            JS_ASSERT(i < unsigned(parser.stackDepthAtPC(pc)));
-            atom = findLetVar(pc, i);
-            if (!atom)
-                return decompilePCForStackOperand(pc, i); // Destructing temporary
-        } else {
-            atom = getVar(i);
-        }
-        JS_ASSERT(atom);
-        return write(atom);
+        if (JSAtom *atom = getFixed(i, pc))
+            return write(atom);
+        return write("(intermediate value)");
       }
       case JSOP_CALLALIASEDVAR:
       case JSOP_GETALIASEDVAR: {
         JSAtom *atom = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
         JS_ASSERT(atom);
         return write(atom);
       }
       case JSOP_LENGTH:
@@ -1636,50 +1635,49 @@ ExpressionDecompiler::quote(JSString *s,
 
 JSAtom *
 ExpressionDecompiler::loadAtom(jsbytecode *pc)
 {
     return script->getAtom(GET_UINT32_INDEX(pc));
 }
 
 JSAtom *
-ExpressionDecompiler::findLetVar(jsbytecode *pc, uint32_t depth)
-{
-    for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) {
-        if (!chain->is<StaticBlockObject>())
-            continue;
-        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());
-            }
-        }
-    }
-    return nullptr;
-}
-
-JSAtom *
 ExpressionDecompiler::getArg(unsigned slot)
 {
     JS_ASSERT(fun);
     JS_ASSERT(slot < script->bindings.count());
     return (*localNames)[slot].name();
 }
 
 JSAtom *
-ExpressionDecompiler::getVar(uint32_t slot)
+ExpressionDecompiler::getFixed(uint32_t slot, jsbytecode *pc)
 {
-    JS_ASSERT(fun);
-    slot += fun->nargs();
-    JS_ASSERT(slot < script->bindings.count());
-    return (*localNames)[slot].name();
+    if (slot < script->nfixedvars()) {
+        JS_ASSERT(fun);
+        slot += fun->nargs();
+        JS_ASSERT(slot < script->bindings.count());
+        return (*localNames)[slot].name();
+    }
+    for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) {
+        if (!chain->is<StaticBlockObject>())
+            continue;
+        StaticBlockObject &block = chain->as<StaticBlockObject>();
+        if (slot < block.localOffset())
+            continue;
+        slot -= block.localOffset();
+        if (slot >= block.slotCount())
+            return nullptr;
+        for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
+            const Shape &shape = r.front();
+            if (shape.shortid() == int(slot))
+                return JSID_TO_ATOM(shape.propid());
+        }
+        break;
+    }
+    return nullptr;
 }
 
 bool
 ExpressionDecompiler::getOutput(char **res)
 {
     ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0);
     *res = cx->pod_malloc<char>(len + 1);
     if (!*res)
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -67,28 +67,31 @@ Bindings::argumentsVarIndex(ExclusiveCon
     while (bi->name() != arguments)
         bi++;
     return bi.frameIndex();
 }
 
 bool
 Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
                                    unsigned numArgs, uint32_t numVars,
-                                   Binding *bindingArray)
+                                   Binding *bindingArray, uint32_t numBlockScoped)
 {
     JS_ASSERT(!self->callObjShape_);
     JS_ASSERT(self->bindingArrayAndFlag_ == TEMPORARY_STORAGE_BIT);
     JS_ASSERT(!(uintptr_t(bindingArray) & TEMPORARY_STORAGE_BIT));
     JS_ASSERT(numArgs <= ARGC_LIMIT);
     JS_ASSERT(numVars <= LOCALNO_LIMIT);
-    JS_ASSERT(UINT32_MAX - numArgs >= numVars);
+    JS_ASSERT(numBlockScoped <= LOCALNO_LIMIT);
+    JS_ASSERT(numVars <= LOCALNO_LIMIT - numBlockScoped);
+    JS_ASSERT(UINT32_MAX - numArgs >= numVars + numBlockScoped);
 
     self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT;
     self->numArgs_ = numArgs;
     self->numVars_ = numVars;
+    self->numBlockScoped_ = numBlockScoped;
 
     // Get the initial shape to use when creating CallObjects for this script.
     // After creation, a CallObject's shape may change completely (via direct eval() or
     // other operations that mutate the lexical scope). However, since the
     // lexical bindings added to the initial shape are permanent and the
     // allocKind/nfixed of a CallObject cannot change, one may assume that the
     // slot location (whether in the fixed or dynamic slots) of a variable is
     // the same as in the initial shape. (This is assumed by the interpreter and
@@ -137,17 +140,17 @@ Bindings::initWithTemporaryStorage(Exclu
                                  BaseShape::VAROBJ | BaseShape::DELEGATE);
 
         UnownedBaseShape *base = BaseShape::getUnowned(cx, stackBase);
         if (!base)
             return false;
 
         unsigned attrs = JSPROP_PERMANENT |
                          JSPROP_ENUMERATE |
-                         (bi->kind() == CONSTANT ? JSPROP_READONLY : 0);
+                         (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0);
         StackShape child(base, NameToId(bi->name()), slot, attrs, 0, 0);
 
         shape = cx->compartment()->propertyTree.getChild(cx, shape, child);
         if (!shape)
             return false;
 
         JS_ASSERT(slot < nslots);
         slot++;
@@ -181,32 +184,33 @@ Bindings::clone(JSContext *cx, InternalB
     JS_ASSERT(off >= 0);
     JS_ASSERT(size_t(off) <= srcScript->dataSize());
     Binding *dstPackedBindings = (Binding *)(dstScriptData + off);
 
     /*
      * Since atoms are shareable throughout the runtime, we can simply copy
      * the source's bindingArray directly.
      */
-    if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.bindingArray()))
+    if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.bindingArray(),
+                                  src.numBlockScoped()))
         return false;
     self->switchToScriptStorage(dstPackedBindings);
     return true;
 }
 
 /* static */ Bindings
 GCMethods<Bindings>::initial()
 {
     return Bindings();
 }
 
 template<XDRMode mode>
 static bool
 XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars,
-                  HandleScript script)
+                  HandleScript script, unsigned numBlockScoped)
 {
     JSContext *cx = xdr->cx();
 
     if (mode == XDR_ENCODE) {
         for (BindingIter bi(script); bi; bi++) {
             RootedAtom atom(cx, bi->name());
             if (!XDRAtom(xdr, &atom))
                 return false;
@@ -234,24 +238,25 @@ XDRScriptBindings(XDRState<mode> *xdr, L
         if (!bindingArray)
             return false;
         for (uint32_t i = 0; i < nameCount; i++) {
             uint8_t u8;
             if (!xdr->codeUint8(&u8))
                 return false;
 
             PropertyName *name = atoms[i].toString()->asAtom().asPropertyName();
-            BindingKind kind = BindingKind(u8 >> 1);
+            Binding::Kind kind = Binding::Kind(u8 >> 1);
             bool aliased = bool(u8 & 1);
 
             bindingArray[i] = Binding(name, kind, aliased);
         }
 
         InternalBindingsHandle bindings(script, &script->bindings);
-        if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray))
+        if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray,
+                                                numBlockScoped))
             return false;
     }
 
     return true;
 }
 
 bool
 Bindings::bindingIsAliased(uint32_t bindingIndex)
@@ -476,26 +481,30 @@ js::XDRScript(XDRState<mode> *xdr, Handl
 
     JSContext *cx = xdr->cx();
     RootedScript script(cx);
     natoms = nsrcnotes = 0;
     nconsts = nobjects = nregexps = ntrynotes = nblockscopes = 0;
 
     /* XDR arguments and vars. */
     uint16_t nargs = 0;
+    uint16_t nblocklocals = 0;
     uint32_t nvars = 0;
     if (mode == XDR_ENCODE) {
         script = scriptp.get();
         JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment());
 
         nargs = script->bindings.numArgs();
+        nblocklocals = script->bindings.numBlockScoped();
         nvars = script->bindings.numVars();
     }
     if (!xdr->codeUint16(&nargs))
         return false;
+    if (!xdr->codeUint16(&nblocklocals))
+        return false;
     if (!xdr->codeUint32(&nvars))
         return false;
 
     if (mode == XDR_ENCODE)
         length = script->length();
     if (!xdr->codeUint32(&length))
         return false;
 
@@ -625,17 +634,17 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)),
                                   options, /* staticLevel = */ 0, sourceObject, 0, 0);
         if (!script)
             return false;
     }
 
     /* JSScript::partiallyInit assumes script->bindings is fully initialized. */
     LifoAllocScope las(&cx->tempLifoAlloc());
-    if (!XDRScriptBindings(xdr, las, nargs, nvars, script))
+    if (!XDRScriptBindings(xdr, las, nargs, nvars, script, nblocklocals))
         return false;
 
     if (mode == XDR_DECODE) {
         if (!JSScript::partiallyInit(cx, script, nconsts, nobjects, nregexps, ntrynotes,
                                      nblockscopes, nTypeSets))
         {
             return false;
         }
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -122,51 +122,46 @@ struct TryNoteArray {
     uint32_t        length;     // Count of indexed try notes.
 };
 
 struct BlockScopeArray {
     BlockScopeNote *vector;     // Array of indexed BlockScopeNote records.
     uint32_t        length;     // Count of indexed try notes.
 };
 
-/*
- * A "binding" is a formal, 'var' or 'const' declaration. A function's lexical
- * scope is composed of these three kinds of bindings.
- */
-
-enum BindingKind { ARGUMENT, VARIABLE, CONSTANT };
-
 class Binding
 {
-    /*
-     * One JSScript stores one Binding per formal/variable so we use a
-     * packed-word representation.
-     */
+    // One JSScript stores one Binding per formal/variable so we use a
+    // packed-word representation.
     uintptr_t bits_;
 
     static const uintptr_t KIND_MASK = 0x3;
     static const uintptr_t ALIASED_BIT = 0x4;
     static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT);
 
   public:
+    // A "binding" is a formal, 'var', or 'const' declaration. A function's
+    // lexical scope is composed of these three kinds of bindings.
+    enum Kind { ARGUMENT, VARIABLE, CONSTANT };
+
     explicit Binding() : bits_(0) {}
 
-    Binding(PropertyName *name, BindingKind kind, bool aliased) {
+    Binding(PropertyName *name, Kind kind, bool aliased) {
         JS_STATIC_ASSERT(CONSTANT <= KIND_MASK);
         JS_ASSERT((uintptr_t(name) & ~NAME_MASK) == 0);
         JS_ASSERT((uintptr_t(kind) & ~KIND_MASK) == 0);
         bits_ = uintptr_t(name) | uintptr_t(kind) | (aliased ? ALIASED_BIT : 0);
     }
 
     PropertyName *name() const {
         return (PropertyName *)(bits_ & NAME_MASK);
     }
 
-    BindingKind kind() const {
-        return BindingKind(bits_ & KIND_MASK);
+    Kind kind() const {
+        return Kind(bits_ & KIND_MASK);
     }
 
     bool aliased() const {
         return bool(bits_ & ALIASED_BIT);
     }
 };
 
 JS_STATIC_ASSERT(sizeof(Binding) == sizeof(uintptr_t));
@@ -183,16 +178,17 @@ typedef InternalHandle<Bindings *> Inter
 class Bindings
 {
     friend class BindingIter;
     friend class AliasedFormalIter;
 
     HeapPtr<Shape> callObjShape_;
     uintptr_t bindingArrayAndFlag_;
     uint16_t numArgs_;
+    uint16_t numBlockScoped_;
     uint32_t numVars_;
 
     /*
      * During parsing, bindings are allocated out of a temporary LifoAlloc.
      * After parsing, a JSScript object is created and the bindings are
      * permanently transferred to it. On error paths, the JSScript object may
      * end up with bindings that still point to the (new released) LifoAlloc
      * memory. To avoid tracing these bindings during GC, we keep track of
@@ -215,29 +211,47 @@ class Bindings
     /*
      * Initialize a Bindings with a pointer into temporary storage.
      * bindingArray must have length numArgs+numVars. Before the temporary
      * storage is release, switchToScriptStorage must be called, providing a
      * pointer into the Binding array stored in script->data.
      */
     static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
                                          unsigned numArgs, uint32_t numVars,
-                                         Binding *bindingArray);
+                                         Binding *bindingArray, unsigned numBlockScoped);
+
+    // CompileScript parses and compiles one statement at a time, but the result
+    // is one Script object.  There will be no vars or bindings, because those
+    // go on the global, but there may be block-scoped locals, and the number of
+    // block-scoped locals may increase as we parse more expressions.  This
+    // helper updates the number of block scoped variables in a script as it is
+    // being parsed.
+    void updateNumBlockScoped(unsigned numBlockScoped) {
+        JS_ASSERT(!callObjShape_);
+        JS_ASSERT(numVars_ == 0);
+        JS_ASSERT(numBlockScoped < LOCALNO_LIMIT);
+        JS_ASSERT(numBlockScoped >= numBlockScoped_);
+        numBlockScoped_ = numBlockScoped;
+    }
 
     uint8_t *switchToScriptStorage(Binding *newStorage);
 
     /*
      * Clone srcScript's bindings (as part of js::CloneScript). dstScriptData
      * is the pointer to what will eventually be dstScript->data.
      */
     static bool clone(JSContext *cx, InternalBindingsHandle self, uint8_t *dstScriptData,
                       HandleScript srcScript);
 
     unsigned numArgs() const { return numArgs_; }
     uint32_t numVars() const { return numVars_; }
+    unsigned numBlockScoped() const { return numBlockScoped_; }
+    uint32_t numLocals() const { return numVars() + numBlockScoped(); }
+
+    // Return the size of the bindingArray.
     uint32_t count() const { return numArgs() + numVars(); }
 
     /* Return the initial shape of call objects created for this scope. */
     Shape *callObjShape() const { return callObjShape_; }
 
     /* Convenience method to get the var index of 'arguments'. */
     static uint32_t argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle);
 
@@ -919,18 +933,26 @@ class JSScript : public js::gc::Barriere
 
     size_t column() const {
         js::AutoThreadSafeAccess ts(this);
         return column_;
     }
 
     void setColumn(size_t column) { column_ = column; }
 
+    // The fixed part of a stack frame is comprised of vars (in function code)
+    // and block-scoped locals (in all kinds of code).
     size_t nfixed() const {
         js::AutoThreadSafeAccess ts(this);
+        return function_ ? bindings.numLocals() : bindings.numBlockScoped();
+    }
+
+    // Number of fixed slots reserved for vars.  Only nonzero for function code.
+    size_t nfixedvars() const {
+        js::AutoThreadSafeAccess ts(this);
         return function_ ? bindings.numVars() : 0;
     }
 
     size_t nslots() const {
         js::AutoThreadSafeAccess ts(this);
         return nslots_;
     }
 
@@ -1569,17 +1591,17 @@ static_assert(sizeof(JSScript) % js::gc:
               "Size of JSScript must be an integral multiple of js::gc::CellSize");
 
 namespace js {
 
 /*
  * Iterator over a script's bindings (formals and variables).
  * The order of iteration is:
  *  - first, formal arguments, from index 0 to numArgs
- *  - next, variables, from index 0 to numVars
+ *  - next, variables, from index 0 to numLocals
  */
 class BindingIter
 {
     const InternalBindingsHandle bindings_;
     uint32_t i_;
 
     friend class Bindings;
 
@@ -1609,17 +1631,17 @@ class BindingIter
 typedef Vector<Binding, 32> BindingVector;
 
 extern bool
 FillBindingVector(HandleScript fromScript, BindingVector *vec);
 
 /*
  * Iterator over the aliased formal bindings in ascending index order. This can
  * be veiwed as a filtering of BindingIter with predicate
- *   bi->aliased() && bi->kind() == ARGUMENT
+ *   bi->aliased() && bi->kind() == Binding::ARGUMENT
  */
 class AliasedFormalIter
 {
     const Binding *begin_, *p_, *end_;
     unsigned slot_;
 
     void settle() {
         while (p_ != end_ && !p_->aliased())
--- a/js/src/jsscriptinlines.h
+++ b/js/src/jsscriptinlines.h
@@ -16,17 +16,18 @@
 #include "jscompartmentinlines.h"
 
 #include "vm/Shape-inl.h"
 
 namespace js {
 
 inline
 Bindings::Bindings()
-    : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT), numArgs_(0), numVars_(0)
+    : callObjShape_(nullptr), bindingArrayAndFlag_(TEMPORARY_STORAGE_BIT),
+      numArgs_(0), numBlockScoped_(0), numVars_(0)
 {}
 
 inline
 AliasedFormalIter::AliasedFormalIter(JSScript *script)
   : begin_(script->bindingArray()),
     p_(begin_),
     end_(begin_ + (script->funHasAnyAliasedFormal() ? script->numArgs() : 0)),
     slot_(CallObject::RESERVED_SLOTS)
--- a/js/src/tests/js1_8_1/regress/regress-420399.js
+++ b/js/src/tests/js1_8_1/regress/regress-420399.js
@@ -15,17 +15,17 @@ test();
 //-----------------------------------------------------------------------------
 
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
-  expect = "TypeError: undefined has no properties";
+  expect = "TypeError: a is undefined";
   try
   {
     (let (a=undefined) a).b = 3;
   }
   catch(ex)
   {
     actual = ex + '';
   }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1739,24 +1739,24 @@ 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);
 END_CASE(JSOP_POPN)
 
-CASE(JSOP_POPNV)
+CASE(JSOP_DUPAT)
 {
-    JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth());
-    Value val = REGS.sp[-1];
-    REGS.sp -= GET_UINT16(REGS.pc);
-    REGS.sp[-1] = val;
+    JS_ASSERT(GET_UINT24(REGS.pc) < REGS.stackDepth());
+    unsigned i = GET_UINT24(REGS.pc);
+    const Value &rref = REGS.sp[-int(i + 1)];
+    PUSH_COPY(rref);
 }
-END_CASE(JSOP_POPNV)
+END_CASE(JSOP_DUPAT)
 
 CASE(JSOP_SETRVAL)
     POP_RETURN_VALUE();
 END_CASE(JSOP_SETRVAL)
 
 CASE(JSOP_ENTERWITH)
 {
     RootedValue &val = rootValue0;
@@ -3347,37 +3347,31 @@ CASE(JSOP_DEBUGGER)
 }
 END_CASE(JSOP_DEBUGGER)
 
 CASE(JSOP_PUSHBLOCKSCOPE)
 {
     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.fp()->pushBlock(cx, blockObj))
         goto error;
 }
 END_CASE(JSOP_PUSHBLOCKSCOPE)
 
 CASE(JSOP_POPBLOCKSCOPE)
 {
 #ifdef DEBUG
     // Pop block from scope chain.
     JS_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK);
     NestedScopeObject *scope = script->getStaticScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH);
     JS_ASSERT(scope && scope->is<StaticBlockObject>());
     StaticBlockObject &blockObj = scope->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());
 #endif
 
     // Pop block from scope chain.
     REGS.fp()->popBlock(cx);
 }
 END_CASE(JSOP_POPBLOCKSCOPE)
 
 CASE(JSOP_DEBUGLEAVEBLOCK)
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -95,18 +95,18 @@ 1234567890123456789012345678901234567890
     \
     /* spreadcall variant of JSOP_CALL */ \
     macro(JSOP_SPREADCALL,41, "spreadcall", NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \
     /* spreadcall variant of JSOP_NEW */ \
     macro(JSOP_SPREADNEW, 42, "spreadnew",  NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \
     /* spreadcall variant of JSOP_EVAL */ \
     macro(JSOP_SPREADEVAL,43, "spreadeval", NULL,         1,  3,  1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \
     \
-    /* Pop N values, preserving top value. */ \
-    macro(JSOP_POPNV,     44, "popnv",      NULL,         3, -1,  1,  JOF_UINT16) \
+    /* Dup the Nth value from the top. */ \
+    macro(JSOP_DUPAT,     44, "dupat",      NULL,         4,  0,  1,  JOF_UINT24) \
     \
     macro(JSOP_UNUSED45,  45, "unused45",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED46,  46, "unused46",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED47,  47, "unused47",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED48,  48, "unused48",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED49,  49, "unused49",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED50,  50, "unused50",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED51,  51, "unused51",   NULL,         1,  0,  0,  JOF_BYTE) \
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -662,42 +662,39 @@ ClonedBlockObject::create(JSContext *cx,
         if (!JSObject::setParent(cx, obj, global))
             return nullptr;
     }
 
     JS_ASSERT(!obj->inDictionaryMode());
     JS_ASSERT(obj->slotSpan() >= block->slotCount() + RESERVED_SLOTS);
 
     obj->setReservedSlot(SCOPE_CHAIN_SLOT, ObjectValue(*frame.scopeChain()));
-    obj->setReservedSlot(DEPTH_SLOT, PrivateUint32Value(block->stackDepth()));
 
     /*
      * Copy in the closed-over locals. Closed-over locals don't need
      * any fixup since the initial value is 'undefined'.
      */
     unsigned nslots = block->slotCount();
-    unsigned base = frame.script()->nfixed() + block->stackDepth();
     for (unsigned i = 0; i < nslots; ++i) {
         if (block->isAliased(i))
-            obj->as<ClonedBlockObject>().setVar(i, frame.unaliasedLocal(base + i));
+            obj->as<ClonedBlockObject>().setVar(i, frame.unaliasedLocal(block->varToLocalIndex(i)));
     }
 
     JS_ASSERT(obj->isDelegate());
 
     return &obj->as<ClonedBlockObject>();
 }
 
 void
 ClonedBlockObject::copyUnaliasedValues(AbstractFramePtr frame)
 {
     StaticBlockObject &block = staticBlock();
-    unsigned base = frame.script()->nfixed() + block.stackDepth();
     for (unsigned i = 0; i < slotCount(); ++i) {
         if (!block.isAliased(i))
-            setVar(i, frame.unaliasedLocal(base + i), DONT_CHECK_ALIASING);
+            setVar(i, frame.unaliasedLocal(block.varToLocalIndex(i)), DONT_CHECK_ALIASING);
     }
 }
 
 StaticBlockObject *
 StaticBlockObject::create(ExclusiveContext *cx)
 {
     RootedTypeObject type(cx, cx->getNewType(&BlockObject::class_, nullptr));
     if (!type)
@@ -716,17 +713,17 @@ StaticBlockObject::create(ExclusiveConte
     return &obj->as<StaticBlockObject>();
 }
 
 /* static */ Shape *
 StaticBlockObject::addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id,
                           unsigned index, bool *redeclared)
 {
     JS_ASSERT(JSID_IS_ATOM(id) || (JSID_IS_INT(id) && JSID_TO_INT(id) == (int)index));
-    JS_ASSERT(index < VAR_INDEX_LIMIT);
+    JS_ASSERT(index < LOCAL_INDEX_LIMIT);
 
     *redeclared = false;
 
     /* Inline JSObject::addProperty in order to trap the redefinition case. */
     Shape **spp;
     if (Shape::search(cx, block->lastProperty(), id, &spp, true)) {
         *redeclared = true;
         return nullptr;
@@ -764,43 +761,39 @@ bool
 js::XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope,
                          StaticBlockObject **objp)
 {
     /* NB: Keep this in sync with CloneStaticBlockObject. */
 
     JSContext *cx = xdr->cx();
 
     Rooted<StaticBlockObject*> obj(cx);
-    uint32_t count = 0;
-    uint32_t depthAndCount = 0;
+    uint32_t count = 0, offset = 0;
 
     if (mode == XDR_ENCODE) {
         obj = *objp;
-        uint32_t depth = obj->stackDepth();
-        JS_ASSERT(depth <= UINT16_MAX);
         count = obj->slotCount();
-        JS_ASSERT(count <= UINT16_MAX);
-        depthAndCount = (depth << 16) | uint16_t(count);
+        offset = obj->localOffset();
     }
 
     if (mode == XDR_DECODE) {
         obj = StaticBlockObject::create(cx);
         if (!obj)
             return false;
         obj->initEnclosingNestedScope(enclosingScope);
         *objp = obj;
     }
 
-    if (!xdr->codeUint32(&depthAndCount))
+    if (!xdr->codeUint32(&count))
+        return false;
+    if (!xdr->codeUint32(&offset))
         return false;
 
     if (mode == XDR_DECODE) {
-        uint32_t depth = uint16_t(depthAndCount >> 16);
-        count = uint16_t(depthAndCount);
-        obj->setStackDepth(depth);
+        obj->setLocalOffset(offset);
 
         /*
          * XDR the block object's properties. We know that there are 'count'
          * properties to XDR, stored as id/shortid pairs.
          */
         for (unsigned i = 0; i < count; i++) {
             RootedAtom atom(cx);
             if (!XDRAtom(xdr, &atom))
@@ -875,17 +868,17 @@ CloneStaticBlockObject(JSContext *cx, Ha
 {
     /* NB: Keep this in sync with XDRStaticBlockObject. */
 
     Rooted<StaticBlockObject*> clone(cx, StaticBlockObject::create(cx));
     if (!clone)
         return nullptr;
 
     clone->initEnclosingNestedScope(enclosingScope);
-    clone->setStackDepth(srcBlock->stackDepth());
+    clone->setLocalOffset(srcBlock->localOffset());
 
     /* Shape::Range is reverse order, so build a list in forward order. */
     AutoShapeVector shapes(cx);
     if (!shapes.growBy(srcBlock->slotCount()))
         return nullptr;
     for (Shape::Range<NoGC> r(srcBlock->lastProperty()); !r.empty(); r.popFront())
         shapes[r.front().shortid()] = &r.front();
 
@@ -1189,17 +1182,17 @@ class DebugScopeProxy : public BaseProxy
 
             Bindings &bindings = script->bindings;
             BindingIter bi(script);
             while (bi && NameToId(bi->name()) != id)
                 bi++;
             if (!bi)
                 return false;
 
-            if (bi->kind() == VARIABLE || bi->kind() == CONSTANT) {
+            if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) {
                 uint32_t i = bi.frameIndex();
                 if (script->varIsAliased(i))
                     return false;
 
                 if (maybeLiveScope) {
                     AbstractFramePtr frame = maybeLiveScope->frame();
                     if (action == GET)
                         vp.set(frame.unaliasedVar(i));
@@ -1211,17 +1204,17 @@ class DebugScopeProxy : public BaseProxy
                     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);
+                JS_ASSERT(bi->kind() == Binding::ARGUMENT);
                 unsigned i = bi.frameIndex();
                 if (script->formalIsAliased(i))
                     return false;
 
                 if (maybeLiveScope) {
                     AbstractFramePtr frame = maybeLiveScope->frame();
                     if (script->argsObjAliasesFormals() && frame.hasArgsObj()) {
                         if (action == GET)
@@ -1261,22 +1254,22 @@ class DebugScopeProxy : public BaseProxy
 
             unsigned i = shape->shortid();
             if (block->staticBlock().isAliased(i))
                 return false;
 
             if (maybeLiveScope) {
                 AbstractFramePtr frame = maybeLiveScope->frame();
                 JSScript *script = frame.script();
-                uint32_t local = block->slotToLocalIndex(script->bindings, shape->slot());
+                uint32_t local = block->staticBlock().varToLocalIndex(i);
                 if (action == GET)
                     vp.set(frame.unaliasedLocal(local));
                 else
                     frame.unaliasedLocal(local) = vp;
-                JS_ASSERT(analyze::LocalSlot(script, local) >= analyze::TotalSlots(script));
+                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);
             }
 
             return true;
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -408,52 +408,65 @@ class BlockObject : public NestedScopeOb
         return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
     }
 
     /* Return the number of variables associated with this block. */
     uint32_t slotCount() const {
         return propertyCountForCompilation();
     }
 
-    /*
-     * Return the local corresponding to the ith binding where i is in the
-     * range [0, slotCount()) and the return local index is in the range
-     * [script->nfixed, script->nfixed + script->nslots).
-     */
-    uint32_t slotToLocalIndex(const Bindings &bindings, uint32_t slot) {
-        JS_ASSERT(slot < RESERVED_SLOTS + slotCount());
-        return bindings.numVars() + stackDepth() + (slot - RESERVED_SLOTS);
-    }
-
-    uint32_t localIndexToSlot(const Bindings &bindings, uint32_t i) {
-        return RESERVED_SLOTS + (i - (bindings.numVars() + stackDepth()));
-    }
-
   protected:
     /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */
     const Value &slotValue(unsigned i) {
         return getSlotRef(RESERVED_SLOTS + i);
     }
 
     void setSlotValue(unsigned i, const Value &v) {
         setSlot(RESERVED_SLOTS + i, v);
     }
 };
 
 class StaticBlockObject : public BlockObject
 {
+    static const unsigned LOCAL_OFFSET_SLOT = 1;
+
   public:
     static StaticBlockObject *create(ExclusiveContext *cx);
 
+    /* See StaticScopeIter comment. */
+    JSObject *enclosingStaticScope() const {
+        AutoThreadSafeAccess ts(this);
+        return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
+    }
+
     /*
-     * Return whether this StaticBlockObject contains a variable stored at
-     * the given stack depth (i.e., fp->base()[depth]).
+     * A refinement of enclosingStaticScope that returns nullptr if the enclosing
+     * static scope is a JSFunction.
      */
-    bool containsVarAtDepth(uint32_t depth) {
-        return depth >= stackDepth() && depth < stackDepth() + slotCount();
+    inline StaticBlockObject *enclosingBlock() const;
+
+    uint32_t localOffset() {
+        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
+    }
+
+    // Return the local corresponding to the 'var'th binding where 'var' is in the
+    // range [0, slotCount()).
+    uint32_t varToLocalIndex(uint32_t var) {
+        JS_ASSERT(var < slotCount());
+        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + var;
+    }
+
+    // Return the slot corresponding to local variable 'local', where 'local' is
+    // in the range [localOffset(), localOffset() + slotCount()).  The result is
+    // in the range [RESERVED_SLOTS, RESERVED_SLOTS + slotCount()).
+    uint32_t localIndexToSlot(uint32_t local) {
+        JS_ASSERT(local >= localOffset());
+        local -= localOffset();
+        JS_ASSERT(local < slotCount());
+        return RESERVED_SLOTS + local;
     }
 
     /*
      * A let binding is aliased if accessed lexically by nested functions or
      * dynamically through dynamic name lookup (eval, with, function::, etc).
      */
     bool isAliased(unsigned i) {
         return slotValue(i).isTrue();
@@ -477,19 +490,19 @@ class StaticBlockObject : public BlockOb
         JS_ASSERT_IF(i > 0, slotValue(i-1).isBoolean());
         setSlotValue(i, BooleanValue(aliased));
         if (aliased && !needsClone()) {
             setSlotValue(0, MagicValue(JS_BLOCK_NEEDS_CLONE));
             JS_ASSERT(needsClone());
         }
     }
 
-    void setStackDepth(uint32_t depth) {
-        JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined());
-        initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth));
+    void setLocalOffset(uint32_t offset) {
+        JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined());
+        initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset));
     }
 
     /*
      * Frontend compilation temporarily uses the object's slots to link
      * a let var to its associated Definition parse node.
      */
     void setDefinitionParseNode(unsigned i, frontend::Definition *def) {
         JS_ASSERT(slotValue(i).isUndefined());
@@ -503,17 +516,17 @@ class StaticBlockObject : public BlockOb
     }
 
     /*
      * While ScopeCoordinate can generally reference up to 2^24 slots, block objects have an
      * additional limitation that all slot indices must be storable as uint16_t short-ids in the
      * associated Shape. If we could remove the block dependencies on shape->shortid, we could
      * remove INDEX_LIMIT.
      */
-    static const unsigned VAR_INDEX_LIMIT = JS_BIT(16);
+    static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16);
 
     static Shape *addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id,
                          unsigned index, bool *redeclared);
 };
 
 class ClonedBlockObject : public BlockObject
 {
   public:
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -95,23 +95,24 @@ StackFrame::initVarsToUndefined()
 {
     SetValueRangeToUndefined(slots(), script()->nfixed());
 }
 
 inline Value &
 StackFrame::unaliasedVar(uint32_t i, MaybeCheckAliasing checkAliasing)
 {
     JS_ASSERT_IF(checkAliasing, !script()->varIsAliased(i));
-    JS_ASSERT(i < script()->nfixed());
+    JS_ASSERT(i < script()->nfixedvars());
     return slots()[i];
 }
 
 inline Value &
 StackFrame::unaliasedLocal(uint32_t i, MaybeCheckAliasing checkAliasing)
 {
+    JS_ASSERT(i < script()->nfixed());
 #ifdef DEBUG
     CheckLocalUnaliased(checkAliasing, script(), i);
 #endif
     return slots()[i];
 }
 
 inline Value &
 StackFrame::unaliasedFormal(unsigned i, MaybeCheckAliasing checkAliasing)
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1263,18 +1263,18 @@ AbstractFramePtr::hasPushedSPSFrame() co
 
 #ifdef DEBUG
 void
 js::CheckLocalUnaliased(MaybeCheckAliasing checkAliasing, JSScript *script, uint32_t i)
 {
     if (!checkAliasing)
         return;
 
-    JS_ASSERT(i < script->nslots());
-    if (i < script->nfixed()) {
+    JS_ASSERT(i < script->nfixed());
+    if (i < script->bindings.numVars()) {
         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.
     }
 }
 #endif
 
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -18,17 +18,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 166);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 167);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { }
 
     JSContext *cx() const {
         return context;