Bug 966912 - Part 1: Rename blockChain to staticScope r=luke
authorAndy Wingo <wingo@igalia.com>
Tue, 04 Feb 2014 14:24:42 +0100
changeset 166710 c717600bee44a2ae398ec6d9a659709bcf690b68
parent 166709 e7485fef3041c065f38f023a40a2c61056eccbca
child 166711 c0a84f517f4f44ed056afd9e63282f5572a45649
push id39268
push userwingo@igalia.com
push dateTue, 04 Feb 2014 13:27:20 +0000
treeherdermozilla-inbound@c717600bee44 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs966912
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 966912 - Part 1: Rename blockChain to staticScope r=luke
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SharedContext.h
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/Interpreter.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -91,17 +91,17 @@ BytecodeEmitter::BytecodeEmitter(Bytecod
     script(sc->context, script),
     prolog(sc->context, lineNum),
     main(sc->context, lineNum),
     current(&main),
     parser(parser),
     evalCaller(evalCaller),
     topStmt(nullptr),
     topScopeStmt(nullptr),
-    blockChain(sc->context),
+    staticScope(sc->context),
     atomIndices(sc->context),
     firstLine(lineNum),
     stackDepth(0), maxStackDepth(0),
     arrayCompDepth(0),
     emitLevel(0),
     constList(sc->context),
     tryNoteList(sc->context),
     blockScopeList(sc->context),
@@ -488,17 +488,17 @@ class NonLocalExitScope {
 
   public:
     explicit NonLocalExitScope(ExclusiveContext *cx_, BytecodeEmitter *bce_)
       : cx(cx_),
         bce(bce_),
         savedScopeIndex(bce->blockScopeList.length()),
         savedDepth(bce->stackDepth),
         openScopeIndex(UINT32_MAX) {
-        if (bce->blockChain) {
+        if (bce->staticScope) {
             StmtInfoBCE *stmt = bce->topStmt;
             while (1) {
                 JS_ASSERT(stmt);
                 if (stmt->isBlockScope) {
                     openScopeIndex = stmt->blockScopeIndex;
                     break;
                 }
                 stmt = stmt->down;
@@ -573,18 +573,17 @@ NonLocalExitScope::prepareForNonLocalJum
              */
             npops += 2;
             break;
 
           default:;
         }
 
         if (stmt->isBlockScope) {
-            JS_ASSERT(stmt->blockObj);
-            StaticBlockObject &blockObj = *stmt->blockObj;
+            StaticBlockObject &blockObj = stmt->staticBlock();
             if (!popScopeForNonLocalExit(blockObj, stmt->blockScopeIndex))
                 return false;
             npops += blockObj.slotCount();
         }
     }
 
     FLUSH_POPS();
     return true;
@@ -641,18 +640,18 @@ PushStatementBCE(BytecodeEmitter *bce, S
 
 /*
  * Return the enclosing lexical scope, which is the innermost enclosing static
  * block object or compiler created function.
  */
 static JSObject *
 EnclosingStaticScope(BytecodeEmitter *bce)
 {
-    if (bce->blockChain)
-        return bce->blockChain;
+    if (bce->staticScope)
+        return bce->staticScope;
 
     if (!bce->sc->isFunctionBox()) {
         JS_ASSERT(!bce->parent);
         return nullptr;
     }
 
     return bce->sc->asFunctionBox()->function();
 }
@@ -739,17 +738,17 @@ EmitInternedObjectOp(ExclusiveContext *c
 // If no variable declared in the scope is "aliased", then no scope chain node
 // is allocated.
 //
 // To help debuggers, the bytecode emitter arranges to record the PC ranges
 // comprehended by a block scope, and ultimately attach them to the JSScript.
 // An element in the "block scope array" specifies the PC range, and links to a
 // StaticBlockObject in the object list of the script.  That block is linked to
 // the previous block in the scope, if any.  The static block chain at any
-// pre-retire PC can be retrieved using JSScript::getBlockScope(jsbytecode *pc).
+// 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.
@@ -778,19 +777,19 @@ EmitInternedObjectOp(ExclusiveContext *c
 // LeaveBlockScope.  Push locals before entering a scope, and pop them
 // afterwards.  Brush your teeth, and clean behind your ears!
 //
 static bool
 EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
                 unsigned extraSlots)
 {
     uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
-    if (bce->blockChain) {
+    if (bce->staticScope) {
         StmtInfoBCE *stmt = bce->topScopeStmt;
-        for (; stmt->blockObj != bce->blockChain; stmt = stmt->down) {}
+        for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {}
         parent = stmt->blockScopeIndex;
     }
 
     Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>());
 
     uint32_t scopeObjectIndex = bce->objectList.add(objbox);
 
     int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots);
@@ -805,17 +804,17 @@ EnterBlockScope(ExclusiveContext *cx, By
             return false;
     }
 
     stmt->blockScopeIndex = bce->blockScopeList.length();
     if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent))
         return false;
 
     PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset());
-    blockObj->initEnclosingStaticScope(EnclosingStaticScope(bce));
+    blockObj->initEnclosingNestedScope(EnclosingStaticScope(bce));
     FinishPushBlockScope(bce, stmt, *blockObj);
 
     JS_ASSERT(stmt->isBlockScope);
 
     return true;
 }
 
 // Patches |breaks| and |continues| unless the top statement info record
@@ -841,22 +840,23 @@ LeaveBlockScope(ExclusiveContext *cx, By
     StmtInfoBCE *stmt = bce->topStmt;
     JS_ASSERT(stmt->isBlockScope);
     uint32_t blockScopeIndex = stmt->blockScopeIndex;
 
 #ifdef DEBUG
     JS_ASSERT(bce->blockScopeList.list[blockScopeIndex].length == 0);
     uint32_t blockObjIndex = bce->blockScopeList.list[blockScopeIndex].index;
     ObjectBox *blockObjBox = bce->objectList.find(blockObjIndex);
-    StaticBlockObject *blockObj = &blockObjBox->object->as<StaticBlockObject>();
-    JS_ASSERT(stmt->blockObj == blockObj);
-    JS_ASSERT(blockObj == bce->blockChain);
+    NestedScopeObject *staticScope = &blockObjBox->object->as<NestedScopeObject>();
+    JS_ASSERT(stmt->staticScope == staticScope);
+    JS_ASSERT(staticScope == bce->staticScope);
 #endif
 
-    bool blockOnChain = bce->blockChain->needsClone();
+    JS_ASSERT(bce->staticScope->is<StaticBlockObject>());
+    bool blockOnChain = bce->staticScope->as<StaticBlockObject>().needsClone();
 
     if (!PopStatementBCE(cx, bce))
         return false;
 
     if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
         return false;
 
     bce->blockScopeList.recordEnd(blockScopeIndex, bce->offset());
@@ -993,26 +993,28 @@ EmitAliasedVarOp(ExclusiveContext *cx, J
     SET_SCOPECOORD_HOPS(pc, sc.hops());
     pc += SCOPECOORD_HOPS_LEN;
     SET_SCOPECOORD_SLOT(pc, sc.slot());
     pc += SCOPECOORD_SLOT_LEN;
     CheckTypeSet(cx, bce, op);
     return true;
 }
 
+// Compute the number of nested scope objects that will actually be on the scope
+// chain at runtime, given the BCE's current staticScope.
 static unsigned
-ClonedBlockDepth(BytecodeEmitter *bce)
-{
-    unsigned clonedBlockDepth = 0;
-    for (StaticBlockObject *b = bce->blockChain; b; b = b->enclosingBlock()) {
-        if (b->needsClone())
-            ++clonedBlockDepth;
-    }
-
-    return clonedBlockDepth;
+DynamicNestedScopeDepth(BytecodeEmitter *bce)
+{
+    unsigned depth = 0;
+    for (NestedScopeObject *b = bce->staticScope; b; b = b->enclosingNestedScope()) {
+        if (!b->is<StaticBlockObject>() || b->as<StaticBlockObject>().needsClone())
+            ++depth;
+    }
+
+    return depth;
 }
 
 static bool
 LookupAliasedName(HandleScript script, PropertyName *name, uint32_t *pslot)
 {
     /*
      * Beware: BindingIter may contain more than one Binding for a given name
      * (in the case of |function f(x,x) {}|) but only one will be aliased.
@@ -1073,55 +1075,58 @@ EmitAliasedVarOp(ExclusiveContext *cx, J
     BytecodeEmitter *bceOfDef = bce;
     if (pn->isUsed()) {
         /*
          * As explained in BindNameToSlot, the 'level' of a use indicates how
          * many function scopes (i.e., BytecodeEmitters) to skip to find the
          * enclosing function scope of the definition being accessed.
          */
         for (unsigned i = pn->pn_cookie.level(); i; i--) {
-            skippedScopes += ClonedBlockDepth(bceOfDef);
+            skippedScopes += DynamicNestedScopeDepth(bceOfDef);
             FunctionBox *funbox = bceOfDef->sc->asFunctionBox();
             if (funbox->isHeavyweight()) {
                 skippedScopes++;
                 if (funbox->function()->isNamedLambda())
                     skippedScopes++;
             }
             bceOfDef = bceOfDef->parent;
         }
     } else {
         JS_ASSERT(pn->isDefn());
         JS_ASSERT(pn->pn_cookie.level() == bce->script->staticLevel());
     }
 
     /*
-     * The final part of the skippedScopes computation depends on the type of variable. An arg or
-     * local variable is at the outer scope of a function and so includes the full
-     * ClonedBlockDepth. A let/catch-binding requires a search of the block chain to see how many
-     * (dynamic) block objects to skip.
+     * The final part of the skippedScopes computation depends on the type of
+     * variable. An arg or local variable is at the outer scope of a function
+     * and so includes the full DynamicNestedScopeDepth. A let/catch-binding
+     * requires a search of the block chain to see how many (dynamic) block
+     * objects to skip.
      */
     ScopeCoordinate sc;
     if (IsArgOp(pn->getOp())) {
-        if (!AssignHops(bce, pn, skippedScopes + ClonedBlockDepth(bceOfDef), &sc))
+        if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
             return false;
         JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
     } 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 + ClonedBlockDepth(bceOfDef), &sc))
+            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();
-            Rooted<StaticBlockObject*> b(cx, bceOfDef->blockChain);
+            JS_ASSERT(bceOfDef->staticScope->is<StaticBlockObject>());
+            Rooted<StaticBlockObject*> b(cx, &bceOfDef->staticScope->as<StaticBlockObject>());
             while (!b->containsVarAtDepth(depth)) {
                 if (b->needsClone())
                     skippedScopes++;
                 b = b->enclosingBlock();
+                JS_ASSERT(b);
             }
             if (!AssignHops(bce, pn, skippedScopes, &sc))
                 return false;
             sc.setSlot(b->localIndexToSlot(bceOfDef->script->bindings, local));
         }
     }
 
     return EmitAliasedVarOp(cx, op, sc, bce);
