Bug 963879 - Part 1: Overhaul ScopeIter and StaticScopeIter to share iteration logic and to go through evals. (r=luke)
☠☠ backed out by a9355863e299 ☠ ☠
authorShu-yu Guo <shu@rfrn.org>
Wed, 14 Jan 2015 15:18:42 -0800
changeset 223872 b14f46d65f73af4afb5658093b34ccc941f97a9e
parent 223871 30796eecd360d4e46d1d01d256cf4b8c4458343c
child 223873 dd7b619dc15253c35c9c6e1dfc9fc4606550292b
push id54056
push usershu@rfrn.org
push dateWed, 14 Jan 2015 23:16:34 +0000
treeherdermozilla-inbound@4acf60209a94 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs963879
milestone38.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 963879 - Part 1: Overhaul ScopeIter and StaticScopeIter to share iteration logic and to go through evals. (r=luke)
js/src/builtin/Eval.cpp
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/jit/BaselineFrame.cpp
js/src/jit/CompileInfo.h
js/src/jit/JitFrames.cpp
js/src/jit/VMFunctions.cpp
js/src/jsapi.cpp
js/src/jsfriendapi.cpp
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jsscript.cpp
js/src/jsscript.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/HelperThreads.cpp
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack.cpp
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -302,16 +302,21 @@ EvalKernel(JSContext *cx, const CallArgs
                                              evalType == DIRECT_EVAL
                                              ? CALLED_FROM_JSOP_EVAL
                                              : NOT_CALLED_FROM_JSOP_EVAL);
 
         const char *introducerFilename = filename;
         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
             introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
+        RootedObject enclosing(cx);
+        if (evalType == DIRECT_EVAL)
+            enclosing = callerScript->innermostStaticScope(pc);
+        Rooted<StaticEvalObject *> staticScope(cx, StaticEvalObject::create(cx, enclosing));
+
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
@@ -321,21 +326,24 @@ EvalKernel(JSContext *cx, const CallArgs
             return false;
 
         const char16_t *chars = flatChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = flatChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, flatStr->length(), ownership);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, callerScript, options,
-                                                     srcBuf, flatStr, staticLevel);
+                                                     scopeobj, callerScript, staticScope,
+                                                     options, srcBuf, flatStr, staticLevel);
         if (!compiled)
             return false;
 
+        if (compiled->strict())
+            staticScope->setStrict();
+
         esg.setNewScript(compiled);
     }
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, thisv, ExecuteType(evalType),
                          NullFramePtr() /* evalInFrame */, args.rval().address());
 }
 
 bool
@@ -376,16 +384,21 @@ js::DirectEvalStringFromIon(JSContext *c
         uint32_t pcOffset;
         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
                                               &mutedErrors, CALLED_FROM_JSOP_EVAL);
 
         const char *introducerFilename = filename;
         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
             introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
+        RootedObject enclosing(cx, callerScript->innermostStaticScope(pc));
+        Rooted<StaticEvalObject *> staticScope(cx, StaticEvalObject::create(cx, enclosing));
+        if (!staticScope)
+            return false;
+
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(IsStrictEvalPC(pc));
@@ -395,21 +408,24 @@ js::DirectEvalStringFromIon(JSContext *c
             return false;
 
         const char16_t *chars = flatChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = flatChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, flatStr->length(), ownership);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, callerScript, options,
-                                                     srcBuf, flatStr, staticLevel);
+                                                     scopeobj, callerScript, staticScope,
+                                                     options, srcBuf, flatStr, staticLevel);
         if (!compiled)
             return false;
 
+        if (compiled->strict())
+            staticScope->setStrict();
+
         esg.setNewScript(compiled);
     }
 
     // Primitive 'this' values should have been filtered out by Ion. If boxed,
     // the calling frame cannot be updated to store the new object.
     MOZ_ASSERT(thisValue.isObject() || thisValue.isUndefined() || thisValue.isNull());
 
     return ExecuteKernel(cx, esg.script(), *scopeobj, thisValue, ExecuteType(DIRECT_EVAL),
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -205,16 +205,17 @@ frontend::CreateScriptSourceObject(Exclu
     }
 
     return sso;
 }
 
 JSScript *
 frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject scopeChain,
                         HandleScript evalCaller,
+                        Handle<StaticEvalObject *> evalStaticScope,
                         const ReadOnlyCompileOptions &options,
                         SourceBufferHolder &srcBuf,
                         JSString *source_ /* = nullptr */,
                         unsigned staticLevel /* = 0 */,
                         SourceCompressionTask *extraSct /* = nullptr */)
 {
     MOZ_ASSERT(srcBuf.get());
 
@@ -279,33 +280,33 @@ frontend::CompileScript(ExclusiveContext
 
     if (!parser.checkOptions())
         return nullptr;
 
     Directives directives(options.strictOption);
     GlobalSharedContext globalsc(cx, scopeChain, directives, options.extraWarningsOption);
 
     bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction();
-    Rooted<JSScript*> script(cx, JSScript::Create(cx, NullPtr(), savedCallerFun,
+    Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun,
                                                   options, staticLevel, sourceObject, 0,
                                                   srcBuf.length()));
     if (!script)
         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;
     MOZ_ASSERT_IF(globalScope, globalScope->isNative());
     MOZ_ASSERT_IF(globalScope, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalScope->getClass()));
 
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
     BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script,
                         /* lazyScript = */ js::NullPtr(), options.forEval,
-                        evalCaller, !!globalScope, options.lineno, emitterMode);
+                        evalCaller, evalStaticScope, !!globalScope, options.lineno, emitterMode);
     if (!bce.init())
         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;
 
@@ -511,16 +512,17 @@ frontend::CompileLazyFunction(JSContext 
         script->setDirectlyInsideEval();
     if (lazy->usesArgumentsApplyAndThis())
         script->setUsesArgumentsApplyAndThis();
     if (lazy->hasBeenCloned())
         script->setHasBeenCloned();
 
     BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
                         options.forEval, /* evalCaller = */ js::NullPtr(),
+                        /* evalStaticScope = */ js::NullPtr(),
                         /* hasGlobalScope = */ true, options.lineno,
                         BytecodeEmitter::LazyFunction);
     if (!bce.init())
         return false;
 
     return EmitFunctionScript(cx, &bce, pn->pn_body);
 }
 
