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 id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke, jandem
bugs962599
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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;