@@ -4860,17 +4865,17 @@ EmitFunc(ExclusiveContext *cx, BytecodeE
             fun->isInterpreted() &&
             (bce->checkSingletonContext() ||
              (!bce->isInLoop() && bce->isRunOnceLambda()));
         if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton))
             return false;
 
         if (fun->isInterpretedLazy()) {
             if (!fun->lazyScript()->sourceObject()) {
-                JSObject *scope = bce->blockChain;
+                JSObject *scope = bce->staticScope;
                 if (!scope && bce->sc->isFunctionBox())
                     scope = bce->sc->asFunctionBox()->function();
                 JSObject *source = bce->script->sourceObject();
                 fun->lazyScript()->setParent(scope, &source->as<ScriptSourceObject>());
             }
             if (bce->emittingRunOnceLambda)
                 fun->lazyScript()->setTreatAsRunOnce();
         } else {
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -102,18 +102,18 @@ struct BytecodeEmitter
 
     /* the parser */
     Parser<FullParseHandler> *const parser;
 
     HandleScript    evalCaller;     /* scripted caller info for eval and dbgapi */
 
     StmtInfoBCE     *topStmt;       /* top of statement info stack */
     StmtInfoBCE     *topScopeStmt;  /* top lexical scope statement */
-    Rooted<StaticBlockObject *> blockChain;
-                                    /* compile time block scope chain */
+    Rooted<NestedScopeObject *> staticScope;
+                                    /* compile time scope chain */
 
     OwnedAtomIndexMapPtr atomIndices; /* literals indexed for mapping */
     unsigned        firstLine;      /* first line, for JSScript::initFromEmitter */
 
     int32_t         stackDepth;     /* current stack depth in script frame */
     uint32_t        maxStackDepth;  /* maximum stack depth so far */
 
     uint32_t        arrayCompDepth; /* stack depth of array in comprehension */
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -44,16 +44,18 @@ using namespace js;
 using namespace js::gc;
 using mozilla::Maybe;
 
 namespace js {
 namespace frontend {
 
 typedef Rooted<StaticBlockObject*> RootedStaticBlockObject;
 typedef Handle<StaticBlockObject*> HandleStaticBlockObject;
+typedef Rooted<NestedScopeObject*> RootedNestedScopeObject;
+typedef Handle<NestedScopeObject*> HandleNestedScopeObject;
 
 
 /*
  * Insist that the next token be of type tt, or report errno and return null.
  * NB: this macro uses cx and ts from its lexical environment.
  */
 #define MUST_MATCH_TOKEN(tt, errno)                                                         \
     JS_BEGIN_MACRO                                                                          \
@@ -2671,17 +2673,17 @@ Parser<ParseHandler>::reportRedeclaratio
     return false;
 }
 
 /*
  * Define a let-variable in a block, let-expression, or comprehension scope. pc
  * must already be in such a scope.
  *
  * Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a
- * property for the new variable on the block object, pc->blockChain;
+ * property for the new variable on the block object, pc->staticScope;
  * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to
  * data->pn in a slot of the block object.
  */
 template <>
 /* static */ bool
 Parser<FullParseHandler>::bindLet(BindData<FullParseHandler> *data,
                                   HandlePropertyName name, Parser<FullParseHandler> *parser)
 {
@@ -2774,25 +2776,28 @@ struct PopLetDecl {
         return true;
     }
 };
 
 template <typename ParseHandler>
 static void
 PopStatementPC(TokenStream &ts, ParseContext<ParseHandler> *pc)
 {
-    RootedStaticBlockObject blockObj(ts.context(), pc->topStmt->blockObj);
-    JS_ASSERT(!!blockObj == (pc->topStmt->isBlockScope));
+    RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope);
+    JS_ASSERT(!!scopeObj == (pc->topStmt->isBlockScope));
 
     FinishPopStatement(pc);
 
-    if (blockObj) {
-        JS_ASSERT(!blockObj->inDictionaryMode());
-        ForEachLetDef(ts, pc, blockObj, PopLetDecl<ParseHandler>());
-        blockObj->resetPrevBlockChainFromParser();
+    if (scopeObj) {
+        if (scopeObj->is<StaticBlockObject>()) {
+            RootedStaticBlockObject blockObj(ts.context(), &scopeObj->as<StaticBlockObject>());
+            JS_ASSERT(!blockObj->inDictionaryMode());
+            ForEachLetDef(ts, pc, blockObj, PopLetDecl<ParseHandler>());
+        }
+        scopeObj->resetEnclosingNestedScopeFromParser();
     }
 }
 
 /*
  * The function LexicalLookup searches a static binding for the given name in
  * the stack of statements enclosing the statement currently being parsed. Each
  * statement that introduces a new scope has a corresponding scope object, on
  * which the bindings for that scope are stored. LexicalLookup either returns
@@ -2815,17 +2820,17 @@ LexicalLookup(ContextT *ct, HandleAtom a
          */
         if (stmt->type == STMT_WITH)
             break;
 
         // Skip statements that do not introduce a new scope
         if (!stmt->isBlockScope)
             continue;
 
-        StaticBlockObject &blockObj = *stmt->blockObj;
+        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();
             return stmt;
         }
@@ -3197,17 +3202,17 @@ Parser<ParseHandler>::pushLexicalScope(H
 {
     JS_ASSERT(blockObj);
 
     ObjectBox *blockbox = newObjectBox(blockObj);
     if (!blockbox)
         return null();
 
     PushStatementPC(pc, stmt, STMT_BLOCK);
-    blockObj->initPrevBlockChainFromParser(pc->blockChain);
+    blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
     FinishPushBlockScope(pc, stmt, *blockObj.get());
 
     Node pn = handler.newLexicalScope(blockbox);
     if (!pn)
         return null();
 
     if (!GenerateBlockId(tokenStream, pc, stmt->blockid))
         return null();
@@ -3555,17 +3560,17 @@ Parser<FullParseHandler>::letDeclaration
          */
         StmtInfoPC *stmt = pc->topStmt;
         if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
             report(ParseError, false, null(), JSMSG_LET_DECL_NOT_IN_BLOCK);
             return null();
         }
 
         if (stmt && stmt->isBlockScope) {
-            JS_ASSERT(pc->blockChain == stmt->blockObj);
+            JS_ASSERT(pc->staticScope == stmt->staticScope);
         } else {
             if (pc->atBodyLevel()) {
                 /*
                  * ES4 specifies that let at top level and at body-block scope
                  * does not shadow var, so convert back to var.
                  */
                 pn = variables(PNK_VAR);
                 if (!pn)
@@ -3601,19 +3606,19 @@ Parser<FullParseHandler>::letDeclaration
              * list stack, if it isn't already there.  If it is there, but it
              * lacks the SIF_SCOPE flag, it must be a try, catch, or finally
              * block.
              */
             stmt->isBlockScope = true;
             stmt->downScope = pc->topScopeStmt;
             pc->topScopeStmt = stmt;
 
-            blockObj->initPrevBlockChainFromParser(pc->blockChain);
-            pc->blockChain = blockObj;
-            stmt->blockObj = blockObj;
+            blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
+            pc->staticScope = blockObj;
+            stmt->staticScope = blockObj;
 
 #ifdef DEBUG
             ParseNode *tmp = pc->blockNode;
             JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
 #endif
 
             /* Create a new lexical scope node for these statements. */
             ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, &handler);
@@ -3623,17 +3628,17 @@ Parser<FullParseHandler>::letDeclaration
             pn1->setOp(JSOP_POPN);
             pn1->pn_pos = pc->blockNode->pn_pos;
             pn1->pn_objbox = blockbox;
             pn1->pn_expr = pc->blockNode;
             pn1->pn_blockid = pc->blockNode->pn_blockid;
             pc->blockNode = pn1;
         }
 
-        pn = variables(PNK_LET, nullptr, pc->blockChain, HoistVars);
+        pn = variables(PNK_LET, nullptr, &pc->staticScope->as<StaticBlockObject>(), HoistVars);
         if (!pn)
             return null();
         pn->pn_xflags = PNX_POPVAR;
     } while (0);
 
     return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr;
 }
 