@@ -643,16 +645,17 @@ CompileFunctionBody(JSContext *cx, Mutab
          * consumers of JS::CompileFunction, namely
          * EventListenerManager::CompileEventHandlerInternal, passes in a
          * nullptr environment. This compiled function is never used, but
          * instead is cloned immediately onto the right scope chain.
          */
         BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script,
                                /* lazyScript = */ js::NullPtr(), /* insideEval = */ false,
                                /* evalCaller = */ js::NullPtr(),
+                               /* evalStaticScope = */ js::NullPtr(),
                                fun->environment() && fun->environment()->is<GlobalObject>(),
                                options.lineno);
         if (!funbce.init())
             return false;
 
         if (!EmitFunctionScript(cx, &funbce, fn->pn_body))
             return false;
     } else {
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -12,23 +12,25 @@
 class JSLinearString;
 
 namespace js {
 
 class AutoNameVector;
 class LazyScript;
 class LifoAlloc;
 class ScriptSourceObject;
+class StaticEvalObject;
 struct SourceCompressionTask;
 
 namespace frontend {
 
 JSScript *
 CompileScript(ExclusiveContext *cx, LifoAlloc *alloc,
               HandleObject scopeChain, HandleScript evalCaller,
+              Handle<StaticEvalObject *> evalStaticScope,
               const ReadOnlyCompileOptions &options, SourceBufferHolder &srcBuf,
               JSString *source_ = nullptr, unsigned staticLevel = 0,
               SourceCompressionTask *extraSct = nullptr);
 
 bool
 CompileLazyFunction(JSContext *cx, Handle<LazyScript*> lazy, const char16_t *chars, size_t length);
 
 /*
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -111,26 +111,28 @@ struct LoopStmtInfo : public StmtInfoBCE
 };
 
 } // anonymous namespace
 
 BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent,
                                  Parser<FullParseHandler> *parser, SharedContext *sc,
                                  HandleScript script, Handle<LazyScript *> lazyScript,
                                  bool insideEval, HandleScript evalCaller,
+                                 Handle<StaticEvalObject *> staticEvalScope,
                                  bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode)
   : sc(sc),
     parent(parent),
     script(sc->context, script),
     lazyScript(sc->context, lazyScript),
     prolog(sc->context, lineNum),
     main(sc->context, lineNum),
     current(&main),
     parser(parser),
     evalCaller(evalCaller),
+    evalStaticScope(staticEvalScope),
     topStmt(nullptr),
     topScopeStmt(nullptr),
     staticScope(sc->context),
     atomIndices(sc->context),
     firstLine(lineNum),
     localsToFrameSlots_(sc->context),
     stackDepth(0), maxStackDepth(0),
     arrayCompDepth(0),
@@ -797,17 +799,20 @@ PushLoopStatement(BytecodeEmitter *bce, 
 static JSObject *
 EnclosingStaticScope(BytecodeEmitter *bce)
 {
     if (bce->staticScope)
         return bce->staticScope;
 
     if (!bce->sc->isFunctionBox()) {
         MOZ_ASSERT(!bce->parent);
-        return nullptr;
+
+        // Top-level eval scripts have a placeholder static scope so that
+        // StaticScopeIter may iterate through evals.
+        return bce->evalStaticScope;
     }
 
     return bce->sc->asFunctionBox()->function();
 }
 
 #ifdef DEBUG
 static bool
 AllLocalsAliased(StaticBlockObject &obj)
@@ -1586,18 +1591,18 @@ TryConvertFreeName(BytecodeEmitter *bce,
             hops++;
             if (funbox->function()->isNamedLambda())
                 hops++;
         }
         if (bce->script->directlyInsideEval())
             return false;
         RootedObject outerScope(bce->sc->context, bce->script->enclosingStaticScope());
         for (StaticScopeIter<CanGC> ssi(bce->sc->context, outerScope); !ssi.done(); ssi++) {
-            if (ssi.type() != StaticScopeIter<CanGC>::FUNCTION) {
-                if (ssi.type() == StaticScopeIter<CanGC>::BLOCK) {
+            if (ssi.type() != StaticScopeIter<CanGC>::Function) {
+                if (ssi.type() == StaticScopeIter<CanGC>::Block) {
                     // Use generic ops if a catch block is encountered.
                     return false;
                 }
                 if (ssi.hasDynamicScopeObject())
                     hops++;
                 continue;
             }
             RootedScript script(bce->sc->context, ssi.funScript());
@@ -5334,19 +5339,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->staticScope;
-                if (!scope && bce->sc->isFunctionBox())
-                    scope = bce->sc->asFunctionBox()->function();
+                JSObject *scope = EnclosingStaticScope(bce);
                 JSObject *source = bce->script->sourceObject();
                 fun->lazyScript()->setParent(scope, &source->as<ScriptSourceObject>());
             }
             if (bce->emittingRunOnceLambda)
                 fun->lazyScript()->setTreatAsRunOnce();
         } else {
             SharedContext *outersc = bce->sc;
 
@@ -5372,18 +5375,18 @@ EmitFunc(ExclusiveContext *cx, BytecodeE
                                                           funbox->bufStart, funbox->bufEnd));
             if (!script)
                 return false;
 
             script->bindings = funbox->bindings;
 
             uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
             BytecodeEmitter bce2(bce, bce->parser, funbox, script, /* lazyScript = */ js::NullPtr(),
-                                 bce->insideEval, bce->evalCaller, bce->hasGlobalScope, lineNum,
-                                 bce->emitterMode);
+                                 bce->insideEval, bce->evalCaller, bce->evalStaticScope,
+                                 bce->hasGlobalScope, lineNum, bce->emitterMode);
             if (!bce2.init())
                 return false;
 
             /* We measured the max scope depth when we parsed the function. */
             if (!EmitFunctionScript(cx, &bce2, pn->pn_body))
                 return false;
 
             if (funbox->usesArguments && funbox->usesApply && funbox->usesThis)
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -14,16 +14,19 @@
 #include "jscntxt.h"
 #include "jsopcode.h"
 #include "jsscript.h"
 
 #include "frontend/ParseMaps.h"
 #include "frontend/SourceNotes.h"
 
 namespace js {
+
+class StaticEvalObject;
+
 namespace frontend {
 
 class FullParseHandler;
 class ObjectBox;
 class ParseNode;
 template <typename ParseHandler> class Parser;
 class SharedContext;
 class TokenStream;
@@ -111,16 +114,18 @@ struct BytecodeEmitter
         {}
     };
     EmitSection prolog, main, *current;
 
     /* the parser */
     Parser<FullParseHandler> *const parser;
 
     HandleScript    evalCaller;     /* scripted caller info for eval and dbgapi */
+    Handle<StaticEvalObject *> evalStaticScope;
+                                   /* compile time scope for eval; does not imply stmt stack */
 
     StmtInfoBCE     *topStmt;       /* top of statement info stack */
     StmtInfoBCE     *topScopeStmt;  /* top lexical scope statement */
     Rooted<NestedScopeObject *> staticScope;
                                     /* compile time scope chain */
 
     OwnedAtomIndexMapPtr atomIndices; /* literals indexed for mapping */
     unsigned        firstLine;      /* first line, for JSScript::initFromEmitter */
@@ -192,17 +197,18 @@ struct BytecodeEmitter
     /*
      * Note that BytecodeEmitters are magic: they own the arena "top-of-stack"
      * space above their tempMark points. This means that you cannot alloc from
      * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter
      * destruction.
      */
     BytecodeEmitter(BytecodeEmitter *parent, Parser<FullParseHandler> *parser, SharedContext *sc,
                     HandleScript script, Handle<LazyScript *> lazyScript,
-                    bool insideEval, HandleScript evalCaller, bool hasGlobalScope,
+                    bool insideEval, HandleScript evalCaller,
+                    Handle<StaticEvalObject *> evalStaticScope, bool hasGlobalScope,
                     uint32_t lineNum, EmitterMode emitterMode = Normal);
     bool init();
     bool updateLocalsToFrameSlots();
 
     bool isAliasedName(ParseNode *pn);
 
     MOZ_ALWAYS_INLINE
     bool makeAtomIndex(JSAtom *atom, jsatomid *indexp) {
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -58,17 +58,17 @@ BaselineFrame::trace(JSTracer *trc, JitF
     JSScript *script = this->script();
     size_t nfixed = script->nfixed();
     size_t nlivefixed = script->nbodyfixed();
 
     if (nfixed != nlivefixed) {
         jsbytecode *pc;
         frameIterator.baselineScriptAndPc(nullptr, &pc);
 
-        NestedScopeObject *staticScope = script->getStaticScope(pc);
+        NestedScopeObject *staticScope = script->getStaticBlockScope(pc);
         while (staticScope && !staticScope->is<StaticBlockObject>())
             staticScope = staticScope->enclosingNestedScope();
 
         if (staticScope) {
             StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>();
             nlivefixed = blockObj.localOffset() + blockObj.numVariables();
         }
     }
--- a/js/src/jit/CompileInfo.h
+++ b/js/src/jit/CompileInfo.h
@@ -173,17 +173,17 @@ 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();
             MOZ_ASSERT(fun_->isTenured());
         }
 
-        osrStaticScope_ = osrPc ? script->getStaticScope(osrPc) : nullptr;
+        osrStaticScope_ = osrPc ? script->getStaticBlockScope(osrPc) : nullptr;
 
         nimplicit_ = StartArgSlot(script)                   /* scope chain and argument obj */
                    + (fun ? 1 : 0);                         /* this */
         nargs_ = fun ? fun->nargs() : 0;
         nbodyfixed_ = script->nbodyfixed();
         nlocals_ = script->nfixed();
         fixedLexicalBegin_ = script->fixedLexicalBegin();
         nstack_ = script->nslots() - script->nfixed();
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -580,17 +580,17 @@ HandleExceptionBaseline(JSContext *cx, c
         HandleClosingGeneratorReturn(cx, frame, pc, *unwoundScopeToPc, rfe, calledDebugEpilogue);
         return;
     }
 
     JSTryNote *tn = script->trynotes()->vector;
     JSTryNote *tnEnd = tn + script->trynotes()->length;
 
     uint32_t pcOffset = uint32_t(pc - script->main());
-    ScopeIter si(frame.baselineFrame(), pc, cx);
+    ScopeIter si(cx, frame.baselineFrame(), pc);
     for (; tn != tnEnd; ++tn) {
         if (pcOffset < tn->start)
             continue;
         if (pcOffset >= tn->start + tn->length)
             continue;
 
         // Skip if the try note's stack depth exceeds the frame's stack depth.
         // See the big comment in TryNoteIter::settle for more info.
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -767,18 +767,18 @@ DebugEpilogueOnBaselineReturn(JSContext 
 
     return true;
 }
 
 bool
 DebugEpilogue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool ok)
 {
     // Unwind scope chain to stack depth 0.
-    ScopeIter si(frame, pc, cx);
-    UnwindAllScopes(cx, si);
+    ScopeIter si(cx, frame, pc);
+    UnwindAllScopesInFrame(cx, si);
     jsbytecode *unwindPc = frame->script()->main();
     frame->setOverridePc(unwindPc);
 
     // If Debugger::onLeaveFrame returns |true| we have to return the frame's
     // return value. If it returns |false|, the debugger threw an exception.
     // In both cases we have to pop debug scopes.
     ok = Debugger::onLeaveFrame(cx, frame, ok);
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3471,16 +3471,35 @@ CreateScopeObjectsForScopeChain(JSContex
         dynamicEnclosingScope = dynamicWith;
     }
 
     dynamicScopeObj.set(dynamicEnclosingScope);
     staticScopeObj.set(staticEnclosingScope);
     return true;
 }
 
+static bool
+IsFunctionCloneable(HandleFunction fun, HandleObject dynamicScope)
+{
+    if (!fun->isInterpreted())
+        return true;
+
+    // If a function was compiled to be lexically nested inside some other
+    // script, we cannot clone it without breaking the compiler's assumptions.
+    JSObject *scope = fun->nonLazyScript()->enclosingStaticScope();
+    if (scope && (!scope->is<StaticEvalObject>() ||
+                  scope->as<StaticEvalObject>().isDirect() ||
+                  scope->as<StaticEvalObject>().isStrict()))
+    {
+        return false;
+    }
+
+    return !fun->nonLazyScript()->compileAndGo() || dynamicScope->is<GlobalObject>();
+}
+
 static JSObject *
 CloneFunctionObject(JSContext *cx, HandleObject funobj, HandleObject dynamicScope)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, dynamicScope);
     MOZ_ASSERT(dynamicScope);
     // Note that funobj can be in a different compartment.
@@ -3493,23 +3512,18 @@ CloneFunctionObject(JSContext *cx, Handl
     }
 
     RootedFunction fun(cx, &funobj->as<JSFunction>());
     if (fun->isInterpretedLazy()) {
         AutoCompartment ac(cx, funobj);
         if (!fun->getOrCreateScript(cx))
             return nullptr;
     }
-    /*
-     * If a function was compiled to be lexically nested inside some other
-     * script, we cannot clone it without breaking the compiler's assumptions.
-     */
-    if (fun->isInterpreted() && (fun->nonLazyScript()->enclosingStaticScope() ||
-        (fun->nonLazyScript()->compileAndGo() && !dynamicScope->is<GlobalObject>())))
-    {
+
+    if (!IsFunctionCloneable(fun, dynamicScope)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_CLONE_FUNOBJ_SCOPE);
         return nullptr;
     }
 
     if (fun->isBoundFunction()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CLONE_OBJECT);
         return nullptr;
     }
@@ -4014,17 +4028,18 @@ JS::Compile(JSContext *cx, HandleObject 
             SourceBufferHolder &srcBuf, MutableHandleScript script)
 {
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     AutoLastFrameCheck lfc(cx);
 
-    script.set(frontend::CompileScript(cx, &cx->tempLifoAlloc(), obj, NullPtr(), options, srcBuf));
+    script.set(frontend::CompileScript(cx, &cx->tempLifoAlloc(), obj, NullPtr(), NullPtr(),
+                                       options, srcBuf));
     return !!script;
 }
 
 bool
 JS::Compile(JSContext *cx, HandleObject obj, const ReadOnlyCompileOptions &options,
             const char16_t *chars, size_t length, MutableHandleScript script)
 {
     SourceBufferHolder srcBuf(chars, length, SourceBufferHolder::NoOwnership);
@@ -4414,17 +4429,17 @@ Evaluate(JSContext *cx, HandleObject obj
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
 
     AutoLastFrameCheck lfc(cx);
 
     options.setCompileAndGo(obj->is<GlobalObject>());
     SourceCompressionTask sct(cx);
     RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                    obj, NullPtr(), options,
+                                                    obj, NullPtr(), NullPtr(), options,
                                                     srcBuf, nullptr, 0, &sct));
     if (!script)
         return false;
 
     MOZ_ASSERT(script->getVersion() == options.version);
 
     bool result = Execute(cx, script, *obj,
                           options.noScriptRval ? nullptr : rval.address());
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -392,17 +392,17 @@ js::GetOutermostEnclosingFunctionOfScrip
     if (iter.done())
         return nullptr;
 
     if (!iter.isFunctionFrame())
         return nullptr;
 
     RootedFunction curr(cx, iter.callee(cx));
     for (StaticScopeIter<NoGC> i(curr); !i.done(); i++) {
-        if (i.type() == StaticScopeIter<NoGC>::FUNCTION)
+        if (i.type() == StaticScopeIter<NoGC>::Function)
             curr = &i.fun();
     }
     return curr;
 }
 
 JS_FRIEND_API(JSFunction *)
 js::DefineFunctionWithReserved(JSContext *cx, JSObject *objArg, const char *name, JSNative call,
                                unsigned nargs, unsigned attrs)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3971,17 +3971,17 @@ js_DumpInterpreterFrame(JSContext *cx, I
         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("staticScope", i.script()->getStaticScope(pc));
+            MaybeDumpObject("staticScope", i.script()->getStaticBlockScope(pc));
         }
         MaybeDumpValue("this", i.thisv(cx));
         if (!i.isJit()) {
             fprintf(stderr, "  rval: ");
             dumpValue(i.interpFrame()->returnValue());
             fputc('\n', stderr);
         }
 
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1687,17 +1687,17 @@ ExpressionDecompiler::getLocal(uint32_t 
     if (local < script->nbodyfixed()) {
         for (BindingIter bi(script); bi; bi++) {
             if (bi->kind() != Binding::ARGUMENT && !bi->aliased() && bi.frameIndex() == local)
                 return bi->name();
         }
 
         MOZ_CRASH("No binding");
     }
-    for (NestedScopeObject *chain = script->getStaticScope(pc);
+    for (NestedScopeObject *chain = script->getStaticBlockScope(pc);
          chain;
          chain = chain->enclosingNestedScope())
     {
         if (!chain->is<StaticBlockObject>())
             continue;
         StaticBlockObject &block = chain->as<StaticBlockObject>();
         if (local < block.localOffset())
             continue;
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -976,20 +976,21 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                     funEnclosingScope = function->nonLazyScript()->enclosingStaticScope();
                 else {
                     MOZ_ASSERT(function->isAsmJSNative());
                     JS_ReportError(cx, "AsmJS modules are not yet supported in XDR serialization.");
                     return false;
                 }
 
                 StaticScopeIter<NoGC> ssi(funEnclosingScope);
-                if (ssi.done() || ssi.type() == StaticScopeIter<NoGC>::FUNCTION) {
+
+                if (ssi.done() || ssi.type() == StaticScopeIter<NoGC>::Function) {
                     MOZ_ASSERT(ssi.done() == !fun);
                     funEnclosingScopeIndex = UINT32_MAX;
-                } else if (ssi.type() == StaticScopeIter<NoGC>::BLOCK) {
+                } else if (ssi.type() == StaticScopeIter<NoGC>::Block) {
                     funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.block());
                     MOZ_ASSERT(funEnclosingScopeIndex < i);
                 } else {
                     funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.staticWith());
                     MOZ_ASSERT(funEnclosingScopeIndex < i);
                 }
             }
 
@@ -3012,19 +3013,19 @@ js::CloneScript(JSContext *cx, HandleObj
                     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>::FUNCTION)
+                    if (ssi.done() || ssi.type() == StaticScopeIter<CanGC>::Function)
                         enclosingScope = fun;
-                    else if (ssi.type() == StaticScopeIter<CanGC>::BLOCK)
+                    else if (ssi.type() == StaticScopeIter<CanGC>::Block)
                         enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())];
                     else
                         enclosingScope = objects[FindScopeObjectIndex(src, ssi.staticWith())];
 
                     clone = CloneFunctionAndScript(cx, enclosingScope, innerFun);
                 }
             } else {
                 /*
@@ -3162,20 +3163,28 @@ bool
 js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone,
                         NewObjectKind newKind /* = GenericObject */)
 {
     MOZ_ASSERT(clone->isInterpreted());
 
     RootedScript script(cx, clone->nonLazyScript());
     MOZ_ASSERT(script);
     MOZ_ASSERT(script->compartment() == original->compartment());
-    MOZ_ASSERT_IF(script->compartment() != cx->compartment(),
-                  !script->enclosingStaticScope());
-
+
+    // The only scripts with enclosing static scopes that may be cloned across
+    // compartments are non-strict, indirect eval scripts, as their dynamic
+    // scope chains terminate in the global scope immediately.
     RootedObject scope(cx, script->enclosingStaticScope());
+    if (script->compartment() != cx->compartment() && scope) {
+        MOZ_ASSERT(!scope->as<StaticEvalObject>().isDirect() &&
+                   !scope->as<StaticEvalObject>().isStrict());
+        scope = StaticEvalObject::create(cx, NullPtr());
+        if (!scope)
+            return false;
+    }
 
     clone->mutableScript().init(nullptr);
 
     JSScript *cscript = CloneScript(cx, scope, clone, script, newKind);
     if (!cscript)
         return false;
 
     clone->setScript(cscript);
@@ -3464,17 +3473,17 @@ LazyScript::markChildren(JSTracer *trc)
 void
 LazyScript::finalize(FreeOp *fop)
 {
     if (table_)
         fop->free_(table_);
 }
 
 NestedScopeObject *
-JSScript::getStaticScope(jsbytecode *pc)
+JSScript::getStaticBlockScope(jsbytecode *pc)
 {
     MOZ_ASSERT(containsPC(pc));
 
     if (!hasBlockScopes())
         return nullptr;
 
     if (pc < main())
         return nullptr;
@@ -3518,16 +3527,32 @@ JSScript::getStaticScope(jsbytecode *pc)
         } else {
             top = mid;
         }
     }
 
     return blockChain;
 }
 