@@ -5073,17 +5078,18 @@ Parser<ParseHandler>::tryStatement()
              */
             MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
 
             /*
              * Contrary to ECMA Ed. 3, the catch variable is lexically
              * scoped, not a property of a new Object instance.  This is
              * an intentional change that anticipates ECMA Ed. 4.
              */
-            data.initLet(HoistVars, *pc->blockChain, JSMSG_TOO_MANY_CATCH_VARS);
+            data.initLet(HoistVars, pc->staticScope->template as<StaticBlockObject>(),
+                         JSMSG_TOO_MANY_CATCH_VARS);
             JS_ASSERT(data.let.blockObj);
 
             tt = tokenStream.getToken();
             Node catchName;
             switch (tt) {
 #if JS_HAS_DESTRUCTURING
               case TOK_LB:
               case TOK_LC:
@@ -6092,18 +6098,18 @@ Parser<FullParseHandler>::comprehensionT
 
     CompExprTransplanter transplanter(kid, this, outerpc, kind == PNK_SEMI, adjust);
     if (!transplanter.init())
         return null();
 
     if (!transplanter.transplant(kid))
         return null();
 
-    JS_ASSERT(pc->blockChain && pc->blockChain == pn->pn_objbox->object);
-    data.initLet(HoistVars, *pc->blockChain, JSMSG_ARRAY_INIT_TOO_BIG);
+    JS_ASSERT(pc->staticScope && pc->staticScope == pn->pn_objbox->object);
+    data.initLet(HoistVars, pc->staticScope->as<StaticBlockObject>(), JSMSG_ARRAY_INIT_TOO_BIG);
 
     do {
         /*
          * FOR node is binary, left is loop control and right is body.  Use
          * index to count each block-local let-variable on the left-hand side
          * of the in/of.
          */
         pn2 = BinaryNode::create(PNK_FOR, &handler);
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -93,18 +93,17 @@ struct ParseContext : public GenericPars
     typedef typename ParseHandler::Node Node;
     typedef typename ParseHandler::DefinitionNode DefinitionNode;
 
     uint32_t        bodyid;         /* block number of program/function body */
     uint32_t        blockidGen;     /* preincremented block number generator */
 
     StmtInfoPC      *topStmt;       /* top of statement info stack */
     StmtInfoPC      *topScopeStmt;  /* top lexical scope statement */
-    Rooted<StaticBlockObject *> blockChain;
-                                    /* compile time block scope chain */
+    Rooted<NestedScopeObject *> staticScope;  /* compile time scope chain */
     Node            maybeFunction;  /* sc->isFunctionBox, the pn where pn->pn_funbox == sc */
 
     const unsigned  staticLevel;    /* static compilation unit nesting level */
 
     // lastYieldOffset stores the offset of the last yield that was parsed.
     // NoYieldOffset is its initial value.
     static const uint32_t NoYieldOffset = UINT32_MAX;
     uint32_t         lastYieldOffset;
@@ -245,17 +244,17 @@ struct ParseContext : public GenericPars
                  Node maybeFunction, SharedContext *sc,
                  Directives *newDirectives,
                  unsigned staticLevel, uint32_t bodyid)
       : GenericParseContext(parent, sc),
         bodyid(0),           // initialized in init()
         blockidGen(bodyid),  // used to set |bodyid| and subsequently incremented in init()
         topStmt(nullptr),
         topScopeStmt(nullptr),
-        blockChain(prs->context),
+        staticScope(prs->context),
         maybeFunction(maybeFunction),
         staticLevel(staticLevel),
         lastYieldOffset(NoYieldOffset),
         blockNode(ParseHandler::null()),
         decls_(prs->context, prs->alloc),
         args_(prs->context),
         vars_(prs->context),
         parserPC(&prs->pc),
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -396,30 +396,36 @@ struct StmtInfoBase {
      * STMT_FINALLY and the block contains at least one let-declaration.
      */
     bool isBlockScope:1;
 
     /* for (let ...) induced block scope */
     bool isForLetBlock:1;
 
     RootedAtom      label;          /* name of LABEL */
-    Rooted<StaticBlockObject *> blockObj; /* block scope object */
+    Rooted<NestedScopeObject *> staticScope; /* scope object */
 
     StmtInfoBase(ExclusiveContext *cx)
-        : isBlockScope(false), isForLetBlock(false), label(cx), blockObj(cx)
+        : isBlockScope(false), isForLetBlock(false), label(cx), staticScope(cx)
     {}
 
     bool maybeScope() const {
         return STMT_BLOCK <= type && type <= STMT_SUBROUTINE && type != STMT_WITH;
     }
 
     bool linksScope() const {
         return (STMT_WITH <= type && type <= STMT_CATCH) || isBlockScope;
     }
 
+    StaticBlockObject& staticBlock() const {
+        JS_ASSERT(isBlockScope);
+        JS_ASSERT(staticScope);
+        return staticScope->as<StaticBlockObject>();
+    }
+
     bool isLoop() const {
         return type >= STMT_DO_LOOP;
     }
 
     bool isTrying() const {
         return STMT_TRY <= type && type <= STMT_SUBROUTINE;
     }
 };
@@ -428,51 +434,51 @@ struct StmtInfoBase {
 template <class ContextT>
 void
 PushStatement(ContextT *ct, typename ContextT::StmtInfo *stmt, StmtType type)
 {
     stmt->type = type;
     stmt->isBlockScope = false;
     stmt->isForLetBlock = false;
     stmt->label = nullptr;
-    stmt->blockObj = nullptr;
+    stmt->staticScope = nullptr;
     stmt->down = ct->topStmt;
     ct->topStmt = stmt;
     if (stmt->linksScope()) {
         stmt->downScope = ct->topScopeStmt;
         ct->topScopeStmt = stmt;
     } else {
         stmt->downScope = nullptr;
     }
 }
 
 template <class ContextT>
 void
-FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt, StaticBlockObject &blockObj)
+FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt, NestedScopeObject &staticScope)
 {
     stmt->isBlockScope = true;
     stmt->downScope = ct->topScopeStmt;
     ct->topScopeStmt = stmt;
-    ct->blockChain = &blockObj;
-    stmt->blockObj = &blockObj;
+    ct->staticScope = &staticScope;
+    stmt->staticScope = &staticScope;
 }
 
 // Pop pc->topStmt. If the top StmtInfoPC struct is not stack-allocated, it
 // is up to the caller to free it.  The dummy argument is just to make the
 // template matching work.
 template <class ContextT>
 void
 FinishPopStatement(ContextT *ct)
 {
     typename ContextT::StmtInfo *stmt = ct->topStmt;
     ct->topStmt = stmt->down;
     if (stmt->linksScope()) {
         ct->topScopeStmt = stmt->downScope;
         if (stmt->isBlockScope)
-            ct->blockChain = stmt->blockObj->enclosingBlock();
+            ct->staticScope = stmt->staticBlock().enclosingBlock();
     }
 }
 
 /*
  * Find a lexically scoped variable (one declared by let, catch, or an array
  * comprehension) named by atom, looking in sc's compile-time scopes.
  *
  * If a WITH statement is reached along the scope stack, return its statement
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5843,17 +5843,17 @@ js_DumpStackFrame(JSContext *cx, StackFr
         fputc('\n', stderr);
 
         fprintf(stderr, "file %s line %u\n",
                 i.script()->filename(), (unsigned) i.script()->lineno());
 
         if (jsbytecode *pc = i.pc()) {
             fprintf(stderr, "  pc = %p\n", pc);
             fprintf(stderr, "  current op: %s\n", js_CodeName[*pc]);
-            MaybeDumpObject("blockChain", i.script()->getBlockScope(pc));
+            MaybeDumpObject("staticScope", i.script()->getStaticScope(pc));
         }
         MaybeDumpValue("this", i.thisv());
         if (!i.isJit()) {
             fprintf(stderr, "  rval: ");
             dumpValue(i.interpFrame()->returnValue());
             fputc('\n', stderr);
         }
 
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1638,17 +1638,19 @@ JSAtom *
 ExpressionDecompiler::loadAtom(jsbytecode *pc)
 {
     return script->getAtom(GET_UINT32_INDEX(pc));
 }
 
 JSAtom *
 ExpressionDecompiler::findLetVar(jsbytecode *pc, uint32_t depth)
 {
-    for (JSObject *chain = script->getBlockScope(pc); chain; chain = chain->getParent()) {
+    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());
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -408,27 +408,27 @@ js::XDRScriptConst(XDRState<mode> *xdr, 
 
 template bool
 js::XDRScriptConst(XDRState<XDR_ENCODE> *, MutableHandleValue);
 
 template bool
 js::XDRScriptConst(XDRState<XDR_DECODE> *, MutableHandleValue);
 
 static inline uint32_t
-FindBlockIndex(JSScript *script, StaticBlockObject &block)
+FindScopeObjectIndex(JSScript *script, NestedScopeObject &scope)
 {
     ObjectArray *objects = script->objects();
     HeapPtrObject *vector = objects->vector;
     unsigned length = objects->length;
     for (unsigned i = 0; i < length; ++i) {
-        if (vector[i] == &block)
+        if (vector[i] == &scope)
             return i;
     }
 
-    MOZ_ASSUME_UNREACHABLE("Block not found");
+    MOZ_ASSUME_UNREACHABLE("Scope not found");
 }
 
 static bool
 SaveSharedScriptData(ExclusiveContext *, Handle<JSScript *>, SharedScriptData *, uint32_t);
 
 enum XDRClassKind {
     CK_BlockObject = 0,
     CK_JSFunction  = 1,
@@ -748,17 +748,17 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                 return false;
             if (mode == XDR_DECODE)
                 vector[i].init(val);
         }
     }
 
     /*
      * Here looping from 0-to-length to xdr objects is essential to ensure that
-     * all references to enclosing blocks (via FindBlockIndex below) happen
+     * all references to enclosing blocks (via FindScopeObjectIndex below) happen
      * after the enclosing block has been XDR'd.
      */
     for (i = 0; i != nobjects; ++i) {
         HeapPtr<JSObject> *objp = &script->objects()->vector[i];
         XDRClassKind classk;
 
         if (mode == XDR_ENCODE) {
             JSObject *obj = *objp;
@@ -775,18 +775,19 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         if (!xdr->codeEnum32(&classk))
             return false;
 
         switch (classk) {
           case CK_BlockObject: {
             /* Code the nested block's enclosing scope. */
             uint32_t blockEnclosingScopeIndex = 0;
             if (mode == XDR_ENCODE) {
-                if (StaticBlockObject *block = (*objp)->as<StaticBlockObject>().enclosingBlock())
-                    blockEnclosingScopeIndex = FindBlockIndex(script, *block);
+                NestedScopeObject &scope = (*objp)->as<NestedScopeObject>();
+                if (NestedScopeObject *enclosing = scope.enclosingNestedScope())
+                    blockEnclosingScopeIndex = FindScopeObjectIndex(script, *enclosing);
                 else
                     blockEnclosingScopeIndex = UINT32_MAX;
             }
             if (!xdr->codeUint32(&blockEnclosingScopeIndex))
                 return false;
             Rooted<JSObject*> blockEnclosingScope(cx);
             if (mode == XDR_DECODE) {
                 if (blockEnclosingScopeIndex != UINT32_MAX) {
@@ -812,17 +813,17 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                 if (!innerScript)
                     return false;
                 RootedObject staticScope(cx, innerScript->enclosingStaticScope());
                 StaticScopeIter<NoGC> ssi(staticScope);
                 if (ssi.done() || ssi.type() == StaticScopeIter<NoGC>::FUNCTION) {
                     JS_ASSERT(ssi.done() == !fun);
                     funEnclosingScopeIndex = UINT32_MAX;
                 } else {
-                    funEnclosingScopeIndex = FindBlockIndex(script, ssi.block());
+                    funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.block());
                     JS_ASSERT(funEnclosingScopeIndex < i);
                 }
             }
             if (!xdr->codeUint32(&funEnclosingScopeIndex))
                 return false;
             Rooted<JSObject*> funEnclosingScope(cx);
             if (mode == XDR_DECODE) {
                 if (funEnclosingScopeIndex == UINT32_MAX) {
@@ -2488,42 +2489,42 @@ js::CloneScript(JSContext *cx, HandleObj
     /* Objects */
 
     AutoObjectVector objects(cx);
     if (nobjects != 0) {
         HeapPtrObject *vector = src->objects()->vector;
         for (unsigned i = 0; i < nobjects; i++) {
             RootedObject obj(cx, vector[i]);
             RootedObject clone(cx);
-            if (obj->is<StaticBlockObject>()) {
-                Rooted<StaticBlockObject*> innerBlock(cx, &obj->as<StaticBlockObject>());
+            if (obj->is<NestedScopeObject>()) {
+                Rooted<NestedScopeObject*> innerBlock(cx, &obj->as<NestedScopeObject>());
 
                 RootedObject enclosingScope(cx);
-                if (StaticBlockObject *enclosingBlock = innerBlock->enclosingBlock())
-                    enclosingScope = objects[FindBlockIndex(src, *enclosingBlock)];
+                if (NestedScopeObject *enclosingBlock = innerBlock->enclosingNestedScope())
+                    enclosingScope = objects[FindScopeObjectIndex(src, *enclosingBlock)];
                 else
                     enclosingScope = fun;
 
-                clone = CloneStaticBlockObject(cx, enclosingScope, innerBlock);
+                clone = CloneNestedScopeObject(cx, enclosingScope, innerBlock);
             } else if (obj->is<JSFunction>()) {
                 RootedFunction innerFun(cx, &obj->as<JSFunction>());
                 if (innerFun->isNative()) {
                     assertSameCompartment(cx, innerFun);
                     clone = innerFun;
                 } else {
                     if (innerFun->isInterpretedLazy()) {
                         AutoCompartment ac(cx, innerFun);
                         if (!innerFun->getOrCreateScript(cx))
                             return nullptr;
                     }
                     RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope());
                     StaticScopeIter<CanGC> ssi(cx, staticScope);
                     RootedObject enclosingScope(cx);
                     if (!ssi.done() && ssi.type() == StaticScopeIter<CanGC>::BLOCK)
-                        enclosingScope = objects[FindBlockIndex(src, ssi.block())];
+                        enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())];
                     else
                         enclosingScope = fun;
 
                     clone = CloneFunctionAndScript(cx, enclosingScope, innerFun);
                 }
             } else {
                 /*
                  * Clone object literals emitted for the JSOP_NEWOBJECT opcode. We only emit that
@@ -2978,31 +2979,31 @@ LazyScript::markChildren(JSTracer *trc)
 
 void
 LazyScript::finalize(FreeOp *fop)
 {
     if (table_)
         fop->free_(table_);
 }
 
-StaticBlockObject *
-JSScript::getBlockScope(jsbytecode *pc)
+NestedScopeObject *
+JSScript::getStaticScope(jsbytecode *pc)
 {
     JS_ASSERT(containsPC(pc));
 
     if (!hasBlockScopes())
         return nullptr;
 
     ptrdiff_t offset = pc - main();
 
     if (offset < 0)
         return nullptr;
 
     BlockScopeArray *scopes = blockScopes();
-    StaticBlockObject *blockChain = nullptr;
+    NestedScopeObject *blockChain = nullptr;
 
     // Find the innermost block chain using a binary search.
     size_t bottom = 0;
     size_t top = scopes->length;
 
     while (bottom < top) {
         size_t mid = bottom + (top - bottom) / 2;
         const BlockScopeNote *note = &scopes->vector[mid];
@@ -3017,17 +3018,17 @@ JSScript::getBlockScope(jsbytecode *pc)
                 const BlockScopeNote *checkNote = &scopes->vector[check];
                 JS_ASSERT(checkNote->start <= offset);
                 if (offset < checkNote->start + checkNote->length) {
                     // We found a matching block chain but there may be inner ones
                     // at a higher block chain index than mid. Continue the binary search.
                     if (checkNote->index == BlockScopeNote::NoBlockScopeIndex)
                         blockChain = nullptr;
                     else
-                        blockChain = &getObject(checkNote->index)->as<StaticBlockObject>();
+                        blockChain = &getObject(checkNote->index)->as<NestedScopeObject>();
                     break;
                 }
                 if (checkNote->parent == UINT32_MAX)
                     break;
                 check = checkNote->parent;
             }
             bottom = mid + 1;
         } else {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -39,17 +39,17 @@ namespace jit {
 
 class BreakpointSite;
 class BindingIter;
 class LazyScript;
 class RegExpObject;
 struct SourceCompressionTask;
 class Shape;
 class WatchpointMap;
-class StaticBlockObject;
+class NestedScopeObject;
 
 namespace analyze {
     class ScriptAnalysis;
 }
 
 namespace frontend {
     class BytecodeEmitter;
 }
@@ -93,17 +93,17 @@ namespace js {
 // are popping the block chain in preparation for a goto.  These exits are also
 // nested with respect to outer scopes.  The scopes in these exits are indicated
 // by the "index" field, just like any other block.  If a nonlocal exit pops the
 // last block scope, the index will be NoBlockScopeIndex.
 //
 struct BlockScopeNote {
     static const uint32_t NoBlockScopeIndex = UINT32_MAX;
 
-    uint32_t        index;      // Index of StaticScopeObject in the object
+    uint32_t        index;      // Index of NestedScopeObject in the object
                                 // array, or NoBlockScopeIndex if there is no
                                 // block scope in this range.
     uint32_t        start;      // Bytecode offset at which this scope starts,
                                 // from script->main().
     uint32_t        length;     // Bytecode length of scope.
     uint32_t        parent;     // Index of parent block scope in notes, or UINT32_MAX.
 };
 
@@ -1417,17 +1417,17 @@ class JSScript : public js::gc::Barriere
     inline js::RegExpObject *getRegExp(jsbytecode *pc);
 
     const js::Value &getConst(size_t index) {
         js::ConstArray *arr = consts();
         JS_ASSERT(index < arr->length);
         return arr->vector[index];
     }
 
-    js::StaticBlockObject *getBlockScope(jsbytecode *pc);
+    js::NestedScopeObject *getStaticScope(jsbytecode *pc);
 
     /*
      * The isEmpty method tells whether this script has code that computes any
      * result (not return value, result AKA normal completion value) other than
      * JSVAL_VOID, or any other effects.
      */
     bool isEmpty() const {
         if (length() > 3)
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1723,30 +1723,36 @@ END_CASE(JSOP_UNDEFINED)
 CASE(JSOP_POP)
     REGS.sp--;
 END_CASE(JSOP_POP)
 
 CASE(JSOP_POPN)
     JS_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth());
     REGS.sp -= GET_UINT16(REGS.pc);
 #ifdef DEBUG
-    if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPN_LENGTH))
-        JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount());
+    if (NestedScopeObject *scope = script->getStaticScope(REGS.pc + JSOP_POPN_LENGTH)) {
+        JS_ASSERT(scope->is<StaticBlockObject>());
+        StaticBlockObject &blockObj = scope->as<StaticBlockObject>();
+        JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
+    }
 #endif
 END_CASE(JSOP_POPN)
 
 CASE(JSOP_POPNV)
 {
     JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth());
     Value val = REGS.sp[-1];
     REGS.sp -= GET_UINT16(REGS.pc);
     REGS.sp[-1] = val;
 #ifdef DEBUG
-    if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPNV_LENGTH))
-        JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount());
+    if (NestedScopeObject *scope = script->getStaticScope(REGS.pc + JSOP_POPNV_LENGTH)) {
+        JS_ASSERT(scope->is<StaticBlockObject>());
+        StaticBlockObject &blockObj = scope->as<StaticBlockObject>();
+        JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
+    }
 #endif
 }
 END_CASE(JSOP_POPNV)
 
 CASE(JSOP_SETRVAL)
     POP_RETURN_VALUE();
 END_CASE(JSOP_SETRVAL)
 
@@ -3364,31 +3370,34 @@ CASE(JSOP_PUSHBLOCKSCOPE)
 }
 END_CASE(JSOP_PUSHBLOCKSCOPE)
 
 CASE(JSOP_POPBLOCKSCOPE)
 {
 #ifdef DEBUG
     // Pop block from scope chain.
     JS_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK);