+JSObject *
+JSScript::innermostStaticScopeInScript(jsbytecode *pc)
+{
+    if (JSObject *scope = getStaticBlockScope(pc))
+        return scope;
+    return functionNonDelazifying();
+}
+
+JSObject *
+JSScript::innermostStaticScope(jsbytecode *pc)
+{
+    if (JSObject *scope = innermostStaticScopeInScript(pc))
+        return scope;
+    return enclosingStaticScope();
+}
+
 void
 JSScript::setArgumentsHasVarBinding()
 {
     argsHasVarBinding_ = true;
     needsArgsAnalysis_ = true;
 }
 
 void
@@ -3832,17 +3857,17 @@ LazyScript::hasUncompiledEnclosingScript
     JSFunction &fun = enclosingScope()->as<JSFunction>();
     return fun.isInterpreted() && (!fun.mutableScript() || !fun.nonLazyScript()->code());
 }
 
 uint32_t
 LazyScript::staticLevel(JSContext *cx) const
 {
     for (StaticScopeIter<NoGC> ssi(enclosingScope()); !ssi.done(); ssi++) {
-        if (ssi.type() == StaticScopeIter<NoGC>::FUNCTION)
+        if (ssi.type() == StaticScopeIter<NoGC>::Function)
             return ssi.funScript()->staticLevel() + 1;
     }
     return 1;
 }
 
 void
 JSScript::updateBaselineOrIonRaw(JSContext *maybecx)
 {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -1615,17 +1615,25 @@ class JSScript : public js::gc::TenuredC
     inline js::RegExpObject *getRegExp(jsbytecode *pc);
 
     const js::Value &getConst(size_t index) {
         js::ConstArray *arr = consts();
         MOZ_ASSERT(index < arr->length);
         return arr->vector[index];
     }
 
-    js::NestedScopeObject *getStaticScope(jsbytecode *pc);
+    js::NestedScopeObject *getStaticBlockScope(jsbytecode *pc);
+
+    // Returns the innermost static scope at pc if it falls within the extent
+    // of the script. Returns nullptr otherwise.
+    JSObject *innermostStaticScopeInScript(jsbytecode *pc);
+
+    // As innermostStaticScopeInScript, but returns the enclosing static scope
+    // if the innermost static scope falls without the extent of the script.
+    JSObject *innermostStaticScope(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/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -5904,42 +5904,47 @@ DebuggerFrame_setOnPop(JSContext *cx, un
  *
  * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env|
  * must be either |frame|'s DebugScopeObject, or some extension of that
  * environment; either way, |frame|'s scope is where newly declared variables
  * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|.
  */
 bool
 js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
-                  mozilla::Range<const char16_t> chars, const char *filename, unsigned lineno,
-                  MutableHandleValue rval)
+                  jsbytecode *pc, mozilla::Range<const char16_t> chars, const char *filename,
+                  unsigned lineno, MutableHandleValue rval)
 {
     assertSameCompartment(cx, env, frame);
     MOZ_ASSERT_IF(frame, thisv.get() == frame.thisValue());
+    MOZ_ASSERT_IF(frame, pc);
 
     MOZ_ASSERT(!IsPoisonedPtr(chars.start().get()));
 
     /*
      * NB: This function breaks the assumption that the compiler can see all
      * calls and properly compute a static level. In practice, any non-zero
      * static level will suffice.
+     *
+     * Pass in NullPtr for evalStaticScope, as ScopeIter should stop at any
+     * non-ScopeObject boundaries, and we are putting a DebugScopeProxy on the
+     * scope chain.
      */
     CompileOptions options(cx);
     options.setCompileAndGo(true)
            .setForEval(true)
            .setNoScriptRval(false)
            .setFileAndLine(filename, lineno)
            .setCanLazilyParse(false)
            .setIntroductionType("debugger eval")
            .maybeMakeStrictMode(frame ? frame.script()->strict() : false);
     RootedScript callerScript(cx, frame ? frame.script() : nullptr);
     SourceBufferHolder srcBuf(chars.start().get(), chars.length(), SourceBufferHolder::NoOwnership);
     RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, callerScript,
-                                                    options, srcBuf,
-                                                    /* source = */ nullptr,
+                                                    /* evalStaticScope = */ js::NullPtr(),
+                                                    options, srcBuf, /* source = */ nullptr,
                                                     /* staticLevel = */ frame ? 1 : 0));
     if (!script)
         return false;
 
     script->setActiveEval();
     ExecuteType type = !frame ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG;
     return ExecuteKernel(cx, script, *env, thisv, type, frame, rval.address());
 }
@@ -6067,22 +6072,23 @@ DebuggerGenericEval(JSContext *cx, const
             }
         }
         env = nenv;
     }
 
     /* Run the code and produce the completion value. */
     RootedValue rval(cx);
     AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
+    jsbytecode *pc = iter ? iter->pc() : nullptr;
     AutoStableStringChars stableChars(cx);
     if (!stableChars.initTwoByte(cx, flat))
         return false;
 
     mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
-    bool ok = EvaluateInEnv(cx, env, thisv, frame, chars, url ? url : "debugger eval code",
+    bool ok = EvaluateInEnv(cx, env, thisv, frame, pc, chars, url ? url : "debugger eval code",
                             lineNumber, &rval);
     return dbg->receiveCompletionValue(ac, ok, rval, vp);
 }
 
 static bool
 DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME_ITER(cx, argc, vp, "eval", args, thisobj, _, iter);
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -907,16 +907,16 @@ Debugger::onLogAllocationSite(JSContext 
     GlobalObject::DebuggerVector *dbgs = cx->global()->getDebuggers();
     if (!dbgs || dbgs->empty())
         return true;
     return Debugger::slowPathOnLogAllocationSite(cx, frame, when, *dbgs);
 }
 
 extern bool
 EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
-              mozilla::Range<const char16_t> chars, const char *filename, unsigned lineno,
-              MutableHandleValue rval);
+              jsbytecode *pc, mozilla::Range<const char16_t> chars, const char *filename,
+              unsigned lineno, MutableHandleValue rval);
 
 bool ReportObjectRequired(JSContext *cx);
 
 } /* namespace js */
 
 #endif /* vm_Debugger_h */
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1185,17 +1185,17 @@ HelperThread::handleParseWorkload()
 
     {
         AutoUnlockHelperThreadState unlock;
         PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
                                               parseTask->exclusiveContextGlobal->runtimeFromAnyThread());
         SourceBufferHolder srcBuf(parseTask->chars, parseTask->length,
                                   SourceBufferHolder::NoOwnership);
         parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
-                                                    NullPtr(), NullPtr(),
+                                                    NullPtr(), NullPtr(), NullPtr(),
                                                     parseTask->options,
                                                     srcBuf);
     }
 
     // The callback is invoked while we are still off the main thread.
     parseTask->callback(parseTask, parseTask->callbackData);
 
     // FinishOffThreadScript will need to be called on the script to
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -881,52 +881,51 @@ js::EnterWithOperation(JSContext *cx, Ab
 static void
 PopScope(JSContext *cx, ScopeIter &si)
 {
     switch (si.type()) {
       case ScopeIter::Block:
         if (cx->compartment()->isDebuggee())
             DebugScopes::onPopBlock(cx, si);
         if (si.staticBlock().needsClone())
-            si.frame().popBlock(cx);
+            si.initialFrame().popBlock(cx);
         break;
       case ScopeIter::With:
-        si.frame().popWith(cx);
+        si.initialFrame().popWith(cx);
         break;
       case ScopeIter::Call:
-      case ScopeIter::StrictEvalScope:
+      case ScopeIter::Eval:
         break;
     }
 }
 
 // Unwind scope chain and iterator to match the static scope corresponding to
 // the given bytecode position.
 void
 js::UnwindScope(JSContext *cx, ScopeIter &si, jsbytecode *pc)
 {
-    if (si.done())
+    if (!si.withinInitialFrame())
         return;
 
-    Rooted<NestedScopeObject *> staticScope(cx, si.frame().script()->getStaticScope(pc));
-
-    for (; si.staticScope() != staticScope; ++si)
+    RootedObject staticScope(cx, si.initialFrame().script()->innermostStaticScope(pc));
+    for (; si.maybeStaticScope() != staticScope; ++si)
         PopScope(cx, si);
 }
 
 // Unwind all scopes. This is needed because block scopes may cover the
 // first bytecode at a script's main(). e.g.,
 //
 //     function f() { { let i = 0; } }
 //
 // will have no pc location distinguishing the first block scope from the
 // outermost function scope.
 void
-js::UnwindAllScopes(JSContext *cx, ScopeIter &si)
+js::UnwindAllScopesInFrame(JSContext *cx, ScopeIter &si)
 {
-    for (; !si.done(); ++si)
+    for (; si.withinInitialFrame(); ++si)
         PopScope(cx, si);
 }
 
 // Compute the pc needed to unwind the scope to the beginning of a try
 // block. We cannot unwind to *after* the JSOP_TRY, because that might be the
 // first opcode of an inner scope, with the same problem as above. e.g.,
 //
 // try { { let x; } }
@@ -937,24 +936,24 @@ jsbytecode *
 js::UnwindScopeToTryPc(JSScript *script, JSTryNote *tn)
 {
     return script->main() + tn->start - js_CodeSpec[JSOP_TRY].length;
 }
 
 static void
 ForcedReturn(JSContext *cx, ScopeIter &si, InterpreterRegs &regs)
 {
-    UnwindAllScopes(cx, si);
+    UnwindAllScopesInFrame(cx, si);
     regs.setToEndOfScript();
 }
 
 static void
 ForcedReturn(JSContext *cx, InterpreterRegs &regs)
 {
-    ScopeIter si(regs.fp(), regs.pc, cx);
+    ScopeIter si(cx, regs.fp(), regs.pc);
     ForcedReturn(cx, si, regs);
 }
 
 void
 js::UnwindForUncatchableException(JSContext *cx, const InterpreterRegs &regs)
 {
     /* c.f. the regular (catchable) TryNoteIter loop in HandleError. */
     for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
@@ -1033,17 +1032,17 @@ enum HandleErrorContinuation
     FinallyContinuation
 };
 
 static HandleErrorContinuation
 HandleError(JSContext *cx, InterpreterRegs &regs)
 {
     MOZ_ASSERT(regs.fp()->script()->containsPC(regs.pc));
 
-    ScopeIter si(regs.fp(), regs.pc, cx);
+    ScopeIter si(cx, regs.fp(), regs.pc);
     bool ok = false;
 
   again:
     if (cx->isExceptionPending()) {
         /* Call debugger throw hooks. */
         RootedValue exception(cx);
         if (!cx->getPendingException(&exception))
             goto again;
@@ -3357,31 +3356,31 @@ CASE(JSOP_PUSHBLOCKSCOPE)
 }
 END_CASE(JSOP_PUSHBLOCKSCOPE)
 
 CASE(JSOP_POPBLOCKSCOPE)
 {
 #ifdef DEBUG
     // Pop block from scope chain.
     MOZ_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK);
-    NestedScopeObject *scope = script->getStaticScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH);
+    NestedScopeObject *scope = script->getStaticBlockScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH);
     MOZ_ASSERT(scope && scope->is<StaticBlockObject>());
     StaticBlockObject &blockObj = scope->as<StaticBlockObject>();
     MOZ_ASSERT(blockObj.needsClone());
 #endif
 
     // Pop block from scope chain.
     REGS.fp()->popBlock(cx);
 }
 END_CASE(JSOP_POPBLOCKSCOPE)
 
 CASE(JSOP_DEBUGLEAVEBLOCK)
 {
-    MOZ_ASSERT(script->getStaticScope(REGS.pc));
-    MOZ_ASSERT(script->getStaticScope(REGS.pc)->is<StaticBlockObject>());
+    MOZ_ASSERT(script->getStaticBlockScope(REGS.pc));
+    MOZ_ASSERT(script->getStaticBlockScope(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()->isDebuggee()))
         DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc);
 }
 END_CASE(JSOP_DEBUGLEAVEBLOCK)