-    StaticBlockObject *blockObj = script->getBlockScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH);
-    JS_ASSERT(blockObj && blockObj->needsClone());
+    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());
+    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)
 {
-    JS_ASSERT(script->getBlockScope(REGS.pc));
+    JS_ASSERT(script->getStaticScope(REGS.pc));
+    JS_ASSERT(script->getStaticScope(REGS.pc)->is<StaticBlockObject>());
 
     // FIXME: This opcode should not be necessary.  The debugger shouldn't need
     // help from bytecode to do its job.  See bug 927782.
 
     if (MOZ_UNLIKELY(cx->compartment()->debugMode()))
         DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc);
 }
 END_CASE(JSOP_DEBUGLEAVEBLOCK)
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -43,18 +43,18 @@ StaticScopeIter<allowGC>::done() const
 {
     return !obj;
 }
 
 template <AllowGC allowGC>
 inline void
 StaticScopeIter<allowGC>::operator++(int)
 {
-    if (obj->template is<StaticBlockObject>()) {
-        obj = obj->template as<StaticBlockObject>().enclosingStaticScope();
+    if (obj->template is<NestedScopeObject>()) {
+        obj = obj->template as<NestedScopeObject>().enclosingScopeForStaticScopeIter();
     } else if (onNamedLambda || !obj->template as<JSFunction>().isNamedLambda()) {
         onNamedLambda = false;
         obj = obj->template as<JSFunction>().nonLazyScript()->enclosingStaticScope();
     } else {
         onNamedLambda = true;
     }
     JS_ASSERT_IF(obj, obj->template is<StaticBlockObject>() || obj->template is<JSFunction>());
     JS_ASSERT_IF(onNamedLambda, obj->template is<JSFunction>());
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -33,19 +33,19 @@ typedef Rooted<ArgumentsObject *> Rooted
 /*****************************************************************************/
 
 static JSObject *
 InnermostStaticScope(JSScript *script, jsbytecode *pc)
 {
     JS_ASSERT(script->containsPC(pc));
     JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
 
-    StaticBlockObject *block = script->getBlockScope(pc);
-    if (block)
-        return block;
+    NestedScopeObject *scope = script->getStaticScope(pc);
+    if (scope)
+        return scope;
     return script->functionNonDelazifying();
 }
 
 Shape *
 js::ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc)
 {
     StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
@@ -716,17 +716,17 @@ js::XDRStaticBlockObject(XDRState<mode> 
         JS_ASSERT(count <= UINT16_MAX);
         depthAndCount = (depth << 16) | uint16_t(count);
     }
 
     if (mode == XDR_DECODE) {
         obj = StaticBlockObject::create(cx);
         if (!obj)
             return false;
-        obj->initEnclosingStaticScope(enclosingScope);
+        obj->initEnclosingNestedScope(enclosingScope);
         *objp = obj;
     }
 
     if (!xdr->codeUint32(&depthAndCount))
         return false;
 
     if (mode == XDR_DECODE) {
         uint32_t depth = uint16_t(depthAndCount >> 16);
@@ -801,26 +801,26 @@ js::XDRStaticBlockObject(XDRState<mode> 
 }
 
 template bool
 js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *, HandleObject, StaticBlockObject **);
 
 template bool
 js::XDRStaticBlockObject(XDRState<XDR_DECODE> *, HandleObject, StaticBlockObject **);
 
-JSObject *
-js::CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock)
+static JSObject *
+CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock)
 {
     /* NB: Keep this in sync with XDRStaticBlockObject. */
 
     Rooted<StaticBlockObject*> clone(cx, StaticBlockObject::create(cx));
     if (!clone)
         return nullptr;
 
-    clone->initEnclosingStaticScope(enclosingScope);
+    clone->initEnclosingNestedScope(enclosingScope);
     clone->setStackDepth(srcBlock->stackDepth());
 
     /* 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();
@@ -836,67 +836,75 @@ js::CloneStaticBlockObject(JSContext *cx
         }
 
         clone->setAliased(i, srcBlock->isAliased(i));
     }
 
     return clone;
 }
 
+JSObject *
+js::CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> srcBlock)
+{
+    JS_ASSERT(srcBlock->is<StaticBlockObject>());
+    Rooted<StaticBlockObject *> blockObj(cx, &srcBlock->as<StaticBlockObject>());
+    return CloneStaticBlockObject(cx, enclosingScope, blockObj);
+}
+
 /*****************************************************************************/
 
 // Any name atom for a function which will be added as a DeclEnv object to the
 // scope chain above call objects for fun.
 static inline JSAtom *
 CallObjectLambdaName(JSFunction &fun)
 {
     return fun.isNamedLambda() ? fun.atom() : nullptr;
 }
 
 ScopeIter::ScopeIter(const ScopeIter &si, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(si.frame_),
     cur_(cx, si.cur_),
-    block_(cx, si.block_),
+    staticScope_(cx, si.staticScope_),
     type_(si.type_),
     hasScopeObject_(si.hasScopeObject_)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeIter::ScopeIter(JSObject &enclosingScope, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(NullFramePtr()),
     cur_(cx, &enclosingScope),
-    block_(cx, nullptr),
+    staticScope_(cx, nullptr),
     type_(Type(-1))
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(frame),
     cur_(cx, frame.scopeChain()),
-    block_(cx, frame.script()->getBlockScope(pc))
+    staticScope_(cx, frame.script()->getStaticScope(pc))
 {
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeIter::ScopeIter(const ScopeIterVal &val, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(val.frame_),
     cur_(cx, val.cur_),
-    block_(cx, val.block_),
+    staticScope_(cx, val.staticScope_),
     type_(val.type_),
     hasScopeObject_(val.hasScopeObject_)
 {
     assertSameCompartment(cx, val.frame_);
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeObject &
@@ -915,17 +923,17 @@ ScopeIter::operator++()
         if (hasScopeObject_) {
             cur_ = &cur_->as<CallObject>().enclosingScope();
             if (CallObjectLambdaName(*frame_.fun()))
                 cur_ = &cur_->as<DeclEnvObject>().enclosingScope();
         }
         frame_ = NullFramePtr();
         break;
       case Block:
-        block_ = block_->enclosingBlock();
+        staticScope_ = staticScope_->as<StaticBlockObject>().enclosingBlock();
         if (hasScopeObject_)
             cur_ = &cur_->as<ClonedBlockObject>().enclosingScope();
         settle();
         break;
       case With:
         JS_ASSERT(hasScopeObject_);
         cur_ = &cur_->as<WithObject>().enclosingScope();
         settle();
@@ -938,17 +946,17 @@ ScopeIter::operator++()
     }
     return *this;
 }
 
 void
 ScopeIter::settle()
 {
     /*
-     * Given an iterator state (cur_, block_), figure out which (potentially
+     * Given an iterator state (cur_, staticScope_), figure out which (potentially
      * optimized) scope the iterator should report. Thus, the result is a pair
      * (type_, hasScopeObject_) where hasScopeObject_ indicates whether the
      * scope object has been optimized away and does not exist on the scope
      * chain. Beware: while ScopeIter iterates over the scopes of a single
      * frame, the scope chain (pointed to by cur_) continues into the scopes of
      * enclosing frames. Thus, it is important not to look at cur_ until it is
      * certain that cur_ points to a scope object in the current frame. In
      * particular, there are three tricky corner cases:
@@ -961,89 +969,93 @@ ScopeIter::settle()
      * uncloned blocks. In the last case, since we haven't entered the
      * function, we simply return a ScopeIter where done() == true.
      *
      * Note: DebugScopeObject falls nicely into this plan: since they are only
      * ever introduced as the *enclosing* scope of a frame, they should never
      * show up in scope iteration and fall into the final non-scope case.
      */
     if (frame_.isNonEvalFunctionFrame() && !frame_.fun()->isHeavyweight()) {
-        if (block_) {
+        if (staticScope_) {
+            JS_ASSERT(staticScope_->is<StaticBlockObject>());
             type_ = Block;
-            hasScopeObject_ = block_->needsClone();
+            hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone();
         } else {
             type_ = Call;
             hasScopeObject_ = false;
         }
     } else if (frame_.isNonStrictDirectEvalFrame() && cur_ == frame_.evalPrevScopeChain(cx)) {
-        if (block_) {
-            JS_ASSERT(!block_->needsClone());
+        if (staticScope_) {
+            JS_ASSERT(staticScope_->is<StaticBlockObject>());
+            JS_ASSERT(!staticScope_->as<StaticBlockObject>().needsClone());
             type_ = Block;
             hasScopeObject_ = false;
         } else {
             frame_ = NullFramePtr();
         }
     } else if (frame_.isNonEvalFunctionFrame() && !frame_.hasCallObj()) {
         JS_ASSERT(cur_ == frame_.fun()->environment());
         frame_ = NullFramePtr();
     } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) {
         JS_ASSERT(cur_ == frame_.evalPrevScopeChain(cx));
         frame_ = NullFramePtr();
     } else if (cur_->is<WithObject>()) {
         JS_ASSERT_IF(frame_.isFunctionFrame(), frame_.fun()->isHeavyweight());
-        JS_ASSERT_IF(block_, block_->needsClone());
-        JS_ASSERT_IF(block_, block_->stackDepth() < cur_->as<WithObject>().stackDepth());
+        JS_ASSERT_IF(staticScope_, staticScope_->as<StaticBlockObject>().needsClone());
+        JS_ASSERT_IF(staticScope_,
+                     staticScope_->as<StaticBlockObject>().stackDepth() <
+                     cur_->as<WithObject>().stackDepth());
         type_ = With;
         hasScopeObject_ = true;
-    } else if (block_) {
+    } else if (staticScope_) {
         type_ = Block;
-        hasScopeObject_ = block_->needsClone();
-        JS_ASSERT_IF(hasScopeObject_, cur_->as<ClonedBlockObject>().staticBlock() == *block_);
+        hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone();
+        JS_ASSERT_IF(hasScopeObject_, cur_->as<ClonedBlockObject>().staticBlock() == *staticScope_);
     } else if (cur_->is<CallObject>()) {
         CallObject &callobj = cur_->as<CallObject>();
         type_ = callobj.isForEval() ? StrictEvalScope : Call;
         hasScopeObject_ = true;
         JS_ASSERT_IF(type_ == Call, callobj.callee().nonLazyScript() == frame_.script());
     } else {
         JS_ASSERT(!cur_->is<ScopeObject>());
         JS_ASSERT(frame_.isGlobalFrame() || frame_.isDebuggerFrame());
         frame_ = NullFramePtr();
     }
 }
 
 /* static */ HashNumber
 ScopeIterKey::hash(ScopeIterKey si)
 {
     /* hasScopeObject_ is determined by the other fields. */
-    return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.block_) ^ si.type_;
+    return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.staticScope_) ^ si.type_;
 }
 
 /* static */ bool
 ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2)
 {
     /* hasScopeObject_ is determined by the other fields. */
     return si1.frame_ == si2.frame_ &&
            (!si1.frame_ ||
             (si1.cur_   == si2.cur_   &&
-             si1.block_ == si2.block_ &&
+             si1.staticScope_ == si2.staticScope_ &&
              si1.type_  == si2.type_));
 }
 
 // Live ScopeIter values may be added to DebugScopes::liveScopes, as
 // ScopeIterVal instances.  They need to have write barriers when they are added
 // to the hash table, but no barriers when rehashing inside GC.  It's a nasty
 // hack, but the important thing is that ScopeIterKey and ScopeIterVal need to
 // alias each other.
 void ScopeIterVal::staticAsserts() {
     static_assert(sizeof(ScopeIterVal) == sizeof(ScopeIterKey),
                   "ScopeIterVal must be same size of ScopeIterKey");
     static_assert(offsetof(ScopeIterVal, cur_) == offsetof(ScopeIterKey, cur_),
                   "ScopeIterVal.cur_ must alias ScopeIterKey.cur_");
-    static_assert(offsetof(ScopeIterVal, block_) == offsetof(ScopeIterKey, block_),
-                  "ScopeIterVal.block_ must alias ScopeIterKey.block_");
+    static_assert(offsetof(ScopeIterVal, staticScope_) == offsetof(ScopeIterKey, staticScope_),
+                  "ScopeIterVal.staticScope_ must alias ScopeIterKey.staticScope_");
 }
 
 /*****************************************************************************/
 
 namespace {
 
 /*
  * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a
@@ -1666,23 +1678,23 @@ DebugScopes::checkHashTablesAfterMovingG
      */
     JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtime);
     for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) {
         JS_ASSERT(!IsInsideNursery(rt, r.front().key().get()));
         JS_ASSERT(!IsInsideNursery(rt, r.front().value().get()));
     }
     for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) {
         JS_ASSERT(!IsInsideNursery(rt, r.front().key().cur()));
-        JS_ASSERT(!IsInsideNursery(rt, r.front().key().block()));
+        JS_ASSERT(!IsInsideNursery(rt, r.front().key().staticScope()));
         JS_ASSERT(!IsInsideNursery(rt, r.front().value().get()));
     }
     for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) {
         JS_ASSERT(!IsInsideNursery(rt, r.front().key()));
         JS_ASSERT(!IsInsideNursery(rt, r.front().value().cur_.get()));
-        JS_ASSERT(!IsInsideNursery(rt, r.front().value().block_.get()));
+        JS_ASSERT(!IsInsideNursery(rt, r.front().value().staticScope_.get()));
     }
 }
 #endif
 
 /*
  * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode
  * (in particular, JS_GetFrameScopeChain does not require debug mode). Since
  * DebugScopes::onPop* are only called in debug mode, this means we cannot
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -304,16 +304,47 @@ class NestedScopeObject : public ScopeOb
   protected:
     static const unsigned DEPTH_SLOT = 1;
 
   public:
     /* Return the abstract stack depth right before entering this nested scope. */
     uint32_t stackDepth() const {
         return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
     }
+
+    /*
+     * A refinement of enclosingScope that returns nullptr if the enclosing
+     * scope is not a NestedScopeObject.
+     */
+    inline NestedScopeObject *enclosingNestedScope() const;
+
+    // At compile-time it's possible for the scope chain to be null.
+    JSObject *enclosingScopeForStaticScopeIter() {
+        return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
+    }
+
+    void initEnclosingNestedScope(JSObject *obj) {
+        JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
+        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
+    }
+
+    /*
+     * The parser uses 'enclosingNestedScope' as the prev-link in the
+     * pc->staticScope stack. Note: in the case of hoisting, this prev-link will
+     * not ultimately be the same as enclosingNestedScope;
+     * initEnclosingNestedScope must be called separately in the
+     * emitter. 'reset' is just for asserting stackiness.
+     */
+    void initEnclosingNestedScopeFromParser(NestedScopeObject *prev) {
+        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
+    }
+
+    void resetEnclosingNestedScopeFromParser() {
+        setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
+    }
 };
 
 class WithObject : public NestedScopeObject
 {
     static const unsigned THIS_SLOT = 2;
 
     /* Use WithObject::object() instead. */
     JSObject *getProto() const;
@@ -376,24 +407,18 @@ class BlockObject : public NestedScopeOb
     }
 };
 
 class StaticBlockObject : public BlockObject
 {
   public:
     static StaticBlockObject *create(ExclusiveContext *cx);
 
-    /* See StaticScopeIter comment. */
-    JSObject *enclosingStaticScope() const {
-        AutoThreadSafeAccess ts(this);
-        return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
-    }
-
     /*
-     * A refinement of enclosingStaticScope that returns nullptr if the enclosing
+     * A refinement of enclosingScope that returns nullptr if the enclosing
      * static scope is a JSFunction.
      */
     inline StaticBlockObject *enclosingBlock() const;
 
     /*
      * Return whether this StaticBlockObject contains a variable stored at
      * the given stack depth (i.e., fp->base()[depth]).
      */
@@ -432,51 +457,32 @@ class StaticBlockObject : public BlockOb
         }
     }
 
     void setStackDepth(uint32_t depth) {
         JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined());
         initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth));
     }
 