@@ -4097,17 +4096,17 @@ js::ReportUninitializedLexical(JSContext
                 name = bi->name();
                 break;
             }
         }
 
         // Failing that, it must be a block-local let.
         if (!name) {
             // Skip to the right scope.
-            Rooted<NestedScopeObject *> scope(cx, script->getStaticScope(pc));
+            Rooted<NestedScopeObject *> scope(cx, script->getStaticBlockScope(pc));
             MOZ_ASSERT(scope && scope->is<StaticBlockObject>());
             Rooted<StaticBlockObject *> block(cx, &scope->as<StaticBlockObject>());
             while (slot < block->localOffset())
                 block = &block->enclosingNestedScope()->as<StaticBlockObject>();
 
             // Translate the frame slot to the block slot, then find the name
             // of the slot.
             uint32_t blockSlot = block->localIndexToSlot(slot);
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -241,17 +241,17 @@ HasInstance(JSContext *cx, HandleObject 
 
 // Unwind scope chain and iterator to match the static scope corresponding to
 // the given bytecode position.
 extern void
 UnwindScope(JSContext *cx, ScopeIter &si, jsbytecode *pc);
 
 // Unwind all scopes.
 extern void
-UnwindAllScopes(JSContext *cx, ScopeIter &si);
+UnwindAllScopesInFrame(JSContext *cx, ScopeIter &si);
 
 // Compute the pc needed to unwind the scope to the beginning of the block
 // pointed to by the try note.
 extern jsbytecode *
 UnwindScopeToTryPc(JSScript *script, JSTryNote *tn);
 
 /*
  * Unwind for an uncatchable exception. This means not running finalizers, etc;
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -81,79 +81,95 @@ StaticScopeIter<allowGC>::operator++(int
     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;
     }
-    MOZ_ASSERT_IF(obj, obj->template is<NestedScopeObject>() || obj->template is<JSFunction>());
+    MOZ_ASSERT_IF(obj, obj->template is<NestedScopeObject>() ||
+                       obj->template is<StaticEvalObject>() ||
+                       obj->template is<JSFunction>());
     MOZ_ASSERT_IF(onNamedLambda, obj->template is<JSFunction>());
 }
 
 template <AllowGC allowGC>
 inline bool
 StaticScopeIter<allowGC>::hasDynamicScopeObject() const
 {
     return obj->template is<StaticBlockObject>()
            ? obj->template as<StaticBlockObject>().needsClone()
-           : (obj->template is<StaticWithObject>() ||
-              obj->template as<JSFunction>().isHeavyweight());
+           : (obj->template is<StaticEvalObject>()
+              ? obj->template as<StaticEvalObject>().isStrict()
+              : (obj->template is<StaticWithObject>() ||
+                 obj->template as<JSFunction>().isHeavyweight()));
 }
 
 template <AllowGC allowGC>
 inline Shape *
 StaticScopeIter<allowGC>::scopeShape() const
 {
     MOZ_ASSERT(hasDynamicScopeObject());
-    MOZ_ASSERT(type() != NAMED_LAMBDA);
-    if (type() == BLOCK)
+    MOZ_ASSERT(type() != NamedLambda && type() != Eval);
+    if (type() == Block)
         return block().lastProperty();
     return funScript()->callObjShape();
 }
 
 template <AllowGC allowGC>
 inline typename StaticScopeIter<allowGC>::Type
 StaticScopeIter<allowGC>::type() const
 {
     if (onNamedLambda)
-        return NAMED_LAMBDA;
+        return NamedLambda;
     return obj->template is<StaticBlockObject>()
-           ? BLOCK
-           : (obj->template is<StaticWithObject>() ? WITH : FUNCTION);
+           ? Block
+           : (obj->template is<StaticWithObject>()
+              ? With
+              : (obj->template is<StaticEvalObject>()
+                 ? Eval
+                 : Function));
 }
 
 template <AllowGC allowGC>
 inline StaticBlockObject &
 StaticScopeIter<allowGC>::block() const
 {
-    MOZ_ASSERT(type() == BLOCK);
+    MOZ_ASSERT(type() == Block);
     return obj->template as<StaticBlockObject>();
 }
 
 template <AllowGC allowGC>
 inline StaticWithObject &
 StaticScopeIter<allowGC>::staticWith() const
 {
-    MOZ_ASSERT(type() == WITH);
+    MOZ_ASSERT(type() == With);
     return obj->template as<StaticWithObject>();
 }
 
 template <AllowGC allowGC>
+inline StaticEvalObject &
+StaticScopeIter<allowGC>::eval() const
+{
+    MOZ_ASSERT(type() == Eval);
+    return obj->template as<StaticEvalObject>();
+}
+
+template <AllowGC allowGC>
 inline JSScript *
 StaticScopeIter<allowGC>::funScript() const
 {
-    MOZ_ASSERT(type() == FUNCTION);
+    MOZ_ASSERT(type() == Function);
     return obj->template as<JSFunction>().nonLazyScript();
 }
 
 template <AllowGC allowGC>
 inline JSFunction &
 StaticScopeIter<allowGC>::fun() const
 {
-    MOZ_ASSERT(type() == FUNCTION);
+    MOZ_ASSERT(type() == Function);
     return obj->template as<JSFunction>();
 }
 
 }  /* namespace js */
 
 #endif /* vm_ScopeObject_inl_h */
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -29,32 +29,21 @@ using namespace js::types;
 
 using mozilla::PodZero;
 
 typedef Rooted<ArgumentsObject *> RootedArgumentsObject;
 typedef MutableHandle<ArgumentsObject *> MutableHandleArgumentsObject;
 
 /*****************************************************************************/
 
-static JSObject *
-InnermostStaticScope(JSScript *script, jsbytecode *pc)
-{
-    MOZ_ASSERT(script->containsPC(pc));
-    MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
-
-    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));
+    MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
+    StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
     while (true) {
         MOZ_ASSERT(!ssi.done());
         if (ssi.hasDynamicScopeObject()) {
             if (!hops)
                 break;
             hops--;
         }
@@ -108,27 +97,28 @@ js::ScopeCoordinateName(ScopeCoordinateN
     if (!JSID_IS_ATOM(id))
         return script->runtimeFromAnyThread()->commonNames->empty;
     return JSID_TO_ATOM(id)->asPropertyName();
 }
 
 JSScript *
 js::ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc)
 {
-    StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc));
+    MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPECOORD);
+    StaticScopeIter<NoGC> ssi(script->innermostStaticScopeInScript(pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
     while (true) {
         if (ssi.hasDynamicScopeObject()) {
             if (!hops)
                 break;
             hops--;
         }
         ssi++;
     }
-    if (ssi.type() != StaticScopeIter<NoGC>::FUNCTION)
+    if (ssi.type() != StaticScopeIter<NoGC>::Function)
         return nullptr;
     return ssi.funScript();
 }
 
 /*****************************************************************************/
 
 void
 ScopeObject::setEnclosingScope(HandleObject obj)
@@ -616,16 +606,45 @@ const Class DynamicWithObject::class_ = 
         with_DeleteGeneric,
         nullptr, nullptr,    /* watch/unwatch */
         nullptr,             /* getElements */
         nullptr,             /* enumerate (native enumeration of target doesn't work) */
         with_ThisObject,
     }
 };
 