-    void initEnclosingStaticScope(JSObject *obj) {
-        JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
-        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
-    }
-
     /*
      * 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());
         setSlotValue(i, PrivateValue(def));
     }
 
     frontend::Definition *maybeDefinitionParseNode(unsigned i) {
         Value v = slotValue(i);
         return v.isUndefined() ? nullptr
                                : reinterpret_cast<frontend::Definition *>(v.toPrivate());
     }
 
     /*
-     * The parser uses 'enclosingBlock' as the prev-link in the pc->blockChain
-     * stack. Note: in the case of hoisting, this prev-link will not ultimately
-     * be the same as enclosingBlock, initEnclosingStaticScope must be called
-     * separately in the emitter. 'reset' is just for asserting stackiness.
-     */
-    void initPrevBlockChainFromParser(StaticBlockObject *prev) {
-        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
-    }
-
-    void resetPrevBlockChainFromParser() {
-        setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
-    }
-
-    /*
      * 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 Shape *addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id,
@@ -510,17 +516,17 @@ class ClonedBlockObject : public BlockOb
 };
 
 template<XDRMode mode>
 bool
 XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope,
                      StaticBlockObject **objp);
 
 extern JSObject *
-CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> src);
+CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src);
 
 /*****************************************************************************/
 
 class ScopeIterKey;
 class ScopeIterVal;
 
 /*
  * A scope iterator describes the active scopes enclosing the current point of
@@ -542,17 +548,17 @@ class ScopeIter
 
   public:
     enum Type { Call, Block, With, StrictEvalScope };
 
   private:
     JSContext *cx;
     AbstractFramePtr frame_;
     RootedObject cur_;
-    Rooted<StaticBlockObject *> block_;
+    Rooted<NestedScopeObject *> staticScope_;
     Type type_;
     bool hasScopeObject_;
 
     void settle();
 
     /* ScopeIter does not have value semantics. */
     ScopeIter(const ScopeIter &si) MOZ_DELETE;
 