+/* static */ StaticEvalObject *
+StaticEvalObject::create(JSContext *cx, HandleObject enclosing)
+{
+    RootedTypeObject type(cx, cx->getNewType(&class_, TaggedProto(nullptr)));
+    if (!type)
+        return nullptr;
+
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
+                                                      cx->global(), nullptr, FINALIZE_KIND,
+                                                      BaseShape::DELEGATE));
+    if (!shape)
+        return nullptr;
+
+    RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND,
+                                                                  gc::TenuredHeap, shape, type)));
+    if (!obj)
+        return nullptr;
+
+    obj->as<StaticEvalObject>().initEnclosingNestedScope(enclosing);
+    obj->setFixedSlot(STRICT_SLOT, BooleanValue(false));
+    return &obj->as<StaticEvalObject>();
+}
+
+const Class StaticEvalObject::class_ = {
+    "StaticEval",
+    JSCLASS_HAS_RESERVED_SLOTS(StaticEvalObject::RESERVED_SLOTS) |
+    JSCLASS_IS_ANONYMOUS
+};
+
 /*****************************************************************************/
 
 ClonedBlockObject *
 ClonedBlockObject::create(JSContext *cx, Handle<StaticBlockObject *> block, AbstractFramePtr frame)
 {
     assertSameCompartment(cx, frame);
     MOZ_ASSERT(block->getClass() == &BlockObject::class_);
 
@@ -1059,224 +1078,191 @@ const Class UninitializedLexicalObject::
 // 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
+ScopeIter::ScopeIter(JSContext *cx, const ScopeIter &si
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-  : cx(cx),
-    frame_(si.frame_),
-    cur_(cx, si.cur_),
-    staticScope_(cx, si.staticScope_),
-    type_(si.type_),
-    hasScopeObject_(si.hasScopeObject_)
+  : ssi_(cx, si.ssi_),
+    scope_(cx, si.scope_),
+    frame_(si.frame_)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(JSObject &enclosingScope, JSContext *cx
+ScopeIter::ScopeIter(JSContext *cx, JSObject *scope, JSObject *staticScope
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-  : cx(cx),
-    frame_(NullFramePtr()),
-    cur_(cx, &enclosingScope),
-    staticScope_(cx, nullptr),
-    type_(Type(-1))
+  : ssi_(cx, staticScope),
+    scope_(cx, scope),
+    frame_(NullFramePtr())
 {
+    settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
-ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
+ScopeIter::ScopeIter(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
-  : cx(cx),
-    frame_(frame),
-    cur_(cx, frame.scopeChain()),
-    staticScope_(cx, frame.script()->getStaticScope(pc))
+  : ssi_(cx, frame.script()->innermostStaticScope(pc)),
+    scope_(cx, frame.scopeChain()),
+    frame_(frame)
 {
     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_),
-    staticScope_(cx, val.staticScope_),
-    type_(val.type_),
-    hasScopeObject_(val.hasScopeObject_)
+void
+ScopeIter::settle()
 {
-    assertSameCompartment(cx, val.frame_);
-    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    // For named lambdas, DeclEnvObject scopes are always attached to their
+    // CallObjects. Skip it here, as they are special cased in users of
+    // ScopeIter.
+    if (!ssi_.done() && ssi_.type() == StaticScopeIter<CanGC>::NamedLambda)
+        ssi_++;
+
+    // Check for trying to iterate a heavyweight function frame before
+    // the prologue has created the CallObject, in which case we have to skip.
+    if (frame_ && frame_.isNonEvalFunctionFrame() &&
+        frame_.fun()->isHeavyweight() && !frame_.hasCallObj())
+    {
+        MOZ_ASSERT(ssi_.type() == StaticScopeIter<CanGC>::Function);
+        ssi_++;
+    }
+
+    // Check if we have left the extent of the initial frame. This check must
+    // come between the named lambda check above and the direct eval check
+    // below. We must check the static scope after skipping the named lambda,
+    // as an SSI settled on a named lambda scope has no static scope. We must
+    // check the static scope before skipping the direct eval, as by then we
+    // would have already left the frame.
+    if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
+        frame_ = NullFramePtr();
+
+#ifdef DEBUG
+    if (!ssi_.done() && hasScopeObject()) {
+        switch (ssi_.type()) {
+          case StaticScopeIter<CanGC>::Function:
+            MOZ_ASSERT(scope_->as<CallObject>().callee().nonLazyScript() == ssi_.funScript());
+            break;
+          case StaticScopeIter<CanGC>::Block:
+            MOZ_ASSERT(scope_->as<ClonedBlockObject>().staticBlock() == staticBlock());
+            break;
+          case StaticScopeIter<CanGC>::With:
+            MOZ_ASSERT(scope_->as<DynamicWithObject>().staticScope() == &staticWith());
+            break;
+          case StaticScopeIter<CanGC>::Eval:
+            MOZ_ASSERT(scope_->as<CallObject>().isForEval());
+            break;
+          case StaticScopeIter<CanGC>::NamedLambda:
+            MOZ_CRASH("named lambda static scopes should have been skipped");
+        }
+    }
+#endif
+}
+
+ScopeIter &
+ScopeIter::operator++()
+{
+    if (hasScopeObject()) {
+        scope_ = &scope_->as<ScopeObject>().enclosingScope();
+        if (scope_->is<DeclEnvObject>())
+            scope_ = &scope_->as<DeclEnvObject>().enclosingScope();
+    }
+
+    ssi_++;
+    settle();
+
+    return *this;
+}
+
+ScopeIter::Type
+ScopeIter::type() const
+{
+    MOZ_ASSERT(!done());
+
+    switch (ssi_.type()) {
+      case StaticScopeIter<CanGC>::Function:
+        return Call;
+      case StaticScopeIter<CanGC>::Block:
+        return Block;
+      case StaticScopeIter<CanGC>::With:
+        return With;
+      case StaticScopeIter<CanGC>::Eval:
+        return Eval;
+      case StaticScopeIter<CanGC>::NamedLambda:
+        MOZ_CRASH("named lambda static scopes should have been skipped");
+      default:
+        MOZ_CRASH("bad SSI type");
+    }
 }
 
 ScopeObject &
 ScopeIter::scope() const
 {
     MOZ_ASSERT(hasScopeObject());
-    return cur_->as<ScopeObject>();
+    return scope_->as<ScopeObject>();
 }
 
-ScopeIter &
-ScopeIter::operator++()
-{
-    MOZ_ASSERT(!done());
-    switch (type_) {
-      case Call:
-        if (hasScopeObject_) {
-            cur_ = &cur_->as<CallObject>().enclosingScope();
-            if (CallObjectLambdaName(*frame_.fun()))
-                cur_ = &cur_->as<DeclEnvObject>().enclosingScope();
-        }
-        frame_ = NullFramePtr();
-        break;
-      case Block:
-        MOZ_ASSERT(staticScope_ && staticScope_->is<StaticBlockObject>());
-        staticScope_ = staticScope_->as<StaticBlockObject>().enclosingNestedScope();
-        if (hasScopeObject_)
-            cur_ = &cur_->as<ClonedBlockObject>().enclosingScope();
-        settle();
-        break;
-      case With:
-        MOZ_ASSERT(staticScope_ && staticScope_->is<StaticWithObject>());
-        MOZ_ASSERT(hasScopeObject_);
-        staticScope_ = staticScope_->as<StaticWithObject>().enclosingNestedScope();
-        cur_ = &cur_->as<DynamicWithObject>().enclosingScope();
-        settle();
-        break;
-      case StrictEvalScope:
-        if (hasScopeObject_)
-            cur_ = &cur_->as<CallObject>().enclosingScope();
-        frame_ = NullFramePtr();
-        break;
-    }
-    return *this;
-}
-
-void
-ScopeIter::settle()
+JSObject *
+ScopeIter::maybeStaticScope() const
 {
-    /*
-     * 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:
-     *  - non-heavyweight functions;
-     *  - non-strict direct eval.
-     *  - heavyweight functions observed before the prologue has finished;
-     * In all cases, cur_ can already be pointing into an enclosing frame's
-     * scope chain. Furthermore, in the first two cases: even if cur_ points
-     * into an enclosing frame's scope chain, the current frame may still have
-     * 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 (staticScope_) {
-            // If staticScope_ were a StaticWithObject, the function would be
-            // heavyweight.
-            MOZ_ASSERT(staticScope_->is<StaticBlockObject>());
-            type_ = Block;
-            hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone();
-        } else {
-            type_ = Call;
-            hasScopeObject_ = false;
-        }
-    } else if (frame_.isNonStrictDirectEvalFrame() && cur_ == frame_.evalPrevScopeChain(cx)) {
-        if (staticScope_) {
-            MOZ_ASSERT(staticScope_->is<StaticBlockObject>());
-            MOZ_ASSERT(!staticScope_->as<StaticBlockObject>().needsClone());
-            type_ = Block;
-            hasScopeObject_ = false;
-        } else {
-            frame_ = NullFramePtr();
-        }
-    } else if (frame_.isNonEvalFunctionFrame() && !frame_.hasCallObj()) {
-        MOZ_ASSERT(cur_ == frame_.fun()->environment());
-        frame_ = NullFramePtr();
-    } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) {
-        MOZ_ASSERT(cur_ == frame_.evalPrevScopeChain(cx));
-        frame_ = NullFramePtr();
-    } else if (staticScope_) {
-        if (staticScope_->is<StaticWithObject>()) {
-            MOZ_ASSERT(cur_);
-            MOZ_ASSERT(cur_->as<DynamicWithObject>().staticScope() == staticScope_);
-            type_ = With;
-            hasScopeObject_ = true;
-        } else {
-            type_ = Block;
-            hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone();
-            MOZ_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;
-        MOZ_ASSERT_IF(type_ == Call, callobj.callee().nonLazyScript() == frame_.script());
-    } else {
-        MOZ_ASSERT(!cur_->is<ScopeObject>() ||
-                   (cur_->is<DynamicWithObject>() &&
-                    !cur_->as<DynamicWithObject>().isSyntactic()));
-        MOZ_ASSERT(frame_.isGlobalFrame() || frame_.isDebuggerEvalFrame());
-        frame_ = NullFramePtr();
+    if (ssi_.done())
+        return nullptr;
+
+    switch (ssi_.type()) {
+      case StaticScopeIter<CanGC>::Function:
+        return &fun();
+      case StaticScopeIter<CanGC>::Block:
+        return &staticBlock();
+      case StaticScopeIter<CanGC>::With:
+        return &staticWith();
+      case StaticScopeIter<CanGC>::Eval:
+        return &staticEval();
+      case StaticScopeIter<CanGC>::NamedLambda:
+        MOZ_CRASH("named lambda static scopes should have been skipped");
+      default:
+        MOZ_CRASH("bad SSI type");
     }
 }
 
 /* static */ HashNumber
-ScopeIterKey::hash(ScopeIterKey si)
+MissingScopeKey::hash(MissingScopeKey sk)
 {
-    /* hasScopeObject_ is determined by the other fields. */
-    return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.staticScope_) ^ si.type_;
+    return size_t(sk.frame_.raw()) ^ size_t(sk.staticScope_);
 }
 
 /* static */ bool
-ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2)
+MissingScopeKey::match(MissingScopeKey sk1, MissingScopeKey sk2)
 {
-    /* hasScopeObject_ is determined by the other fields. */
-    return si1.frame_ == si2.frame_ &&
-           (!si1.frame_ ||
-            (si1.cur_   == si2.cur_   &&
-             si1.staticScope_ == si2.staticScope_ &&
-             si1.type_  == si2.type_));
+    return sk1.frame_ == sk2.frame_ && sk1.staticScope_ == sk2.staticScope_;
 }
 
 void
-ScopeIterVal::sweep()
+LiveScopeVal::sweep()
 {
-    /* We need to update possibly moved pointers on sweep. */
-    MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalizedFromAnyThread(cur_.unsafeGet()));
     if (staticScope_)
         MOZ_ALWAYS_FALSE(IsObjectAboutToBeFinalizedFromAnyThread(staticScope_.unsafeGet()));
 }
 
 // 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, staticScope_) == offsetof(ScopeIterKey, staticScope_),
-                  "ScopeIterVal.staticScope_ must alias ScopeIterKey.staticScope_");
+void
+LiveScopeVal::staticAsserts()
+{
+    static_assert(sizeof(LiveScopeVal) == sizeof(MissingScopeKey),
+                  "LiveScopeVal must be same size of MissingScopeKey");
+    static_assert(offsetof(LiveScopeVal, staticScope_) == offsetof(MissingScopeKey, staticScope_),
+                  "LiveScopeVal.staticScope_ must alias MissingScopeKey.staticScope_");
 }
 
 /*****************************************************************************/
 
 namespace {
 
 /*
  * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a
@@ -1331,17 +1317,17 @@ class DebugScopeProxy : public BaseProxy
      *  - ACCESS_LOST      if the value has been lost to the debugger
      */
     bool handleUnaliasedAccess(JSContext *cx, Handle<DebugScopeObject*> debugScope,
                                Handle<ScopeObject*> scope, jsid id, Action action,
                                MutableHandleValue vp, AccessResult *accessResult) const
     {
         MOZ_ASSERT(&debugScope->scope() == scope);
         *accessResult = ACCESS_GENERIC;
-        ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope);
+        LiveScopeVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope);
 
         /* Handle unaliased formals, vars, lets, and consts at function scope. */
         if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
             CallObject &callobj = scope->as<CallObject>();
             RootedScript script(cx, callobj.callee().nonLazyScript());
             if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx))
                 return false;
 
@@ -1510,17 +1496,17 @@ class DebugScopeProxy : public BaseProxy
      * Create a missing arguments object. If the function returns true but
      * argsObj is null, it means the scope is dead.
      */
     static bool createMissingArguments(JSContext *cx, ScopeObject &scope,
                                        MutableHandleArgumentsObject argsObj)
     {
         argsObj.set(nullptr);
 
-        ScopeIterVal *maybeScope = DebugScopes::hasLiveScope(scope);
+        LiveScopeVal *maybeScope = DebugScopes::hasLiveScope(scope);
         if (!maybeScope)
             return true;
 
         argsObj.set(ArgumentsObject::createUnexpected(cx, maybeScope->frame()));
         return !!argsObj;
     }
 
   public:
@@ -1890,50 +1876,23 @@ js_IsDebugScopeSlow(ProxyObject *proxy)
 /* static */ MOZ_ALWAYS_INLINE void
 DebugScopes::proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map,
                                            const PreBarrieredObject &key)
 {
     if (key && IsInsideNursery(key))
         rt->gc.storeBuffer.putGeneric(UnbarrieredRef(map, key.get()));
 }
 
-class DebugScopes::MissingScopesRef : public gc::BufferableRef
-{
-    MissingScopeMap *map;
-    ScopeIterKey key;
-
-  public:
-    MissingScopesRef(MissingScopeMap *m, const ScopeIterKey &k) : map(m), key(k) {}
-
-    void mark(JSTracer *trc) {
-        ScopeIterKey prior = key;
-        MissingScopeMap::Ptr p = map->lookup(key);
-        if (!p)
-            return;
-        trc->setTracingLocation(&const_cast<ScopeIterKey &>(p->key()).enclosingScope());
-        Mark(trc, &key.enclosingScope(), "MissingScopesRef");
-        map->rekeyIfMoved(prior, key);
-    }
-};
-
-/* static */ MOZ_ALWAYS_INLINE void
-DebugScopes::missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map,
-                                           const ScopeIterKey &key)
-{
-    if (key.enclosingScope() && IsInsideNursery(key.enclosingScope()))
-        rt->gc.storeBuffer.putGeneric(MissingScopesRef(map, key));
-}
-
 /* static */ MOZ_ALWAYS_INLINE void
 DebugScopes::liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map, ScopeObject *key)
 {
     // As above.  Otherwise, barriers could fire during GC when moving the
     // value.
     typedef HashMap<ScopeObject *,
-                    ScopeIterKey,
+                    MissingScopeKey,
                     DefaultHasher<ScopeObject *>,
                     RuntimeAllocPolicy> UnbarrieredLiveScopeMap;
     typedef gc::HashKeyRef<UnbarrieredLiveScopeMap, ScopeObject *> Ref;
     if (key && IsInsideNursery(key))
         rt->gc.storeBuffer.putGeneric(Ref(reinterpret_cast<UnbarrieredLiveScopeMap *>(map), key));
 }
 
 DebugScopes::DebugScopes(JSContext *cx)
@@ -1991,28 +1950,21 @@ DebugScopes::sweep(JSRuntime *rt)
              * liveness; we should assume that anything could be marked.
              *
              * Thus, we must explicitly remove the entries from both liveScopes
              * and missingScopes here.
              */
             liveScopes.remove(&(*debugScope)->scope());
             e.removeFront();
         } else {
-            ScopeIterKey key = e.front().key();
-            bool needsUpdate = false;
-            if (IsForwarded(key.cur())) {
-                key.updateCur(&gc::Forwarded(key.cur())->as<NativeObject>());
-                needsUpdate = true;
+            MissingScopeKey key = e.front().key();
+            if (IsForwarded(key.staticScope())) {
+                key.updateStaticScope(Forwarded(key.staticScope()));
+                e.rekeyFront(key);
             }
-            if (key.staticScope() && IsForwarded(key.staticScope())) {
-                key.updateStaticScope(Forwarded(key.staticScope()));
-                needsUpdate = true;
-            }
-            if (needsUpdate)
-                e.rekeyFront(key);
         }
     }
 
     for (LiveScopeMap::Enum e(liveScopes); !e.empty(); e.popFront()) {
         ScopeObject *scope = e.front().key();
 
         e.front().value().sweep();
 
@@ -2036,23 +1988,21 @@ DebugScopes::checkHashTablesAfterMovingG
      * postbarriers have worked and that no hashtable keys (or values) are left
      * pointing into the nursery.
      */
     for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) {
         CheckGCThingAfterMovingGC(r.front().key().get());
         CheckGCThingAfterMovingGC(r.front().value().get());
     }
     for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) {
-        CheckGCThingAfterMovingGC(r.front().key().cur());
         CheckGCThingAfterMovingGC(r.front().key().staticScope());
         CheckGCThingAfterMovingGC(r.front().value().get());
     }
     for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) {
         CheckGCThingAfterMovingGC(r.front().key());
-        CheckGCThingAfterMovingGC(r.front().value().cur_.get());
         CheckGCThingAfterMovingGC(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
@@ -2126,46 +2076,47 @@ DebugScopeObject *
 DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si)
 {
     MOZ_ASSERT(!si.hasScopeObject());
 
     DebugScopes *scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return nullptr;
 
-    if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(ScopeIterKey(si))) {
+    if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
         MOZ_ASSERT(CanUseDebugScopeMaps(cx));
         return p->value();
     }
     return nullptr;
 }
 
 bool
 DebugScopes::addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope)
 {
     MOZ_ASSERT(!si.hasScopeObject());
     MOZ_ASSERT(cx->compartment() == debugScope.compartment());
-    MOZ_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator());
+    MOZ_ASSERT_IF(si.withinInitialFrame() && si.initialFrame().isFunctionFrame(),
+                  !si.initialFrame().callee()->isGenerator());
 
     if (!CanUseDebugScopeMaps(cx))
         return true;
 
     DebugScopes *scopes = ensureCompartmentData(cx);
     if (!scopes)
         return false;
 
-    MOZ_ASSERT(!scopes->missingScopes.has(ScopeIterKey(si)));
-    if (!scopes->missingScopes.put(ScopeIterKey(si), ReadBarriered<DebugScopeObject*>(&debugScope))) {
+    MissingScopeKey key(si);
+    MOZ_ASSERT(!scopes->missingScopes.has(key));
+    if (!scopes->missingScopes.put(key, ReadBarriered<DebugScopeObject*>(&debugScope))) {
         js_ReportOutOfMemory(cx);
         return false;
     }
-    missingScopesPostWriteBarrier(cx->runtime(), &scopes->missingScopes, ScopeIterKey(si));
 
     MOZ_ASSERT(!scopes->liveScopes.has(&debugScope.scope()));
-    if (!scopes->liveScopes.put(&debugScope.scope(), ScopeIterVal(si))) {
+    if (!scopes->liveScopes.put(&debugScope.scope(), LiveScopeVal(si))) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &debugScope.scope());
 
     return true;
 }
 
@@ -2191,18 +2142,18 @@ DebugScopes::onPopCall(AbstractFramePtr 
         if (frame.fun()->isGenerator())
             return;
 
         CallObject &callobj = frame.scopeChain()->as<CallObject>();
         scopes->liveScopes.remove(&callobj);
         if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj))
             debugScope = &p->value()->as<DebugScopeObject>();
     } else {
-        ScopeIter si(frame, frame.script()->main(), cx);
-        if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(ScopeIterKey(si))) {
+        ScopeIter si(cx, frame, frame.script()->main());
+        if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
             debugScope = p->value();
             scopes->liveScopes.remove(&debugScope->scope().as<CallObject>());
             scopes->missingScopes.remove(p);
         }
     }
 
     /*
      * When the JS stack frame is popped, the values of unaliased variables
@@ -2254,37 +2205,38 @@ void
 DebugScopes::onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
 {
     assertSameCompartment(cx, frame);
 
     DebugScopes *scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return;
 
-    ScopeIter si(frame, pc, cx);
+    ScopeIter si(cx, frame, pc);
     onPopBlock(cx, si);
 }
 
 void
 DebugScopes::onPopBlock(JSContext *cx, const ScopeIter &si)
 {
     DebugScopes *scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return;
 
+    MOZ_ASSERT(si.withinInitialFrame());
     MOZ_ASSERT(si.type() == ScopeIter::Block);
 
     if (si.staticBlock().needsClone()) {
         ClonedBlockObject &clone = si.scope().as<ClonedBlockObject>();
-        clone.copyUnaliasedValues(si.frame());
+        clone.copyUnaliasedValues(si.initialFrame());
         scopes->liveScopes.remove(&clone);
     } else {
-        if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(ScopeIterKey(si))) {
+        if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
             ClonedBlockObject &clone = p->value()->scope().as<ClonedBlockObject>();
-            clone.copyUnaliasedValues(si.frame());
+            clone.copyUnaliasedValues(si.initialFrame());
             scopes->liveScopes.remove(&clone);
             scopes->missingScopes.remove(p);
         }
     }
 }
 
 void
 DebugScopes::onPopWith(AbstractFramePtr frame)
@@ -2345,38 +2297,38 @@ DebugScopes::updateLiveScopes(JSContext 
             continue;
 
         if (frame.isFunctionFrame() && frame.callee()->isGenerator())
             continue;
 
         if (!frame.isDebuggee())
             continue;
 
-        for (ScopeIter si(frame, i.pc(), cx); !si.done(); ++si) {
+        for (ScopeIter si(cx, frame, i.pc()); si.withinInitialFrame(); ++si) {
             if (si.hasScopeObject()) {
                 MOZ_ASSERT(si.scope().compartment() == cx->compartment());
                 DebugScopes *scopes = ensureCompartmentData(cx);
                 if (!scopes)
                     return false;
-                if (!scopes->liveScopes.put(&si.scope(), ScopeIterVal(si)))
+                if (!scopes->liveScopes.put(&si.scope(), LiveScopeVal(si)))
                     return false;
                 liveScopesPostWriteBarrier(cx->runtime(), &scopes->liveScopes, &si.scope());
             }
         }
 
         if (frame.prevUpToDate())
             return true;
         MOZ_ASSERT(frame.scopeChain()->compartment()->isDebuggee());
         frame.setPrevUpToDate();
     }
 
     return true;
 }
 
-ScopeIterVal*
+LiveScopeVal *
 DebugScopes::hasLiveScope(ScopeObject &scope)
 {
     DebugScopes *scopes = scope.compartment()->debugScopes;
     if (!scopes)
         return nullptr;
 
     if (LiveScopeMap::Ptr p = scopes->liveScopes.lookup(&scope))
         return &p->value();
@@ -2412,42 +2364,44 @@ DebugScopes::unsetPrevUpToDateUntil(JSCo
 /* static */ void
 DebugScopes::forwardLiveFrame(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to)
 {
     DebugScopes *scopes = cx->compartment()->debugScopes;
     if (!scopes)
         return;
 
     for (MissingScopeMap::Enum e(scopes->missingScopes); !e.empty(); e.popFront()) {
-        ScopeIterKey key = e.front().key();
+        MissingScopeKey key = e.front().key();
         if (key.frame() == from) {
             key.updateFrame(to);
             e.rekeyFront(key);
         }
     }
 
     for (LiveScopeMap::Enum e(scopes->liveScopes); !e.empty(); e.popFront()) {
-        ScopeIterVal &val = e.front().value();
+        LiveScopeVal &val = e.front().value();
         if (val.frame() == from)
             val.updateFrame(to);
     }
 }
 
 /*****************************************************************************/
 
 static JSObject *
 GetDebugScope(JSContext *cx, const ScopeIter &si);
 
 static DebugScopeObject *
-GetDebugScopeForScope(JSContext *cx, Handle<ScopeObject*> scope, const ScopeIter &enclosing)
+GetDebugScopeForScope(JSContext *cx, const ScopeIter &si)
 {
+    Rooted<ScopeObject *> scope(cx, &si.scope());
     if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, *scope))
         return debugScope;
 
-    RootedObject enclosingDebug(cx, GetDebugScope(cx, enclosing));
+    ScopeIter copy(cx, si);
+    RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
     if (!enclosingDebug)
         return nullptr;
 
     JSObject &maybeDecl = scope->enclosingScope();
     if (maybeDecl.is<DeclEnvObject>()) {
         MOZ_ASSERT(CallObjectLambdaName(scope->as<CallObject>().callee()));
         enclosingDebug = DebugScopeObject::create(cx, maybeDecl.as<DeclEnvObject>(), enclosingDebug);
         if (!enclosingDebug)
@@ -2462,20 +2416,26 @@ GetDebugScopeForScope(JSContext *cx, Han
         return nullptr;
 
     return debugScope;
 }
 
 static DebugScopeObject *
 GetDebugScopeForMissing(JSContext *cx, const ScopeIter &si)
 {
+    MOZ_ASSERT(!si.hasScopeObject() && si.canHaveScopeObject());
+
+    // FIXMEshu completely optimized-out scopes
+    if (!si.withinInitialFrame())
+        MOZ_CRASH("NYI");
+
     if (DebugScopeObject *debugScope = DebugScopes::hasDebugScope(cx, si))
         return debugScope;
 
-    ScopeIter copy(si, cx);
+    ScopeIter copy(cx, si);
     RootedObject enclosingDebug(cx, GetDebugScope(cx, ++copy));
     if (!enclosingDebug)
         return nullptr;
 
     /*
      * Create the missing scope object. For block objects, this takes care of
      * storing variable values after the stack frame has been popped. For call
      * objects, we only use the pretend call object to access callee, bindings
@@ -2485,117 +2445,106 @@ GetDebugScopeForMissing(JSContext *cx, c
      * Note: to preserve scopeChain depth invariants, these lazily-reified
      * scopes must not be put on the frame's scope chain; instead, they are
      * maintained via DebugScopes hooks.
      */
     DebugScopeObject *debugScope = nullptr;
     switch (si.type()) {
       case ScopeIter::Call: {
         // Generators should always reify their scopes.
-        MOZ_ASSERT(!si.frame().callee()->isGenerator());
-        Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.frame()));
+        MOZ_ASSERT(!si.initialFrame().callee()->isGenerator());
+        Rooted<CallObject*> callobj(cx, CallObject::createForFunction(cx, si.initialFrame()));
         if (!callobj)
             return nullptr;
 
         if (callobj->enclosingScope().is<DeclEnvObject>()) {
             MOZ_ASSERT(CallObjectLambdaName(callobj->callee()));
             DeclEnvObject &declenv = callobj->enclosingScope().as<DeclEnvObject>();
             enclosingDebug = DebugScopeObject::create(cx, declenv, enclosingDebug);
             if (!enclosingDebug)
                 return nullptr;
         }
 
         debugScope = DebugScopeObject::create(cx, *callobj, enclosingDebug);
         break;
       }
       case ScopeIter::Block: {
         // Generators should always reify their scopes.
-        MOZ_ASSERT_IF(si.frame().isFunctionFrame(), !si.frame().callee()->isGenerator());
+        MOZ_ASSERT_IF(si.initialFrame().isFunctionFrame(),
+                      !si.initialFrame().callee()->isGenerator());
         Rooted<StaticBlockObject *> staticBlock(cx, &si.staticBlock());
-        ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.frame());
+        ClonedBlockObject *block = ClonedBlockObject::create(cx, staticBlock, si.initialFrame());
         if (!block)
             return nullptr;
 
         debugScope = DebugScopeObject::create(cx, *block, enclosingDebug);
         break;
       }
       case ScopeIter::With:
-      case ScopeIter::StrictEvalScope:
+      case ScopeIter::Eval:
         MOZ_CRASH("should already have a scope");
     }
     if (!debugScope)
         return nullptr;
 
     if (!DebugScopes::addDebugScope(cx, si, *debugScope))
         return nullptr;
 
     return debugScope;
 }
 
 static JSObject *
-GetDebugScope(JSContext *cx, JSObject &obj)
+GetDebugScopeForNonScopeObject(const ScopeIter &si)
 {
-    /*
-     * As an engine invariant (maintained internally and asserted by Execute),
-     * ScopeObjects and non-ScopeObjects cannot be interleaved on the scope
-     * chain; every scope chain must start with zero or more ScopeObjects and
-     * terminate with one or more non-ScopeObjects (viz., GlobalObject).
-     */
-    if (!obj.is<ScopeObject>()) {
+    JSObject &enclosing = si.enclosingScope();
+    MOZ_ASSERT(!enclosing.is<ScopeObject>());
 #ifdef DEBUG
-        JSObject *o = &obj;
-        while ((o = o->enclosingScope()))
-            MOZ_ASSERT(!o->is<ScopeObject>());
+    JSObject *o = &enclosing;
+    while ((o = o->enclosingScope()))
+        MOZ_ASSERT(!o->is<ScopeObject>());
 #endif
-        return &obj;
-    }
-
-    Rooted<ScopeObject*> scope(cx, &obj.as<ScopeObject>());
-    if (ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope)) {
-        ScopeIter si(*maybeLiveScope, cx);
-        return GetDebugScope(cx, si);
-    }
-    ScopeIter si(scope->enclosingScope(), cx);
-    return GetDebugScopeForScope(cx, scope, si);
+    return &enclosing;
 }
 
 static JSObject *
 GetDebugScope(JSContext *cx, const ScopeIter &si)
 {
     JS_CHECK_RECURSION(cx, return nullptr);
 
     if (si.done())
-        return GetDebugScope(cx, si.enclosingScope());
-
-    if (!si.hasScopeObject())
+        return GetDebugScopeForNonScopeObject(si);
+
+    if (si.hasScopeObject())
+        return GetDebugScopeForScope(cx, si);
+
+    if (si.canHaveScopeObject())
         return GetDebugScopeForMissing(cx, si);
 
-    Rooted<ScopeObject*> scope(cx, &si.scope());
-
-    ScopeIter copy(si, cx);
-    return GetDebugScopeForScope(cx, scope, ++copy);
+    ScopeIter copy(cx, si);
+    return GetDebugScope(cx, ++copy);
 }
 
 JSObject *
 js::GetDebugScopeForFunction(JSContext *cx, HandleFunction fun)
 {
     assertSameCompartment(cx, fun);
     MOZ_ASSERT(CanUseDebugScopeMaps(cx));
     if (!DebugScopes::updateLiveScopes(cx))
         return nullptr;
-    return GetDebugScope(cx, *fun->environment());
+    ScopeIter si(cx, fun->environment(), fun->nonLazyScript()->enclosingStaticScope());
+    return GetDebugScope(cx, si);
 }
 
 JSObject *
 js::GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc)
 {
     assertSameCompartment(cx, frame);
     if (CanUseDebugScopeMaps(cx) && !DebugScopes::updateLiveScopes(cx))
         return nullptr;
-    ScopeIter si(frame, pc, cx);
+    ScopeIter si(cx, frame, pc);
     return GetDebugScope(cx, si);
 }
 
 // See declaration and documentation in jsfriendapi.h
 JS_FRIEND_API(JSObject *)
 js::GetObjectEnvironmentObjectForFunction(JSFunction *fun)
 {
     if (!fun->isInterpreted())
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -15,16 +15,17 @@
 #include "vm/ArgumentsObject.h"
 #include "vm/ProxyObject.h"
 
 namespace js {
 
 namespace frontend { struct Definition; }
 
 class StaticWithObject;
+class StaticEvalObject;
 
 /*****************************************************************************/
 
 /*
  * All function scripts have an "enclosing static scope" that refers to the
  * innermost enclosing let or function in the program text. This allows full
  * reconstruction of the lexical scope for debugging or compiling efficient
  * access to variables in enclosing scopes. The static scope is represented at
@@ -37,30 +38,16 @@ class StaticWithObject;
  * There is an additional scope for named lambdas. E.g., in:
  *
  *   (function f() { var x; function g() { } })
  *
  * g's innermost enclosing scope will first be the function scope containing
  * 'x', enclosed by a scope containing only the name 'f'. (This separate scope
  * is necessary due to the fact that declarations in the function scope shadow
  * (dynamically, in the case of 'eval') the lambda name.)
- *
- * There are two limitations to the current lexical nesting information:
- *
- *  - 'with' is completely absent; this isn't a problem for the current use
- *    cases since 'with' causes every static scope to be on the dynamic scope
- *    chain (so the debugger can find everything) and inhibits all upvar
- *    optimization.
- *
- *  - The "enclosing static scope" chain stops at 'eval'. For example in:
- *      let (x) { eval("function f() {}") }
- *    f does not have an enclosing static scope. This is fine for current uses
- *    for the same reason as 'with'.
- *
- * (See also AssertDynamicScopeMatchesStaticScope.)
  */
 template <AllowGC allowGC>
 class StaticScopeIter
 {
     typename MaybeRooted<JSObject*, allowGC>::RootType obj;
     bool onNamedLambda;
 
   public:
@@ -68,43 +55,60 @@ class StaticScopeIter
       : obj(cx, obj), onNamedLambda(false)
     {
         static_assert(allowGC == CanGC,
                       "the context-accepting constructor should only be used "
                       "in CanGC code");
         MOZ_ASSERT_IF(obj,
                       obj->is<StaticBlockObject>() ||
                       obj->is<StaticWithObject>() ||
+                      obj->is<StaticEvalObject>() ||
                       obj->is<JSFunction>());
     }
 
+    StaticScopeIter(ExclusiveContext *cx, const StaticScopeIter<CanGC> &ssi)
+      : obj(cx, ssi.obj), onNamedLambda(ssi.onNamedLambda)
+    {
+        JS_STATIC_ASSERT(allowGC == CanGC);
+    }
+
     explicit StaticScopeIter(JSObject *obj)
       : obj((ExclusiveContext *) nullptr, obj), onNamedLambda(false)
     {
         static_assert(allowGC == NoGC,
                       "the constructor not taking a context should only be "
                       "used in NoGC code");
         MOZ_ASSERT_IF(obj,
                       obj->is<StaticBlockObject>() ||
                       obj->is<StaticWithObject>() ||
+                      obj->is<StaticEvalObject>() ||
                       obj->is<JSFunction>());
     }
 
+    explicit StaticScopeIter(const StaticScopeIter<NoGC> &ssi)
+      : obj((ExclusiveContext *) nullptr, ssi.obj), onNamedLambda(ssi.onNamedLambda)
+    {
+        static_assert(allowGC == NoGC,
+                      "the constructor not taking a context should only be "
+                      "used in NoGC code");
+    }
+
     bool done() const;
     void operator++(int);
 
     /* Return whether this static scope will be on the dynamic scope chain. */
     bool hasDynamicScopeObject() const;
     Shape *scopeShape() const;
 
-    enum Type { WITH, BLOCK, FUNCTION, NAMED_LAMBDA };
+    enum Type { Function, Block, With, NamedLambda, Eval };
     Type type() const;
 
     StaticBlockObject &block() const;
     StaticWithObject &staticWith() const;
+    StaticEvalObject &eval() const;
     JSScript *funScript() const;
     JSFunction &fun() const;
 };
 
 /*****************************************************************************/
 
 /*
  * A "scope coordinate" describes how to get from head of the scope chain to a
@@ -452,16 +456,45 @@ class DynamicWithObject : public NestedS
         return OBJECT_SLOT;
     }
 
     static inline size_t thisSlot() {
         return THIS_SLOT;
     }
 };
 
+// Static eval scope template objects on the static scope. Created at the
+// time of compiling the eval script, and set as its static enclosing scope.
+class StaticEvalObject : public NestedScopeObject
+{
+    static const uint32_t STRICT_SLOT = 1;
+
+  public:
+    static const unsigned RESERVED_SLOTS = 2;
+    static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND;
+
+    static const Class class_;
+
+    static StaticEvalObject *create(JSContext *cx, HandleObject enclosing);
+
+    void setStrict() {
+        setReservedSlot(STRICT_SLOT, BooleanValue(true));
+    }
+
+    bool isStrict() const {
+        return getReservedSlot(STRICT_SLOT).isTrue();
+    }
+
+    // Indirect evals terminate in the global at run time, and has no static
+    // enclosing scope.
+    bool isDirect() const {
+        return getReservedSlot(SCOPE_CHAIN_SLOT).isObject();
+    }
+};
+
 class BlockObject : public NestedScopeObject
 {
   protected:
     static const unsigned DEPTH_SLOT = 1;
 
   public:
     static const unsigned RESERVED_SLOTS = 2;
     static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
@@ -661,162 +694,129 @@ bool
 XDRStaticWithObject(XDRState<mode> *xdr, HandleObject enclosingScope,
                     MutableHandle<StaticWithObject*> objp);
 
 extern JSObject *
 CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src);
 
 /*****************************************************************************/
 
-class ScopeIterKey;
-class ScopeIterVal;
-
-/*
- * A scope iterator describes the active scopes enclosing the current point of
- * execution for a single frame, proceeding from inner to outer. Here, "frame"
- * means a single activation of: a function, eval, or global code. By design,
- * ScopeIter exposes *all* scopes, even those that have been optimized away
- * (i.e., no ScopeObject was created when entering the scope and thus there is
- * no ScopeObject on fp->scopeChain representing the scope).
- *
- * Note: ScopeIter iterates over all scopes *within* a frame which means that
- * all scopes are ScopeObjects. In particular, the GlobalObject enclosing
- * global code (and any random objects passed as scopes to Execute) will not
- * be included.
- */
+// A scope iterator describes the active scopes starting from a dynamic scope,
+// static scope pair. This pair may be derived from the current point of
+// execution in a frame. If derived in such a fashion, the ScopeIter tracks
+// whether the current scope is within the extent of this initial frame.
+// Here, "frame" means a single activation of: a function, eval, or global
+// code.
 class ScopeIter
 {
-    friend class ScopeIterKey;
-    friend class ScopeIterVal;
-
-  public:
-    enum Type { Call, Block, With, StrictEvalScope };
-
-  private:
-    JSContext *cx;
+    StaticScopeIter<CanGC> ssi_;
+    RootedObject scope_;
     AbstractFramePtr frame_;
-    RootedObject cur_;
-    Rooted<NestedScopeObject *> staticScope_;
-    Type type_;
-    bool hasScopeObject_;
 
     void settle();
 
-    /* ScopeIter does not have value semantics. */
+    // No value semantics.
     ScopeIter(const ScopeIter &si) = delete;
 
-    ScopeIter(JSContext *cx) = delete;
-
   public:
-
-    /* Constructing from a copy of an existing ScopeIter. */
-    ScopeIter(const ScopeIter &si, JSContext *cx
+    // Constructing from a copy of an existing ScopeIter.
+    ScopeIter(JSContext *cx, const ScopeIter &si
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
-    /* Constructing from AbstractFramePtr places ScopeIter on the innermost scope. */
-    ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
+    // Constructing from a dynamic scope, static scope pair. All scopes are
+    // considered not to be withinInitialFrame, since no frame is given.
+    ScopeIter(JSContext *cx, JSObject *scope, JSObject *staticScope
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
-    /*
-     * Without a stack frame, the resulting ScopeIter is done() with
-     * enclosingScope() as given.
-     */
-    ScopeIter(JSObject &enclosingScope, JSContext *cx
+    // Constructing from a frame. Places the ScopeIter on the innermost scope
+    // at pc.
+    ScopeIter(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc
               MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
 
-    ScopeIter(const ScopeIterVal &hashVal, JSContext *cx
-              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
-
-    bool done() const { return !frame_; }
-
-    /* If done(): */
-
-    JSObject &enclosingScope() const { MOZ_ASSERT(done()); return *cur_; }
-
-    /* If !done(): */
-
+    inline bool done() const;
     ScopeIter &operator++();
 
-    AbstractFramePtr frame() const { MOZ_ASSERT(!done()); return frame_; }
-    Type type() const { MOZ_ASSERT(!done()); return type_; }
-    bool hasScopeObject() const { MOZ_ASSERT(!done()); return hasScopeObject_; }
+    // If done():
+    inline JSObject &enclosingScope() const;
+
+    // If !done():
+    enum Type { Call, Block, With, Eval };
+    Type type() const;
+
+    inline bool hasScopeObject() const;
+    inline bool canHaveScopeObject() const;
     ScopeObject &scope() const;
-    NestedScopeObject* staticScope() const { return staticScope_; }
 
-    StaticBlockObject &staticBlock() const {
-        MOZ_ASSERT(type() == Block);
-        return staticScope_->as<StaticBlockObject>();
-    }
+    JSObject *maybeStaticScope() const;
+    StaticBlockObject &staticBlock() const { return ssi_.block(); }
+    StaticWithObject &staticWith() const { return ssi_.staticWith(); }
+    StaticEvalObject &staticEval() const { return ssi_.eval(); }
+    JSFunction &fun() const { return ssi_.fun(); }
+
+    bool withinInitialFrame() const { return !!frame_; }
+    AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; }
+    AbstractFramePtr maybeInitialFrame() const { return frame_; }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
-class ScopeIterKey
+// The key in MissingScopeMap, used to map live frames to their synthesized
+// scopes.
+class MissingScopeKey
 {
-    friend class ScopeIterVal;
+    friend class LiveScopeVal;
 
     AbstractFramePtr frame_;
-    JSObject *cur_;
-    NestedScopeObject *staticScope_;
-    ScopeIter::Type type_;
-    bool hasScopeObject_;
+    JSObject *staticScope_;
 
   public:
-    explicit ScopeIterKey(const ScopeIter &si)
-      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
-        hasScopeObject_(si.hasScopeObject_) {}
+    MissingScopeKey(const ScopeIter &si)
+      : frame_(si.maybeInitialFrame()),
+        staticScope_(si.maybeStaticScope())
+    { }
 
     AbstractFramePtr frame() const { return frame_; }
-    JSObject *cur() const { return cur_; }
-    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_; }
+    JSObject *staticScope() const { return staticScope_; }
 
-    void updateCur(JSObject *obj) { cur_ = obj; }
-    void updateStaticScope(NestedScopeObject *obj) { staticScope_ = obj; }
+    void updateStaticScope(JSObject *obj) { staticScope_ = obj; }
     void updateFrame(AbstractFramePtr frame) { frame_ = frame; }
 
-    /* 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_ ||
-               staticScope_ != other.staticScope_ ||
-               type_ != other.type_;
+    // For use as hash policy.
+    typedef MissingScopeKey Lookup;
+    static HashNumber hash(MissingScopeKey sk);
+    static bool match(MissingScopeKey sk1, MissingScopeKey sk2);
+    bool operator!=(const MissingScopeKey &other) const {
+        return frame_ != other.frame_ || staticScope_ != other.staticScope_;
     }
-    static void rekey(ScopeIterKey &k, const ScopeIterKey& newKey) {
+    static void rekey(MissingScopeKey &k, const MissingScopeKey& newKey) {
         k = newKey;
     }
 };
 
-class ScopeIterVal
+// The value in LiveScopeMap, mapped from by live scope objects.
+class LiveScopeVal
 {
-    friend class ScopeIter;
     friend class DebugScopes;
+    friend class MissingScopeKey;
 
     AbstractFramePtr frame_;
-    RelocatablePtrObject cur_;
-    RelocatablePtrNestedScopeObject staticScope_;
-    ScopeIter::Type type_;
-    bool hasScopeObject_;
+    RelocatablePtrObject staticScope_;
 
     void sweep();
-
     static void staticAsserts();
 
   public:
-    explicit ScopeIterVal(const ScopeIter &si)
-      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
-        hasScopeObject_(si.hasScopeObject_) {}
+    explicit LiveScopeVal(const ScopeIter &si)
+      : frame_(si.initialFrame()),
+        staticScope_(si.maybeStaticScope())
+    { }
 
     AbstractFramePtr frame() const { return frame_; }
+    JSObject *staticScope() const { return staticScope_; }
+
     void updateFrame(AbstractFramePtr frame) { frame_ = frame; }
 };
 
 /*****************************************************************************/
 
 /*
  * Debug scope objects
  *
@@ -889,39 +889,36 @@ class DebugScopes
     ObjectWeakMap proxiedScopes;
     static MOZ_ALWAYS_INLINE void proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map,
                                                                const PreBarrieredObject &key);
 
     /*
      * The map from live frames which have optimized-away scopes to the
      * corresponding debug scopes.
      */
-    typedef HashMap<ScopeIterKey,
+    typedef HashMap<MissingScopeKey,
                     ReadBarrieredDebugScopeObject,
-                    ScopeIterKey,
+                    MissingScopeKey,
                     RuntimeAllocPolicy> MissingScopeMap;
     MissingScopeMap missingScopes;
-    class MissingScopesRef;
-    static MOZ_ALWAYS_INLINE void missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map,
-                                                               const ScopeIterKey &key);
 
     /*
      * The map from scope objects of live frames to the live frame. This map
      * updated lazily whenever the debugger needs the information. In between
      * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop*
      * removes scopes as they are popped). Thus, two consecutive debugger lazy
      * updates of liveScopes need only fill in the new scopes.
      */
     typedef HashMap<ScopeObject *,
-                    ScopeIterVal,
+                    LiveScopeVal,
                     DefaultHasher<ScopeObject *>,
                     RuntimeAllocPolicy> LiveScopeMap;
     LiveScopeMap liveScopes;
     static MOZ_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map,
-                                                            ScopeObject *key);
+                                                             ScopeObject *key);
 
   public:
     explicit DebugScopes(JSContext *c);
     ~DebugScopes();
 
   private:
     bool init();
 
@@ -936,17 +933,17 @@ class DebugScopes
 
     static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope);
     static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope);
 
     static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si);
     static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope);
 
     static bool updateLiveScopes(JSContext *cx);
-    static ScopeIterVal *hasLiveScope(ScopeObject &scope);
+    static LiveScopeVal *hasLiveScope(ScopeObject &scope);
     static void unsetPrevUpToDateUntil(JSContext *cx, AbstractFramePtr frame);
 
     // When a frame bails out from Ion to Baseline, there might be missing
     // scopes keyed on, and live scopes containing, the old
     // RematerializedFrame. Forward those values to the new BaselineFrame.
     static void forwardLiveFrame(JSContext *cx, AbstractFramePtr from, AbstractFramePtr to);
 
     // In debug-mode, these must be called whenever exiting a scope that might
@@ -960,24 +957,29 @@ class DebugScopes
 };
 
 }  /* namespace js */
 
 template<>
 inline bool
 JSObject::is<js::NestedScopeObject>() const
 {
-    return is<js::BlockObject>() || is<js::StaticWithObject>() || is<js::DynamicWithObject>();
+    return is<js::BlockObject>() ||
+           is<js::StaticWithObject>() ||
+           is<js::DynamicWithObject>() ||
+           is<js::StaticEvalObject>();
 }
 
 template<>
 inline bool
 JSObject::is<js::ScopeObject>() const
 {
-    return is<js::CallObject>() || is<js::DeclEnvObject>() || is<js::NestedScopeObject>() ||
+    return is<js::CallObject>() ||
+           is<js::DeclEnvObject>() ||
+           is<js::NestedScopeObject>() ||
            is<js::UninitializedLexicalObject>();
 }
 
 template<>
 inline bool
 JSObject::is<js::DebugScopeObject>() const
 {
     extern bool js_IsDebugScopeSlow(js::ProxyObject *proxy);
@@ -1022,16 +1024,47 @@ ScopeObject::aliasedVar(ScopeCoordinate 
 
 inline NestedScopeObject *
 NestedScopeObject::enclosingNestedScope() const
 {
     JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
     return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr;
 }
 
+inline bool
+ScopeIter::done() const
+{
+    return ssi_.done();
+}
+
+inline bool
+ScopeIter::hasScopeObject() const
+{
+    return ssi_.hasDynamicScopeObject();
+}
+
+inline bool
+ScopeIter::canHaveScopeObject() const
+{
+    // Non-strict eval scopes cannot have dynamic scope objects.
+    return !ssi_.done() && (type() != Eval || staticEval().isStrict());
+}
+
+inline JSObject &
+ScopeIter::enclosingScope() const
+{
+    // As an engine invariant (maintained internally and asserted by Execute),
+    // ScopeObjects and non-ScopeObjects cannot be interleaved on the scope
+    // chain; every scope chain must start with zero or more ScopeObjects and
+    // terminate with one or more non-ScopeObjects (viz., GlobalObject).
+    MOZ_ASSERT(done());
+    MOZ_ASSERT(!scope_->is<ScopeObject>());
+    return *scope_;
+}
+
 #ifdef DEBUG
 bool
 AnalyzeEntrainedVariables(JSContext *cx, HandleScript script);
 #endif
 
 } // namespace js
 
 #endif /* vm_ScopeObject_h */
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -142,40 +142,43 @@ InterpreterFrame::createRestParameter(JS
 static inline void
 AssertDynamicScopeMatchesStaticScope(JSContext *cx, JSScript *script, JSObject *scope)
 {
 #ifdef DEBUG
     RootedObject enclosingScope(cx, script->enclosingStaticScope());
     for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) {
         if (i.hasDynamicScopeObject()) {
             switch (i.type()) {
-              case StaticScopeIter<NoGC>::BLOCK:
+              case StaticScopeIter<NoGC>::Function:
+                MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript());
+                scope = &scope->as<CallObject>().enclosingScope();
+                break;
+              case StaticScopeIter<NoGC>::Block:
                 MOZ_ASSERT(&i.block() == scope->as<ClonedBlockObject>().staticScope());
                 scope = &scope->as<ClonedBlockObject>().enclosingScope();
                 break;
-              case StaticScopeIter<NoGC>::WITH:
+              case StaticScopeIter<NoGC>::With:
                 MOZ_ASSERT(&i.staticWith() == scope->as<DynamicWithObject>().staticScope());
                 scope = &scope->as<DynamicWithObject>().enclosingScope();
                 break;
-              case StaticScopeIter<NoGC>::FUNCTION:
-                MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript());
+              case StaticScopeIter<NoGC>::NamedLambda:
+                scope = &scope->as<DeclEnvObject>().enclosingScope();
+                break;
+              case StaticScopeIter<NoGC>::Eval:
                 scope = &scope->as<CallObject>().enclosingScope();
                 break;
-              case StaticScopeIter<NoGC>::NAMED_LAMBDA:
-                scope = &scope->as<DeclEnvObject>().enclosingScope();
-                break;
             }
         }
     }
 