@@ -588,75 +594,78 @@ class ScopeIter
 
     ScopeIter &operator++();
 
     AbstractFramePtr frame() const { JS_ASSERT(!done()); return frame_; }
     Type type() const { JS_ASSERT(!done()); return type_; }
     bool hasScopeObject() const { JS_ASSERT(!done()); return hasScopeObject_; }
     ScopeObject &scope() const;
 
-    StaticBlockObject &staticBlock() const { JS_ASSERT(type() == Block); return *block_; }
+    StaticBlockObject &staticBlock() const {
+        JS_ASSERT(type() == Block);
+        return staticScope_->as<StaticBlockObject>();
+    }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 class ScopeIterKey
 {
     friend class ScopeIterVal;
 
     AbstractFramePtr frame_;
     JSObject *cur_;
-    StaticBlockObject *block_;
+    NestedScopeObject *staticScope_;
     ScopeIter::Type type_;
     bool hasScopeObject_;
 
   public:
     ScopeIterKey(const ScopeIter &si)
-      : frame_(si.frame()), cur_(si.cur_), block_(si.block_), type_(si.type_),
+      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
         hasScopeObject_(si.hasScopeObject_) {}
 
     AbstractFramePtr frame() const { return frame_; }
     JSObject *cur() const { return cur_; }
-    StaticBlockObject *block() const { return block_; }
+    NestedScopeObject *staticScope() const { return staticScope_; }
     ScopeIter::Type type() const { return type_; }
     bool hasScopeObject() const { return hasScopeObject_; }
     JSObject *enclosingScope() const { return cur_; }
     JSObject *&enclosingScope() { return cur_; }
 
     /* For use as hash policy */
     typedef ScopeIterKey Lookup;
     static HashNumber hash(ScopeIterKey si);
     static bool match(ScopeIterKey si1, ScopeIterKey si2);
     bool operator!=(const ScopeIterKey &other) const {
         return frame_ != other.frame_ ||
                cur_ != other.cur_ ||
-               block_ != other.block_ ||
+               staticScope_ != other.staticScope_ ||
                type_ != other.type_;
     }
     static void rekey(ScopeIterKey &k, const ScopeIterKey& newKey) {
         k = newKey;
     }
 };
 
 class ScopeIterVal
 {
     friend class ScopeIter;
     friend class DebugScopes;
 
     AbstractFramePtr frame_;
     RelocatablePtr<JSObject> cur_;
-    RelocatablePtr<StaticBlockObject> block_;
+    RelocatablePtr<NestedScopeObject> staticScope_;
     ScopeIter::Type type_;
     bool hasScopeObject_;
 
     static void staticAsserts();
 
   public:
     ScopeIterVal(const ScopeIter &si)
-      : frame_(si.frame()), cur_(si.cur_), block_(si.block_), type_(si.type_),
+      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
         hasScopeObject_(si.hasScopeObject_) {}
 
     AbstractFramePtr frame() const { return frame_; }
 };
 
 /*****************************************************************************/
 
 /*
@@ -846,16 +855,23 @@ namespace js {
 
 inline const Value &
 ScopeObject::aliasedVar(ScopeCoordinate sc)
 {
     JS_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
     return getSlot(sc.slot());
 }
 
+inline NestedScopeObject *
+NestedScopeObject::enclosingNestedScope() const
+{
+    JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
+    return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr;
+}
+
 inline StaticBlockObject *
 StaticBlockObject::enclosingBlock() const
 {
     JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
     return obj && obj->is<StaticBlockObject>() ? &obj->as<StaticBlockObject>() : nullptr;
 }
 
 #ifdef DEBUG