-    /*
-     * Ideally, we'd MOZ_ASSERT(!scope->is<ScopeObject>()) but the enclosing
-     * lexical scope chain stops at eval() boundaries. See StaticScopeIter
-     * comment.
-     */
+    // The scope chain is always ended by one or more non-syntactic
+    // ScopeObjects (viz. GlobalObject or a non-syntactic WithObject).
+    MOZ_ASSERT(!scope->is<ScopeObject>() ||
+               (scope->is<DynamicWithObject>() &&
+                !scope->as<DynamicWithObject>().isSyntactic()));
 #endif
 }
 
 bool
 InterpreterFrame::initFunctionScopeObjects(JSContext *cx)
 {
     CallObject *callobj = CallObject::createForFunction(cx, this);
     if (!callobj)
@@ -348,17 +351,17 @@ InterpreterFrame::markValues(JSTracer *t
 {
     MOZ_ASSERT(sp >= slots());
 
     JSScript *script = this->script();
     size_t nfixed = script->nfixed();
     size_t nlivefixed = script->nbodyfixed();
 
     if (nfixed != nlivefixed) {
-        NestedScopeObject *staticScope = script->getStaticScope(pc);
+        NestedScopeObject *staticScope = script->getStaticBlockScope(pc);
         while (staticScope && !staticScope->is<StaticBlockObject>())
             staticScope = staticScope->enclosingNestedScope();
 
         if (staticScope) {
             StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>();
             nlivefixed = blockObj.localOffset() + blockObj.numVariables();
         }
     }