Bug 1263355 - Rewrite the frontend: bindings. (r=jorendorff,Waldo)
authorShu-yu Guo <shu@rfrn.org>
Thu, 25 Aug 2016 01:28:47 -0700
changeset 354679 cb6fc6d38f8df50e25502dc55ee1ed2dd3d7a304
parent 354678 181336fdda6625d8ffa5e5764b817cc3da1f9659
child 354680 18bec78f348ee0c68deba76a5da5529ece455393
push id1324
push usermtabara@mozilla.com
push dateMon, 16 Jan 2017 13:07:44 +0000
treeherdermozilla-release@a01c49833940 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, Waldo
bugs1263355
milestone51.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 1263355 - Rewrite the frontend: bindings. (r=jorendorff,Waldo)
devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
devtools/server/tests/unit/test_framebindings-02.js
devtools/server/tests/unit/test_framebindings-05.js
js/public/Class.h
js/public/GCHashTable.h
js/public/GCVariant.h
js/public/MemoryMetrics.h
js/public/RootingAPI.h
js/public/TraceKind.h
js/public/TracingAPI.h
js/src/NamespaceImports.h
js/src/asmjs/AsmJS.cpp
js/src/asmjs/AsmJS.h
js/src/builtin/Eval.cpp
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/builtin/ReflectParse.cpp
js/src/builtin/RegExp.cpp
js/src/builtin/SelfHostingDefines.h
js/src/builtin/TestingFunctions.cpp
js/src/ds/InlineMap.h
js/src/ds/InlineTable.h
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/FoldConstants.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/NameAnalysisTypes.h
js/src/frontend/NameCollections.h
js/src/frontend/NameFunctions.cpp
js/src/frontend/ParseMaps-inl.h
js/src/frontend/ParseMaps.cpp
js/src/frontend/ParseMaps.h
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser-inl.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SharedContext.h
js/src/frontend/SyntaxParseHandler.h
js/src/gc/Allocator.cpp
js/src/gc/Barrier.cpp
js/src/gc/Barrier.h
js/src/gc/GCInternals.h
js/src/gc/Heap.h
js/src/gc/Marking.cpp
js/src/gc/Marking.h
js/src/gc/Nursery.cpp
js/src/gc/Policy.h
js/src/gc/Rooting.h
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/jit-test/tests/arguments/alias-function-closed.js
js/src/jit-test/tests/arguments/alias-function-not-closed.js
js/src/jit-test/tests/arguments/defaults-bound-to-function.js
js/src/jit-test/tests/arguments/defaults-call-function.js
js/src/jit-test/tests/arguments/defaults-destructuring-with-rest.js
js/src/jit-test/tests/arguments/defaults-evaluation-order.js
js/src/jit-test/tests/arguments/defaults-scoping.js
js/src/jit-test/tests/arguments/defaults-with-rest.js
js/src/jit-test/tests/arguments/destructuring-after-defaults.js
js/src/jit-test/tests/basic/bug646968-4.js
js/src/jit-test/tests/basic/bug678087.js
js/src/jit-test/tests/basic/letTDZDelete.js
js/src/jit-test/tests/basic/testLet.js
js/src/jit-test/tests/coverage/simple.js
js/src/jit-test/tests/debug/Debugger-debuggees-19.js
js/src/jit-test/tests/debug/Environment-getVariable-02.js
js/src/jit-test/tests/debug/Environment-getVariable-05.js
js/src/jit-test/tests/debug/Environment-getVariable-06.js
js/src/jit-test/tests/debug/Environment-getVariable-11.js
js/src/jit-test/tests/debug/Environment-getVariable-12.js
js/src/jit-test/tests/debug/Environment-inspectable-01.js
js/src/jit-test/tests/debug/Environment-optimizedOut-01.js
js/src/jit-test/tests/debug/Environment-setVariable-01.js
js/src/jit-test/tests/debug/Environment-setVariable-02.js
js/src/jit-test/tests/debug/Frame-environment-02.js
js/src/jit-test/tests/parser/bug-1263355-51.js
js/src/jit-test/tests/parser/bug-1263881-1.js
js/src/jit-test/tests/parser/bug-1263881-2.js
js/src/jit-test/tests/parser/bug-1263881-3.js
js/src/jit-test/tests/parser/letContextualKeyword.js
js/src/jit/Bailouts.cpp
js/src/jit/Bailouts.h
js/src/jit/BaselineBailouts.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineFrame-inl.h
js/src/jit/BaselineFrame.cpp
js/src/jit/BaselineFrame.h
js/src/jit/BaselineFrameInfo.h
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineICList.h
js/src/jit/BaselineInspector.cpp
js/src/jit/BaselineInspector.h
js/src/jit/BaselineJIT.cpp
js/src/jit/BaselineJIT.h
js/src/jit/BytecodeAnalysis.cpp
js/src/jit/BytecodeAnalysis.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/CompileInfo.h
js/src/jit/Ion.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonCaches.cpp
js/src/jit/IonCaches.h
js/src/jit/JitAllocPolicy.h
js/src/jit/JitCompartment.h
js/src/jit/JitFrameIterator.h
js/src/jit/JitFrames.cpp
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MIRGraph.cpp
js/src/jit/MIRGraph.h
js/src/jit/MOpcodes.h
js/src/jit/MacroAssembler.cpp
js/src/jit/RematerializedFrame.cpp
js/src/jit/RematerializedFrame.h
js/src/jit/ScalarReplacement.cpp
js/src/jit/SharedIC.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jit/shared/LIR-shared.h
js/src/jit/shared/LOpcodes-shared.h
js/src/js.msg
js/src/jsapi-tests/testForceLexicalInitialization.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsfun.cpp
js/src/jsfun.h
js/src/jsfuninlines.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jshashutil.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsscriptinlines.h
js/src/jsstr.cpp
js/src/moz.build
js/src/shell/js.cpp
js/src/tests/ecma_5/Global/cross-global-implicit-this.js
js/src/tests/ecma_6/Function/arguments-parameter-shadowing.js
js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-annex-b-eval.js
js/src/tests/ecma_6/LexicalEnvironment/const-declaration-in-for-loop.js
js/src/tests/ecma_6/LexicalEnvironment/for-loop.js
js/src/tests/ecma_6/LexicalEnvironment/redeclaring-global-properties.js
js/src/tests/ecma_6/Statements/for-inof-loop-const-declaration.js
js/src/tests/ecma_6/Statements/for-loop-declaration-contains-computed-name.js
js/src/tests/ecma_6/Statements/for-loop-declaration-contains-initializer.js
js/src/tests/js1_5/Regress/regress-290575.js
js/src/tests/js1_5/extensions/regress-367923.js
js/src/tests/js1_8_5/reflect-parse/declarations.js
js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js
js/src/tests/js1_8_5/regress/regress-610026.js
js/src/tests/shell/warning.js
js/src/vm/ArgumentsObject-inl.h
js/src/vm/ArgumentsObject.cpp
js/src/vm/ArgumentsObject.h
js/src/vm/Caches.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/EnvironmentObject-inl.h
js/src/vm/EnvironmentObject.cpp
js/src/vm/EnvironmentObject.h
js/src/vm/GeneratorObject.cpp
js/src/vm/GeneratorObject.h
js/src/vm/GlobalObject.cpp
js/src/vm/GlobalObject.h
js/src/vm/HelperThreads.cpp
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/MemoryMetrics.cpp
js/src/vm/NativeObject.cpp
js/src/vm/Opcodes.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/Scope.cpp
js/src/vm/Scope.h
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/SelfHosting.cpp
js/src/vm/Shape-inl.h
js/src/vm/Shape.h
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
js/src/vm/TypeInference.cpp
js/src/vm/UbiNode.cpp
js/src/vm/Xdr.cpp
js/xpconnect/loader/mozJSComponentLoader.cpp
js/xpconnect/loader/mozJSComponentLoader.h
js/xpconnect/loader/mozJSSubScriptLoader.cpp
js/xpconnect/src/XPCShellImpl.cpp
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js
@@ -48,25 +48,23 @@ function test() {
 
     let oncePaused = gTarget.once("thread-paused");
     EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
     yield oncePaused;
 
     yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
     let variables = gDebugger.DebuggerView.Variables;
 
-    is(variables._store.length, 4, "Correct number of scopes available");
+    is(variables._store.length, 3, "Correct number of scopes available");
     is(variables.getScopeAtIndex(0).name, "Function scope [interval<]",
         "Paused with correct scope (0)");
     is(variables.getScopeAtIndex(1).name, "Block scope",
         "Paused with correct scope (1)");
-    is(variables.getScopeAtIndex(2).name, "Block scope",
+    is(variables.getScopeAtIndex(2).name, "Global scope [Window]",
         "Paused with correct scope (2)");
-    is(variables.getScopeAtIndex(3).name, "Global scope [Window]",
-        "Paused with correct scope (3)");
 
     yield evalInTab(gTab, "clearInterval(interval)");
     let onceResumed = gTarget.once("thread-resumed");
     EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
     yield onceResumed;
   });
 
   let testEvent = Task.async(function* () {
--- a/devtools/server/tests/unit/test_framebindings-02.js
+++ b/devtools/server/tests/unit/test_framebindings-02.js
@@ -31,18 +31,18 @@ function test_pause_frame()
     let args = bindings.arguments;
     let vars = bindings.variables;
     do_check_neq(parentEnv, undefined);
     do_check_eq(args.length, 0);
     do_check_eq(vars.stopMe.value.type, "object");
     do_check_eq(vars.stopMe.value.class, "Function");
     do_check_true(!!vars.stopMe.value.actor);
 
-    // Skip both the eval lexical scope and the global lexical scope.
-    parentEnv = parentEnv.parent.parent.parent;
+    // Skip the global lexical scope.
+    parentEnv = parentEnv.parent.parent;
     do_check_neq(parentEnv, undefined);
     let objClient = gThreadClient.pauseGrip(parentEnv.object);
     objClient.getPrototypeAndProperties(function (aResponse) {
       do_check_eq(aResponse.ownProperties.Object.value.type, "object");
       do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
       do_check_true(!!aResponse.ownProperties.Object.value.actor);
 
       gThreadClient.resume(function () {
--- a/devtools/server/tests/unit/test_framebindings-05.js
+++ b/devtools/server/tests/unit/test_framebindings-05.js
@@ -31,18 +31,18 @@ function test_pause_frame()
 
     let objClient = gThreadClient.pauseGrip(env.object);
     objClient.getPrototypeAndProperties(function (aResponse) {
       do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
       do_check_eq(aResponse.ownProperties.cos.value.type, "object");
       do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
       do_check_true(!!aResponse.ownProperties.cos.value.actor);
 
-      // Skip both the eval lexical scope and the global lexical scope.
-      let parentEnv = env.parent.parent.parent;
+      // Skip the global lexical scope.
+      let parentEnv = env.parent.parent;
       do_check_neq(parentEnv, undefined);
 
       let parentClient = gThreadClient.pauseGrip(parentEnv.object);
       parentClient.getPrototypeAndProperties(function (aResponse) {
         do_check_eq(aResponse.ownProperties.a.value, Math.PI * 100);
         do_check_eq(aResponse.ownProperties.r.value, 10);
         do_check_eq(aResponse.ownProperties.Object.value.type, "object");
         do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -782,17 +782,17 @@ struct JSClass {
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
 // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at
 // the beginning of every global object's slots for use by the
 // application.
 #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
 #define JSCLASS_GLOBAL_SLOT_COUNT                                             \
-    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 36)
+    (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 37)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
 
--- a/js/public/GCHashTable.h
+++ b/js/public/GCHashTable.h
@@ -232,17 +232,17 @@ class WeakCacheBase<JS::GCHashMap<A,B,C,
 {};
 
 } // namespace js
 
 namespace JS {
 
 // A GCHashSet is a HashSet with an additional trace method that knows
 // be traced to be kept alive will generally want to use this GCHashSet
-// specializeation in lieu of HashSet.
+// specialization in lieu of HashSet.
 //
 // Most types of GC pointers can be traced with no extra infrastructure. For
 // structs and non-gc-pointer members, ensure that there is a specialization of
 // GCPolicy<T> with an appropriate trace method available to handle the custom
 // type. Generic helpers can be found in js/public/TracingAPI.h.
 //
 // Note that although this HashSet's trace will deal correctly with moved
 // elements, it does not itself know when to barrier or trace elements. To
--- a/js/public/GCVariant.h
+++ b/js/public/GCVariant.h
@@ -43,17 +43,17 @@ struct GCVariantImplementation;
 
 // The base case.
 template <typename T>
 struct GCVariantImplementation<T>
 {
     template <typename ConcreteVariant>
     static void trace(JSTracer* trc, ConcreteVariant* v, const char* name) {
         T& thing = v->template as<T>();
-        if (thing)
+        if (!mozilla::IsPointer<T>::value || thing)
             GCPolicy<T>::trace(trc, &thing, name);
     }
 
     template <typename Matcher, typename ConcreteVariant>
     static typename Matcher::ReturnType
     match(Matcher& matcher, Handle<ConcreteVariant> v) {
         const T& thing = v.get().template as<T>();
         return matcher.match(Handle<T>::fromMarkedLocation(&thing));
@@ -72,17 +72,17 @@ template <typename T, typename... Ts>
 struct GCVariantImplementation<T, Ts...>
 {
     using Next = GCVariantImplementation<Ts...>;
 
     template <typename ConcreteVariant>
     static void trace(JSTracer* trc, ConcreteVariant* v, const char* name) {
         if (v->template is<T>()) {
             T& thing = v->template as<T>();
-            if (thing)
+            if (!mozilla::IsPointer<T>::value || thing)
                 GCPolicy<T>::trace(trc, &thing, name);
         } else {
             Next::trace(trc, v, name);
         }
     }
 
     template <typename Matcher, typename ConcreteVariant>
     static typename Matcher::ReturnType
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -754,16 +754,17 @@ struct CompartmentStats
     macro(Other,   MallocHeap, compartmentObject) \
     macro(Other,   MallocHeap, compartmentTables) \
     macro(Other,   MallocHeap, innerViewsTable) \
     macro(Other,   MallocHeap, lazyArrayBuffersTable) \
     macro(Other,   MallocHeap, objectMetadataTable) \
     macro(Other,   MallocHeap, crossCompartmentWrappersTable) \
     macro(Other,   MallocHeap, regexpCompartment) \
     macro(Other,   MallocHeap, savedStacksSet) \
+    macro(Other,   MallocHeap, varNamesSet) \
     macro(Other,   MallocHeap, nonSyntacticLexicalScopesTable) \
     macro(Other,   MallocHeap, jitCompartment) \
     macro(Other,   MallocHeap, privateData)
 
     CompartmentStats()
       : FOR_EACH_SIZE(ZERO_SIZE)
         classInfo(),
         extra(),
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -16,16 +16,17 @@
 
 #include "jspubtd.h"
 
 #include "js/GCAnnotations.h"
 #include "js/GCAPI.h"
 #include "js/GCPolicyAPI.h"
 #include "js/HeapAPI.h"
 #include "js/TypeDecls.h"
+#include "js/UniquePtr.h"
 #include "js/Utility.h"
 
 /*
  * Moving GC Stack Rooting
  *
  * A moving GC may change the physical location of GC allocated things, even
  * when they are rooted, updating all pointers to the thing to refer to its new
  * location. The GC must therefore know about all live pointers to a thing,
@@ -1118,16 +1119,55 @@ class JS_PUBLIC_API(ObjectPtr)
     JSObject& operator*() const { return *value; }
     JSObject* operator->() const { return value; }
     operator JSObject*() const { return value; }
 };
 
 } /* namespace JS */
 
 namespace js {
+
+template <typename Outer, typename T, typename D>
+class UniquePtrOperations
+{
+    const UniquePtr<T, D>& uniquePtr() const { return static_cast<const Outer*>(this)->get(); }
+
+  public:
+    explicit operator bool() const { return !!uniquePtr(); }
+};
+
+template <typename Outer, typename T, typename D>
+class MutableUniquePtrOperations : public UniquePtrOperations<Outer, T, D>
+{
+    UniquePtr<T, D>& uniquePtr() { return static_cast<Outer*>(this)->get(); }
+
+  public:
+    MOZ_MUST_USE typename UniquePtr<T, D>::Pointer release() { return uniquePtr().release(); }
+};
+
+template <typename T, typename D>
+class RootedBase<UniquePtr<T, D>>
+  : public MutableUniquePtrOperations<JS::Rooted<UniquePtr<T, D>>, T, D>
+{ };
+
+template <typename T, typename D>
+class MutableHandleBase<UniquePtr<T, D>>
+  : public MutableUniquePtrOperations<JS::MutableHandle<UniquePtr<T, D>>, T, D>
+{ };
+
+template <typename T, typename D>
+class HandleBase<UniquePtr<T, D>>
+  : public UniquePtrOperations<JS::Handle<UniquePtr<T, D>>, T, D>
+{ };
+
+template <typename T, typename D>
+class PersistentRootedBase<UniquePtr<T, D>>
+  : public MutableUniquePtrOperations<JS::PersistentRooted<UniquePtr<T, D>>, T, D>
+{ };
+
 namespace gc {
 
 template <typename T, typename TraceCallbacks>
 void
 CallTraceCallbackOnNonHeap(T* v, const TraceCallbacks& aCallbacks, const char* aName, void* aClosure)
 {
     static_assert(sizeof(T) == sizeof(JS::Heap<T>), "T and Heap<T> must be compatible.");
     MOZ_ASSERT(v);
--- a/js/public/TraceKind.h
+++ b/js/public/TraceKind.h
@@ -12,16 +12,17 @@
 #include "js/TypeDecls.h"
 
 // Forward declarations of all the types a TraceKind can denote.
 namespace js {
 class BaseShape;
 class LazyScript;
 class ObjectGroup;
 class Shape;
+class Scope;
 namespace jit {
 class JitCode;
 } // namespace jit
 } // namespace js
 
 namespace JS {
 
 // When tracing a thing, the GC needs to know about the layout of the object it
@@ -50,38 +51,41 @@ enum class TraceKind
     ObjectGroup = 0x05,
 
     // The kind associated with a nullptr.
     Null = 0x06,
 
     // The following kinds do not have an exposed C++ idiom.
     BaseShape = 0x0F,
     JitCode = 0x1F,
-    LazyScript = 0x2F
+    LazyScript = 0x2F,
+    Scope = 0x3F
 };
 const static uintptr_t OutOfLineTraceKindMask = 0x07;
 static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set");
 static_assert(uintptr_t(JS::TraceKind::JitCode) & OutOfLineTraceKindMask, "mask bits are set");
 static_assert(uintptr_t(JS::TraceKind::LazyScript) & OutOfLineTraceKindMask, "mask bits are set");
+static_assert(uintptr_t(JS::TraceKind::Scope) & OutOfLineTraceKindMask, "mask bits are set");
 
 // When this header is imported inside SpiderMonkey, the class definitions are
 // available and we can query those definitions to find the correct kind
 // directly from the class hierarchy.
 template <typename T>
 struct MapTypeToTraceKind {
     static const JS::TraceKind kind = T::TraceKind;
 };
 
 // When this header is used outside SpiderMonkey, the class definitions are not
 // available, so the following table containing all public GC types is used.
 #define JS_FOR_EACH_TRACEKIND(D) \
  /* PrettyName       TypeName           AddToCCKind */ \
     D(BaseShape,     js::BaseShape,     true) \
     D(JitCode,       js::jit::JitCode,  true) \
     D(LazyScript,    js::LazyScript,    true) \
+    D(Scope,         js::Scope,         true) \
     D(Object,        JSObject,          true) \
     D(ObjectGroup,   js::ObjectGroup,   true) \
     D(Script,        JSScript,          true) \
     D(Shape,         js::Shape,         true) \
     D(String,        JSString,          false) \
     D(Symbol,        JS::Symbol,        false)
 
 // Map from all public types to their trace kind.
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -153,16 +153,19 @@ class JS_PUBLIC_API(CallbackTracer) : pu
         onChild(JS::GCCellPtr(*basep, JS::TraceKind::BaseShape));
     }
     virtual void onJitCodeEdge(js::jit::JitCode** codep) {
         onChild(JS::GCCellPtr(*codep, JS::TraceKind::JitCode));
     }
     virtual void onLazyScriptEdge(js::LazyScript** lazyp) {
         onChild(JS::GCCellPtr(*lazyp, JS::TraceKind::LazyScript));
     }
+    virtual void onScopeEdge(js::Scope** scopep) {
+        onChild(JS::GCCellPtr(*scopep, JS::TraceKind::Scope));
+    }
 
     // Override this method to receive notification when a node in the GC
     // heap graph is visited.
     virtual void onChild(const JS::GCCellPtr& thing) = 0;
 
     // Access to the tracing context:
     // When tracing with a JS::CallbackTracer, we invoke the callback with the
     // edge location and the type of target. This is useful for operating on
@@ -222,16 +225,17 @@ class JS_PUBLIC_API(CallbackTracer) : pu
     void dispatchToOnEdge(JSString** strp) { onStringEdge(strp); }
     void dispatchToOnEdge(JS::Symbol** symp) { onSymbolEdge(symp); }
     void dispatchToOnEdge(JSScript** scriptp) { onScriptEdge(scriptp); }
     void dispatchToOnEdge(js::Shape** shapep) { onShapeEdge(shapep); }
     void dispatchToOnEdge(js::ObjectGroup** groupp) { onObjectGroupEdge(groupp); }
     void dispatchToOnEdge(js::BaseShape** basep) { onBaseShapeEdge(basep); }
     void dispatchToOnEdge(js::jit::JitCode** codep) { onJitCodeEdge(codep); }
     void dispatchToOnEdge(js::LazyScript** lazyp) { onLazyScriptEdge(lazyp); }
+    void dispatchToOnEdge(js::Scope** scopep) { onScopeEdge(scopep); }
 
   private:
     friend class AutoTracingName;
     const char* contextName_;
 
     friend class AutoTracingIndex;
     size_t contextIndex_;
 
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -64,16 +64,17 @@ using JS::Float32Value;
 using JS::Int32Value;
 using JS::MagicValue;
 using JS::NullValue;
 using JS::NumberValue;
 using JS::ObjectOrNullValue;
 using JS::ObjectValue;
 using JS::PrivateUint32Value;
 using JS::PrivateValue;
+using JS::PrivateGCThingValue;
 using JS::StringValue;
 using JS::UndefinedValue;
 
 using JS::Latin1Char;
 using JS::Latin1Chars;
 using JS::Latin1CharsZ;
 using JS::ConstTwoByteChars;
 using JS::TwoByteChars;
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -39,17 +39,16 @@
 #include "js/MemoryMetrics.h"
 #include "vm/StringBuffer.h"
 #include "vm/Time.h"
 #include "vm/TypedArrayObject.h"
 
 #include "jsobjinlines.h"
 
 #include "frontend/ParseNode-inl.h"
-#include "frontend/Parser-inl.h"
 #include "vm/ArrayBufferObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CeilingLog2;
@@ -656,20 +655,23 @@ FunctionName(ParseNode* fn)
     if (JSAtom* name = FunctionObject(fn)->name())
         return name->asPropertyName();
     return nullptr;
 }
 
 static inline ParseNode*
 FunctionStatementList(ParseNode* fn)
 {
-    MOZ_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY));
+    MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY));
     ParseNode* last = fn->pn_body->last();
-    MOZ_ASSERT(last->isKind(PNK_STATEMENTLIST));
-    return last;
+    MOZ_ASSERT(last->isKind(PNK_LEXICALSCOPE));
+    MOZ_ASSERT(last->isEmptyScope());
+    ParseNode* body = last->scopeBody();
+    MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST));
+    return body;
 }
 
 static inline bool
 IsNormalObjectField(ExclusiveContext* cx, ParseNode* pn)
 {
     return pn->isKind(PNK_COLON) &&
            pn->getOp() == JSOP_INITPROP &&
            BinaryLeft(pn)->isKind(PNK_OBJECT_PROPERTY_NAME);
@@ -685,26 +687,19 @@ ObjectNormalFieldName(ExclusiveContext* 
 
 static inline ParseNode*
 ObjectNormalFieldInitializer(ExclusiveContext* cx, ParseNode* pn)
 {
     MOZ_ASSERT(IsNormalObjectField(cx, pn));
     return BinaryRight(pn);
 }
 
-static inline bool
-IsDefinition(ParseNode* pn)
-{
-    return pn->isKind(PNK_NAME) && pn->isDefn();
-}
-
 static inline ParseNode*
-MaybeDefinitionInitializer(ParseNode* pn)
-{
-    MOZ_ASSERT(IsDefinition(pn));
+MaybeInitializer(ParseNode* pn)
+{
     return pn->expr();
 }
 
 static inline bool
 IsUseOfName(ParseNode* pn, PropertyName* name)
 {
     return pn->isKind(PNK_NAME) && pn->name() == name;
 }
@@ -1691,17 +1686,18 @@ class MOZ_STACK_CLASS ModuleValidator
 
     bool init() {
         asmJSMetadata_ = cx_->new_<AsmJSMetadata>();
         if (!asmJSMetadata_)
             return false;
 
         asmJSMetadata_->srcStart = moduleFunctionNode_->pn_body->pn_pos.begin;
         asmJSMetadata_->srcBodyStart = parser_.tokenStream.currentToken().pos.end;
-        asmJSMetadata_->strict = parser_.pc->sc->strict() && !parser_.pc->sc->hasExplicitUseStrict();
+        asmJSMetadata_->strict = parser_.pc->sc()->strict() &&
+                                 !parser_.pc->sc()->hasExplicitUseStrict();
         asmJSMetadata_->scriptSource.reset(parser_.ss);
 
         if (!globalMap_.init() || !sigMap_.init() || !importMap_.init())
             return false;
 
         if (!standardLibraryMathNames_.init() ||
             !addStandardLibraryMathName("sin", AsmJSMathBuiltin_sin) ||
             !addStandardLibraryMathName("cos", AsmJSMathBuiltin_cos) ||
@@ -3162,21 +3158,19 @@ CheckFunctionHead(ModuleValidator& m, Pa
         return m.fail(fn, "destructuring args not allowed");
     return true;
 }
 
 static bool
 CheckArgument(ModuleValidator& m, ParseNode* arg, PropertyName** name)
 {
     *name = nullptr;
-    if (!IsDefinition(arg))
-        return m.fail(arg, "duplicate argument name not allowed");
-
-    if (arg->isKind(PNK_ASSIGN))
-        return m.fail(arg, "default arguments not allowed");
+
+    if (!arg->isKind(PNK_NAME))
+        return m.fail(arg, "argument is not a plain name");
 
     if (!CheckIdentifier(m, arg, arg->name()))
         return false;
 
     *name = arg->name();
     return true;
 }
 
@@ -3191,17 +3185,17 @@ CheckModuleArgument(ModuleValidator& m, 
 
     return true;
 }
 
 static bool
 CheckModuleArguments(ModuleValidator& m, ParseNode* fn)
 {
     unsigned numFormals;
-    ParseNode* arg1 = FunctionArgsList(fn, &numFormals);
+    ParseNode* arg1 = FunctionFormalParametersList(fn, &numFormals);
     ParseNode* arg2 = arg1 ? NextNode(arg1) : nullptr;
     ParseNode* arg3 = arg2 ? NextNode(arg2) : nullptr;
 
     if (numFormals > 3)
         return m.fail(fn, "asm.js modules takes at most 3 argument");
 
     PropertyName* arg1Name = nullptr;
     if (arg1 && !CheckModuleArgument(m, arg1, &arg1Name))
@@ -3614,23 +3608,23 @@ CheckGlobalDotImport(ModuleValidator& m,
         return m.failName(base, "expecting SIMD constructor name, got %s", field);
 
     return CheckGlobalSimdOperationImport(m, global, initNode, varName, field);
 }
 
 static bool
 CheckModuleGlobal(ModuleValidator& m, ParseNode* var, bool isConst)
 {
-    if (!IsDefinition(var))
-        return m.fail(var, "import variable names must be unique");
+    if (!var->isKind(PNK_NAME))
+        return m.fail(var, "import variable is not a plain name");
 
     if (!CheckModuleLevelName(m, var, var->name()))
         return false;
 
-    ParseNode* initNode = MaybeDefinitionInitializer(var);
+    ParseNode* initNode = MaybeInitializer(var);
     if (!initNode)
         return m.fail(var, "module import needs initializer");
 
     if (IsNumericLiteral(m, initNode))
         return CheckGlobalVariableInitConstant(m, var->name(), initNode, isConst);
 
     if (initNode->isKind(PNK_BITOR) || initNode->isKind(PNK_POS) || initNode->isKind(PNK_CALL))
         return CheckGlobalVariableInitImport(m, var->name(), initNode, isConst);
@@ -3733,17 +3727,17 @@ CheckProcessingDirectives(ModuleValidato
 }
 
 static bool
 CheckArguments(FunctionValidator& f, ParseNode** stmtIter, ValTypeVector* argTypes)
 {
     ParseNode* stmt = *stmtIter;
 
     unsigned numFormals;
-    ParseNode* argpn = FunctionArgsList(f.fn(), &numFormals);
+    ParseNode* argpn = FunctionFormalParametersList(f.fn(), &numFormals);
 
     for (unsigned i = 0; i < numFormals; i++, argpn = NextNode(argpn), stmt = NextNode(stmt)) {
         PropertyName* name;
         if (!CheckArgument(f.m(), argpn, &name))
             return false;
 
         Type type;
         if (!CheckArgumentType(f, stmt, name, &type))
@@ -3795,25 +3789,25 @@ CheckFinalReturn(FunctionValidator& f, P
         return f.fail(lastNonEmptyStmt, "void incompatible with previous return type");
 
     return true;
 }
 
 static bool
 CheckVariable(FunctionValidator& f, ParseNode* var, ValTypeVector* types, Vector<NumLit>* inits)
 {
-    if (!IsDefinition(var))
-        return f.fail(var, "local variable names must not restate argument names");
+    if (!var->isKind(PNK_NAME))
+        return f.fail(var, "local variable is not a plain name");
 
     PropertyName* name = var->name();
 
     if (!CheckIdentifier(f.m(), var, name))
         return false;
 
-    ParseNode* initNode = MaybeDefinitionInitializer(var);
+    ParseNode* initNode = MaybeInitializer(var);
     if (!initNode)
         return f.failName(var, "var '%s' needs explicit type declaration via an initial value", name);
 
     NumLit lit;
     if (!IsLiteralOrConst(f, initNode, &lit))
         return f.failName(var, "var '%s' initializer must be literal or const literal", name);
 
     if (!lit.valid())
@@ -6706,18 +6700,21 @@ CheckSwitchExpr(FunctionValidator& f, Pa
 static bool
 CheckSwitch(FunctionValidator& f, ParseNode* switchStmt)
 {
     MOZ_ASSERT(switchStmt->isKind(PNK_SWITCH));
 
     ParseNode* switchExpr = BinaryLeft(switchStmt);
     ParseNode* switchBody = BinaryRight(switchStmt);
 
-    if (!switchBody->isKind(PNK_STATEMENTLIST))
-        return f.fail(switchBody, "switch body may not contain 'let' declarations");
+    if (switchBody->isKind(PNK_LEXICALSCOPE)) {
+        if (!switchBody->isEmptyScope())
+            return f.fail(switchBody, "switch body may not contain lexical declarations");
+        switchBody = switchBody->scopeBody();
+    }
 
     ParseNode* stmt = ListHead(switchBody);
     if (!stmt)
         return CheckSwitchExpr(f, switchExpr);
 
     if (!CheckDefaultAtEnd(f, stmt))
         return false;
 
@@ -6882,16 +6879,27 @@ CheckStatementList(FunctionValidator& f,
     }
 
     if (!f.popUnbreakableBlock(labels))
         return false;
     return true;
 }
 
 static bool
+CheckLexicalScope(FunctionValidator& f, ParseNode* lexicalScope)
+{
+    MOZ_ASSERT(lexicalScope->isKind(PNK_LEXICALSCOPE));
+
+    if (!lexicalScope->isEmptyScope())
+        return f.fail(lexicalScope, "cannot have 'let' or 'const' declarations");
+
+    return CheckStatement(f, lexicalScope->scopeBody());
+}
+
+static bool
 CheckBreakOrContinue(FunctionValidator& f, bool isBreak, ParseNode* stmt)
 {
     if (PropertyName* maybeLabel = LoopControlMaybeLabel(stmt))
         return f.writeLabeledBreakOrContinue(maybeLabel, isBreak);
     return f.writeUnlabeledBreakOrContinue(isBreak);
 }
 
 static bool
@@ -6906,16 +6914,17 @@ CheckStatement(FunctionValidator& f, Par
       case PNK_DOWHILE:       return CheckDoWhile(f, stmt);
       case PNK_LABEL:         return CheckLabel(f, stmt);
       case PNK_IF:            return CheckIf(f, stmt);
       case PNK_SWITCH:        return CheckSwitch(f, stmt);
       case PNK_RETURN:        return CheckReturn(f, stmt);
       case PNK_STATEMENTLIST: return CheckStatementList(f, stmt);
       case PNK_BREAK:         return CheckBreakOrContinue(f, true, stmt);
       case PNK_CONTINUE:      return CheckBreakOrContinue(f, false, stmt);
+      case PNK_LEXICALSCOPE:  return CheckLexicalScope(f, stmt);
       default:;
     }
 
     return f.fail(stmt, "unexpected statement kind");
 }
 
 static bool
 ParseFunction(ModuleValidator& m, ParseNode** fnOut, unsigned* line)
@@ -6943,39 +6952,39 @@ ParseFunction(ModuleValidator& m, ParseN
     ParseNode* fn = m.parser().handler.newFunctionDefinition();
     if (!fn)
         return false;
 
     RootedFunction& fun = m.dummyFunction();
     fun->setAtom(name);
     fun->setArgCount(0);
 
-    AsmJSParseContext* outerpc = m.parser().pc;
+    ParseContext* outerpc = m.parser().pc;
     Directives directives(outerpc);
-    FunctionBox* funbox = m.parser().newFunctionBox(fn, fun, outerpc, directives, NotGenerator);
+    FunctionBox* funbox = m.parser().newFunctionBox(fn, fun, directives, NotGenerator,
+                                                    /* tryAnnexB = */ false);
     if (!funbox)
         return false;
+    funbox->initWithEnclosingParseContext(outerpc, frontend::Statement);
 
     Directives newDirectives = directives;
-    AsmJSParseContext funpc(&m.parser(), outerpc, fn, funbox, &newDirectives);
-    if (!funpc.init(m.parser()))
-        return false;
-
-    if (!m.parser().functionArgsAndBodyGeneric(InAllowed, YieldIsName, fn, fun, Statement)) {
+    ParseContext funpc(&m.parser(), funbox, &newDirectives);
+    if (!funpc.init())
+        return false;
+
+    if (!m.parser().functionFormalParametersAndBody(InAllowed, YieldIsName, fn, Statement)) {
         if (tokenStream.hadError() || directives == newDirectives)
             return false;
 
         return m.fail(fn, "encountered new directive in function");
     }
 
     MOZ_ASSERT(!tokenStream.hadError());
     MOZ_ASSERT(directives == newDirectives);
 
-    fn->pn_blockid = outerpc->blockid();
-
     *fnOut = fn;
     return true;
 }
 
 static bool
 CheckFunction(ModuleValidator& m)
 {
     // asm.js modules can be quite large when represented as parse trees so pop
@@ -7061,20 +7070,20 @@ CheckFunctions(ModuleValidator& m)
     }
 
     return CheckAllFunctionsDefined(m);
 }
 
 static bool
 CheckFuncPtrTable(ModuleValidator& m, ParseNode* var)
 {
-    if (!IsDefinition(var))
-        return m.fail(var, "function-pointer table name must be unique");
-
-    ParseNode* arrayLiteral = MaybeDefinitionInitializer(var);
+    if (!var->isKind(PNK_NAME))
+        return m.fail(var, "function-pointer table name is not a plain name");
+
+    ParseNode* arrayLiteral = MaybeInitializer(var);
     if (!arrayLiteral || !arrayLiteral->isKind(PNK_ARRAY))
         return m.fail(var, "function-pointer table's initializer must be an array literal");
 
     unsigned length = ListLength(arrayLiteral);
 
     if (!IsPowerOfTwo(length))
         return m.failf(arrayLiteral, "function-pointer table length must be a power of 2 (is %u)", length);
 
@@ -7208,21 +7217,16 @@ CheckModuleReturn(ModuleValidator& m)
     if (returnExpr->isKind(PNK_OBJECT)) {
         if (!CheckModuleExportObject(m, returnExpr))
             return false;
     } else {
         if (!CheckModuleExportFunction(m, returnExpr))
             return false;
     }
 
-    // Function statements are not added to the lexical scope in ParseContext
-    // (since cx->tempLifoAlloc is marked/released after each function
-    // statement) and thus all the identifiers in the return statement will be
-    // mistaken as free variables and added to lexdeps. Clear these now.
-    m.parser().pc->lexdeps->clear();
     return true;
 }
 
 static bool
 CheckModuleEnd(ModuleValidator &m)
 {
     TokenKind tk;
     if (!GetToken(m.parser(), &tk))
@@ -7235,17 +7239,17 @@ CheckModuleEnd(ModuleValidator &m)
     return true;
 }
 
 static SharedModule
 CheckModule(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* stmtList, unsigned* time)
 {
     int64_t before = PRMJ_Now();
 
-    ParseNode* moduleFunctionNode = parser.pc->maybeFunction;
+    ParseNode* moduleFunctionNode = parser.pc->functionBox()->functionNode;
     MOZ_ASSERT(moduleFunctionNode);
 
     ModuleValidator m(cx, parser, moduleFunctionNode);
     if (!m.init())
         return nullptr;
 
     if (!CheckFunctionHead(m, moduleFunctionNode))
         return nullptr;
@@ -8133,17 +8137,17 @@ namespace {
 class ModuleChars
 {
   protected:
     uint32_t isFunCtor_;
     Vector<CacheableChars, 0, SystemAllocPolicy> funCtorArgs_;
 
   public:
     static uint32_t beginOffset(AsmJSParser& parser) {
-        return parser.pc->maybeFunction->pn_pos.begin;
+        return parser.pc->functionBox()->functionNode->pn_pos.begin;
     }
 
     static uint32_t endOffset(AsmJSParser& parser) {
         TokenPos pos(0, 0);  // initialize to silence GCC warning
         MOZ_ALWAYS_TRUE(parser.tokenStream.peekTokenPos(&pos, TokenStream::Operand));
         return pos.end;
     }
 };
@@ -8181,20 +8185,21 @@ class ModuleCharsForStore : ModuleChars
         // An unnamed function expression captures the same thing, sans 'f'.
         // Since asm.js modules do not contain any free variables, equality of
         // [beginOffset, endOffset) is sufficient to guarantee identical code
         // generation, modulo Assumptions.
         //
         // For functions created with 'new Function', function arguments are
         // not present in the source so we must manually explicitly serialize
         // and match the formals as a Vector of PropertyName.
-        isFunCtor_ = parser.pc->isFunctionConstructorBody();
+        isFunCtor_ = parser.pc->isStandaloneFunctionBody();
         if (isFunCtor_) {
             unsigned numArgs;
-            ParseNode* arg = FunctionArgsList(parser.pc->maybeFunction, &numArgs);
+            ParseNode* functionNode = parser.pc->functionBox()->functionNode;
+            ParseNode* arg = FunctionFormalParametersList(functionNode, &numArgs);
             for (unsigned i = 0; i < numArgs; i++, arg = arg->pn_next) {
                 UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->name());
                 if (!name || !funCtorArgs_.append(Move(name)))
                     return false;
             }
         }
 
         return true;
@@ -8251,30 +8256,31 @@ class ModuleCharsForLookup : ModuleChars
     bool match(AsmJSParser& parser) const {
         const char16_t* parseBegin = parser.tokenStream.rawCharPtrAt(beginOffset(parser));
         const char16_t* parseLimit = parser.tokenStream.rawLimit();
         MOZ_ASSERT(parseLimit >= parseBegin);
         if (uint32_t(parseLimit - parseBegin) < chars_.length())
             return false;
         if (!PodEqual(chars_.begin(), parseBegin, chars_.length()))
             return false;
-        if (isFunCtor_ != parser.pc->isFunctionConstructorBody())
+        if (isFunCtor_ != parser.pc->isStandaloneFunctionBody())
             return false;
         if (isFunCtor_) {
             // For function statements, the closing } is included as the last
             // character of the matched source. For Function constructor,
             // parsing terminates with EOF which we must explicitly check. This
             // prevents
             //   new Function('"use asm"; function f() {} return f')
             // from incorrectly matching
             //   new Function('"use asm"; function f() {} return ff')
             if (parseBegin + chars_.length() != parseLimit)
                 return false;
             unsigned numArgs;
-            ParseNode* arg = FunctionArgsList(parser.pc->maybeFunction, &numArgs);
+            ParseNode* functionNode = parser.pc->functionBox()->functionNode;
+            ParseNode* arg = FunctionFormalParametersList(functionNode, &numArgs);
             if (funCtorArgs_.length() != numArgs)
                 return false;
             for (unsigned i = 0; i < funCtorArgs_.length(); i++, arg = arg->pn_next) {
                 UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->name());
                 if (!name || strcmp(funCtorArgs_[i].get(), name.get()))
                     return false;
             }
         }
@@ -8382,19 +8388,19 @@ LookupAsmJSModuleInCache(ExclusiveContex
 
     cursor = Module::deserialize(cursor, module, asmJSMetadata.get());
     if (!cursor) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     // See AsmJSMetadata comment as well as ModuleValidator::init().
-    asmJSMetadata->srcStart = parser.pc->maybeFunction->pn_body->pn_pos.begin;
+    asmJSMetadata->srcStart = parser.pc->functionBox()->functionNode->pn_body->pn_pos.begin;
     asmJSMetadata->srcBodyStart = parser.tokenStream.currentToken().pos.end;
-    asmJSMetadata->strict = parser.pc->sc->strict() && !parser.pc->sc->hasExplicitUseStrict();
+    asmJSMetadata->strict = parser.pc->sc()->strict() && !parser.pc->sc()->hasExplicitUseStrict();
     asmJSMetadata->scriptSource.reset(parser.ss);
 
     bool atEnd = cursor == entry.memory + entry.serializedSize;
     MOZ_ASSERT(atEnd, "Corrupt cache file");
     if (!atEnd)
         return true;
 
     Assumptions assumptions;
@@ -8552,17 +8558,17 @@ js::CompileAsmJS(ExclusiveContext* cx, A
     // Hand over ownership to a GC object wrapper which can then be referenced
     // from the module function.
     Rooted<WasmModuleObject*> moduleObj(cx, WasmModuleObject::create(cx, *module));
     if (!moduleObj)
         return false;
 
     // The module function dynamically links the AsmJSModule when called and
     // generates a set of functions wrapping all the exports.
-    FunctionBox* funbox = parser.pc->maybeFunction->pn_funbox;
+    FunctionBox* funbox = parser.pc->functionBox();
     RootedFunction moduleFun(cx, NewAsmJSModuleFunction(cx, funbox->function(), moduleObj));
     if (!moduleFun)
         return false;
 
     // Finished! Clobber the default function created by the parser with the new
     // asm.js module function. Special cases in the bytecode emitter avoid
     // generating bytecode for asm.js functions, allowing this asm.js module
     // function to be the finished result.
--- a/js/src/asmjs/AsmJS.h
+++ b/js/src/asmjs/AsmJS.h
@@ -21,22 +21,21 @@
 
 #include "NamespaceImports.h"
 
 namespace js {
 
 class ExclusiveContext;
 namespace frontend {
     template <typename ParseHandler> class Parser;
-    template <typename ParseHandler> struct ParseContext;
+    class ParseContext;
     class FullParseHandler;
     class ParseNode;
 }
 typedef frontend::Parser<frontend::FullParseHandler> AsmJSParser;
-typedef frontend::ParseContext<frontend::FullParseHandler> AsmJSParseContext;
 
 // This function takes over parsing of a function starting with "use asm". The
 // return value indicates whether an error was reported which the caller should
 // propagate. If no error was reported, the function may still fail to validate
 // as asm.js. In this case, the parser.tokenStream has been advanced an
 // indeterminate amount and the entire function should be reparsed from the
 // beginning.
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -5,52 +5,52 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "builtin/Eval.h"
 
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Range.h"
 
 #include "jscntxt.h"
+#include "jshashutil.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "vm/Debugger.h"
 #include "vm/GlobalObject.h"
 #include "vm/JSONParser.h"
 
 #include "vm/Interpreter-inl.h"
 
 using namespace js;
 
 using mozilla::AddToHash;
 using mozilla::HashString;
 using mozilla::RangedPtr;
 
 using JS::AutoCheckCannotGC;
 
-// We should be able to assert this for *any* fp->scopeChain().
+// We should be able to assert this for *any* fp->environmentChain().
 static void
-AssertInnerizedScopeChain(JSContext* cx, JSObject& scopeobj)
+AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env)
 {
 #ifdef DEBUG
     RootedObject obj(cx);
-    for (obj = &scopeobj; obj; obj = obj->enclosingScope())
+    for (obj = &env; obj; obj = obj->enclosingEnvironment())
         MOZ_ASSERT(!IsWindowProxy(obj));
 #endif
 }
 
 static bool
 IsEvalCacheCandidate(JSScript* script)
 {
     // Make sure there are no inner objects which might use the wrong parent
-    // and/or call scope by reusing the previous eval's script. Skip the
-    // script's first object, which entrains the eval's scope.
-    return script->savedCallerFun() &&
+    // and/or call scope by reusing the previous eval's script.
+    return script->isDirectEvalInFunction() &&
            !script->hasSingletons() &&
-           script->objects()->length == 1;
+           !script->hasObjects();
 }
 
 /* static */ HashNumber
 EvalCacheHashPolicy::hash(const EvalCacheLookup& l)
 {
     AutoCheckCannotGC nogc;
     uint32_t hash = l.str->hasLatin1Chars()
                     ? HashString(l.str->latin1Chars(nogc), l.str->length())
@@ -74,53 +74,48 @@ EvalCacheHashPolicy::match(const EvalCac
 // Add the script to the eval cache when EvalKernel is finished
 class EvalScriptGuard
 {
     JSContext* cx_;
     Rooted<JSScript*> script_;
 
     /* These fields are only valid if lookup_.str is non-nullptr. */
     EvalCacheLookup lookup_;
-    EvalCache::AddPtr p_;
-    uint64_t originalGcNumber_;
+    mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
 
     RootedLinearString lookupStr_;
 
   public:
     explicit EvalScriptGuard(JSContext* cx)
         : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
 
     ~EvalScriptGuard() {
-        if (script_) {
+        if (script_ && !cx_->isExceptionPending()) {
             script_->cacheForEval();
             EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript, lookup_.pc};
             lookup_.str = lookupStr_;
             if (lookup_.str && IsEvalCacheCandidate(script_)) {
-                bool gcHappened = originalGcNumber_ != cx_->zone()->gcNumber();
-                auto& cache = cx_->caches.evalCache;
-                if (gcHappened)
-                    p_ = cache.lookupForAdd(lookup_);
-                bool ok = cache.relookupOrAdd(p_, lookup_, cacheEntry);
-                (void)ok; // Ignore failure to add cache entry.
+                // Ignore failure to add cache entry.
+                if (!p_->add(cx_, cx_->caches.evalCache, lookup_, cacheEntry))
+                    cx_->recoverFromOutOfMemory();
             }
         }
     }
 
     void lookupInEvalCache(JSLinearString* str, JSScript* callerScript, jsbytecode* pc)
     {
         lookupStr_ = str;
         lookup_.str = str;
         lookup_.callerScript = callerScript;
         lookup_.version = cx_->findVersion();
         lookup_.pc = pc;
-        p_ = cx_->caches.evalCache.lookupForAdd(lookup_);
-        originalGcNumber_ = cx_->zone()->gcNumber();
-        if (p_) {
-            script_ = p_->script;
-            cx_->caches.evalCache.remove(p_);
+        p_.emplace(cx_, cx_->caches.evalCache, lookup_);
+        if (*p_) {
+            script_ = (*p_)->script;
+            p_->remove(cx_, cx_->caches.evalCache, lookup_);
             script_->uncacheForEval();
         }
     }
 
     void setNewScript(JSScript* script) {
         // JSScript::initFromEmitter has already called js_CallNewScriptHook.
         MOZ_ASSERT(!script_ && script);
         script_ = script;
@@ -220,31 +215,31 @@ TryEvalJSON(JSContext* cx, JSLinearStrin
 }
 
 enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
 
 // Common code implementing direct and indirect eval.
 //
 // Evaluate call.argv[2], if it is a string, in the context of the given calling
 // frame, with the provided scope chain, with the semantics of either a direct
-// or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
+// or indirect eval (see ES5 10.4.2).  If this is an indirect eval, env
 // must be a global object.
 //
 // On success, store the completion value in call.rval and return true.
 static bool
 EvalKernel(JSContext* cx, HandleValue v, EvalType evalType, AbstractFramePtr caller,
-           HandleObject scopeobj, jsbytecode* pc, MutableHandleValue vp)
+           HandleObject env, jsbytecode* pc, MutableHandleValue vp)
 {
     MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
     MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
-    MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalScope(scopeobj));
-    AssertInnerizedScopeChain(cx, *scopeobj);
+    MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
+    AssertInnerizedEnvironmentChain(cx, *env);
 
-    Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeobj->global());
-    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
+    Rooted<GlobalObject*> envGlobal(cx, &env->global());
+    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
     // ES5 15.1.2.1 step 1.
     if (!v.isString()) {
         vp.set(v);
         return true;
@@ -252,17 +247,17 @@ EvalKernel(JSContext* cx, HandleValue v,
     RootedString str(cx, v.toString());
 
     // ES5 15.1.2.1 steps 2-8.
 
     // Per ES5, indirect eval runs in the global scope. (eval is specified this
     // way so that the compiler can make assumptions about what bindings may or
     // may not exist in the current frame if it doesn't see 'eval'.)
     MOZ_ASSERT_IF(evalType != DIRECT_EVAL,
-                  cx->global() == &scopeobj->as<ClonedBlockObject>().global());
+                  cx->global() == &env->as<LexicalEnvironmentObject>().global());
 
     RootedLinearString linearStr(cx, str->ensureLinear(cx));
     if (!linearStr)
         return false;
 
     RootedScript callerScript(cx, caller ? caller.script() : nullptr);
     EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
     if (ejr != EvalJSON_NotJSON)
@@ -284,28 +279,24 @@ EvalKernel(JSContext* cx, HandleValue v,
                                              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);
+        RootedScope enclosing(cx);
         if (evalType == DIRECT_EVAL)
-            enclosing = callerScript->innermostStaticScope(pc);
+            enclosing = callerScript->innermostScope(pc);
         else
-            enclosing = &cx->global()->lexicalScope().staticBlock();
-        Rooted<StaticEvalScope*> staticScope(cx, StaticEvalScope::create(cx, enclosing));
-        if (!staticScope)
-            return false;
+            enclosing = &cx->global()->emptyGlobalScope();
 
         CompileOptions options(cx);
         options.setIsRunOnce(true)
-               .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
 
         if (introducerFilename) {
             options.setFileAndLine(filename, 1);
             options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
         } else {
@@ -317,44 +308,41 @@ EvalKernel(JSContext* cx, HandleValue v,
         if (!linearChars.initTwoByte(cx, linearStr))
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
-        JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, staticScope, callerScript,
-                                                     options, srcBuf, linearStr);
+        JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
+                                                         env, enclosing,
+                                                         options, srcBuf);
         if (!compiled)
             return false;
 
-        if (compiled->strict())
-            staticScope->setStrict();
-
         esg.setNewScript(compiled);
     }
 
     // Look up the newTarget from the frame iterator.
     Value newTargetVal = NullValue();
-    return ExecuteKernel(cx, esg.script(), *scopeobj, newTargetVal,
+    return ExecuteKernel(cx, esg.script(), *env, newTargetVal,
                          NullFramePtr() /* evalInFrame */, vp.address());
 }
 
 bool
 js::DirectEvalStringFromIon(JSContext* cx,
-                            HandleObject scopeObj, HandleScript callerScript,
+                            HandleObject env, HandleScript callerScript,
                             HandleValue newTargetValue, HandleString str,
                             jsbytecode* pc, MutableHandleValue vp)
 {
-    AssertInnerizedScopeChain(cx, *scopeObj);
+    AssertInnerizedEnvironmentChain(cx, *env);
 
-    Rooted<GlobalObject*> scopeObjGlobal(cx, &scopeObj->global());
-    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, scopeObjGlobal)) {
+    Rooted<GlobalObject*> envGlobal(cx, &env->global());
+    if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
     // ES5 15.1.2.1 steps 2-8.
 
     RootedLinearString linearStr(cx, str->ensureLinear(cx));
     if (!linearStr)
@@ -376,24 +364,20 @@ 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<StaticEvalScope*> staticScope(cx, StaticEvalScope::create(cx, enclosing));
-        if (!staticScope)
-            return false;
+        RootedScope enclosing(cx, callerScript->innermostScope(pc));
 
         CompileOptions options(cx);
         options.setIsRunOnce(true)
-               .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .maybeMakeStrictMode(IsStrictEvalPC(pc));
 
         if (introducerFilename) {
             options.setFileAndLine(filename, 1);
             options.setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset);
         } else {
@@ -405,39 +389,36 @@ js::DirectEvalStringFromIon(JSContext* c
         if (!linearChars.initTwoByte(cx, linearStr))
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
-        JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeObj, staticScope, callerScript,
-                                                     options, srcBuf, linearStr);
+        JSScript* compiled = frontend::CompileEvalScript(cx, cx->tempLifoAlloc(),
+                                                         env, enclosing,
+                                                         options, srcBuf);
         if (!compiled)
             return false;
 
-        if (compiled->strict())
-            staticScope->setStrict();
-
         esg.setNewScript(compiled);
     }
 
-    return ExecuteKernel(cx, esg.script(), *scopeObj, newTargetValue,
+    return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
                          NullFramePtr() /* evalInFrame */, vp.address());
 }
 
 bool
 js::IndirectEval(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     Rooted<GlobalObject*> global(cx, &args.callee().global());
-    RootedObject globalLexical(cx, &global->lexicalScope());
+    RootedObject globalLexical(cx, &global->lexicalEnvironment());
 
     // Note we'll just pass |undefined| here, then return it directly (or throw
     // if runtime codegen is disabled), if no argument is provided.
     return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(), globalLexical, nullptr,
                       args.rval());
 }
 
 bool
@@ -448,63 +429,57 @@ js::DirectEval(JSContext* cx, HandleValu
     AbstractFramePtr caller = iter.abstractFramePtr();
 
     MOZ_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
                JSOp(*iter.pc()) == JSOP_STRICTEVAL ||
                JSOp(*iter.pc()) == JSOP_SPREADEVAL ||
                JSOp(*iter.pc()) == JSOP_STRICTSPREADEVAL);
     MOZ_ASSERT(caller.compartment() == caller.script()->compartment());
 
-    RootedObject scopeChain(cx, caller.scopeChain());
-    return EvalKernel(cx, v, DIRECT_EVAL, caller, scopeChain, iter.pc(), vp);
+    RootedObject envChain(cx, caller.environmentChain());
+    return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
 }
 
 bool
 js::IsAnyBuiltinEval(JSFunction* fun)
 {
     return fun->maybeNative() == IndirectEval;
 }
 
 JS_FRIEND_API(bool)
 js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
-                                  MutableHandleObject scopeArg)
+                                  MutableHandleObject envArg)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, global);
     MOZ_ASSERT(global->is<GlobalObject>());
     MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
 
     RootedScript script(cx, scriptArg);
     Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
     if (script->compartment() != cx->compartment()) {
-        Rooted<StaticScope*> staticScope(cx, &globalRoot->lexicalScope().staticBlock());
-        staticScope = StaticNonSyntacticScope::create(cx, staticScope);
-        if (!staticScope)
-            return false;
-        script = CloneGlobalScript(cx, staticScope, script);
+        script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
         if (!script)
             return false;
 
         Debugger::onNewScript(cx, script);
     }
 
-    Rooted<ClonedBlockObject*> globalLexical(cx, &globalRoot->lexicalScope());
-    Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalLexical));
-    if (!scope)
+    Rooted<EnvironmentObject*> env(cx, NonSyntacticVariablesObject::create(cx));
+    if (!env)
         return false;
 
     // Unlike the non-syntactic scope chain API used by the subscript loader,
     // this API creates a fresh block scope each time.
-    RootedObject enclosingStaticScope(cx, script->enclosingStaticScope());
-    scope = ClonedBlockObject::createNonSyntactic(cx, enclosingStaticScope, scope);
-    if (!scope)
+    env = LexicalEnvironmentObject::createNonSyntactic(cx, env);
+    if (!env)
         return false;
 
     RootedValue rval(cx);
-    if (!ExecuteKernel(cx, script, *scope, UndefinedValue(),
+    if (!ExecuteKernel(cx, script, *env, UndefinedValue(),
                        NullFramePtr() /* evalInFrame */, rval.address()))
     {
         return false;
     }
 
-    scopeArg.set(scope);
+    envArg.set(env);
     return true;
 }
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -584,25 +584,24 @@ DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject,
 
 /* static */ bool
 ModuleObject::isInstance(HandleValue value)
 {
     return value.isObject() && value.toObject().is<ModuleObject>();
 }
 
 /* static */ ModuleObject*
-ModuleObject::create(ExclusiveContext* cx, HandleObject enclosingStaticScope)
+ModuleObject::create(ExclusiveContext* cx)
 {
     RootedObject proto(cx, cx->global()->getModulePrototype());
     RootedObject obj(cx, NewObjectWithGivenProto(cx, &class_, proto));
     if (!obj)
         return nullptr;
 
     RootedModuleObject self(cx, &obj->as<ModuleObject>());
-    self->initReservedSlot(StaticScopeSlot, ObjectOrNullValue(enclosingStaticScope));
 
     Zone* zone = cx->zone();
     IndirectBindingMap* bindings = zone->new_<IndirectBindingMap>(zone);
     if (!bindings || !bindings->init()) {
         ReportOutOfMemory(cx);
         js_delete<IndirectBindingMap>(bindings);
         return nullptr;
     }
@@ -767,28 +766,25 @@ ModuleObject::IsFrozen(JSContext* cx, Ha
            IsObjectFrozen(cx, self);
 }
 
 #endif
 
 inline static void
 AssertModuleScopesMatch(ModuleObject* module)
 {
-    MOZ_ASSERT(IsStaticGlobalLexicalScope(module->enclosingStaticScope()));
-    MOZ_ASSERT(module->initialEnvironment().enclosingScope().as<ClonedBlockObject>().staticScope() ==
-               module->enclosingStaticScope());
+    MOZ_ASSERT(module->enclosingScope()->is<GlobalScope>());
+    MOZ_ASSERT(IsGlobalLexicalEnvironment(&module->initialEnvironment().enclosingEnvironment()));
 }
 
 void
-ModuleObject::fixScopesAfterCompartmentMerge(JSContext* cx)
+ModuleObject::fixEnvironmentsAfterCompartmentMerge(JSContext* cx)
 {
     AssertModuleScopesMatch(this);
-    Rooted<ClonedBlockObject*> lexicalScope(cx, &script()->global().lexicalScope());
-    setReservedSlot(StaticScopeSlot, ObjectValue(lexicalScope->staticBlock()));
-    initialEnvironment().setEnclosingScope(lexicalScope);
+    initialEnvironment().fixEnclosingEnvironmentAfterCompartmentMerge(script()->global());
     AssertModuleScopesMatch(this);
 }
 
 bool
 ModuleObject::hasScript() const
 {
     // When modules are parsed via the Reflect.parse() API, the module object
     // doesn't have a script.
@@ -828,20 +824,20 @@ ModuleObject::setHostDefinedField(JS::Va
 }
 
 ModuleEnvironmentObject&
 ModuleObject::initialEnvironment() const
 {
     return getReservedSlot(InitialEnvironmentSlot).toObject().as<ModuleEnvironmentObject>();
 }
 
-JSObject*
-ModuleObject::enclosingStaticScope() const
+Scope*
+ModuleObject::enclosingScope() const
 {
-    return getReservedSlot(StaticScopeSlot).toObjectOrNull();
+    return script()->enclosingScope();
 }
 
 /* static */ void
 ModuleObject::trace(JSTracer* trc, JSObject* obj)
 {
     ModuleObject& module = obj->as<ModuleObject>();
     if (module.hasScript()) {
         JSScript* script = module.script();
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -203,17 +203,16 @@ using FunctionDeclarationVector = GCVect
 using ModuleState = int32_t;
 
 class ModuleObject : public NativeObject
 {
   public:
     enum
     {
         ScriptSlot = 0,
-        StaticScopeSlot,
         InitialEnvironmentSlot,
         EnvironmentSlot,
         NamespaceSlot,
         StateSlot,
         HostDefinedSlot,
         RequestedModulesSlot,
         ImportEntriesSlot,
         LocalExportEntriesSlot,
@@ -228,32 +227,32 @@ class ModuleObject : public NativeObject
 
     static_assert(EnvironmentSlot == MODULE_OBJECT_ENVIRONMENT_SLOT,
                   "EnvironmentSlot must match self-hosting define");
 
     static const Class class_;
 
     static bool isInstance(HandleValue value);
 
-    static ModuleObject* create(ExclusiveContext* cx, HandleObject enclosingStaticScope);
+    static ModuleObject* create(ExclusiveContext* cx);
     void init(HandleScript script);
     void setInitialEnvironment(Handle<ModuleEnvironmentObject*> initialEnvironment);
     void initImportExportData(HandleArrayObject requestedModules,
                               HandleArrayObject importEntries,
                               HandleArrayObject localExportEntries,
                               HandleArrayObject indiretExportEntries,
                               HandleArrayObject starExportEntries);
     static bool Freeze(JSContext* cx, HandleModuleObject self);
 #ifdef DEBUG
     static bool IsFrozen(JSContext* cx, HandleModuleObject self);
 #endif
-    void fixScopesAfterCompartmentMerge(JSContext* cx);
+    void fixEnvironmentsAfterCompartmentMerge(JSContext* cx);
 
     JSScript* script() const;
-    JSObject* enclosingStaticScope() const;
+    Scope* enclosingScope() const;
     ModuleEnvironmentObject& initialEnvironment() const;
     ModuleEnvironmentObject* environment() const;
     ModuleNamespaceObject* namespace_();
     ModuleState state() const;
     Value hostDefinedField() const;
     ArrayObject& requestedModules() const;
     ArrayObject& importEntries() const;
     ArrayObject& localExportEntries() const;
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -524,18 +524,16 @@ class NodeBuilder
     bool switchStatement(HandleValue disc, NodeVector& elts, bool lexical, TokenPos* pos,
                          MutableHandleValue dst);
 
     bool tryStatement(HandleValue body, NodeVector& guarded, HandleValue unguarded,
                       HandleValue finally, TokenPos* pos, MutableHandleValue dst);
 
     bool debuggerStatement(TokenPos* pos, MutableHandleValue dst);
 
-    bool letStatement(NodeVector& head, HandleValue stmt, TokenPos* pos, MutableHandleValue dst);
-
     bool importDeclaration(NodeVector& elts, HandleValue moduleSpec, TokenPos* pos, MutableHandleValue dst);
 
     bool importSpecifier(HandleValue importName, HandleValue bindingName, TokenPos* pos, MutableHandleValue dst);
 
     bool exportDeclaration(HandleValue decl, NodeVector& elts, HandleValue moduleSpec,
                            HandleValue isDefault, TokenPos* pos, MutableHandleValue dst);
 
     bool exportSpecifier(HandleValue bindingName, HandleValue exportName, TokenPos* pos, MutableHandleValue dst);
@@ -1399,33 +1397,16 @@ NodeBuilder::generatorExpression(HandleV
                    "body", body,
                    "blocks", array,
                    "filter", filter,
                    "style", style,
                    dst);
 }
 
 bool
-NodeBuilder::letStatement(NodeVector& head, HandleValue stmt, TokenPos* pos, MutableHandleValue dst)
-{
-    RootedValue array(cx);
-    if (!newArray(head, &array))
-        return false;
-
-    RootedValue cb(cx, callbacks[AST_LET_STMT]);
-    if (!cb.isNull())
-        return callback(cb, array, stmt, pos, dst);
-
-    return newNode(AST_LET_STMT, pos,
-                   "head", array,
-                   "body", stmt,
-                   dst);
-}
-
-bool
 NodeBuilder::importDeclaration(NodeVector& elts, HandleValue moduleSpec, TokenPos* pos,
                                MutableHandleValue dst)
 {
     RootedValue array(cx);
     if (!newArray(elts, &array))
         return false;
 
     RootedValue cb(cx, callbacks[AST_IMPORT_DECL]);
@@ -1744,25 +1725,24 @@ class ASTSerializer
     BinaryOperator binop(ParseNodeKind kind, JSOp op);
     UnaryOperator unop(ParseNodeKind kind, JSOp op);
     AssignmentOperator aop(JSOp op);
 
     bool statements(ParseNode* pn, NodeVector& elts);
     bool expressions(ParseNode* pn, NodeVector& elts);
     bool leftAssociate(ParseNode* pn, MutableHandleValue dst);
     bool rightAssociate(ParseNode* pn, MutableHandleValue dst);
-    bool functionArgs(ParseNode* pn, ParseNode* pnargs, ParseNode* pnbody,
+    bool functionArgs(ParseNode* pn, ParseNode* pnargs,
                       NodeVector& args, NodeVector& defaults, MutableHandleValue rest);
 
     bool sourceElement(ParseNode* pn, MutableHandleValue dst);
 
     bool declaration(ParseNode* pn, MutableHandleValue dst);
     bool variableDeclaration(ParseNode* pn, bool lexical, MutableHandleValue dst);
     bool variableDeclarator(ParseNode* pn, MutableHandleValue dst);
-    bool letBlock(ParseNode* pn, MutableHandleValue dst);
     bool importDeclaration(ParseNode* pn, MutableHandleValue dst);
     bool importSpecifier(ParseNode* pn, MutableHandleValue dst);
     bool exportDeclaration(ParseNode* pn, MutableHandleValue dst);
     bool exportSpecifier(ParseNode* pn, MutableHandleValue dst);
     bool classDefinition(ParseNode* pn, bool expr, MutableHandleValue dst);
 
     bool optStatement(ParseNode* pn, MutableHandleValue dst) {
         if (!pn) {
@@ -1804,17 +1784,16 @@ class ASTSerializer
             dst.setMagic(JS_SERIALIZE_NO_NODE);
             return true;
         }
         return identifier(atom, pos, dst);
     }
 
     bool identifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst);
     bool identifier(ParseNode* pn, MutableHandleValue dst);
-    bool objectPropertyName(ParseNode* pn, MutableHandleValue dst);
     bool literal(ParseNode* pn, MutableHandleValue dst);
 
     bool pattern(ParseNode* pn, MutableHandleValue dst);
     bool arrayPattern(ParseNode* pn, MutableHandleValue dst);
     bool objectPattern(ParseNode* pn, MutableHandleValue dst);
 
     bool function(ParseNode* pn, ASTType type, MutableHandleValue dst);
     bool functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
@@ -2036,19 +2015,16 @@ ASTSerializer::declaration(ParseNode* pn
                pn->isKind(PNK_VAR) ||
                pn->isKind(PNK_LET) ||
                pn->isKind(PNK_CONST));
 
     switch (pn->getKind()) {
       case PNK_FUNCTION:
         return function(pn, AST_FUNC_DECL, dst);
 
-      case PNK_ANNEXB_FUNCTION:
-        return function(pn->pn_left, AST_FUNC_DECL, dst);
-
       case PNK_VAR:
         return variableDeclaration(pn, false, dst);
 
       default:
         MOZ_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_CONST));
         return variableDeclaration(pn, true, dst);
     }
 }
@@ -2082,17 +2058,17 @@ ASTSerializer::variableDeclaration(Parse
 bool
 ASTSerializer::variableDeclarator(ParseNode* pn, MutableHandleValue dst)
 {
     ParseNode* pnleft;
     ParseNode* pnright;
 
     if (pn->isKind(PNK_NAME)) {
         pnleft = pn;
-        pnright = pn->isUsed() ? nullptr : pn->pn_expr;
+        pnright = pn->pn_expr;
         MOZ_ASSERT_IF(pnright, pn->pn_pos.encloses(pnright->pn_pos));
     } else if (pn->isKind(PNK_ASSIGN)) {
         pnleft = pn->pn_left;
         pnright = pn->pn_right;
         MOZ_ASSERT(pn->pn_pos.encloses(pnleft->pn_pos));
         MOZ_ASSERT(pn->pn_pos.encloses(pnright->pn_pos));
     } else {
         /* This happens for a destructuring declarator in a for-in/of loop. */
@@ -2102,45 +2078,16 @@ ASTSerializer::variableDeclarator(ParseN
 
     RootedValue left(cx), right(cx);
     return pattern(pnleft, &left) &&
            optExpression(pnright, &right) &&
            builder.variableDeclarator(left, right, &pn->pn_pos, dst);
 }
 
 bool
-ASTSerializer::letBlock(ParseNode* pn, MutableHandleValue dst)
-{
-    MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-    MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
-
-    ParseNode* letHead = pn->pn_left;
-    LOCAL_ASSERT(letHead->isArity(PN_LIST));
-
-    ParseNode* letBody = pn->pn_right;
-    LOCAL_ASSERT(letBody->isKind(PNK_LEXICALSCOPE));
-
-    NodeVector dtors(cx);
-    if (!dtors.reserve(letHead->pn_count))
-        return false;
-
-    for (ParseNode* next = letHead->pn_head; next; next = next->pn_next) {
-        RootedValue child(cx);
-
-        if (!variableDeclarator(next, &child))
-            return false;
-        dtors.infallibleAppend(child);
-    }
-
-    RootedValue v(cx);
-    return statement(letBody->pn_expr, &v) &&
-           builder.letStatement(dtors, v, &pn->pn_pos, dst);
-}
-
-bool
 ASTSerializer::importDeclaration(ParseNode* pn, MutableHandleValue dst)
 {
     MOZ_ASSERT(pn->isKind(PNK_IMPORT));
     MOZ_ASSERT(pn->isArity(PN_BINARY));
     MOZ_ASSERT(pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST));
     MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
 
     NodeVector elts(cx);
@@ -2410,46 +2357,28 @@ bool
 ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
 {
     JS_CHECK_RECURSION(cx, return false);
     switch (pn->getKind()) {
       case PNK_FUNCTION:
       case PNK_VAR:
         return declaration(pn, dst);
 
-      case PNK_ANNEXB_FUNCTION:
-        // XXXshu NOP check used only for phasing in block-scope function
-        // XXXshu early errors.
-        // XXXshu
-        // XXXshu Back out when major version >= 50. See [1].
-        // XXXshu
-        // XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
-        if (pn->pn_left->isKind(PNK_NOP))
-            return builder.emptyStatement(&pn->pn_pos, dst);
-        return declaration(pn->pn_left, dst);
-
-      case PNK_LETBLOCK:
-        return letBlock(pn, dst);
-
       case PNK_LET:
       case PNK_CONST:
         return declaration(pn, dst);
 
       case PNK_IMPORT:
         return importDeclaration(pn, dst);
 
       case PNK_EXPORT:
       case PNK_EXPORT_DEFAULT:
       case PNK_EXPORT_FROM:
         return exportDeclaration(pn, dst);
 
-      case PNK_NAME:
-        LOCAL_ASSERT(pn->isUsed());
-        return statement(pn->pn_lexdef, dst);
-
       case PNK_SEMI:
         if (pn->pn_kid) {
             RootedValue expr(cx);
             return expression(pn->pn_kid, &expr) &&
                    builder.expressionStatement(expr, &pn->pn_pos, dst);
         }
         return builder.emptyStatement(&pn->pn_pos, dst);
 
@@ -2522,21 +2451,24 @@ ASTSerializer::statement(ParseNode* pn, 
         MOZ_ASSERT_IF(head->pn_kid3, head->pn_pos.encloses(head->pn_kid3->pn_pos));
 
         RootedValue stmt(cx);
         if (!statement(pn->pn_right, &stmt))
             return false;
 
         if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) {
             RootedValue var(cx);
-            if (!head->pn_kid1) {
-                if (!pattern(head->pn_kid2, &var))
+            if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) {
+                if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var))
                     return false;
-            } else if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) {
-                if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var))
+            } else if (!head->pn_kid1->isKind(PNK_VAR) &&
+                       !head->pn_kid1->isKind(PNK_LET) &&
+                       !head->pn_kid1->isKind(PNK_CONST))
+            {
+                if (!pattern(head->pn_kid1, &var))
                     return false;
             } else {
                 if (!variableDeclaration(head->pn_kid1,
                                          head->pn_kid1->isKind(PNK_LET) ||
                                          head->pn_kid1->isKind(PNK_CONST),
                                          &var))
                 {
                     return false;
@@ -2744,18 +2676,23 @@ ASTSerializer::comprehensionBlock(ParseN
 
     ParseNode* in = pn->pn_left;
 
     LOCAL_ASSERT(in && (in->isKind(PNK_FORIN) || in->isKind(PNK_FOROF)));
 
     bool isForEach = in->isKind(PNK_FORIN) && (pn->pn_iflags & JSITER_FOREACH);
     bool isForOf = in->isKind(PNK_FOROF);
 
+    ParseNode* decl = in->pn_kid1;
+    if (decl->isKind(PNK_LEXICALSCOPE))
+        decl = decl->pn_expr;
+    MOZ_ASSERT(decl->pn_count == 1);
+
     RootedValue patt(cx), src(cx);
-    return pattern(in->pn_kid2, &patt) &&
+    return pattern(decl->pn_head, &patt) &&
            expression(in->pn_kid3, &src) &&
            builder.comprehensionBlock(patt, src, isForEach, isForOf, &in->pn_pos, dst);
 }
 
 bool
 ASTSerializer::comprehensionIf(ParseNode* pn, MutableHandleValue dst)
 {
     LOCAL_ASSERT(pn->isKind(PNK_IF));
@@ -3436,27 +3373,16 @@ ASTSerializer::identifier(ParseNode* pn,
     LOCAL_ASSERT(pn->isArity(PN_NAME) || pn->isArity(PN_NULLARY));
     LOCAL_ASSERT(pn->pn_atom);
 
     RootedAtom pnAtom(cx, pn->pn_atom);
     return identifier(pnAtom, &pn->pn_pos, dst);
 }
 
 bool
-ASTSerializer::objectPropertyName(ParseNode* pn, MutableHandleValue dst)
-{
-    LOCAL_ASSERT(pn->isKind(PNK_OBJECT_PROPERTY_NAME));
-    LOCAL_ASSERT(pn->isArity(PN_NULLARY));
-    LOCAL_ASSERT(pn->pn_atom);
-
-    RootedAtom pnAtom(cx, pn->pn_atom);
-    return identifier(pnAtom, &pn->pn_pos, dst);
-}
-
-bool
 ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst)
 {
     RootedFunction func(cx, pn->pn_funbox->function());
 
     GeneratorStyle generatorStyle =
         pn->pn_funbox->isGenerator()
         ? (pn->pn_funbox->isLegacyGenerator()
            ? GeneratorStyle::Legacy
@@ -3491,100 +3417,105 @@ ASTSerializer::function(ParseNode* pn, A
 bool
 ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
                                    MutableHandleValue body, MutableHandleValue rest)
 {
     ParseNode* pnargs;
     ParseNode* pnbody;
 
     /* Extract the args and body separately. */
-    if (pn->isKind(PNK_ARGSBODY)) {
+    if (pn->isKind(PNK_PARAMSBODY)) {
         pnargs = pn;
         pnbody = pn->last();
     } else {
         pnargs = nullptr;
         pnbody = pn;
     }
 
+    if (pnbody->isKind(PNK_LEXICALSCOPE))
+        pnbody = pnbody->scopeBody();
+
     /* Serialize the arguments and body. */
     switch (pnbody->getKind()) {
       case PNK_RETURN: /* expression closure, no destructured args */
-        return functionArgs(pn, pnargs, pnbody, args, defaults, rest) &&
+        return functionArgs(pn, pnargs, args, defaults, rest) &&
                expression(pnbody->pn_kid, body);
 
       case PNK_STATEMENTLIST:     /* statement closure */
       {
         ParseNode* pnstart = pnbody->pn_head;
 
         // Skip over initial yield in generator.
         if (pnstart && pnstart->isKind(PNK_YIELD)) {
             MOZ_ASSERT(pnstart->getOp() == JSOP_INITIALYIELD);
             pnstart = pnstart->pn_next;
         }
 
-        return functionArgs(pn, pnargs, pnbody, args, defaults, rest) &&
+        return functionArgs(pn, pnargs, args, defaults, rest) &&
                functionBody(pnstart, &pnbody->pn_pos, body);
       }
 
       default:
         LOCAL_NOT_REACHED("unexpected function contents");
     }
 }
 
 bool
 ASTSerializer::functionArgs(ParseNode* pn, ParseNode* pnargs,
-                            ParseNode* pnbody, NodeVector& args, NodeVector& defaults,
+                            NodeVector& args, NodeVector& defaults,
                             MutableHandleValue rest)
 {
     if (!pnargs)
         return true;
 
     RootedValue node(cx);
     bool defaultsNull = true;
     MOZ_ASSERT(defaults.empty(),
                "must be initially empty for it to be proper to clear this "
                "when there are no defaults");
 
-    for (ParseNode* arg = pnargs->pn_head; arg && arg != pnbody; arg = arg->pn_next) {
-        MOZ_ASSERT(arg->isKind(PNK_NAME) || arg->isKind(PNK_ASSIGN));
-        ParseNode* argName = nullptr;
-        ParseNode* defNode = nullptr;
-        if (arg->isKind(PNK_ASSIGN)) {
-            argName = arg->pn_left;
+    for (ParseNode* arg = pnargs->pn_head; arg && arg != pnargs->last(); arg = arg->pn_next) {
+        ParseNode* pat;
+        ParseNode* defNode;
+        if (arg->isKind(PNK_NAME) || arg->isKind(PNK_ARRAY) || arg->isKind(PNK_OBJECT)) {
+            pat = arg;
+            defNode = nullptr;
+        } else {
+            MOZ_ASSERT(arg->isKind(PNK_ASSIGN));
+            pat = arg->pn_left;
             defNode = arg->pn_right;
-        } else if (arg->pn_atom == cx->names().empty) {
-            ParseNode* destruct = arg->expr();
-            if (destruct->isKind(PNK_ASSIGN)) {
-                defNode = destruct->pn_right;
-                destruct = destruct->pn_left;
-            }
-            if (!pattern(destruct, &node) || !args.append(node))
-                return false;
+        }
+
+        // Process the name or pattern.
+        MOZ_ASSERT(pat->isKind(PNK_NAME) || pat->isKind(PNK_ARRAY) || pat->isKind(PNK_OBJECT));
+        if (!pattern(pat, &node))
+            return false;
+        if (rest.isUndefined() && arg->pn_next == pnargs->last()) {
+            MOZ_ASSERT(arg->isKind(PNK_NAME));
+            rest.setObject(node.toObject());
         } else {
-            argName = arg;
-        }
-        if (argName) {
-            if (!identifier(argName, &node))
-                return false;
-            if (rest.isUndefined() && arg->pn_next == pnbody)
-                rest.setObject(node.toObject());
-            else if (!args.append(node))
+            if (!args.append(node))
                 return false;
         }
+
+        // Process its default (or lack thereof).
         if (defNode) {
             defaultsNull = false;
             RootedValue def(cx);
             if (!expression(defNode, &def) || !defaults.append(def))
                 return false;
         } else {
             if (!defaults.append(NullValue()))
                 return false;
         }
     }
-    MOZ_ASSERT(!rest.isUndefined());
+    MOZ_ASSERT(!rest.isUndefined(),
+               "if a rest argument was present (signified by "
+               "|rest.isUndefined()| initially), the rest node was properly "
+               "recorded");
 
     if (defaultsNull)
         defaults.clear();
 
     return true;
 }
 
 bool
@@ -3730,38 +3661,43 @@ reflect_parse(JSContext* cx, uint32_t ar
     AutoStableStringChars linearChars(cx);
     if (!linearChars.initTwoByte(cx, linear))
         return false;
 
     CompileOptions options(cx);
     options.setFileAndLine(filename, lineno);
     options.setCanLazilyParse(false);
     mozilla::Range<const char16_t> chars = linearChars.twoByteRange();
-    Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars.start().get(),
-                                    chars.length(), /* foldConstants = */ false, nullptr, nullptr);
+    UsedNameTracker usedNames(cx);
+    if (!usedNames.init())
+        return false;
+    Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars.start().get(),
+                                    chars.length(), /* foldConstants = */ false, usedNames,
+                                    nullptr, nullptr);
     if (!parser.checkOptions())
         return false;
 
     serialize.setParser(&parser);
 
     ParseNode* pn;
     if (target == ParseTarget::Script) {
         pn = parser.parse();
         if (!pn)
             return false;
     } else {
         if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global()))
             return false;
 
-        Rooted<ModuleObject*> module(cx, ModuleObject::create(cx, nullptr));
+        Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
         if (!module)
             return false;
 
         ModuleBuilder builder(cx, module);
-        pn = parser.standaloneModule(module, builder);
+        ModuleSharedContext modulesc(cx, module, &cx->global()->emptyGlobalScope(), builder);
+        pn = parser.moduleBody(&modulesc);
         if (!pn)
             return false;
 
         MOZ_ASSERT(pn->getKind() == PNK_MODULE);
         pn = pn->pn_body;
     }
 
     RootedValue val(cx);
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -1676,26 +1676,26 @@ js::intrinsic_GetElemBaseForLambda(JSCon
     JSScript* script = fun->getOrCreateScript(cx);
     if (!script)
         return false;
 
     jsbytecode* pc = script->code();
 
     /*
      * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'.
-     * Rule out the (unlikely) possibility of a function with a call object
-     * since it would make our scope walk off by 1.
+     * Rule out the (unlikely) possibility of a function with environment
+     * objects since it would make our environment walk off.
      */
-    if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->needsCallObject())
+    if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->needsSomeEnvironmentObject())
         return true;
-    ScopeCoordinate sc(pc);
-    ScopeObject* scope = &fun->environment()->as<ScopeObject>();
-    for (unsigned i = 0; i < sc.hops(); ++i)
-        scope = &scope->enclosingScope().as<ScopeObject>();
-    Value b = scope->aliasedVar(sc);
+    EnvironmentCoordinate ec(pc);
+    EnvironmentObject* env = &fun->environment()->as<EnvironmentObject>();
+    for (unsigned i = 0; i < ec.hops(); ++i)
+        env = &env->enclosingEnvironment().as<EnvironmentObject>();
+    Value b = env->aliasedBinding(ec);
     pc += JSOP_GETALIASEDVAR_LENGTH;
 
     /* Look for 'a' to be the lambda's first argument. */
     if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0)
         return true;
     pc += JSOP_GETARG_LENGTH;
 
     /* 'b[a]' */
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -94,16 +94,16 @@
 #define REGEXP_FLAGS_SLOT 2
 
 #define REGEXP_IGNORECASE_FLAG  0x01
 #define REGEXP_GLOBAL_FLAG      0x02
 #define REGEXP_MULTILINE_FLAG   0x04
 #define REGEXP_STICKY_FLAG      0x08
 #define REGEXP_UNICODE_FLAG     0x10
 
-#define MODULE_OBJECT_ENVIRONMENT_SLOT 3
+#define MODULE_OBJECT_ENVIRONMENT_SLOT 2
 
 #define MODULE_STATE_FAILED       0
 #define MODULE_STATE_PARSED       1
 #define MODULE_STATE_INSTANTIATED 2
 #define MODULE_STATE_EVALUATED    3
 
 #endif
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -45,18 +45,18 @@
 #include "vm/SavedStacks.h"
 #include "vm/Stack.h"
 #include "vm/StringBuffer.h"
 #include "vm/TraceLogging.h"
 
 #include "jscntxtinlines.h"
 #include "jsobjinlines.h"
 
+#include "vm/EnvironmentObject-inl.h"
 #include "vm/NativeObject-inl.h"
-#include "vm/ScopeObject-inl.h"
 
 using namespace js;
 
 using mozilla::ArrayLength;
 using mozilla::Move;
 
 // If fuzzingSafe is set, remove functionality that could cause problems with
 // fuzzers. Set this via the environment variable MOZ_FUZZING_SAFE.
@@ -2977,17 +2977,17 @@ EvalReturningScope(JSContext* cx, unsign
         // If we're switching globals here, ExecuteInGlobalAndReturnScope will
         // take care of cloning the script into that compartment before
         // executing it.
         AutoCompartment ac(cx, global);
 
         if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &lexicalScope))
             return false;
 
-        varObj = lexicalScope->enclosingScope();
+        varObj = lexicalScope->enclosingEnvironment();
     }
 
     RootedObject rv(cx, JS_NewPlainObject(cx));
     if (!rv)
         return false;
 
     RootedValue varObjVal(cx, ObjectValue(*varObj));
     if (!cx->compartment()->wrap(cx, &varObjVal))
deleted file mode 100644
--- a/js/src/ds/InlineMap.h
+++ /dev/null
@@ -1,385 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * vim: set ts=8 sts=4 et sw=4 tw=99:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef ds_InlineMap_h
-#define ds_InlineMap_h
-
-#include "jsalloc.h"
-
-#include "js/HashTable.h"
-
-namespace js {
-
-/*
- * A type can only be used as an InlineMap key if zero is an invalid key value
- * (and thus may be used as a tombstone value by InlineMap).
- */
-template <typename T> struct ZeroIsReserved         { static const bool result = false; };
-template <typename T> struct ZeroIsReserved<T*>    { static const bool result = true; };
-
-template <typename K, typename V, size_t InlineElems>
-class InlineMap
-{
-  public:
-    typedef HashMap<K, V, DefaultHasher<K>, SystemAllocPolicy> WordMap;
-
-    struct InlineElem
-    {
-        K key;
-        V value;
-    };
-
-  private:
-    typedef typename WordMap::Ptr       WordMapPtr;
-    typedef typename WordMap::AddPtr    WordMapAddPtr;
-    typedef typename WordMap::Range     WordMapRange;
-
-    size_t          inlNext;
-    size_t          inlCount;
-    InlineElem      inl[InlineElems];
-    WordMap         map;
-
-    static_assert(ZeroIsReserved<K>::result,
-                  "zero as tombstone requires that zero keys be invalid");
-
-#ifdef DEBUG
-    bool keyNonZero(const K& key) {
-        // Zero as tombstone means zero keys are invalid.
-        return !!key;
-    }
-#endif
-
-    bool usingMap() const {
-        return inlNext > InlineElems;
-    }
-
-    MOZ_MUST_USE bool switchToMap() {
-        MOZ_ASSERT(inlNext == InlineElems);
-
-        if (map.initialized()) {
-            map.clear();
-        } else {
-            if (!map.init(count()))
-                return false;
-            MOZ_ASSERT(map.initialized());
-        }
-
-        InlineElem* end = inl + inlNext;
-        for (InlineElem* it = inl; it != end; ++it) {
-            if (it->key && !map.putNew(it->key, it->value))
-                return false;
-        }
-
-        inlNext = InlineElems + 1;
-        MOZ_ASSERT(map.count() == inlCount);
-        MOZ_ASSERT(usingMap());
-        return true;
-    }
-
-    MOZ_NEVER_INLINE
-    MOZ_MUST_USE bool switchAndAdd(const K& key, const V& value) {
-        if (!switchToMap())
-            return false;
-
-        return map.putNew(key, value);
-    }
-
-  public:
-    explicit InlineMap() : inlNext(0), inlCount(0) { }
-
-    class Entry
-    {
-        friend class InlineMap;
-        const K& key_;
-        V& value_;
-
-        Entry(const K& key, V& value) : key_(key), value_(value) {}
-
-      public:
-        const K& key() { return key_; }
-        V& value() { return value_; }
-    }; /* class Entry */
-
-    class Ptr
-    {
-        friend class InlineMap;
-
-        WordMapPtr  mapPtr;
-        InlineElem* inlPtr;
-        bool        isInlinePtr;
-
-        explicit Ptr(WordMapPtr p) : mapPtr(p), isInlinePtr(false) {}
-        explicit Ptr(InlineElem* ie) : inlPtr(ie), isInlinePtr(true) {}
-        void operator==(const Ptr& other);
-
-      public:
-        /* Leaves Ptr uninitialized. */
-        Ptr() {
-#ifdef DEBUG
-            inlPtr = (InlineElem*) 0xbad;
-            isInlinePtr = true;
-#endif
-        }
-
-        /* Default copy constructor works for this structure. */
-
-        bool found() const {
-            return isInlinePtr ? bool(inlPtr) : mapPtr.found();
-        }
-
-        explicit operator bool() const {
-            return found();
-        }
-
-        K& key() {
-            MOZ_ASSERT(found());
-            return isInlinePtr ? inlPtr->key : mapPtr->key();
-        }
-
-        V& value() {
-            MOZ_ASSERT(found());
-            return isInlinePtr ? inlPtr->value : mapPtr->value();
-        }
-    }; /* class Ptr */
-
-    class AddPtr
-    {
-        friend class InlineMap;
-
-        WordMapAddPtr   mapAddPtr;
-        InlineElem*     inlAddPtr;
-        bool            isInlinePtr;
-        /* Indicates whether inlAddPtr is a found result or an add pointer. */
-        bool            inlPtrFound;
-
-        AddPtr(InlineElem* ptr, bool found)
-          : inlAddPtr(ptr), isInlinePtr(true), inlPtrFound(found)
-        {}
-
-        explicit AddPtr(const WordMapAddPtr& p) : mapAddPtr(p), isInlinePtr(false) {}
-
-        void operator==(const AddPtr& other);
-
-      public:
-        AddPtr() {}
-
-        bool found() const {
-            return isInlinePtr ? inlPtrFound : mapAddPtr.found();
-        }
-
-        explicit operator bool() const {
-            return found();
-        }
-
-        V& value() {
-            MOZ_ASSERT(found());
-            if (isInlinePtr)
-                return inlAddPtr->value;
-            return mapAddPtr->value();
-        }
-    }; /* class AddPtr */
-
-    size_t count() {
-        return usingMap() ? map.count() : inlCount;
-    }
-
-    bool empty() const {
-        return usingMap() ? map.empty() : !inlCount;
-    }
-
-    void clear() {
-        inlNext = 0;
-        inlCount = 0;
-    }
-
-    bool isMap() const {
-        return usingMap();
-    }
-
-    const WordMap& asMap() const {
-        MOZ_ASSERT(isMap());
-        return map;
-    }
-
-    const InlineElem* asInline() const {
-        MOZ_ASSERT(!isMap());
-        return inl;
-    }
-
-    const InlineElem* inlineEnd() const {
-        MOZ_ASSERT(!isMap());
-        return inl + inlNext;
-    }
-
-    MOZ_ALWAYS_INLINE
-    Ptr lookup(const K& key) {
-        MOZ_ASSERT(keyNonZero(key));
-
-        if (usingMap())
-            return Ptr(map.lookup(key));
-
-        InlineElem* end = inl + inlNext;
-        for (InlineElem* it = inl; it != end; ++it) {
-            if (it->key == key)
-                return Ptr(it);
-        }
-
-        return Ptr(nullptr);
-    }
-
-    MOZ_ALWAYS_INLINE
-    AddPtr lookupForAdd(const K& key) {
-        MOZ_ASSERT(keyNonZero(key));
-
-        if (usingMap())
-            return AddPtr(map.lookupForAdd(key));
-
-        InlineElem* end = inl + inlNext;
-        for (InlineElem* it = inl; it != end; ++it) {
-            if (it->key == key)
-                return AddPtr(it, true);
-        }
-
-        /*
-         * The add pointer that's returned here may indicate the limit entry of
-         * the linear space, in which case the |add| operation will initialize
-         * the map if necessary and add the entry there.
-         */
-        return AddPtr(inl + inlNext, false);
-    }
-
-    MOZ_ALWAYS_INLINE
-    MOZ_MUST_USE bool add(AddPtr& p, const K& key, const V& value) {
-        MOZ_ASSERT(!p);
-        MOZ_ASSERT(keyNonZero(key));
-
-        if (p.isInlinePtr) {
-            InlineElem* addPtr = p.inlAddPtr;
-            MOZ_ASSERT(addPtr == inl + inlNext);
-
-            /* Switching to map mode before we add this pointer. */
-            if (addPtr == inl + InlineElems)
-                return switchAndAdd(key, value);
-
-            MOZ_ASSERT(!p.found());
-            MOZ_ASSERT(uintptr_t(inl + inlNext) == uintptr_t(p.inlAddPtr));
-            p.inlAddPtr->key = key;
-            p.inlAddPtr->value = value;
-            ++inlCount;
-            ++inlNext;
-            return true;
-        }
-
-        return map.add(p.mapAddPtr, key, value);
-    }
-
-    MOZ_ALWAYS_INLINE
-    MOZ_MUST_USE bool put(const K& key, const V& value) {
-        AddPtr p = lookupForAdd(key);
-        if (p) {
-            p.value() = value;
-            return true;
-        }
-        return add(p, key, value);
-    }
-
-    void remove(Ptr p) {
-        MOZ_ASSERT(p);
-        if (p.isInlinePtr) {
-            MOZ_ASSERT(inlCount > 0);
-            MOZ_ASSERT(p.inlPtr->key != nullptr);
-            p.inlPtr->key = nullptr;
-            --inlCount;
-            return;
-        }
-        MOZ_ASSERT(map.initialized() && usingMap());
-        map.remove(p.mapPtr);
-    }
-
-    void remove(const K& key) {
-        if (Ptr p = lookup(key))
-            remove(p);
-    }
-
-    class Range
-    {
-        friend class InlineMap;
-
-        WordMapRange    mapRange;
-        InlineElem*     cur;
-        InlineElem*     end;
-        bool            isInline;
-
-        explicit Range(WordMapRange r)
-          : cur(nullptr), end(nullptr), /* Avoid GCC 4.3.3 over-warning. */
-            isInline(false) {
-            mapRange = r;
-            MOZ_ASSERT(!isInlineRange());
-        }
-
-        Range(const InlineElem* begin, const InlineElem* end_)
-          : cur(const_cast<InlineElem*>(begin)),
-            end(const_cast<InlineElem*>(end_)),
-            isInline(true) {
-            advancePastNulls(cur);
-            MOZ_ASSERT(isInlineRange());
-        }
-
-        bool checkInlineRangeInvariants() const {
-            MOZ_ASSERT(uintptr_t(cur) <= uintptr_t(end));
-            MOZ_ASSERT_IF(cur != end, cur->key != nullptr);
-            return true;
-        }
-
-        bool isInlineRange() const {
-            MOZ_ASSERT_IF(isInline, checkInlineRangeInvariants());
-            return isInline;
-        }
-
-        void advancePastNulls(InlineElem* begin) {
-            InlineElem* newCur = begin;
-            while (newCur < end && nullptr == newCur->key)
-                ++newCur;
-            MOZ_ASSERT(uintptr_t(newCur) <= uintptr_t(end));
-            cur = newCur;
-        }
-
-        void bumpCurPtr() {
-            MOZ_ASSERT(isInlineRange());
-            advancePastNulls(cur + 1);
-        }
-
-        void operator==(const Range& other);
-
-      public:
-        bool empty() const {
-            return isInlineRange() ? cur == end : mapRange.empty();
-        }
-
-        Entry front() {
-            MOZ_ASSERT(!empty());
-            if (isInlineRange())
-                return Entry(cur->key, cur->value);
-            return Entry(mapRange.front().key(), mapRange.front().value());
-        }
-
-        void popFront() {
-            MOZ_ASSERT(!empty());
-            if (isInlineRange())
-                bumpCurPtr();
-            else
-                mapRange.popFront();
-        }
-    }; /* class Range */
-
-    Range all() const {
-        return usingMap() ? Range(map.all()) : Range(inl, inl + inlNext);
-    }
-}; /* class InlineMap */
-
-} /* namespace js */
-
-#endif /* ds_InlineMap_h */
new file mode 100644
--- /dev/null
+++ b/js/src/ds/InlineTable.h
@@ -0,0 +1,687 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ds_InlineTable_h
+#define ds_InlineTable_h
+
+#include "mozilla/Move.h"
+
+#include "jsalloc.h"
+
+#include "js/HashTable.h"
+
+namespace js {
+
+namespace detail {
+
+template <typename InlineEntry,
+          typename Entry,
+          typename Table,
+          typename HashPolicy,
+          typename AllocPolicy,
+          size_t InlineEntries>
+class InlineTable
+{
+  private:
+    using TablePtr    = typename Table::Ptr;
+    using TableAddPtr = typename Table::AddPtr;
+    using TableRange  = typename Table::Range;
+    using Lookup      = typename HashPolicy::Lookup;
+
+    size_t      inlNext_;
+    size_t      inlCount_;
+    InlineEntry inl_[InlineEntries];
+    Table       table_;
+
+#ifdef DEBUG
+    template <typename Key>
+    static bool keyNonZero(const Key& key) {
+        // Zero as tombstone means zero keys are invalid.
+        return !!key;
+    }
+#endif
+
+    InlineEntry* inlineStart() {
+        MOZ_ASSERT(!usingTable());
+        return inl_;
+    }
+
+    const InlineEntry* inlineStart() const {
+        MOZ_ASSERT(!usingTable());
+        return inl_;
+    }
+
+    InlineEntry* inlineEnd() {
+        MOZ_ASSERT(!usingTable());
+        return inl_ + inlNext_;
+    }
+
+    const InlineEntry* inlineEnd() const {
+        MOZ_ASSERT(!usingTable());
+        return inl_ + inlNext_;
+    }
+
+    bool usingTable() const {
+        return inlNext_ > InlineEntries;
+    }
+
+    MOZ_MUST_USE bool switchToTable() {
+        MOZ_ASSERT(inlNext_ == InlineEntries);
+
+        if (table_.initialized()) {
+            table_.clear();
+        } else {
+            if (!table_.init(count()))
+                return false;
+            MOZ_ASSERT(table_.initialized());
+        }
+
+        InlineEntry* end = inlineEnd();
+        for (InlineEntry* it = inlineStart(); it != end; ++it) {
+            if (it->key && !it->moveTo(table_))
+                return false;
+        }
+
+        inlNext_ = InlineEntries + 1;
+        MOZ_ASSERT(table_.count() == inlCount_);
+        MOZ_ASSERT(usingTable());
+        return true;
+    }
+
+    MOZ_NEVER_INLINE
+    MOZ_MUST_USE bool switchAndAdd(const InlineEntry& entry) {
+        if (!switchToTable())
+            return false;
+
+        return entry.putNew(table_);
+    }
+
+  public:
+    static const size_t SizeOfInlineEntries = sizeof(InlineEntry) * InlineEntries;
+
+    explicit InlineTable(AllocPolicy a = AllocPolicy())
+      : inlNext_(0),
+        inlCount_(0),
+        table_(a)
+    { }
+
+    class Ptr
+    {
+        friend class InlineTable;
+
+      protected:
+        Entry        entry_;
+        TablePtr     tablePtr_;
+        InlineEntry* inlPtr_;
+        bool         isInlinePtr_;
+
+        explicit Ptr(TablePtr p)
+          : entry_(p.found() ? &*p : nullptr),
+            tablePtr_(p),
+            isInlinePtr_(false)
+        { }
+
+        explicit Ptr(InlineEntry* inlineEntry)
+          : entry_(inlineEntry),
+            inlPtr_(inlineEntry),
+            isInlinePtr_(true)
+        { }
+
+        void operator==(const Ptr& other);
+
+      public:
+        // Leaves Ptr uninitialized.
+        Ptr() {
+#ifdef DEBUG
+            inlPtr_ = (InlineEntry*) 0xbad;
+            isInlinePtr_ = true;
+#endif
+        }
+
+        // Default copy constructor works for this structure.
+
+        bool found() const {
+            return isInlinePtr_ ? bool(inlPtr_) : tablePtr_.found();
+        }
+
+        explicit operator bool() const {
+            return found();
+        }
+
+        bool operator==(const Ptr& other) const {
+            MOZ_ASSERT(found() && other.found());
+            if (isInlinePtr_ != other.isInlinePtr_)
+                return false;
+            if (isInlinePtr_)
+                return inlPtr_ == other.inlPtr_;
+            return tablePtr_ == other.tablePtr_;
+        }
+
+        bool operator!=(const Ptr& other) const {
+            return !(*this == other);
+        }
+
+        Entry& operator*() {
+            MOZ_ASSERT(found());
+            return entry_;
+        }
+
+        Entry* operator->() {
+            MOZ_ASSERT(found());
+            return &entry_;
+        }
+    };
+
+    class AddPtr
+    {
+        friend class InlineTable;
+
+      protected:
+        Entry        entry_;
+        TableAddPtr  tableAddPtr_;
+        InlineEntry* inlAddPtr_;
+        bool         isInlinePtr_;
+        // Indicates whether inlAddPtr is a found result or an add pointer.
+        bool         inlPtrFound_;
+
+        AddPtr(InlineEntry* ptr, bool found)
+          : entry_(ptr),
+            inlAddPtr_(ptr),
+            isInlinePtr_(true),
+            inlPtrFound_(found)
+        {}
+
+        explicit AddPtr(const TableAddPtr& p)
+          : entry_(p.found() ? &*p : nullptr),
+            tableAddPtr_(p),
+            isInlinePtr_(false)
+        { }
+
+      public:
+        AddPtr() {}
+
+        bool found() const {
+            return isInlinePtr_ ? inlPtrFound_ : tableAddPtr_.found();
+        }
+
+        explicit operator bool() const {
+            return found();
+        }
+
+        bool operator==(const AddPtr& other) const {
+            MOZ_ASSERT(found() && other.found());
+            if (isInlinePtr_ != other.isInlinePtr_)
+                return false;
+            if (isInlinePtr_)
+                return inlAddPtr_ == other.inlAddPtr_;
+            return tableAddPtr_ == other.tableAddPtr_;
+        }
+
+        bool operator!=(const AddPtr& other) const {
+            return !(*this == other);
+        }
+
+        Entry& operator*() {
+            MOZ_ASSERT(found());
+            return entry_;
+        }
+
+        Entry* operator->() {
+            MOZ_ASSERT(found());
+            return &entry_;
+        }
+    };
+
+    size_t count() const {
+        return usingTable() ? table_.count() : inlCount_;
+    }
+
+    bool empty() const {
+        return usingTable() ? table_.empty() : !inlCount_;
+    }
+
+    void clear() {
+        inlNext_ = 0;
+        inlCount_ = 0;
+    }
+
+    MOZ_ALWAYS_INLINE
+    Ptr lookup(const Lookup& l) {
+        MOZ_ASSERT(keyNonZero(l));
+
+        if (usingTable())
+            return Ptr(table_.lookup(l));
+
+        InlineEntry* end = inlineEnd();
+        for (InlineEntry* it = inlineStart(); it != end; ++it) {
+            if (it->key && HashPolicy::match(it->key, l))
+                return Ptr(it);
+        }
+
+        return Ptr(nullptr);
+    }
+
+    MOZ_ALWAYS_INLINE
+    AddPtr lookupForAdd(const Lookup& l) {
+        MOZ_ASSERT(keyNonZero(l));
+
+        if (usingTable())
+            return AddPtr(table_.lookupForAdd(l));
+
+        InlineEntry* end = inlineEnd();
+        for (InlineEntry* it = inlineStart(); it != end; ++it) {
+            if (it->key && HashPolicy::match(it->key, l))
+                return AddPtr(it, true);
+        }
+
+        // The add pointer that's returned here may indicate the limit entry of
+        // the linear space, in which case the |add| operation will initialize
+        // the table if necessary and add the entry there.
+        return AddPtr(inlineEnd(), false);
+    }
+
+    template <typename KeyInput,
+              typename... Args>
+    MOZ_ALWAYS_INLINE
+    MOZ_MUST_USE bool add(AddPtr& p, KeyInput&& key, Args&&... args) {
+        MOZ_ASSERT(!p);
+        MOZ_ASSERT(keyNonZero(key));
+
+        if (p.isInlinePtr_) {
+            InlineEntry* addPtr = p.inlAddPtr_;
+            MOZ_ASSERT(addPtr == inlineEnd());
+
+            // Switching to table mode before we add this pointer.
+            if (addPtr == inlineStart() + InlineEntries) {
+                if (!switchToTable())
+                    return false;
+                return table_.putNew(mozilla::Forward<KeyInput>(key),
+                                     mozilla::Forward<Args>(args)...);
+            }
+
+            MOZ_ASSERT(!p.found());
+            MOZ_ASSERT(uintptr_t(inlineEnd()) == uintptr_t(p.inlAddPtr_));
+            addPtr->update(mozilla::Forward<KeyInput>(key),
+                           mozilla::Forward<Args>(args)...);
+            ++inlCount_;
+            ++inlNext_;
+            return true;
+        }
+
+        return table_.add(p.tableAddPtr_,
+                          mozilla::Forward<KeyInput>(key),
+                          mozilla::Forward<Args>(args)...);
+    }
+
+    void remove(Ptr& p) {
+        MOZ_ASSERT(p);
+        if (p.isInlinePtr_) {
+            MOZ_ASSERT(inlCount_ > 0);
+            MOZ_ASSERT(p.inlPtr_->key != nullptr);
+            p.inlPtr_->key = nullptr;
+            --inlCount_;
+            return;
+        }
+        MOZ_ASSERT(table_.initialized() && usingTable());
+        table_.remove(p.tablePtr_);
+    }
+
+    void remove(const Lookup& l) {
+        if (Ptr p = lookup(l))
+            remove(p);
+    }
+
+    class Range
+    {
+        friend class InlineTable;
+
+      protected:
+        TableRange   tableRange_;
+        InlineEntry* cur_;
+        InlineEntry* end_;
+        bool         isInline_;
+
+        explicit Range(TableRange r)
+          : cur_(nullptr),
+            end_(nullptr),
+            isInline_(false)
+        {
+            tableRange_ = r;
+            MOZ_ASSERT(!isInlineRange());
+        }
+
+        Range(const InlineEntry* begin, const InlineEntry* end)
+          : cur_(const_cast<InlineEntry*>(begin)),
+            end_(const_cast<InlineEntry*>(end)),
+            isInline_(true)
+        {
+            advancePastNulls(cur_);
+            MOZ_ASSERT(isInlineRange());
+        }
+
+        bool assertInlineRangeInvariants() const {
+            MOZ_ASSERT(uintptr_t(cur_) <= uintptr_t(end_));
+            MOZ_ASSERT_IF(cur_ != end_, cur_->key != nullptr);
+            return true;
+        }
+
+        bool isInlineRange() const {
+            MOZ_ASSERT_IF(isInline_, assertInlineRangeInvariants());
+            return isInline_;
+        }
+
+        void advancePastNulls(InlineEntry* begin) {
+            InlineEntry* newCur = begin;
+            while (newCur < end_ && nullptr == newCur->key)
+                ++newCur;
+            MOZ_ASSERT(uintptr_t(newCur) <= uintptr_t(end_));
+            cur_ = newCur;
+        }
+
+        void bumpCurPtr() {
+            MOZ_ASSERT(isInlineRange());
+            advancePastNulls(cur_ + 1);
+        }
+
+      public:
+        bool empty() const {
+            return isInlineRange() ? cur_ == end_ : tableRange_.empty();
+        }
+
+        Entry front() {
+            MOZ_ASSERT(!empty());
+            if (isInlineRange())
+                return Entry(cur_);
+            return Entry(&tableRange_.front());
+        }
+
+        void popFront() {
+            MOZ_ASSERT(!empty());
+            if (isInlineRange())
+                bumpCurPtr();
+            else
+                tableRange_.popFront();
+        }
+    };
+
+    Range all() const {
+        return usingTable() ? Range(table_.all()) : Range(inlineStart(), inlineEnd());
+    }
+};
+
+} // namespace detail
+
+// A map with InlineEntries number of entries kept inline in an array.
+//
+// The Key type must be zeroable as zeros are used as tombstone keys.
+// The Value type must have a default constructor.
+//
+// The API is very much like HashMap's.
+template <typename Key,
+          typename Value,
+          size_t InlineEntries,
+          typename HashPolicy = DefaultHasher<Key>,
+          typename AllocPolicy = TempAllocPolicy>
+class InlineMap
+{
+    using Map = HashMap<Key, Value, HashPolicy, AllocPolicy>;
+
+    struct InlineEntry
+    {
+        Key   key;
+        Value value;
+
+        template <typename KeyInput, typename ValueInput>
+        void update(KeyInput&& key, ValueInput&& value) {
+            this->key = mozilla::Forward<KeyInput>(key);
+            this->value = mozilla::Forward<ValueInput>(value);
+        }
+
+        MOZ_MUST_USE bool moveTo(Map& map) {
+            return map.putNew(mozilla::Move(key), mozilla::Move(value));
+        }
+    };
+
+    class Entry
+    {
+        using MapEntry = typename Map::Entry;
+
+        MapEntry*    mapEntry_;
+        InlineEntry* inlineEntry_;
+
+      public:
+        Entry() = default;
+
+        explicit Entry(MapEntry* mapEntry)
+          : mapEntry_(mapEntry),
+            inlineEntry_(nullptr)
+        { }
+
+        explicit Entry(InlineEntry* inlineEntry)
+          : mapEntry_(nullptr),
+            inlineEntry_(inlineEntry)
+        { }
+
+        const Key& key() const {
+            MOZ_ASSERT(!!mapEntry_ != !!inlineEntry_);
+            if (mapEntry_)
+                return mapEntry_->key();
+            return inlineEntry_->key;
+        }
+
+        Value& value() {
+            MOZ_ASSERT(!!mapEntry_ != !!inlineEntry_);
+            if (mapEntry_)
+                return mapEntry_->value();
+            return inlineEntry_->value;
+        }
+    };
+
+    using Impl = detail::InlineTable<InlineEntry, Entry,
+                                     Map, HashPolicy, AllocPolicy,
+                                     InlineEntries>;
+
+    Impl impl_;
+
+  public:
+    using Table  = Map;
+    using Ptr    = typename Impl::Ptr;
+    using AddPtr = typename Impl::AddPtr;
+    using Range  = typename Impl::Range;
+    using Lookup = typename HashPolicy::Lookup;
+
+    static const size_t SizeOfInlineEntries = Impl::SizeOfInlineEntries;
+
+    explicit InlineMap(AllocPolicy a = AllocPolicy())
+      : impl_(a)
+    { }
+
+    size_t count() const {
+        return impl_.count();
+    }
+
+    bool empty() const {
+        return impl_.empty();
+    }
+
+    void clear() {
+        impl_.clear();
+    }
+
+    Range all() const {
+        return impl_.all();
+    }
+
+    MOZ_ALWAYS_INLINE
+    Ptr lookup(const Lookup& l) {
+        return impl_.lookup(l);
+    }
+
+    MOZ_ALWAYS_INLINE
+    bool has(const Lookup& l) const {
+        return const_cast<InlineMap*>(this)->lookup(l).found();
+    }
+
+    MOZ_ALWAYS_INLINE
+    AddPtr lookupForAdd(const Lookup& l) {
+        return impl_.lookupForAdd(l);
+    }
+
+    template <typename KeyInput, typename ValueInput>
+    MOZ_ALWAYS_INLINE
+    MOZ_MUST_USE bool add(AddPtr& p, KeyInput&& key, ValueInput&& value) {
+        return impl_.add(p, mozilla::Forward<KeyInput>(key), mozilla::Forward<ValueInput>(value));
+    }
+
+    template <typename KeyInput, typename ValueInput>
+    MOZ_MUST_USE bool put(KeyInput&& key, ValueInput&& value) {
+        AddPtr p = lookupForAdd(key);
+        if (p) {
+            p->value() = mozilla::Forward<ValueInput>(value);
+            return true;
+        }
+        return add(p, mozilla::Forward<KeyInput>(key), mozilla::Forward<ValueInput>(value));
+    }
+
+    void remove(Ptr& p) {
+        impl_.remove(p);
+    }
+
+    void remove(const Lookup& l) {
+        impl_.remove(l);
+    }
+};
+
+// A set with InlineEntries number of entries kept inline in an array.
+//
+// The T type must be zeroable as zeros are used as tombstone keys.
+// The T type must have a default constructor.
+//
+// The API is very much like HashMap's.
+template <typename T,
+          size_t InlineEntries,
+          typename HashPolicy = DefaultHasher<T>,
+          typename AllocPolicy = TempAllocPolicy>
+class InlineSet
+{
+    using Set = HashSet<T, HashPolicy, AllocPolicy>;
+
+    struct InlineEntry
+    {
+        T key;
+
+        template <typename TInput>
+        void update(TInput&& key) {
+            this->key = mozilla::Forward<TInput>(key);
+        }
+
+        MOZ_MUST_USE bool moveTo(Set& set) {
+            return set.putNew(mozilla::Move(key));
+        }
+    };
+
+    class Entry
+    {
+        using SetEntry = typename Set::Entry;
+
+        SetEntry*    setEntry_;
+        InlineEntry* inlineEntry_;
+
+      public:
+        Entry() = default;
+
+        explicit Entry(const SetEntry* setEntry)
+          : setEntry_(const_cast<SetEntry*>(setEntry)),
+            inlineEntry_(nullptr)
+        { }
+
+        explicit Entry(InlineEntry* inlineEntry)
+          : setEntry_(nullptr),
+            inlineEntry_(inlineEntry)
+        { }
+
+        operator T() const {
+            MOZ_ASSERT(!!setEntry_ != !!inlineEntry_);
+            if (setEntry_)
+                return *setEntry_;
+            return inlineEntry_->key;
+        }
+    };
+
+    using Impl = detail::InlineTable<InlineEntry, Entry,
+                                     Set, HashPolicy, AllocPolicy,
+                                     InlineEntries>;
+
+    Impl impl_;
+
+  public:
+    using Table  = Set;
+    using Ptr    = typename Impl::Ptr;
+    using AddPtr = typename Impl::AddPtr;
+    using Range  = typename Impl::Range;
+    using Lookup = typename HashPolicy::Lookup;
+
+    static const size_t SizeOfInlineEntries = Impl::SizeOfInlineEntries;
+
+    explicit InlineSet(AllocPolicy a = AllocPolicy())
+      : impl_(a)
+    { }
+
+    size_t count() const {
+        return impl_.count();
+    }
+
+    bool empty() const {
+        return impl_.empty();
+    }
+
+    void clear() {
+        impl_.clear();
+    }
+
+    Range all() const {
+        return impl_.all();
+    }
+
+    MOZ_ALWAYS_INLINE
+    Ptr lookup(const Lookup& l) {
+        return impl_.lookup(l);
+    }
+
+    MOZ_ALWAYS_INLINE
+    bool has(const Lookup& l) const {
+        return const_cast<InlineSet*>(this)->lookup(l).found();
+    }
+
+    MOZ_ALWAYS_INLINE
+    AddPtr lookupForAdd(const Lookup& l) {
+        return impl_.lookupForAdd(l);
+    }
+
+    template <typename TInput>
+    MOZ_ALWAYS_INLINE
+    MOZ_MUST_USE bool add(AddPtr& p, TInput&& key) {
+        return impl_.add(p, mozilla::Forward<TInput>(key));
+    }
+
+    template <typename TInput>
+    MOZ_MUST_USE bool put(TInput&& key) {
+        AddPtr p = lookupForAdd(key);
+        return p ? true : add(p, mozilla::Forward<TInput>(key));
+    }
+
+    void remove(Ptr& p) {
+        impl_.remove(p);
+    }
+
+    void remove(const Lookup& l) {
+        impl_.remove(l);
+    }
+};
+
+} // namespace js
+
+#endif // ds_InlineTable_h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -1,158 +1,149 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "frontend/BytecodeCompiler.h"
 
+#include "mozilla/IntegerPrintfMacros.h"
+
 #include "jscntxt.h"
 #include "jsscript.h"
 
 #include "asmjs/AsmJS.h"
 #include "builtin/ModuleObject.h"
 #include "frontend/BytecodeEmitter.h"
 #include "frontend/FoldConstants.h"
 #include "frontend/NameFunctions.h"
 #include "frontend/Parser.h"
 #include "vm/GlobalObject.h"
 #include "vm/TraceLogging.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
-#include "frontend/Parser-inl.h"
-#include "vm/ScopeObject-inl.h"
+#include "vm/EnvironmentObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 using mozilla::Maybe;
 
 class MOZ_STACK_CLASS AutoCompilationTraceLogger
 {
   public:
     AutoCompilationTraceLogger(ExclusiveContext* cx, const TraceLoggerTextId id,
-            const ReadOnlyCompileOptions& options);
+                               const ReadOnlyCompileOptions& options);
 
   private:
     TraceLoggerThread* logger;
     TraceLoggerEvent event;
     AutoTraceLog scriptLogger;
     AutoTraceLog typeLogger;
 };
 
 // The BytecodeCompiler class contains resources common to compiling scripts and
 // function bodies.
 class MOZ_STACK_CLASS BytecodeCompiler
 {
   public:
     // Construct an object passing mandatory arguments.
     BytecodeCompiler(ExclusiveContext* cx,
-                     LifoAlloc* alloc,
+                     LifoAlloc& alloc,
                      const ReadOnlyCompileOptions& options,
                      SourceBufferHolder& sourceBuffer,
-                     Handle<StaticScope*> enclosingStaticScope,
+                     HandleScope enclosingScope,
                      TraceLoggerTextId logId);
 
     // Call setters for optional arguments.
     void maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor);
     void setSourceArgumentsNotIncluded();
 
-    JSScript* compileScript(HandleObject scopeChain, HandleScript evalCaller);
+    JSScript* compileGlobalScript(ScopeKind scopeKind);
+    JSScript* compileEvalScript(HandleObject environment, HandleScope enclosingScope);
     ModuleObject* compileModule();
     bool compileFunctionBody(MutableHandleFunction fun, Handle<PropertyNameVector> formals,
                              GeneratorKind generatorKind);
 
     ScriptSourceObject* sourceObjectPtr() const;
 
   private:
+    JSScript* compileScript(HandleObject environment, SharedContext* sc);
     bool checkLength();
     bool createScriptSource();
     bool maybeCompressSource();
     bool canLazilyParse();
     bool createParser();
     bool createSourceAndParser();
-    bool createScript(HandleObject staticScope, bool savedCallerFun = false);
-    bool createEmitter(SharedContext* sharedContext, HandleScript evalCaller = nullptr,
-                       bool insideNonGlobalEval = false);
-    bool isEvalCompilationUnit();
-    bool isNonGlobalEvalCompilationUnit();
-    bool isNonSyntacticCompilationUnit();
-    bool saveCallerFun(HandleScript evalCaller);
+    bool createScript();
+    bool emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext);
     bool handleParseFailure(const Directives& newDirectives);
-    bool prepareAndEmitTree(ParseNode** pn);
-    bool checkArgumentsWithinEval(JSContext* cx, HandleFunction fun);
-    bool maybeCheckEvalFreeVariables(HandleScript evalCaller, HandleObject scopeChain,
-                                     ParseContext<FullParseHandler>& pc);
-    bool maybeSetDisplayURL(TokenStream& tokenStream);
-    bool maybeSetSourceMap(TokenStream& tokenStream);
-    bool maybeSetSourceMapFromOptions();
-    bool emitFinalReturn();
-    bool initGlobalOrEvalBindings(ParseContext<FullParseHandler>& pc);
+    bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment);
     bool maybeCompleteCompressSource();
 
     AutoCompilationTraceLogger traceLogger;
     AutoKeepAtoms keepAtoms;
 
     ExclusiveContext* cx;
-    LifoAlloc* alloc;
+    LifoAlloc& alloc;
     const ReadOnlyCompileOptions& options;
     SourceBufferHolder& sourceBuffer;
 
-    Rooted<StaticScope*> enclosingStaticScope;
+    RootedScope enclosingScope;
     bool sourceArgumentsNotIncluded;
 
     RootedScriptSource sourceObject;
     ScriptSource* scriptSource;
 
     Maybe<SourceCompressionTask> maybeSourceCompressor;
     SourceCompressionTask* sourceCompressor;
 
+    Maybe<UsedNameTracker> usedNames;
     Maybe<Parser<SyntaxParseHandler>> syntaxParser;
     Maybe<Parser<FullParseHandler>> parser;
 
     Directives directives;
     TokenStream::Position startPosition;
 
     RootedScript script;
-    Maybe<BytecodeEmitter> emitter;
 };
 
 AutoCompilationTraceLogger::AutoCompilationTraceLogger(ExclusiveContext* cx,
         const TraceLoggerTextId id, const ReadOnlyCompileOptions& options)
   : logger(cx->isJSContext() ? TraceLoggerForMainThread(cx->asJSContext()->runtime())
                              : TraceLoggerForCurrentThread()),
     event(logger, TraceLogger_AnnotateScripts, options),
     scriptLogger(logger, event),
     typeLogger(logger, id)
 {}
 
 BytecodeCompiler::BytecodeCompiler(ExclusiveContext* cx,
-                                   LifoAlloc* alloc,
+                                   LifoAlloc& alloc,
                                    const ReadOnlyCompileOptions& options,
                                    SourceBufferHolder& sourceBuffer,
-                                   Handle<StaticScope*> enclosingStaticScope,
+                                   HandleScope enclosingScope,
                                    TraceLoggerTextId logId)
   : traceLogger(cx, logId, options),
     keepAtoms(cx->perThreadData),
     cx(cx),
     alloc(alloc),
     options(options),
     sourceBuffer(sourceBuffer),
-    enclosingStaticScope(cx, enclosingStaticScope),
+    enclosingScope(cx, enclosingScope),
     sourceArgumentsNotIncluded(false),
     sourceObject(cx),
     scriptSource(nullptr),
     sourceCompressor(nullptr),
     directives(options.strictOption),
     startPosition(keepAtoms),
     script(cx)
 {
+    MOZ_ASSERT(sourceBuffer.get());
 }
 
 void
 BytecodeCompiler::maybeSetSourceCompressor(SourceCompressionTask* sourceCompressor)
 {
     this->sourceCompressor = sourceCompressor;
 }
 
@@ -211,37 +202,41 @@ BytecodeCompiler::maybeCompressSource()
 
     return true;
 }
 
 bool
 BytecodeCompiler::canLazilyParse()
 {
     return options.canLazilyParse &&
-           !HasNonSyntacticStaticScopeChain(enclosingStaticScope) &&
+           !(enclosingScope && enclosingScope->hasOnChain(ScopeKind::NonSyntactic)) &&
            !cx->compartment()->behaviors().disableLazyParsing() &&
            !cx->compartment()->behaviors().discardSource() &&
            !options.sourceIsLazy &&
            !cx->lcovEnabled();
 }
 
 bool
 BytecodeCompiler::createParser()
 {
+    usedNames.emplace(cx);
+    if (!usedNames->init())
+        return false;
+
     if (canLazilyParse()) {
         syntaxParser.emplace(cx, alloc, options, sourceBuffer.get(), sourceBuffer.length(),
-                             /* foldConstants = */ false, (Parser<SyntaxParseHandler>*) nullptr,
-                             (LazyScript*) nullptr);
+                             /* foldConstants = */ false, *usedNames,
+                             (Parser<SyntaxParseHandler>*) nullptr, (LazyScript*) nullptr);
 
         if (!syntaxParser->checkOptions())
             return false;
     }
 
     parser.emplace(cx, alloc, options, sourceBuffer.get(), sourceBuffer.length(),
-                   /* foldConstants = */ true, syntaxParser.ptrOr(nullptr), nullptr);
+                   /* foldConstants = */ true, *usedNames, syntaxParser.ptrOr(nullptr), nullptr);
     parser->sct = sourceCompressor;
     parser->ss = scriptSource;
     if (!parser->checkOptions())
         return false;
 
     parser->tokenStream.tell(&startPosition);
     return true;
 }
@@ -250,82 +245,34 @@ bool
 BytecodeCompiler::createSourceAndParser()
 {
     return createScriptSource() &&
            maybeCompressSource() &&
            createParser();
 }
 
 bool
-BytecodeCompiler::createScript(HandleObject staticScope, bool savedCallerFun)
+BytecodeCompiler::createScript()
 {
-    script = JSScript::Create(cx, staticScope, savedCallerFun, options,
+    script = JSScript::Create(cx, options,
                               sourceObject, /* sourceStart = */ 0, sourceBuffer.length());
-
     return script != nullptr;
 }
 
 bool
-BytecodeCompiler::createEmitter(SharedContext* sharedContext, HandleScript evalCaller,
-                                bool insideNonGlobalEval)
+BytecodeCompiler::emplaceEmitter(Maybe<BytecodeEmitter>& emitter, SharedContext* sharedContext)
 {
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
     emitter.emplace(/* parent = */ nullptr, parser.ptr(), sharedContext, script,
-                    /* lazyScript = */ nullptr, options.forEval, evalCaller,
-                    insideNonGlobalEval, options.lineno, emitterMode);
+                    /* lazyScript = */ nullptr, options.lineno, emitterMode);
     return emitter->init();
 }
 
 bool
-BytecodeCompiler::isEvalCompilationUnit()
-{
-    return enclosingStaticScope->is<StaticEvalScope>();
-}
-
-bool
-BytecodeCompiler::isNonGlobalEvalCompilationUnit()
-{
-    if (!isEvalCompilationUnit())
-        return false;
-    StaticEvalScope& eval = enclosingStaticScope->as<StaticEvalScope>();
-    JSObject* enclosing = eval.enclosingScopeForStaticScopeIter();
-    return !IsStaticGlobalLexicalScope(enclosing);
-}
-
-bool
-BytecodeCompiler::isNonSyntacticCompilationUnit()
-{
-    return enclosingStaticScope->is<StaticNonSyntacticScope>();
-}
-
-bool
-BytecodeCompiler::saveCallerFun(HandleScript evalCaller)
-{
-    /*
-     * An eval script in a caller frame needs to have its enclosing
-     * function captured in case it refers to an upvar, and someone
-     * wishes to decompile it while it's running.
-     *
-     * This ends up as script->objects()->vector[0] in the compiled script.
-     */
-    RootedFunction fun(cx, evalCaller->functionOrCallerFunction());
-    MOZ_ASSERT_IF(fun->strict(), options.strictOption);
-    Directives directives(/* strict = */ options.strictOption);
-    ObjectBox* funbox = parser->newFunctionBox(/* fn = */ nullptr, fun,
-                                               directives, fun->generatorKind(),
-                                               enclosingStaticScope);
-    if (!funbox)
-        return false;
-
-    emitter->objectList.add(funbox);
-    return true;
-}
-
-bool
 BytecodeCompiler::handleParseFailure(const Directives& newDirectives)
 {
     if (parser->hadAbortedSyntaxParse()) {
         // Hit some unrecoverable ambiguity during an inner syntax parse.
         // Syntax parsing has now been disabled in the parser, so retry
         // the parse.
         parser->clearAbortedSyntaxParse();
     } else if (parser->tokenStream.hadError() || directives == newDirectives) {
@@ -337,284 +284,151 @@ BytecodeCompiler::handleParseFailure(con
     // Assignment must be monotonic to prevent reparsing iloops
     MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
     MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
     directives = newDirectives;
     return true;
 }
 
 bool
-BytecodeCompiler::prepareAndEmitTree(ParseNode** ppn)
-{
-    if (!FoldConstants(cx, ppn, parser.ptr()) ||
-        !NameFunctions(cx, *ppn) ||
-        !emitter->updateLocalsToFrameSlots())
-    {
-        return false;
-    }
-
-    emitter->setFunctionBodyEndPos((*ppn)->pn_pos);
-
-    if (!emitter->emitTree(*ppn))
-        return false;
-
-    return true;
-}
-
-bool
-BytecodeCompiler::maybeSetDisplayURL(TokenStream& tokenStream)
+BytecodeCompiler::deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment)
 {
-    if (tokenStream.hasDisplayURL()) {
-        if (!scriptSource->setDisplayURL(cx, tokenStream.displayURL()))
-            return false;
-    }
-    return true;
-}
-
-bool
-BytecodeCompiler::maybeSetSourceMap(TokenStream& tokenStream)
-{
-    if (tokenStream.hasSourceMapURL()) {
-        MOZ_ASSERT(!scriptSource->hasSourceMapURL());
-        if (!scriptSource->setSourceMapURL(cx, tokenStream.sourceMapURL()))
-            return false;
-    }
-    return true;
-}
-
-bool
-BytecodeCompiler::maybeSetSourceMapFromOptions()
-{
-    /*
-     * Source map URLs passed as a compile option (usually via a HTTP source map
-     * header) override any source map urls passed as comment pragmas.
-     */
-    if (options.sourceMapURL()) {
-        // Warn about the replacement, but use the new one.
-        if (scriptSource->hasSourceMapURL()) {
-            if(!parser->report(ParseWarning, false, nullptr, JSMSG_ALREADY_HAS_PRAGMA,
-                              scriptSource->filename(), "//# sourceMappingURL"))
+    RootedObject env(cx, environment);
+    while (env->is<EnvironmentObject>() || env->is<DebugEnvironmentProxy>()) {
+        if (env->is<CallObject>()) {
+            RootedScript script(cx, env->as<CallObject>().callee().getOrCreateScript(cx));
+            if (!script)
                 return false;
+            if (script->argumentsHasVarBinding()) {
+                if (!JSScript::argumentsOptimizationFailed(cx, script))
+                    return false;
+            }
         }
-
-        if (!scriptSource->setSourceMapURL(cx, options.sourceMapURL()))
-            return false;
+        env = env->enclosingEnvironment();
     }
 
     return true;
 }
 
 bool
-BytecodeCompiler::checkArgumentsWithinEval(JSContext* cx, HandleFunction fun)
-{
-    RootedScript script(cx, fun->getOrCreateScript(cx));
-    if (!script)
-        return false;
-
-    // It's an error to use |arguments| in a legacy generator expression.
-    if (script->isGeneratorExp() && script->isLegacyGenerator()) {
-        parser->report(ParseError, false, nullptr, JSMSG_BAD_GENEXP_BODY, js_arguments_str);
-        return false;
-    }
-
-    return true;
-}
-
-bool
-BytecodeCompiler::maybeCheckEvalFreeVariables(HandleScript evalCaller, HandleObject scopeChain,
-                                              ParseContext<FullParseHandler>& pc)
-{
-    if (!evalCaller || !evalCaller->functionOrCallerFunction())
-        return true;
-
-    // Eval scripts are only compiled on the main thread.
-    JSContext* cx = this->cx->asJSContext();
-
-    // Watch for uses of 'arguments' within the evaluated script, both as
-    // free variables and as variables redeclared with 'var'.
-    RootedFunction fun(cx, evalCaller->functionOrCallerFunction());
-    HandlePropertyName arguments = cx->names().arguments;
-    for (AtomDefnRange r = pc.lexdeps->all(); !r.empty(); r.popFront()) {
-        if (r.front().key() == arguments) {
-            if (!checkArgumentsWithinEval(cx, fun))
-                return false;
-        }
-    }
-    for (AtomDefnListMap::Range r = pc.decls().all(); !r.empty(); r.popFront()) {
-        if (r.front().key() == arguments) {
-            if (!checkArgumentsWithinEval(cx, fun))
-                return false;
-        }
-    }
-
-    // If the eval'ed script contains any debugger statement, force construction
-    // of arguments objects for the caller script and any other scripts it is
-    // transitively nested inside. The debugger can access any variable on the
-    // scope chain.
-    if (pc.sc->hasDebuggerStatement()) {
-        RootedObject scope(cx, scopeChain);
-        while (scope->is<ScopeObject>() || scope->is<DebugScopeObject>()) {
-            if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
-                RootedScript script(cx, scope->as<CallObject>().callee().getOrCreateScript(cx));
-                if (!script)
-                    return false;
-                if (script->argumentsHasVarBinding()) {
-                    if (!JSScript::argumentsOptimizationFailed(cx, script))
-                        return false;
-                }
-            }
-            scope = scope->enclosingScope();
-        }
-    }
-
-    return true;
-}
-
-bool
-BytecodeCompiler::emitFinalReturn()
-{
-    /*
-     * Nowadays the threaded interpreter needs a last return instruction, so we
-     * do have to emit that here.
-     */
-    return emitter->emit1(JSOP_RETRVAL);
-}
-
-bool
-BytecodeCompiler::initGlobalOrEvalBindings(ParseContext<FullParseHandler>& pc)
-{
-    Rooted<Bindings> bindings(cx, script->bindings);
-    if (!pc.generateBindings(cx, parser->tokenStream, *alloc, &bindings))
-        return false;
-    script->bindings = bindings;
-    return true;
-}
-
-bool
 BytecodeCompiler::maybeCompleteCompressSource()
 {
     return !maybeSourceCompressor || maybeSourceCompressor->complete();
 }
 
 JSScript*
-BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller)
+BytecodeCompiler::compileScript(HandleObject environment, SharedContext* sc)
 {
     if (!createSourceAndParser())
         return nullptr;
 
-    RootedFunction savedCallerFun(cx);
-    if (evalCaller)
-        savedCallerFun = evalCaller->functionOrCallerFunction();
-
-    if (!createScript(enclosingStaticScope, savedCallerFun))
+    if (!createScript())
         return nullptr;
 
-    GlobalSharedContext globalsc(cx, enclosingStaticScope, directives, options.extraWarningsOption,
-                                 savedCallerFun);
-    if (!createEmitter(&globalsc, evalCaller, isNonGlobalEvalCompilationUnit()))
-        return nullptr;
-
-    if (savedCallerFun && !saveCallerFun(evalCaller))
+    Maybe<BytecodeEmitter> emitter;
+    if (!emplaceEmitter(emitter, sc))
         return nullptr;
 
     for (;;) {
-        ParseContext<FullParseHandler> pc(parser.ptr(),
-                                          /* parent = */ nullptr,
-                                          /* maybeFunction = */ nullptr,
-                                          &globalsc,
-                                          /* newDirectives = */ nullptr);
-        if (!pc.init(*parser))
-            return nullptr;
-
         ParseNode* pn;
-        if (isEvalCompilationUnit())
-            pn = parser->evalBody();
+        if (sc->isEvalContext())
+            pn = parser->evalBody(sc->asEvalContext());
         else
-            pn = parser->globalBody();
+            pn = parser->globalBody(sc->asGlobalContext());
 
         // Successfully parsed. Emit the script.
         if (pn) {
-            if (!initGlobalOrEvalBindings(pc))
+            if (sc->isEvalContext() && sc->hasDebuggerStatement() && cx->isJSContext()) {
+                // If the eval'ed script contains any debugger statement, force construction
+                // of arguments objects for the caller script and any other scripts it is
+                // transitively nested inside. The debugger can access any variable on the
+                // scope chain.
+                if (!deoptimizeArgumentsInEnclosingScripts(cx->asJSContext(), environment))
+                    return nullptr;
+            }
+            if (!NameFunctions(cx, pn))
                 return nullptr;
-            if (!maybeCheckEvalFreeVariables(evalCaller, scopeChain, pc))
-                return nullptr;
-            if (!prepareAndEmitTree(&pn))
+            if (!emitter->emitScript(pn))
                 return nullptr;
             parser->handler.freeTree(pn);
 
             break;
         }
 
         // Maybe we aborted a syntax parse. See if we can try again.
         if (!handleParseFailure(directives))
             return nullptr;
+
+        // Reset UsedNameTracker state before trying again.
+        usedNames->reset();
     }
 
-    if (!maybeSetDisplayURL(parser->tokenStream) ||
-        !maybeSetSourceMap(parser->tokenStream) ||
-        !maybeSetSourceMapFromOptions() ||
-        !emitFinalReturn() ||
-        !JSScript::fullyInitFromEmitter(cx, script, emitter.ptr()))
-    {
-        return nullptr;
-    }
-
-    emitter->tellDebuggerAboutCompiledScript(cx);
-
     if (!maybeCompleteCompressSource())
         return nullptr;
 
     MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
+
     return script;
 }
 
-ModuleObject* BytecodeCompiler::compileModule()
+JSScript*
+BytecodeCompiler::compileGlobalScript(ScopeKind scopeKind)
+{
+    GlobalSharedContext globalsc(cx, scopeKind, directives, options.extraWarningsOption);
+    return compileScript(nullptr, &globalsc);
+}
+
+JSScript*
+BytecodeCompiler::compileEvalScript(HandleObject environment, HandleScope enclosingScope)
+{
+    EvalSharedContext evalsc(cx, environment, enclosingScope,
+                             directives, options.extraWarningsOption);
+    return compileScript(environment, &evalsc);
+}
+
+ModuleObject*
+BytecodeCompiler::compileModule()
 {
     if (!createSourceAndParser())
         return nullptr;
 
-    Rooted<ModuleObject*> module(cx, ModuleObject::create(cx, enclosingStaticScope));
+    Rooted<ModuleObject*> module(cx, ModuleObject::create(cx));
     if (!module)
         return nullptr;
 
-    if (!createScript(module))
+    if (!createScript())
         return nullptr;
 
     module->init(script);
 
     ModuleBuilder builder(cx, module);
-    ParseNode* pn = parser->standaloneModule(module, builder);
+    ModuleSharedContext modulesc(cx, module, enclosingScope, builder);
+    ParseNode* pn = parser->moduleBody(&modulesc);
     if (!pn)
         return nullptr;
 
-    if (!NameFunctions(cx, pn) ||
-        !maybeSetDisplayURL(parser->tokenStream) ||
-        !maybeSetSourceMap(parser->tokenStream))
-    {
-        return nullptr;
-    }
-
-    script->bindings = pn->pn_modulebox->bindings;
-
-    RootedModuleEnvironmentObject dynamicScope(cx, ModuleEnvironmentObject::create(cx, module));
-    if (!dynamicScope)
+    if (!NameFunctions(cx, pn))
         return nullptr;
 
-    module->setInitialEnvironment(dynamicScope);
+    Maybe<BytecodeEmitter> emitter;
+    if (!emplaceEmitter(emitter, &modulesc))
+        return nullptr;
+    if (!emitter->emitScript(pn->pn_body))
+        return nullptr;
 
-    if (!createEmitter(pn->pn_modulebox) ||
-        !emitter->emitModuleScript(pn->pn_body))
-    {
-        return nullptr;
-    }
+    parser->handler.freeTree(pn);
 
     if (!builder.initModule())
         return nullptr;
 
-    parser->handler.freeTree(pn);
+    RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module));
+    if (!env)
+        return nullptr;
+
+    module->setInitialEnvironment(env);
 
     if (!maybeCompleteCompressSource())
         return nullptr;
 
     MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
     return module;
 }
 
@@ -634,42 +448,36 @@ BytecodeCompiler::compileFunctionBody(Mu
     // Speculatively parse using the default directives implied by the context.
     // If a directive is encountered (e.g., "use strict") that changes how the
     // function should have been parsed, we backup and reparse with the new set
     // of directives.
 
     ParseNode* fn;
     do {
         Directives newDirectives = directives;
-        fn = parser->standaloneFunctionBody(fun, formals, generatorKind, directives,
-                                            &newDirectives, enclosingStaticScope);
+        fn = parser->standaloneFunctionBody(fun, enclosingScope, formals, generatorKind,
+                                            directives, &newDirectives);
         if (!fn && !handleParseFailure(newDirectives))
             return false;
     } while (!fn);
 
-    if (!NameFunctions(cx, fn) ||
-        !maybeSetDisplayURL(parser->tokenStream) ||
-        !maybeSetSourceMap(parser->tokenStream))
-    {
+    if (!NameFunctions(cx, fn))
         return false;
-    }
 
     if (fn->pn_funbox->function()->isInterpreted()) {
         MOZ_ASSERT(fun == fn->pn_funbox->function());
 
-        if (!createScript(enclosingStaticScope))
+        if (!createScript())
             return false;
 
-        script->bindings = fn->pn_funbox->bindings;
-
-        if (!createEmitter(fn->pn_funbox) ||
-            !emitter->emitFunctionScript(fn->pn_body))
-        {
+        Maybe<BytecodeEmitter> emitter;
+        if (!emplaceEmitter(emitter, fn->pn_funbox))
             return false;
-        }
+        if (!emitter->emitFunctionScript(fn->pn_body))
+            return false;
     } else {
         fun.set(fn->pn_funbox->function());
         MOZ_ASSERT(IsAsmJSModule(fun));
     }
 
     if (!maybeCompleteCompressSource())
         return false;
 
@@ -709,97 +517,104 @@ frontend::CreateScriptSourceObject(Exclu
     if (cx->isJSContext()) {
         if (!ScriptSourceObject::initFromOptions(cx->asJSContext(), sso, options))
             return nullptr;
     }
 
     return sso;
 }
 
-JSScript*
-frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject scopeChain,
-                        Handle<StaticScope*> enclosingStaticScope,
-                        HandleScript evalCaller,
-                        const ReadOnlyCompileOptions& options,
-                        SourceBufferHolder& srcBuf,
-                        JSString* source_ /* = nullptr */,
-                        SourceCompressionTask* extraSct /* = nullptr */,
-                        ScriptSourceObject** sourceObjectOut /* = nullptr */)
+// CompileScript independently returns the ScriptSourceObject (SSO) for the
+// compile.  This is used by off-main-thread script compilation (OMT-SC).
+//
+// OMT-SC cannot initialize the SSO when it is first constructed because the
+// SSO is allocated initially in a separate compartment.
+//
+// After OMT-SC, the separate compartment is merged with the main compartment,
+// at which point the JSScripts created become observable by the debugger via
+// memory-space scanning.
+//
+// Whatever happens to the top-level script compilation (even if it fails and
+// returns null), we must finish initializing the SSO.  This is because there
+// may be valid inner scripts observable by the debugger which reference the
+// partially-initialized SSO.
+class MOZ_STACK_CLASS AutoInitializeSourceObject
 {
-    MOZ_ASSERT(srcBuf.get());
+    BytecodeCompiler& compiler_;
+    ScriptSourceObject** sourceObjectOut_;
 
-    /*
-     * The scripted callerFrame can only be given for compile-and-go scripts
-     * and non-zero static level requires callerFrame.
-     */
-    MOZ_ASSERT_IF(evalCaller, options.isRunOnce);
-    MOZ_ASSERT_IF(evalCaller, options.forEval);
-    MOZ_ASSERT_IF(evalCaller && evalCaller->strict(), options.strictOption);
-
-    MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
+  public:
+    AutoInitializeSourceObject(BytecodeCompiler& compiler, ScriptSourceObject** sourceObjectOut)
+      : compiler_(compiler),
+        sourceObjectOut_(sourceObjectOut)
+    { }
 
-    BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingStaticScope,
-                              TraceLogger_ParserCompileScript);
-    compiler.maybeSetSourceCompressor(extraSct);
-    JSScript* script = compiler.compileScript(scopeChain, evalCaller);
+    ~AutoInitializeSourceObject() {
+        if (sourceObjectOut_)
+            *sourceObjectOut_ = compiler_.sourceObjectPtr();
+    }
+};
 
-    // frontend::CompileScript independently returns the
-    // ScriptSourceObject (SSO) for the compile.  This is used by
-    // off-main-thread script compilation (OMT-SC).
-    //
-    // OMT-SC cannot initialize the SSO when it is first constructed
-    // because the SSO is allocated initially in a separate compartment.
-    //
-    // After OMT-SC, the separate compartment is merged with the main
-    // compartment, at which point the JSScripts created become observable
-    // by the debugger via memory-space scanning.
-    //
-    // Whatever happens to the top-level script compilation (even if it
-    // fails and returns null), we must finish initializing the SSO.  This
-    // is because there may be valid inner scripts observable by the debugger
-    // which reference the partially-initialized SSO.
-    if (sourceObjectOut)
-        *sourceObjectOut = compiler.sourceObjectPtr();
+JSScript*
+frontend::CompileGlobalScript(ExclusiveContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
+                              const ReadOnlyCompileOptions& options,
+                              SourceBufferHolder& srcBuf,
+                              SourceCompressionTask* extraSct,
+                              ScriptSourceObject** sourceObjectOut)
+{
+    MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
+    BytecodeCompiler compiler(cx, alloc, options, srcBuf, /* enclosingScope = */ nullptr,
+                              TraceLogger_ParserCompileScript);
+    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
+    compiler.maybeSetSourceCompressor(extraSct);
+    return compiler.compileGlobalScript(scopeKind);
+}
 
-    return script;
+JSScript*
+frontend::CompileEvalScript(ExclusiveContext* cx, LifoAlloc& alloc,
+                            HandleObject environment, HandleScope enclosingScope,
+                            const ReadOnlyCompileOptions& options,
+                            SourceBufferHolder& srcBuf,
+                            SourceCompressionTask* extraSct,
+                            ScriptSourceObject** sourceObjectOut)
+{
+    BytecodeCompiler compiler(cx, alloc, options, srcBuf, enclosingScope,
+                              TraceLogger_ParserCompileScript);
+    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
+    compiler.maybeSetSourceCompressor(extraSct);
+    return compiler.compileEvalScript(environment, enclosingScope);
 }
 
 ModuleObject*
 frontend::CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& optionsInput,
-                        SourceBufferHolder& srcBuf, LifoAlloc* alloc,
+                        SourceBufferHolder& srcBuf, LifoAlloc& alloc,
                         ScriptSourceObject** sourceObjectOut /* = nullptr */)
 {
     MOZ_ASSERT(srcBuf.get());
-    MOZ_ASSERT(alloc);
     MOZ_ASSERT_IF(sourceObjectOut, *sourceObjectOut == nullptr);
 
     CompileOptions options(cx, optionsInput);
     options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code.
     options.setIsRunOnce(true);
 
-    Rooted<StaticScope*> staticScope(cx, &cx->global()->lexicalScope().staticBlock());
-    BytecodeCompiler compiler(cx, alloc, options, srcBuf, staticScope,
+    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+    BytecodeCompiler compiler(cx, alloc, options, srcBuf, emptyGlobalScope,
                               TraceLogger_ParserCompileModule);
-    ModuleObject* module = compiler.compileModule();
-
-    // See the comment about sourceObjectOut above.
-    if (sourceObjectOut)
-        *sourceObjectOut = compiler.sourceObjectPtr();
-
-    return module;
+    AutoInitializeSourceObject autoSSO(compiler, sourceObjectOut);
+    return compiler.compileModule();
 }
 
 ModuleObject*
 frontend::CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
                         SourceBufferHolder& srcBuf)
 {
     if (!GlobalObject::ensureModulePrototypesCreated(cx, cx->global()))
         return nullptr;
 
-    LifoAlloc* alloc = &cx->asJSContext()->tempLifoAlloc();
+    LifoAlloc& alloc = cx->asJSContext()->tempLifoAlloc();
     RootedModuleObject module(cx, CompileModule(cx, options, srcBuf, alloc));
     if (!module)
         return nullptr;
 
     // This happens in GlobalHelperThreadState::finishModuleParseTask() when a
     // module is compiled off main thread.
     if (!ModuleObject::Freeze(cx->asJSContext(), module))
         return nullptr;
@@ -816,102 +631,94 @@ frontend::CompileLazyFunction(JSContext*
     options.setMutedErrors(lazy->mutedErrors())
            .setFileAndLine(lazy->filename(), lazy->lineno())
            .setColumn(lazy->column())
            .setNoScriptRval(false)
            .setSelfHostingMode(false);
 
     AutoCompilationTraceLogger traceLogger(cx, TraceLogger_ParserCompileLazy, options);
 
-    Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars, length,
-                                    /* foldConstants = */ true, nullptr, lazy);
+    UsedNameTracker usedNames(cx);
+    if (!usedNames.init())
+        return false;
+    Parser<FullParseHandler> parser(cx, cx->tempLifoAlloc(), options, chars, length,
+                                    /* foldConstants = */ true, usedNames, nullptr, lazy);
     if (!parser.checkOptions())
         return false;
 
     Rooted<JSFunction*> fun(cx, lazy->functionNonDelazifying());
     MOZ_ASSERT(!lazy->isLegacyGenerator());
     ParseNode* pn = parser.standaloneLazyFunction(fun, lazy->strict(), lazy->generatorKind());
     if (!pn)
         return false;
 
     if (!NameFunctions(cx, pn))
         return false;
 
-    RootedObject enclosingScope(cx, lazy->enclosingScope());
     RootedScriptSource sourceObject(cx, lazy->sourceObject());
     MOZ_ASSERT(sourceObject);
 
-    Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
-                                                  sourceObject, lazy->begin(), lazy->end()));
+    Rooted<JSScript*> script(cx, JSScript::Create(cx, options, sourceObject,
+                                                  lazy->begin(), lazy->end()));
     if (!script)
         return false;
 
-    script->bindings = pn->pn_funbox->bindings;
-
     if (lazy->isLikelyConstructorWrapper())
         script->setLikelyConstructorWrapper();
     if (lazy->hasBeenCloned())
         script->setHasBeenCloned();
 
-    /*
-     * We just pass false for insideNonGlobalEval and insideEval, because we
-     * don't actually know whether we are or not.  The only consumer of those
-     * booleans is TryConvertFreeName, and it has special machinery to avoid
-     * doing bad things when a lazy function is inside eval.
-     */
-    MOZ_ASSERT(!options.forEval);
     BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
-                        /* insideEval = */ false, /* evalCaller = */ nullptr,
-                        /* insideNonGlobalEval = */ false, pn->pn_pos,
-                        BytecodeEmitter::LazyFunction);
+                        pn->pn_pos, BytecodeEmitter::LazyFunction);
     if (!bce.init())
         return false;
 
     return bce.emitFunctionScript(pn->pn_body);
 }
 
 // Compile a JS function body, which might appear as the value of an event
 // handler attribute in an HTML <INPUT> tag, or in a Function() constructor.
 static bool
 CompileFunctionBody(JSContext* cx, MutableHandleFunction fun, const ReadOnlyCompileOptions& options,
                     Handle<PropertyNameVector> formals, SourceBufferHolder& srcBuf,
-                    Handle<StaticScope*> enclosingStaticScope, GeneratorKind generatorKind)
+                    HandleScope enclosingScope, GeneratorKind generatorKind)
 {
     MOZ_ASSERT(!options.isRunOnce);
 
     // FIXME: make Function pass in two strings and parse them as arguments and
     // ProgramElements respectively.
 
-    BytecodeCompiler compiler(cx, &cx->tempLifoAlloc(), options, srcBuf, enclosingStaticScope,
+    BytecodeCompiler compiler(cx, cx->tempLifoAlloc(), options, srcBuf, enclosingScope,
                               TraceLogger_ParserCompileFunction);
     compiler.setSourceArgumentsNotIncluded();
     return compiler.compileFunctionBody(fun, formals, generatorKind);
 }
 
 bool
 frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
                               const ReadOnlyCompileOptions& options,
                               Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf,
-                              Handle<StaticScope*> enclosingStaticScope)
+                              HandleScope enclosingScope)
 {
-    return CompileFunctionBody(cx, fun, options, formals, srcBuf,
-                               enclosingStaticScope, NotGenerator);
+    return CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingScope, NotGenerator);
 }
 
 bool
 frontend::CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
                               const ReadOnlyCompileOptions& options,
                               Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf)
 {
-    Rooted<StaticScope*> staticLexical(cx, &cx->global()->lexicalScope().staticBlock());
-    return CompileFunctionBody(cx, fun, options, formals, srcBuf, staticLexical, NotGenerator);
+    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+    return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
+                               NotGenerator);
 }
 
 
 bool
 frontend::CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun,
                                    const ReadOnlyCompileOptions& options,
                                    Handle<PropertyNameVector> formals,
                                    JS::SourceBufferHolder& srcBuf)
 {
-    Rooted<StaticScope*> staticLexical(cx, &cx->global()->lexicalScope().staticBlock());
-    return CompileFunctionBody(cx, fun, options, formals, srcBuf, staticLexical, StarGenerator);
+    RootedScope emptyGlobalScope(cx, &cx->global()->emptyGlobalScope());
+    return CompileFunctionBody(cx, fun, options, formals, srcBuf, emptyGlobalScope,
+                               StarGenerator);
 }
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -4,63 +4,65 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_BytecodeCompiler_h
 #define frontend_BytecodeCompiler_h
 
 #include "NamespaceImports.h"
 
+#include "vm/Scope.h"
 #include "vm/String.h"
 
 class JSLinearString;
 
 namespace js {
 
 class LazyScript;
 class LifoAlloc;
 class ModuleObject;
 class ScriptSourceObject;
-class StaticScope;
 struct SourceCompressionTask;
 
 namespace frontend {
 
 JSScript*
-CompileScript(ExclusiveContext* cx, LifoAlloc* alloc,
-              HandleObject scopeChain, Handle<StaticScope*> enclosingStaticScope,
-              HandleScript evalCaller, const ReadOnlyCompileOptions& options,
-              SourceBufferHolder& srcBuf, JSString* source_ = nullptr,
-              SourceCompressionTask* extraSct = nullptr,
-              ScriptSourceObject** sourceObjectOut = nullptr);
+CompileGlobalScript(ExclusiveContext* cx, LifoAlloc& alloc, ScopeKind scopeKind,
+                    const ReadOnlyCompileOptions& options,
+                    SourceBufferHolder& srcBuf,
+                    SourceCompressionTask* extraSct = nullptr,
+                    ScriptSourceObject** sourceObjectOut = nullptr);
+
+JSScript*
+CompileEvalScript(ExclusiveContext* cx, LifoAlloc& alloc,
+                  HandleObject scopeChain, HandleScope enclosingScope,
+                  const ReadOnlyCompileOptions& options,
+                  SourceBufferHolder& srcBuf,
+                  SourceCompressionTask* extraSct = nullptr,
+                  ScriptSourceObject** sourceObjectOut = nullptr);
 
 ModuleObject*
 CompileModule(JSContext* cx, const ReadOnlyCompileOptions& options,
               SourceBufferHolder& srcBuf);
 
 ModuleObject*
 CompileModule(ExclusiveContext* cx, const ReadOnlyCompileOptions& options,
-              SourceBufferHolder& srcBuf, LifoAlloc* alloc,
+              SourceBufferHolder& srcBuf, LifoAlloc& alloc,
               ScriptSourceObject** sourceObjectOut = nullptr);
 
 MOZ_MUST_USE bool
 CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
 
-/*
- * enclosingStaticScope is a static enclosing scope (e.g. a StaticWithScope).
- * Must be null if the enclosing scope is a global.
- */
 MOZ_MUST_USE bool
 CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
                     const ReadOnlyCompileOptions& options,
                     Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf,
-                    Handle<StaticScope*> enclosingStaticScope);
+                    HandleScope enclosingScope);
 
-// As above, but defaults to the global lexical scope as the enclosing static
-// scope.
+// As above, but defaults to the global lexical scope as the enclosing scope.
 MOZ_MUST_USE bool
 CompileFunctionBody(JSContext* cx, MutableHandleFunction fun,
                     const ReadOnlyCompileOptions& options,
                     Handle<PropertyNameVector> formals, JS::SourceBufferHolder& srcBuf);
 
 MOZ_MUST_USE bool
 CompileStarGeneratorBody(JSContext* cx, MutableHandleFunction fun,
                          const ReadOnlyCompileOptions& options,
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -34,180 +34,1576 @@
 #include "vm/Debugger.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Stack.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
-#include "frontend/ParseMaps-inl.h"
 #include "frontend/ParseNode-inl.h"
+#include "vm/EnvironmentObject-inl.h"
 #include "vm/NativeObject-inl.h"
-#include "vm/ScopeObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::frontend;
 
+using mozilla::AssertedCast;
+using mozilla::DebugOnly;
 using mozilla::Maybe;
-using mozilla::Some;
-using mozilla::DebugOnly;
+using mozilla::Nothing;
 using mozilla::NumberIsInt32;
 using mozilla::PodCopy;
-
-struct frontend::StmtInfoBCE : public StmtInfoBase
-{
-    StmtInfoBCE*    enclosing;
-    StmtInfoBCE*    enclosingScope;
-
-    JumpTarget      update;         /* loop update offset (top if none) */
-    JumpList        breaks;         /* offset of last break in loop */
-    JumpList        continues;      /* offset of last continue in loop */
-    uint32_t        blockScopeIndex; /* index of scope in BlockScopeArray */
-
-    explicit StmtInfoBCE(ExclusiveContext* cx) : StmtInfoBase(cx) {}
-
-    void setTop(JumpTarget top) {
-        update = top;
-        breaks = JumpList();
-        continues = JumpList();
-    }
-
-    /*
-     * To reuse space, alias two of the ptrdiff_t fields for use during
-     * try/catch/finally code generation and backpatching.
-     *
-     * Only a loop, switch, or label statement info record can have breaks and
-     * continues, and only a for loop has an update backpatch chain, so it's
-     * safe to overlay these for the "trying" StmtTypes.
-     */
-
-    JumpList& gosubs() {
-        MOZ_ASSERT(type == StmtType::FINALLY);
-        return breaks;
-    }
-
-    JumpList& guardJump() {
-        MOZ_ASSERT(type == StmtType::TRY || type == StmtType::FINALLY);
-        return continues;
+using mozilla::Some;
+
+class BreakableControl;
+class LabelControl;
+class LoopControl;
+class TryFinallyControl;
+
+static bool
+ParseNodeRequiresSpecialLineNumberNotes(ParseNode* pn)
+{
+    return pn->getKind() == PNK_WHILE || pn->getKind() == PNK_FOR;
+}
+
+// A cache that tracks superfluous TDZ checks.
+//
+// Each basic block should have a TDZCheckCache in scope. Some NestableControl
+// subclasses contain a TDZCheckCache.
+class BytecodeEmitter::TDZCheckCache : public Nestable<BytecodeEmitter::TDZCheckCache>
+{
+    PooledMapPtr<CheckTDZMap> cache_;
+
+    MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
+        return cache_ || cache_.acquire(bce->cx);
+    }
+
+  public:
+    explicit TDZCheckCache(BytecodeEmitter* bce)
+      : Nestable<TDZCheckCache>(&bce->innermostTDZCheckCache),
+        cache_(bce->cx->frontendCollectionPool())
+    { }
+
+    Maybe<MaybeCheckTDZ> needsTDZCheck(BytecodeEmitter* bce, JSAtom* name);
+    MOZ_MUST_USE bool noteTDZCheck(BytecodeEmitter* bce, JSAtom* name, MaybeCheckTDZ check);
+};
+
+class BytecodeEmitter::NestableControl : public Nestable<BytecodeEmitter::NestableControl>
+{
+    StatementKind kind_;
+
+    // The innermost scope when this was pushed.
+    EmitterScope* emitterScope_;
+
+  protected:
+    NestableControl(BytecodeEmitter* bce, StatementKind kind)
+      : Nestable<NestableControl>(&bce->innermostNestableControl),
+        kind_(kind),
+        emitterScope_(bce->innermostEmitterScope)
+    { }
+
+  public:
+    using Nestable<NestableControl>::enclosing;
+    using Nestable<NestableControl>::findNearest;
+
+    StatementKind kind() const {
+        return kind_;
+    }
+
+    EmitterScope* emitterScope() const {
+        return emitterScope_;
+    }
+
+    template <typename T>
+    bool is() const;
+
+    template <typename T>
+    T& as() {
+        MOZ_ASSERT(is<T>());
+        return static_cast<T&>(*this);
+    }
+};
+
+// Template specializations are disallowed in different namespaces; specialize
+// all the NestableControl subtypes up front.
+namespace js {
+namespace frontend {
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<BreakableControl>() const
+{
+    return StatementKindIsUnlabeledBreakTarget(kind_) || kind_ == StatementKind::Label;
+}
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<LabelControl>() const
+{
+    return kind_ == StatementKind::Label;
+}
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<LoopControl>() const
+{
+    return StatementKindIsLoop(kind_);
+}
+
+template <>
+bool
+BytecodeEmitter::NestableControl::is<TryFinallyControl>() const
+{
+    return kind_ == StatementKind::Try || kind_ == StatementKind::Finally;
+}
+
+} // namespace frontend
+} // namespace js
+
+class BreakableControl : public BytecodeEmitter::NestableControl
+{
+  public:
+    // Offset of the last break.
+    JumpList breaks;
+
+    BreakableControl(BytecodeEmitter* bce, StatementKind kind)
+      : NestableControl(bce, kind)
+    {
+        MOZ_ASSERT(is<BreakableControl>());
+    }
+
+    MOZ_MUST_USE bool patchBreaks(BytecodeEmitter* bce) {
+        return bce->emitJumpTargetAndPatch(breaks);
+    }
+};
+
+class LabelControl : public BreakableControl
+{
+    RootedAtom label_;
+
+    // The code offset when this was pushed. Used for effectfulness checking.
+    ptrdiff_t startOffset_;
+
+  public:
+    LabelControl(BytecodeEmitter* bce, JSAtom* label, ptrdiff_t startOffset)
+      : BreakableControl(bce, StatementKind::Label),
+        label_(bce->cx, label),
+        startOffset_(startOffset)
+    { }
+
+    HandleAtom label() const {
+        return label_;
+    }
+
+    ptrdiff_t startOffset() const {
+        return startOffset_;
+    }
+};
+
+class LoopControl : public BreakableControl
+{
+    // Loops' children are emitted in dominance order, so they can always
+    // have a TDZCheckCache.
+    BytecodeEmitter::TDZCheckCache tdzCache_;
+
+    // Stack depth when this loop was pushed on the control stack.
+    int32_t stackDepth_;
+
+    // The loop nesting depth. Used as a hint to Ion.
+    uint32_t loopDepth_;
+
+    // Can we OSR into Ion from here? True unless there is non-loop state on the stack.
+    bool canIonOsr_;
+
+  public:
+    // The target of continue statement jumps, e.g., the update portion of a
+    // for(;;) loop.
+    JumpTarget continueTarget;
+
+    // Offset of the last continue in the loop.
+    JumpList continues;
+
+    LoopControl(BytecodeEmitter* bce, StatementKind loopKind)
+      : BreakableControl(bce, loopKind),
+        tdzCache_(bce),
+        continueTarget({ -1 })
+    {
+        MOZ_ASSERT(is<LoopControl>());
+
+        LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing());
+
+        stackDepth_ = bce->stackDepth;
+        loopDepth_ = enclosingLoop ? enclosingLoop->loopDepth_ + 1 : 1;
+
+        int loopSlots;
+        if (loopKind == StatementKind::Spread)
+            loopSlots = 3;
+        else if (loopKind == StatementKind::ForInLoop || loopKind == StatementKind::ForOfLoop)
+            loopSlots = 2;
+        else
+            loopSlots = 0;
+
+        MOZ_ASSERT(loopSlots <= stackDepth_);
+
+        if (enclosingLoop) {
+            canIonOsr_ = (enclosingLoop->canIonOsr_ &&
+                          stackDepth_ == enclosingLoop->stackDepth_ + loopSlots);
+        } else {
+            canIonOsr_ = stackDepth_ == loopSlots;
+        }
+    }
+
+    uint32_t loopDepth() const {
+        return loopDepth_;
+    }
+
+    bool canIonOsr() const {
+        return canIonOsr_;
+    }
+
+    MOZ_MUST_USE bool patchBreaksAndContinues(BytecodeEmitter* bce) {
+        MOZ_ASSERT(continueTarget.offset != -1);
+        if (!patchBreaks(bce))
+            return false;
+        bce->patchJumpsToTarget(continues, continueTarget);
+        return true;
+    }
+};
+
+class TryFinallyControl : public BytecodeEmitter::NestableControl
+{
+    bool emittingSubroutine_;
+
+  public:
+    // The subroutine when emitting a finally block.
+    JumpList gosubs;
+
+    // Offset of the last catch guard, if any.
+    JumpList guardJump;
+
+    TryFinallyControl(BytecodeEmitter* bce, StatementKind kind)
+      : NestableControl(bce, kind),
+        emittingSubroutine_(false)
+    {
+        MOZ_ASSERT(is<TryFinallyControl>());
+    }
+
+    void setEmittingSubroutine() {
+        emittingSubroutine_ = true;
+    }
+
+    bool emittingSubroutine() const {
+        return emittingSubroutine_;
     }
 };
 
-struct frontend::LoopStmtInfo : public StmtInfoBCE
-{
-    int32_t         stackDepth;     // Stack depth when this loop was pushed.
-    uint32_t        loopDepth;      // Loop depth.
-
-    // Can we OSR into Ion from here?  True unless there is non-loop state on the stack.
-    bool            canIonOsr;
-
-    explicit LoopStmtInfo(ExclusiveContext* cx) : StmtInfoBCE(cx) {}
-
-    static LoopStmtInfo* fromStmtInfo(StmtInfoBCE* stmt) {
-        MOZ_ASSERT(stmt->isLoop());
-        return static_cast<LoopStmtInfo*>(stmt);
+static bool
+ScopeKindIsInBody(ScopeKind kind)
+{
+    return kind == ScopeKind::Lexical ||
+           kind == ScopeKind::Catch ||
+           kind == ScopeKind::With ||
+           kind == ScopeKind::FunctionBodyVar ||
+           kind == ScopeKind::ParameterExpressionVar;
+}
+
+static inline void
+MarkAllBindingsClosedOver(LexicalScope::Data& data)
+{
+    BindingName* names = data.names;
+    for (uint32_t i = 0; i < data.length; i++)
+        names[i] = BindingName(names[i].name(), true);
+}
+
+// A scope that introduces bindings.
+class BytecodeEmitter::EmitterScope : public Nestable<BytecodeEmitter::EmitterScope>
+{
+    // The cache of bound names that may be looked up in the
+    // scope. Initially populated as the set of names this scope binds. As
+    // names are looked up in enclosing scopes, they are cached on the
+    // current scope.
+    PooledMapPtr<NameLocationMap> nameCache_;
+
+    // If this scope's cache does not include free names, such as the
+    // global scope, the NameLocation to return.
+    Maybe<NameLocation> fallbackFreeNameLocation_;
+
+    // True if there is a corresponding EnvironmentObject on the environment
+    // chain, false if all bindings are stored in frame slots on the stack.
+    bool hasEnvironment_;
+
+    // The number of enclosing environments. Used for error checking.
+    uint8_t environmentChainLength_;
+
+    // The next usable slot on the frame for not-closed over bindings.
+    //
+    // The initial frame slot when assigning slots to bindings is the
+    // enclosing scope's nextFrameSlot. For the first scope in a frame,
+    // the initial frame slot is 0.
+    uint32_t nextFrameSlot_;
+
+    // The index in the BytecodeEmitter's interned scope vector, otherwise
+    // ScopeNote::NoScopeIndex.
+    uint32_t scopeIndex_;
+
+    // If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's
+    // block scope note list. Otherwise ScopeNote::NoScopeNote.
+    uint32_t noteIndex_;
+
+    MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce) {
+        return nameCache_.acquire(bce->cx);
+    }
+
+    template <typename BindingIter>
+    MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce, const BindingIter& bi) {
+        if (bi.nextFrameSlot() >= LOCALNO_LIMIT ||
+            bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT)
+        {
+            return bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
+        }
+        return true;
+    }
+
+    MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce) {
+        uint32_t hops;
+        if (EmitterScope* emitterScope = enclosing(&bce))
+            hops = emitterScope->environmentChainLength_;
+        else
+            hops = bce->sc->compilationEnclosingScope()->environmentChainLength();
+        if (hops >= ENVCOORD_HOPS_LIMIT - 1)
+            return bce->reportError(nullptr, JSMSG_TOO_DEEP, js_function_str);
+        environmentChainLength_ = mozilla::AssertedCast<uint8_t>(hops + 1);
+        return true;
+    }
+
+    void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi) {
+        nextFrameSlot_ = bi.nextFrameSlot();
+        if (nextFrameSlot_ > bce->maxFixedSlots)
+            bce->maxFixedSlots = nextFrameSlot_;
+        MOZ_ASSERT_IF(bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isGenerator(),
+                      bce->maxFixedSlots == 0);
+    }
+
+    MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name, NameLocation loc) {
+        NameLocationMap& cache = *nameCache_;
+        NameLocationMap::AddPtr p = cache.lookupForAdd(name);
+        MOZ_ASSERT(!p);
+        if (!cache.add(p, name, loc)) {
+            ReportOutOfMemory(bce->cx);
+            return false;
+        }
+        return true;
+    }
+
+    Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce, JSAtom* name) {
+        if (NameLocationMap::Ptr p = nameCache_->lookup(name))
+            return Some(p->value().wrapped);
+        if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name))
+            return fallbackFreeNameLocation_;
+        return Nothing();
+    }
+
+    friend bool BytecodeEmitter::needsImplicitThis();
+
+    EmitterScope* enclosing(BytecodeEmitter** bce) const {
+        // There is an enclosing scope with access to the same frame.
+        if (EmitterScope* inFrame = enclosingInFrame())
+            return inFrame;
+
+        // We are currently compiling the enclosing script, look in the
+        // enclosing BCE.
+        if ((*bce)->parent) {
+            *bce = (*bce)->parent;
+            return (*bce)->innermostEmitterScope;
+        }
+
+        return nullptr;
+    }
+
+    Scope* enclosingScope(BytecodeEmitter* bce) const {
+        if (EmitterScope* es = enclosing(&bce))
+            return es->scope(bce);
+
+        // The enclosing script is already compiled or the current script is the
+        // global script.
+        return bce->sc->compilationEnclosingScope();
+    }
+
+    static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) {
+        // '.generator' cannot be accessed by name.
+        return name != bce->cx->names().dotGenerator;
+    }
+
+    static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops);
+    NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
+
+    template <typename ScopeCreator>
+    MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope);
+    template <typename ScopeCreator>
+    MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope);
+    MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
+
+    MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
+                                             uint32_t slotEnd);
+
+  public:
+    explicit EmitterScope(BytecodeEmitter* bce)
+      : Nestable<EmitterScope>(&bce->innermostEmitterScope),
+        nameCache_(bce->cx->frontendCollectionPool()),
+        hasEnvironment_(false),
+        environmentChainLength_(0),
+        nextFrameSlot_(0),
+        scopeIndex_(ScopeNote::NoScopeIndex),
+        noteIndex_(ScopeNote::NoScopeNoteIndex)
+    { }
+
+    void dump(BytecodeEmitter* bce);
+
+    MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
+                                   Handle<LexicalScope::Data*> bindings);
+    MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
+    MOZ_MUST_USE bool enterComprehensionFor(BytecodeEmitter* bce,
+                                            Handle<LexicalScope::Data*> bindings);
+    MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
+    MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox);
+    MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce);
+    MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc);
+    MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
+    MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc);
+    MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce);
+    MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce);
+
+    MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false);
+
+    uint32_t index() const {
+        MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, "Did you forget to intern a Scope?");
+        return scopeIndex_;
+    }
+
+    uint32_t noteIndex() const {
+        return noteIndex_;
+    }
+
+    Scope* scope(const BytecodeEmitter* bce) const {
+        return bce->scopeList.vector[index()];
+    }
+
+    bool hasEnvironment() const {
+        return hasEnvironment_;
+    }
+
+    // The first frame slot used.
+    uint32_t frameSlotStart() const {
+        if (EmitterScope* inFrame = enclosingInFrame())
+            return inFrame->nextFrameSlot_;
+        return 0;
+    }
+
+    // The last frame slot used + 1.
+    uint32_t frameSlotEnd() const {
+        return nextFrameSlot_;
+    }
+
+    uint32_t numFrameSlots() const {
+        return frameSlotEnd() - frameSlotStart();
+    }
+
+    EmitterScope* enclosingInFrame() const {
+        return Nestable<EmitterScope>::enclosing();
+    }
+
+    NameLocation lookup(BytecodeEmitter* bce, JSAtom* name) {
+        if (Maybe<NameLocation> loc = lookupInCache(bce, name))
+            return *loc;
+        return searchAndCache(bce, name);
+    }
+
+    Maybe<NameLocation> locationBoundInScope(BytecodeEmitter* bce, JSAtom* name,
+                                             EmitterScope* target);
+};
+
+void
+BytecodeEmitter::EmitterScope::dump(BytecodeEmitter* bce)
+{
+    fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()), this);
+
+    for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) {
+        const NameLocation& l = r.front().value();
+
+        JSAutoByteString bytes;
+        if (!AtomToPrintableString(bce->cx, r.front().key(), &bytes))
+            return;
+        if (l.kind() != NameLocation::Kind::Dynamic)
+            fprintf(stdout, "  %s %s ", BindingKindString(l.bindingKind()), bytes.ptr());
+        else
+            fprintf(stdout, "  %s ", bytes.ptr());
+
+        switch (l.kind()) {
+          case NameLocation::Kind::Dynamic:
+            fprintf(stdout, "dynamic\n");
+            break;
+          case NameLocation::Kind::Global:
+            fprintf(stdout, "global\n");
+            break;
+          case NameLocation::Kind::Intrinsic:
+            fprintf(stdout, "intrinsic\n");
+            break;
+          case NameLocation::Kind::NamedLambdaCallee:
+            fprintf(stdout, "named lambda callee\n");
+            break;
+          case NameLocation::Kind::Import:
+            fprintf(stdout, "import\n");
+            break;
+          case NameLocation::Kind::ArgumentSlot:
+            fprintf(stdout, "arg slot=%u\n", l.argumentSlot());
+            break;
+          case NameLocation::Kind::FrameSlot:
+            fprintf(stdout, "frame slot=%u\n", l.frameSlot());
+            break;
+          case NameLocation::Kind::EnvironmentCoordinate:
+            fprintf(stdout, "environment hops=%u slot=%u\n",
+                    l.environmentCoordinate().hops(), l.environmentCoordinate().slot());
+            break;
+          case NameLocation::Kind::DynamicAnnexBVar:
+            fprintf(stdout, "dynamic annex b var\n");
+            break;
+        }
+    }
+
+    fprintf(stdout, "\n");
+}
+
+template <typename ScopeCreator>
+bool
+BytecodeEmitter::EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope)
+{
+    RootedScope enclosing(bce->cx, enclosingScope(bce));
+    Scope* scope = createScope(bce->cx, enclosing);
+    if (!scope)
+        return false;
+    hasEnvironment_ = scope->hasEnvironment();
+    scopeIndex_ = bce->scopeList.length();
+    return bce->scopeList.append(scope);
+}
+
+template <typename ScopeCreator>
+bool
+BytecodeEmitter::EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope)
+{
+    MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope");
+    bce->bodyScopeIndex = bce->scopeList.length();
+    return internScope(bce, createScope);
+}
+
+bool
+BytecodeEmitter::EmitterScope::appendScopeNote(BytecodeEmitter* bce)
+{
+    MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(),
+               "Scope notes are not needed for body-level scopes.");
+    noteIndex_ = bce->scopeNoteList.length();
+    return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(),
+                                     enclosingInFrame() ? enclosingInFrame()->noteIndex()
+                                                        : ScopeNote::NoScopeNoteIndex);
+}
+
+#ifdef DEBUG
+static bool
+NameIsOnEnvironment(Scope* scope, JSAtom* name)
+{
+    for (BindingIter bi(scope); bi; bi++) {
+        // If found, the name must already be on the environment or an import,
+        // or else there is a bug in the closed-over name analysis in the
+        // Parser.
+        if (bi.name() == name) {
+            BindingLocation::Kind kind = bi.location().kind();
+
+            if (bi.hasArgumentSlot()) {
+                JSScript* script = scope->as<FunctionScope>().script();
+                if (!script->strict() && !script->functionHasParameterExprs()) {
+                    // Check for duplicate positional formal parameters.
+                    for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
+                        if (bi2.name() == name)
+                            kind = bi2.location().kind();
+                    }
+                }
+            }
+
+            return kind == BindingLocation::Kind::Global ||
+                   kind == BindingLocation::Kind::Environment ||
+                   kind == BindingLocation::Kind::Import;
+        }
+    }
+
+    // If not found, assume it's on the global or dynamically accessed.
+    return true;
+}
+#endif
+
+/* static */ NameLocation
+BytecodeEmitter::EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops)
+{
+    for (ScopeIter si(scope); si; si++) {
+        MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name));
+
+        bool hasEnv = si.hasSyntacticEnvironment();
+
+        switch (si.kind()) {
+          case ScopeKind::Function:
+            if (hasEnv) {
+                JSScript* script = si.scope()->as<FunctionScope>().script();
+                if (script->funHasExtensibleScope())
+                    return NameLocation::Dynamic();
+
+                for (BindingIter bi(si.scope()); bi; bi++) {
+                    if (bi.name() != name)
+                        continue;
+
+                    BindingLocation bindLoc = bi.location();
+                    if (bi.hasArgumentSlot() &&
+                        !script->strict() &&
+                        !script->functionHasParameterExprs())
+                    {
+                        // Check for duplicate positional formal parameters.
+                        for (BindingIter bi2(bi); bi2 && bi2.hasArgumentSlot(); bi2++) {
+                            if (bi2.name() == name)
+                                bindLoc = bi2.location();
+                        }
+                    }
+
+                    MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+                    return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
+                }
+            }
+            break;
+
+          case ScopeKind::FunctionBodyVar:
+          case ScopeKind::ParameterExpressionVar:
+          case ScopeKind::Lexical:
+          case ScopeKind::NamedLambda:
+          case ScopeKind::StrictNamedLambda:
+          case ScopeKind::Catch:
+            if (hasEnv) {
+                for (BindingIter bi(si.scope()); bi; bi++) {
+                    if (bi.name() != name)
+                        continue;
+
+                    // The name must already have been marked as closed
+                    // over. If this assertion is hit, there is a bug in the
+                    // name analysis.
+                    BindingLocation bindLoc = bi.location();
+                    MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+                    return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
+                }
+            }
+            break;
+
+          case ScopeKind::Module:
+            if (hasEnv) {
+                for (BindingIter bi(si.scope()); bi; bi++) {
+                    if (bi.name() != name)
+                        continue;
+
+                    BindingLocation bindLoc = bi.location();
+
+                    // Imports are on the environment but are indirect
+                    // bindings and must be accessed dynamically instead of
+                    // using an EnvironmentCoordinate.
+                    if (bindLoc.kind() == BindingLocation::Kind::Import) {
+                        MOZ_ASSERT(si.kind() == ScopeKind::Module);
+                        return NameLocation::Import();
+                    }
+
+                    MOZ_ASSERT(bindLoc.kind() == BindingLocation::Kind::Environment);
+                    return NameLocation::EnvironmentCoordinate(bi.kind(), hops, bindLoc.slot());
+                }
+            }
+            break;
+
+          case ScopeKind::Eval:
+          case ScopeKind::StrictEval:
+            // As an optimization, if the eval doesn't have its own var
+            // environment and its immediate enclosing scope is a global
+            // scope, all accesses are global.
+            if (!hasEnv && si.scope()->enclosing()->is<GlobalScope>())
+                return NameLocation::Global(BindingKind::Var);
+            return NameLocation::Dynamic();
+
+          case ScopeKind::Global:
+            return NameLocation::Global(BindingKind::Var);
+
+          case ScopeKind::With:
+          case ScopeKind::NonSyntactic:
+            return NameLocation::Dynamic();
+        }
+
+        if (hasEnv) {
+            MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
+            hops++;
+        }
+    }
+
+    MOZ_CRASH("Malformed scope chain");
+}
+
+NameLocation
+BytecodeEmitter::EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name)
+{
+    Maybe<NameLocation> loc;
+    uint8_t hops = hasEnvironment() ? 1 : 0;
+    DebugOnly<bool> inCurrentScript = enclosingInFrame();
+
+    // Start searching in the current compilation.
+    for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) {
+        loc = es->lookupInCache(bce, name);
+        if (loc) {
+            if (loc->kind() == NameLocation::Kind::EnvironmentCoordinate)
+                *loc = loc->addHops(hops);
+            break;
+        }
+
+        if (es->hasEnvironment())
+            hops++;
+
+#ifdef DEBUG
+        if (!es->enclosingInFrame())
+            inCurrentScript = false;
+#endif
+    }
+
+    // If the name is not found in the current compilation, walk the Scope
+    // chain encompassing the compilation.
+    if (!loc) {
+        inCurrentScript = false;
+        loc = Some(searchInEnclosingScope(name, bce->sc->compilationEnclosingScope(), hops));
+    }
+
+    // Each script has its own frame. A free name that is accessed
+    // from an inner script must not be a frame slot access. If this
+    // assertion is hit, it is a bug in the free name analysis in the
+    // parser.
+    MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot);
+
+    // It is always correct to not cache the location. Ignore OOMs to make
+    // lookups infallible.
+    if (!putNameInCache(bce, name, *loc))
+        bce->cx->recoverFromOutOfMemory();
+
+    return *loc;
+}
+
+Maybe<NameLocation>
+BytecodeEmitter::EmitterScope::locationBoundInScope(BytecodeEmitter* bce, JSAtom* name,
+                                                    EmitterScope* target)
+{
+    // The target scope must be an intra-frame enclosing scope of this
+    // one. Count the number of extra hops to reach it.
+    uint8_t extraHops = 0;
+    for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) {
+        if (es->hasEnvironment())
+            extraHops++;
+    }
+
+    // Caches are prepopulated with bound names. So if the name is bound in a
+    // particular scope, it must already be in the cache. Furthermore, don't
+    // consult the fallback location as we only care about binding names.
+    Maybe<NameLocation> loc;
+    if (NameLocationMap::Ptr p = target->nameCache_->lookup(name)) {
+        NameLocation l = p->value().wrapped;
+        if (l.kind() == NameLocation::Kind::EnvironmentCoordinate)
+            loc = Some(l.addHops(extraHops));
+        else
+            loc = Some(l);
+    }
+    return loc;
+}
+
+bool
+BytecodeEmitter::EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
+                                                      uint32_t slotEnd)
+{
+    // Lexical bindings throw ReferenceErrors if they are used before
+    // initialization. See ES6 8.1.1.1.6.
+    //
+    // For completeness, lexical bindings are initialized in ES6 by calling
+    // InitializeBinding, after which touching the binding will no longer
+    // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
+    // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
+    if (slotStart != slotEnd) {
+        if (!bce->emit1(JSOP_UNINITIALIZED))
+            return false;
+        for (uint32_t slot = slotStart; slot < slotEnd; slot++) {
+            if (!bce->emitLocalOp(JSOP_INITLEXICAL, slot))
+                return false;
+        }
+        if (!bce->emit1(JSOP_POP))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce)
+{
+    return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind,
+                                            Handle<LexicalScope::Data*> bindings)
+{
+    MOZ_ASSERT(kind != ScopeKind::NamedLambda && kind != ScopeKind::StrictNamedLambda);
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    if (!ensureCache(bce))
+        return false;
+
+    // Marks all names as closed over if the the context requires it. This
+    // cannot be done in the Parser as we may not know if the context requires
+    // all bindings to be closed over until after parsing is finished. For
+    // example, legacy generators require all bindings to be closed over but
+    // it is unknown if a function is a legacy generator until the first
+    // 'yield' expression is parsed.
+    //
+    // This is not a problem with other scopes, as all other scopes with
+    // bindings are body-level. At the time of their creation, whether or not
+    // the context requires all bindings to be closed over is already known.
+    if (bce->sc->allBindingsClosedOver())
+        MarkAllBindingsClosedOver(*bindings);
+
+    // Resolve bindings.
+    TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
+    uint32_t firstFrameSlot = frameSlotStart();
+    BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
+    for (; bi; bi++) {
+        if (!checkSlotLimits(bce, bi))
+            return false;
+
+        NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+        if (!putNameInCache(bce, bi.name(), loc))
+            return false;
+
+        if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ))
+            return false;
+    }
+
+    updateFrameFixedSlots(bce, bi);
+
+    // Put frame slots in TDZ. Environment slots are poisoned during
+    // environment creation.
+    if (!deadZoneFrameSlotRange(bce, firstFrameSlot, frameSlotEnd()))
+        return false;
+
+    // Create and intern the VM scope.
+    auto createScope = [kind, bindings, firstFrameSlot](ExclusiveContext* cx,
+                                                        HandleScope enclosing)
+    {
+        return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing);
+    };
+    if (!internScope(bce, createScope))
+        return false;
+
+    if (ScopeKindIsInBody(kind) && hasEnvironment()) {
+        // After interning the VM scope we can get the scope index.
+        if (!bce->emitInternedScopeOp(index(), JSOP_PUSHLEXICALENV))
+            return false;
+    }
+
+    // Lexical scopes need notes to be mapped from a pc.
+    if (!appendScopeNote(bce))
+        return false;
+
+    return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox)
+{
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+    MOZ_ASSERT(funbox->namedLambdaBindings());
+
+    if (!ensureCache(bce))
+        return false;
+
+    // See comment in enterLexical about allBindingsClosedOver.
+    if (funbox->allBindingsClosedOver())
+        MarkAllBindingsClosedOver(*funbox->namedLambdaBindings());
+
+    BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT, /* isNamedLambda = */ true);
+    MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee);
+
+    // The lambda name, if not closed over, is accessed via JSOP_CALLEE and
+    // not a frame slot. Do not update frame slot information.
+    NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+    if (!putNameInCache(bce, bi.name(), loc))
+        return false;
+
+    bi++;
+    MOZ_ASSERT(!bi, "There should be exactly one binding in a NamedLambda scope");
+
+    auto createScope = [funbox](ExclusiveContext* cx, HandleScope enclosing) {
+        ScopeKind scopeKind =
+            funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda;
+        return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(),
+                                    LOCALNO_LIMIT, enclosing);
+    };
+    if (!internScope(bce, createScope))
+        return false;
+
+    return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterComprehensionFor(BytecodeEmitter* bce,
+                                                     Handle<LexicalScope::Data*> bindings)
+{
+    if (!enterLexical(bce, ScopeKind::Lexical, bindings))
+        return false;
+
+    // For comprehensions, initialize all lexical names up front to undefined
+    // because they're now a dead feature and don't interact properly with
+    // TDZ.
+    auto nop = [](BytecodeEmitter*, const NameLocation&, bool) {
+        return true;
+    };
+
+    if (!bce->emit1(JSOP_UNDEFINED))
+        return false;
+
+    RootedAtom name(bce->cx);
+    for (BindingIter bi(*bindings, frameSlotStart(), /* isNamedLambda = */ false); bi; bi++) {
+        name = bi.name();
+        if (!bce->emitInitializeName(name, nop))
+            return false;
+    }
+
+    if (!bce->emit1(JSOP_POP))
+        return false;
+
+    return true;
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterParameterExpressionVar(BytecodeEmitter* bce)
+{
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    if (!ensureCache(bce))
+        return false;
+
+    // Parameter expressions var scopes have no pre-set bindings and are
+    // always extensible, as they are needed for eval.
+    fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+    // Create and intern the VM scope.
+    uint32_t firstFrameSlot = frameSlotStart();
+    auto createScope = [firstFrameSlot](ExclusiveContext* cx, HandleScope enclosing) {
+        return VarScope::create(cx, ScopeKind::ParameterExpressionVar,
+                                /* data = */ nullptr, firstFrameSlot,
+                                /* needsEnvironment = */ true, enclosing);
+    };
+    if (!internScope(bce, createScope))
+        return false;
+
+    MOZ_ASSERT(hasEnvironment());
+    if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
+        return false;
+
+    // The extra var scope needs a note to be mapped from a pc.
+    if (!appendScopeNote(bce))
+        return false;
+
+    return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox)
+{
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    // If there are parameter expressions, there is an extra var scope.
+    if (!funbox->hasExtraBodyVarScope())
+        bce->setVarEmitterScope(this);
+
+    if (!ensureCache(bce))
+        return false;
+
+    // Resolve body-level bindings, if there are any.
+    auto bindings = funbox->functionScopeBindings();
+    Maybe<uint32_t> lastLexicalSlot;
+    if (bindings) {
+        NameLocationMap& cache = *nameCache_;
+
+        BindingIter bi(*bindings, funbox->hasParameterExprs);
+        for (; bi; bi++) {
+            if (!checkSlotLimits(bce, bi))
+                return false;
+
+            NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+            NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name());
+
+            // The only duplicate bindings that occur are simple formal
+            // parameters, in which case the last position counts, so update the
+            // location.
+            if (p) {
+                MOZ_ASSERT(bi.kind() == BindingKind::FormalParameter);
+                MOZ_ASSERT(!funbox->hasDestructuringArgs);
+                MOZ_ASSERT(!funbox->function()->hasRest());
+                p->value() = loc;
+                continue;
+            }
+
+            if (!cache.add(p, bi.name(), loc)) {
+                ReportOutOfMemory(bce->cx);
+                return false;
+            }
+        }
+
+        updateFrameFixedSlots(bce, bi);
+    } else {
+        nextFrameSlot_ = 0;
+    }
+
+    // If the function's scope may be extended at runtime due to sloppy direct
+    // eval and there is no extra var scope, any names beyond the function
+    // scope must be accessed dynamically as we don't know if the name will
+    // become a 'var' binding due to direct eval.
+    if (!funbox->hasParameterExprs && funbox->hasExtensibleScope())
+        fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+    // In case of parameter expressions, the parameters are lexical
+    // bindings and have TDZ.
+    if (funbox->hasParameterExprs && nextFrameSlot_) {
+        uint32_t paramFrameSlotEnd = 0;
+        for (BindingIter bi(*bindings, true); bi; bi++) {
+            if (!BindingKindIsLexical(bi.kind()))
+                break;
+
+            NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+            if (loc.kind() == NameLocation::Kind::FrameSlot) {
+                MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot());
+                paramFrameSlotEnd = loc.frameSlot() + 1;
+            }
+        }
+
+        if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd))
+            return false;
+    }
+
+    // Create and intern the VM scope.
+    auto createScope = [funbox](ExclusiveContext* cx, HandleScope enclosing) {
+        RootedFunction fun(cx, funbox->function());
+        return FunctionScope::create(cx, funbox->functionScopeBindings(),
+                                     funbox->hasParameterExprs,
+                                     funbox->needsCallObjectRegardlessOfBindings(),
+                                     fun, enclosing);
+    };
+    if (!internBodyScope(bce, createScope))
+        return false;
+
+    return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox)
+{
+    MOZ_ASSERT(funbox->hasParameterExprs);
+    MOZ_ASSERT(funbox->extraVarScopeBindings() ||
+               funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings());
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    // The extra var scope is never popped once it's entered. It replaces the
+    // function scope as the var emitter scope.
+    bce->setVarEmitterScope(this);
+
+    if (!ensureCache(bce))
+        return false;
+
+    // Resolve body-level bindings, if there are any.
+    uint32_t firstFrameSlot = frameSlotStart();
+    if (auto bindings = funbox->extraVarScopeBindings()) {
+        BindingIter bi(*bindings, firstFrameSlot);
+        for (; bi; bi++) {
+            if (!checkSlotLimits(bce, bi))
+                return false;
+
+            NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+            if (!putNameInCache(bce, bi.name(), loc))
+                return false;
+        }
+
+        updateFrameFixedSlots(bce, bi);
+    } else {
+        nextFrameSlot_ = firstFrameSlot;
+    }
+
+    // If the extra var scope may be extended at runtime due to sloppy
+    // direct eval, any names beyond the var scope must be accessed
+    // dynamically as we don't know if the name will become a 'var' binding
+    // due to direct eval.
+    if (funbox->hasExtensibleScope())
+        fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+    // Create and intern the VM scope.
+    auto createScope = [funbox, firstFrameSlot](ExclusiveContext* cx, HandleScope enclosing) {
+        return VarScope::create(cx, ScopeKind::FunctionBodyVar,
+                                funbox->extraVarScopeBindings(), firstFrameSlot,
+                                funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(),
+                                enclosing);
+    };
+    if (!internScope(bce, createScope))
+        return false;
+
+    if (hasEnvironment()) {
+        if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
+            return false;
+    }
+
+    // The extra var scope needs a note to be mapped from a pc.
+    if (!appendScopeNote(bce))
+        return false;
+
+    return checkEnvironmentChainLength(bce);
+}
+
+class DynamicBindingIter : public BindingIter
+{
+    uint32_t functionEnd_;
+
+  public:
+    explicit DynamicBindingIter(GlobalSharedContext* sc)
+      : BindingIter(*sc->bindings),
+        functionEnd_(sc->functionBindingEnd)
+    {
+        MOZ_ASSERT(functionEnd_ >= varStart_ && functionEnd_ <= letStart_);
+    }
+
+    explicit DynamicBindingIter(EvalSharedContext* sc)
+      : BindingIter(*sc->bindings, /* strict = */ false),
+        functionEnd_(sc->functionBindingEnd)
+    {
+        MOZ_ASSERT(!sc->strict());
+        MOZ_ASSERT(functionEnd_ >= varStart_ && functionEnd_ <= letStart_);
+    }
+
+    JSOp bindingOp() const {
+        switch (kind()) {
+          case BindingKind::Var:
+            return JSOP_DEFVAR;
+          case BindingKind::Let:
+            return JSOP_DEFLET;
+          case BindingKind::Const:
+            return JSOP_DEFCONST;
+          default:
+            MOZ_CRASH("Bad BindingKind");
+        }
+    }
+
+    bool isBodyLevelFunction() const {
+        return index_ < functionEnd_;
     }
 };
 
+bool
+BytecodeEmitter::EmitterScope::enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc)
+{
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    bce->setVarEmitterScope(this);
+
+    if (!ensureCache(bce))
+        return false;
+
+    if (bce->emitterMode == BytecodeEmitter::SelfHosting) {
+        // In self-hosting, it is incorrect to consult the global scope because
+        // self-hosted scripts are cloned into their target compartments before
+        // they are run. Instead of Global, Intrinsic is used for all names.
+        //
+        // Intrinsic lookups are redirected to the special intrinsics holder
+        // in the global object, into which any missing values are cloned
+        // lazily upon first access.
+        fallbackFreeNameLocation_ = Some(NameLocation::Intrinsic());
+
+        auto createScope = [](ExclusiveContext* cx, HandleScope enclosing) {
+            MOZ_ASSERT(!enclosing);
+            return &cx->global()->emptyGlobalScope();
+        };
+        return internBodyScope(bce, createScope);
+    }
+
+    // Resolve binding names and emit DEF{VAR,LET,CONST} prologue ops.
+    if (globalsc->bindings) {
+        for (DynamicBindingIter bi(globalsc); bi; bi++) {
+            NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+            JSAtom* name = bi.name();
+            if (!putNameInCache(bce, name, loc))
+                return false;
+
+            // Define the name in the prologue. Do not emit DEFVAR for
+            // functions that we'll emit DEFFUN for.
+            if (bi.isBodyLevelFunction())
+                continue;
+
+            if (!bce->emitAtomOp(name, bi.bindingOp()))
+                return false;
+        }
+    }
+
+    // Note that to save space, we don't add free names to the cache for
+    // global scopes. They are assumed to be global vars in the syntactic
+    // global scope, dynamic accesses under non-syntactic global scope.
+    if (globalsc->scopeKind() == ScopeKind::Global)
+        fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+    else
+        fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+    auto createScope = [globalsc](ExclusiveContext* cx, HandleScope enclosing) {
+        MOZ_ASSERT(!enclosing);
+        return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings);
+    };
+    return internBodyScope(bce, createScope);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc)
+{
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    bce->setVarEmitterScope(this);
+
+    if (!ensureCache(bce))
+        return false;
+
+    // For simplicity, treat all free name lookups in eval scripts as dynamic.
+    fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+    // Create the `var` scope. Note that there is also a lexical scope, created
+    // separately in emitScript().
+    auto createScope = [evalsc](ExclusiveContext* cx, HandleScope enclosing) {
+        ScopeKind scopeKind = evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval;
+        return EvalScope::create(cx, scopeKind, evalsc->bindings, enclosing);
+    };
+    if (!internBodyScope(bce, createScope))
+        return false;
+
+    if (hasEnvironment()) {
+        if (!bce->emitInternedScopeOp(index(), JSOP_PUSHVARENV))
+            return false;
+    } else {
+        // Resolve binding names and emit DEFVAR prologue ops if we don't have
+        // an environment (i.e., a sloppy eval not in a parameter expression).
+        // Eval scripts always have their own lexical scope, but non-strict
+        // scopes may introduce 'var' bindings to the nearest var scope.
+        //
+        // TODO: We may optimize strict eval bindings in the future to be on
+        // the frame. For now, handle everything dynamically.
+        if (!hasEnvironment() && evalsc->bindings) {
+            for (DynamicBindingIter bi(evalsc); bi; bi++) {
+                MOZ_ASSERT(bi.bindingOp() == JSOP_DEFVAR);
+
+                if (bi.isBodyLevelFunction())
+                    continue;
+
+                if (!bce->emitAtomOp(bi.name(), JSOP_DEFVAR))
+                    return false;
+            }
+        }
+
+        // As an optimization, if the eval does not have its own var
+        // environment and is directly enclosed in a global scope, then all
+        // free name lookups are global.
+        if (scope(bce)->enclosing()->is<GlobalScope>())
+            fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterModule(BytecodeEmitter* bce, ModuleSharedContext* modulesc)
+{
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    bce->setVarEmitterScope(this);
+
+    if (!ensureCache(bce))
+        return false;
+
+    // Resolve body-level bindings, if there are any.
+    TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
+    Maybe<uint32_t> firstLexicalFrameSlot;
+    if (ModuleScope::Data* bindings = modulesc->bindings) {
+        BindingIter bi(*bindings);
+        for (; bi; bi++) {
+            if (!checkSlotLimits(bce, bi))
+                return false;
+
+            NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
+            if (!putNameInCache(bce, bi.name(), loc))
+                return false;
+
+            if (BindingKindIsLexical(bi.kind())) {
+                if (loc.kind() == NameLocation::Kind::FrameSlot && !firstLexicalFrameSlot)
+                    firstLexicalFrameSlot = Some(loc.frameSlot());
+
+                if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ))
+                    return false;
+            }
+        }
+
+        updateFrameFixedSlots(bce, bi);
+    } else {
+        nextFrameSlot_ = 0;
+    }
+
+    // Modules are toplevel, so any free names are global.
+    fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
+
+    // Put lexical frame slots in TDZ. Environment slots are poisoned during
+    // environment creation.
+    if (firstLexicalFrameSlot) {
+        if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd()))
+            return false;
+    }
+
+    // Create and intern the VM scope.
+    auto createScope = [modulesc](ExclusiveContext* cx, HandleScope enclosing) {
+        return ModuleScope::create(cx, modulesc->bindings, modulesc->module(), enclosing);
+    };
+    if (!internBodyScope(bce, createScope))
+        return false;
+
+    return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::enterWith(BytecodeEmitter* bce)
+{
+    MOZ_ASSERT(this == bce->innermostEmitterScope);
+
+    if (!ensureCache(bce))
+        return false;
+
+    // 'with' make all accesses dynamic and unanalyzable.
+    fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
+
+    auto createScope = [](ExclusiveContext* cx, HandleScope enclosing) {
+        return WithScope::create(cx, enclosing);
+    };
+    if (!internScope(bce, createScope))
+        return false;
+
+    if (!bce->emitInternedScopeOp(index(), JSOP_ENTERWITH))
+        return false;
+
+    if (!appendScopeNote(bce))
+        return false;
+
+    return checkEnvironmentChainLength(bce);
+}
+
+bool
+BytecodeEmitter::EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal)
+{
+    // If we aren't leaving the scope due to a non-local jump (e.g., break),
+    // we must be the innermost scope.
+    MOZ_ASSERT_IF(!nonLocal, this == bce->innermostEmitterScope);
+
+    ScopeKind kind = scope(bce)->kind();
+    switch (kind) {
+      case ScopeKind::Lexical:
+      case ScopeKind::Catch:
+        if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV : JSOP_DEBUGLEAVELEXICALENV))
+            return false;
+        break;
+
+      case ScopeKind::With:
+        if (!bce->emit1(JSOP_LEAVEWITH))
+            return false;
+        break;
+
+      case ScopeKind::ParameterExpressionVar:
+        MOZ_ASSERT(hasEnvironment());
+        if (!bce->emit1(JSOP_POPVARENV))
+            return false;
+        break;
+
+      case ScopeKind::Function:
+      case ScopeKind::FunctionBodyVar:
+      case ScopeKind::NamedLambda:
+      case ScopeKind::StrictNamedLambda:
+      case ScopeKind::Eval:
+      case ScopeKind::StrictEval:
+      case ScopeKind::Global:
+      case ScopeKind::NonSyntactic:
+      case ScopeKind::Module:
+        break;
+    }
+
+    // Finish up the scope if we are leaving it in LIFO fashion.
+    if (!nonLocal) {
+        // Popping scopes due to non-local jumps generate additional scope
+        // notes. See NonLocalExitControl::prepareForNonLocalJump.
+        if (ScopeKindIsInBody(kind)) {
+            // The extra function var scope is never popped once it's pushed,
+            // so its scope note extends until the end of any possible code.
+            uint32_t offset = kind == ScopeKind::FunctionBodyVar ? UINT32_MAX : bce->offset();
+            bce->scopeNoteList.recordEnd(noteIndex_, offset, bce->inPrologue());
+        }
+    }
+
+    return true;
+}
+
+Maybe<MaybeCheckTDZ>
+BytecodeEmitter::TDZCheckCache::needsTDZCheck(BytecodeEmitter* bce, JSAtom* name)
+{
+    if (!ensureCache(bce))
+        return Nothing();
+
+    CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
+    if (p)
+        return Some(p->value().wrapped);
+
+    MaybeCheckTDZ rv = CheckTDZ;
+    for (TDZCheckCache* it = enclosing(); it; it = it->enclosing()) {
+        if (it->cache_) {
+            if (CheckTDZMap::Ptr p2 = it->cache_->lookup(name)) {
+                rv = p2->value();
+                break;
+            }
+        }
+    }
+
+    if (!cache_->add(p, name, rv)) {
+        ReportOutOfMemory(bce->cx);
+        return Nothing();
+    }
+
+    return Some(rv);
+}
+
+bool
+BytecodeEmitter::TDZCheckCache::noteTDZCheck(BytecodeEmitter* bce, JSAtom* name,
+                                             MaybeCheckTDZ check)
+{
+    if (!ensureCache(bce))
+        return false;
+
+    CheckTDZMap::AddPtr p = cache_->lookupForAdd(name);
+    if (p) {
+        MOZ_ASSERT(!check, "TDZ only needs to be checked once per binding per basic block.");
+        p->value() = check;
+    } else {
+        if (!cache_->add(p, name, check))
+            return false;
+    }
+
+    return true;
+}
+
 BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
                                  Parser<FullParseHandler>* parser, SharedContext* sc,
                                  HandleScript script, Handle<LazyScript*> lazyScript,
-                                 bool insideEval, HandleScript evalCaller,
-                                 bool insideNonGlobalEval, uint32_t lineNum,
-                                 EmitterMode emitterMode)
+                                 uint32_t lineNum, EmitterMode emitterMode)
   : sc(sc),
     cx(sc->context),
     parent(parent),
     script(cx, script),
     lazyScript(cx, lazyScript),
     prologue(cx, lineNum),
     main(cx, lineNum),
     current(&main),
     parser(parser),
-    evalCaller(evalCaller),
-    stmtStack(cx),
-    atomIndices(cx),
+    atomIndices(cx->frontendCollectionPool()),
     firstLine(lineNum),
-    localsToFrameSlots_(cx),
-    stackDepth(0), maxStackDepth(0),
+    maxFixedSlots(0),
+    maxStackDepth(0),
+    stackDepth(0),
     arrayCompDepth(0),
     emitLevel(0),
+    bodyScopeIndex(UINT32_MAX),
+    varEmitterScope(nullptr),
+    innermostNestableControl(nullptr),
+    innermostEmitterScope(nullptr),
+    innermostTDZCheckCache(nullptr),
     constList(cx),
+    scopeList(cx),
     tryNoteList(cx),
-    blockScopeList(cx),
+    scopeNoteList(cx),
     yieldOffsetList(cx),
     typesetCount(0),
     hasSingletons(false),
     hasTryFinally(false),
-    emittingForInit(false),
     emittingRunOnceLambda(false),
-    insideEval(insideEval),
-    insideNonGlobalEval(insideNonGlobalEval),
-    insideModule(false),
     emitterMode(emitterMode),
     functionBodyEndPosSet(false)
 {
-    MOZ_ASSERT_IF(evalCaller, insideEval);
     MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
 }
 
 BytecodeEmitter::BytecodeEmitter(BytecodeEmitter* parent,
                                  Parser<FullParseHandler>* parser, SharedContext* sc,
                                  HandleScript script, Handle<LazyScript*> lazyScript,
-                                 bool insideEval, HandleScript evalCaller,
-                                 bool insideNonGlobalEval, TokenPos bodyPosition,
-                                 EmitterMode emitterMode)
-    : BytecodeEmitter(parent, parser, sc, script, lazyScript, insideEval,
-                      evalCaller, insideNonGlobalEval,
+                                 TokenPos bodyPosition, EmitterMode emitterMode)
+    : BytecodeEmitter(parent, parser, sc, script, lazyScript,
                       parser->tokenStream.srcCoords.lineNum(bodyPosition.begin),
                       emitterMode)
 {
     setFunctionBodyEndPos(bodyPosition);
 }
 
 bool
 BytecodeEmitter::init()
 {
-    return atomIndices.ensureMap(cx);
-}
-
-bool
-BytecodeEmitter::updateLocalsToFrameSlots()
-{
-    // Assign stack slots to unaliased locals (aliased locals are stored in the
-    // call object and don't need their own stack slots). We do this by filling
-    // a Vector that can be used to map a local to its stack slot.
-
-    if (localsToFrameSlots_.length() == script->bindings.numLocals()) {
-        // CompileScript calls updateNumBlockScoped to update the block scope
-        // depth. Do nothing if the depth didn't change.
-        return true;
-    }
-
-    localsToFrameSlots_.clear();
-
-    if (!localsToFrameSlots_.reserve(script->bindings.numLocals()))
-        return false;
-
-    uint32_t slot = 0;
-    for (BindingIter bi(script); !bi.done(); bi++) {
-        if (bi->kind() == Binding::ARGUMENT)
-            continue;
-
-        if (bi->aliased())
-            localsToFrameSlots_.infallibleAppend(UINT32_MAX);
-        else
-            localsToFrameSlots_.infallibleAppend(slot++);
-    }
-
-    for (size_t i = 0; i < script->bindings.numBlockScoped(); i++)
-        localsToFrameSlots_.infallibleAppend(slot++);
-
-    return true;
+    return atomIndices.acquire(cx);
+}
+
+template <typename Predicate /* (NestableControl*) -> bool */>
+BytecodeEmitter::NestableControl*
+BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const
+{
+    return NestableControl::findNearest(innermostNestableControl, predicate);
+}
+
+template <typename T>
+T*
+BytecodeEmitter::findInnermostNestableControl() const
+{
+    return NestableControl::findNearest<T>(innermostNestableControl);
+}
+
+template <typename T, typename Predicate /* (T*) -> bool */>
+T*
+BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const
+{
+    return NestableControl::findNearest<T>(innermostNestableControl, predicate);
+}
+
+NameLocation
+BytecodeEmitter::lookupName(JSAtom* name)
+{
+    return innermostEmitterScope->lookup(this, name);
+}
+
+Maybe<NameLocation>
+BytecodeEmitter::locationOfNameBoundInScope(JSAtom* name, EmitterScope* target)
+{
+    return innermostEmitterScope->locationBoundInScope(this, name, target);
+}
+
+Maybe<NameLocation>
+BytecodeEmitter::locationOfNameBoundInFunctionScope(JSAtom* name, EmitterScope* source)
+{
+    EmitterScope* funScope = source;
+    while (!funScope->scope(this)->is<FunctionScope>())
+        funScope = funScope->enclosingInFrame();
+    return source->locationBoundInScope(this, name, funScope);
 }
 
 bool
 BytecodeEmitter::emitCheck(ptrdiff_t delta, ptrdiff_t* offset)
 {
     *offset = code().length();
 
     // Start it off moderately large to avoid repeated resizings early on.
@@ -228,16 +1624,17 @@ BytecodeEmitter::updateDepth(ptrdiff_t t
     jsbytecode* pc = code(target);
 
     int nuses = StackUses(nullptr, pc);
     int ndefs = StackDefs(nullptr, pc);
 
     stackDepth -= nuses;
     MOZ_ASSERT(stackDepth >= 0);
     stackDepth += ndefs;
+
     if ((uint32_t)stackDepth > maxStackDepth)
         maxStackDepth = stackDepth;
 }
 
 #ifdef DEBUG
 bool
 BytecodeEmitter::checkStrictOrSloppy(JSOp op)
 {
@@ -458,41 +1855,16 @@ BytecodeEmitter::emitDupAt(unsigned slot
 }
 
 bool
 BytecodeEmitter::emitCheckIsObj(CheckIsObjectKind kind)
 {
     return emit2(JSOP_CHECKISOBJ, uint8_t(kind));
 }
 
-static const char*
-StatementName(StmtInfoBCE* stmt)
-{
-    if (!stmt)
-        return js_script_str;
-
-    /* XXX too many "... statement" L10N gaffes -- fix via js.msg! */
-    static const char* const statementName[] = {
-    #define STATEMENT_TYPE_NAME(name, desc) desc,
-        FOR_EACH_STATEMENT_TYPE(STATEMENT_TYPE_NAME)
-    #undef STATEMENT_TYPE_NAME
-    };
-
-    static_assert(MOZ_ARRAY_LENGTH(statementName) == uint16_t(StmtType::LIMIT),
-                  "statementName array and StmtType enum must be consistent");
-
-    return statementName[uint16_t(stmt->type)];
-}
-
-static void
-ReportStatementTooLarge(TokenStream& ts, StmtInfoBCE* stmt)
-{
-    ts.reportError(JSMSG_NEED_DIET, StatementName(stmt));
-}
-
 static inline unsigned
 LengthOfSetLine(unsigned line)
 {
     return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
 }
 
 /* Updates line number notes, not column notes. */
 bool
@@ -560,68 +1932,55 @@ bool
 BytecodeEmitter::emitLoopHead(ParseNode* nextpn, JumpTarget* top)
 {
     if (nextpn) {
         /*
          * Try to give the JSOP_LOOPHEAD the same line number as the next
          * instruction. nextpn is often a block, in which case the next
          * instruction typically comes from the first statement inside.
          */
+        if (nextpn->isKind(PNK_LEXICALSCOPE))
+            nextpn = nextpn->scopeBody();
         MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST));
         if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head)
             nextpn = nextpn->pn_head;
         if (!updateSourceCoordNotes(nextpn->pn_pos.begin))
             return false;
     }
 
     *top = { offset() };
     return emit1(JSOP_LOOPHEAD);
 }
 
 bool
 BytecodeEmitter::emitLoopEntry(ParseNode* nextpn, JumpList entryJump)
 {
     if (nextpn) {
         /* Update the line number, as for LOOPHEAD. */
+        if (nextpn->isKind(PNK_LEXICALSCOPE))
+            nextpn = nextpn->scopeBody();
         MOZ_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST));
         if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head)
             nextpn = nextpn->pn_head;
         if (!updateSourceCoordNotes(nextpn->pn_pos.begin))
             return false;
     }
 
     JumpTarget entry{ offset() };
     patchJumpsToTarget(entryJump, entry);
 
-    LoopStmtInfo* loop = LoopStmtInfo::fromStmtInfo(innermostStmt());
-    MOZ_ASSERT(loop->loopDepth > 0);
-
-    uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loop->loopDepth, loop->canIonOsr);
+    LoopControl& loopInfo = innermostNestableControl->as<LoopControl>();
+    MOZ_ASSERT(loopInfo.loopDepth() > 0);
+
+    uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loopInfo.loopDepth(),
+                                                               loopInfo.canIonOsr());
     return emit2(JSOP_LOOPENTRY, loopDepthAndFlags);
 }
 
 void
-BytecodeEmitter::setContinueTarget(StmtInfoBCE* stmt, JumpTarget target)
-{
-    // Set loop and enclosing "update" offsets, for continue.
-    do {
-        stmt->update = target;
-        stmt = stmt->enclosing;
-    } while (stmt != nullptr && stmt->type == StmtType::LABEL);
-}
-
-void
-BytecodeEmitter::setContinueHere(StmtInfoBCE* stmt)
-{
-    // The next instruction should be a valid jump target.
-    JumpTarget continues{ offset() };
-    setContinueTarget(stmt, continues);
-}
-
-void
 BytecodeEmitter::checkTypeSet(JSOp op)
 {
     if (CodeSpec[op].format & JOF_TYPESET) {
         if (typesetCount < UINT16_MAX)
             typesetCount++;
     }
 }
 
@@ -654,435 +2013,165 @@ BytecodeEmitter::flushPops(int* npops)
         return false;
 
     *npops = 0;
     return true;
 }
 
 namespace {
 
-class NonLocalExitScope {
-    BytecodeEmitter* bce;
-    const uint32_t savedScopeIndex;
-    const int savedDepth;
-    uint32_t openScopeIndex;
-
-    NonLocalExitScope(const NonLocalExitScope&) = delete;
+class NonLocalExitControl {
+    BytecodeEmitter* bce_;
+    const uint32_t savedScopeNoteIndex_;
+    const int savedDepth_;
+    uint32_t openScopeNoteIndex_;
+
+    NonLocalExitControl(const NonLocalExitControl&) = delete;
+
+    MOZ_MUST_USE bool leaveScope(BytecodeEmitter::EmitterScope* scope);
 
   public:
-    explicit NonLocalExitScope(BytecodeEmitter* bce_)
-      : bce(bce_),
-        savedScopeIndex(bce->blockScopeList.length()),
-        savedDepth(bce->stackDepth),
-        openScopeIndex(UINT32_MAX)
-    {
-        if (StmtInfoBCE* stmt = bce->innermostScopeStmt())
-            openScopeIndex = stmt->blockScopeIndex;
-    }
-    ~NonLocalExitScope() {
-        for (uint32_t n = savedScopeIndex; n < bce->blockScopeList.length(); n++)
-            bce->blockScopeList.recordEnd(n, bce->offset(), bce->inPrologue());
-        bce->stackDepth = savedDepth;
-    }
-
-    bool popScopeForNonLocalExit(uint32_t blockScopeIndex) {
-        uint32_t scopeObjectIndex = bce->blockScopeList.findEnclosingScope(blockScopeIndex);
-        uint32_t parent = openScopeIndex;
-
-        if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), bce->inPrologue(), parent))
-            return false;
-        openScopeIndex = bce->blockScopeList.length() - 1;
-        return true;
-    }
-
-    bool prepareForNonLocalJump(StmtInfoBCE* toStmt);
+    explicit NonLocalExitControl(BytecodeEmitter* bce)
+      : bce_(bce),
+        savedScopeNoteIndex_(bce->scopeNoteList.length()),
+        savedDepth_(bce->stackDepth),
+        openScopeNoteIndex_(bce->innermostEmitterScope->noteIndex())
+    { }
+
+    ~NonLocalExitControl() {
+        for (uint32_t n = savedScopeNoteIndex_; n < bce_->scopeNoteList.length(); n++)
+            bce_->scopeNoteList.recordEnd(n, bce_->offset(), bce_->inPrologue());
+        bce_->stackDepth = savedDepth_;
+    }
+
+    MOZ_MUST_USE bool prepareForNonLocalJump(BytecodeEmitter::NestableControl* target);
+
+    MOZ_MUST_USE bool prepareForNonLocalJumpToOutermost() {
+        return prepareForNonLocalJump(nullptr);
+    }
 };
 
+bool
+NonLocalExitControl::leaveScope(BytecodeEmitter::EmitterScope* es)
+{
+    if (!es->leave(bce_, /* nonLocal = */ true))
+        return false;
+
+    // As we pop each scope due to the non-local jump, emit notes that
+    // record the extent of the enclosing scope. These notes will have
+    // their ends recorded in ~NonLocalExitControl().
+    uint32_t enclosingScopeIndex = ScopeNote::NoScopeIndex;
+    if (es->enclosingInFrame())
+        enclosingScopeIndex = es->enclosingInFrame()->index();
+    if (!bce_->scopeNoteList.append(enclosingScopeIndex, bce_->offset(), bce_->inPrologue(),
+                                    openScopeNoteIndex_))
+        return false;
+    openScopeNoteIndex_ = bce_->scopeNoteList.length() - 1;
+
+    return true;
+}
+
 /*
  * Emit additional bytecode(s) for non-local jumps.
  */
 bool
-NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE* toStmt)
-{
+NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* target)
+{
+    using NestableControl = BytecodeEmitter::NestableControl;
+    using EmitterScope = BytecodeEmitter::EmitterScope;
+
+    EmitterScope* es = bce_->innermostEmitterScope;
     int npops = 0;
 
-#define FLUSH_POPS() if (npops && !bce->flushPops(&npops)) return false
-
-    for (StmtInfoBCE* stmt = bce->innermostStmt(); stmt != toStmt; stmt = stmt->enclosing) {
-        switch (stmt->type) {
-          case StmtType::FINALLY:
-            FLUSH_POPS();
-            if (!bce->emitJump(JSOP_GOSUB, &stmt->gosubs()))
-                return false;
+    auto flushPops = [&npops](BytecodeEmitter* bce) {
+        if (npops && !bce->flushPops(&npops))
+            return false;
+        return true;
+    };
+
+    // Walk the nestable control stack and patch jumps.
+    for (NestableControl* control = bce_->innermostNestableControl;
+         control != target;
+         control = control->enclosing())
+    {
+        // Walk the scope stack and leave the scopes we entered. Leaving a scope
+        // may emit administrative ops like JSOP_POPLEXICALENV but never anything
+        // that manipulates the stack.
+        for (; es != control->emitterScope(); es = es->enclosingInFrame()) {
+            if (!leaveScope(es))
+                return false;
+        }
+
+        switch (control->kind()) {
+          case StatementKind::Finally: {
+            TryFinallyControl& finallyControl = control->as<TryFinallyControl>();
+            if (finallyControl.emittingSubroutine()) {
+                /*
+                 * There's a [exception or hole, retsub pc-index] pair and the
+                 * possible return value on the stack that we need to pop.
+                 */
+                npops += 3;
+            } else {
+                if (!flushPops(bce_))
+                    return false;
+                if (!bce_->emitJump(JSOP_GOSUB, &finallyControl.gosubs))
+                    return false;
+            }
             break;
-
-          case StmtType::WITH:
-            if (!bce->emit1(JSOP_LEAVEWITH))
-                return false;
-            MOZ_ASSERT(stmt->linksScope());
-            if (!popScopeForNonLocalExit(stmt->blockScopeIndex))
-                return false;
-            break;
-
-          case StmtType::FOR_OF_LOOP:
+          }
+
+          case StatementKind::ForOfLoop:
             npops += 2;
             break;
 
-          case StmtType::FOR_IN_LOOP:
+          case StatementKind::ForInLoop:
             /* The iterator and the current value are on the stack. */
             npops += 1;
-            FLUSH_POPS();
-            if (!bce->emit1(JSOP_ENDITER))
+            if (!flushPops(bce_))
+                return false;
+            if (!bce_->emit1(JSOP_ENDITER))
                 return false;
             break;
 
-          case StmtType::SPREAD:
-            MOZ_ASSERT_UNREACHABLE("can't break/continue/return from inside a spread");
-            break;
-
-          case StmtType::SUBROUTINE:
-            /*
-             * There's a [exception or hole, retsub pc-index] pair and the
-             * possible return value on the stack that we need to pop.
-             */
-            npops += 3;
+          default:
             break;
-
-          default:;
-        }
-
-        if (stmt->isBlockScope) {
-            StaticBlockScope& blockScope = stmt->staticBlock();
-            if (blockScope.needsClone()) {
-                if (!bce->emit1(JSOP_POPBLOCKSCOPE))
-                    return false;
-            } else {
-                if (!bce->emit1(JSOP_DEBUGLEAVEBLOCK))
-                    return false;
-            }
-            if (!popScopeForNonLocalExit(stmt->blockScopeIndex))
-                return false;
-        }
-    }
-
-    FLUSH_POPS();
-    return true;
-
-#undef FLUSH_POPS
+        }
+    }
+
+    EmitterScope* targetEmitterScope = target ? target->emitterScope() : bce_->varEmitterScope;
+    for (; es != targetEmitterScope; es = es->enclosingInFrame()) {
+        if (!leaveScope(es))
+            return false;
+    }
+
+    return flushPops(bce_);
 }
 
 }  // anonymous namespace
 
 bool
-BytecodeEmitter::emitGoto(StmtInfoBCE* toStmt, JumpList* jumplist, SrcNoteType noteType)
-{
-    NonLocalExitScope nle(this);
-
-    if (!nle.prepareForNonLocalJump(toStmt))
+BytecodeEmitter::emitGoto(NestableControl* target, JumpList* jumplist, SrcNoteType noteType)
+{
+    NonLocalExitControl nle(this);
+
+    if (!nle.prepareForNonLocalJump(target))
         return false;
 
     if (noteType != SRC_NULL) {
         if (!newSrcNote(noteType))
             return false;
     }
 
     return emitJump(JSOP_GOTO, jumplist);
 }
 
-void
-BytecodeEmitter::pushStatementInner(StmtInfoBCE* stmt, StmtType type, JumpTarget top)
-{
-    stmt->setTop(top);
-    stmtStack.push(stmt, type);
-}
-
-void
-BytecodeEmitter::pushStatement(StmtInfoBCE* stmt, StmtType type, JumpTarget top)
-{
-    pushStatementInner(stmt, type, top);
-    MOZ_ASSERT(!stmt->isLoop());
-}
-
-void
-BytecodeEmitter::pushLoopStatement(LoopStmtInfo* stmt, StmtType type, JumpTarget top)
-{
-    pushStatementInner(stmt, type, top);
-    MOZ_ASSERT(stmt->isLoop());
-
-    LoopStmtInfo* enclosingLoop = nullptr;
-    for (StmtInfoBCE* outer = stmt->enclosing; outer; outer = outer->enclosing) {
-        if (outer->isLoop()) {
-            enclosingLoop = LoopStmtInfo::fromStmtInfo(outer);
-            break;
-        }
-    }
-
-    stmt->stackDepth = this->stackDepth;
-    stmt->loopDepth = enclosingLoop ? enclosingLoop->loopDepth + 1 : 1;
-
-    int loopSlots;
-    if (type == StmtType::SPREAD)
-        loopSlots = 3;
-    else if (type == StmtType::FOR_IN_LOOP || type == StmtType::FOR_OF_LOOP)
-        loopSlots = 2;
-    else
-        loopSlots = 0;
-
-    MOZ_ASSERT(loopSlots <= stmt->stackDepth);
-
-    if (enclosingLoop) {
-        stmt->canIonOsr = (enclosingLoop->canIonOsr &&
-                           stmt->stackDepth == enclosingLoop->stackDepth + loopSlots);
-    } else {
-        stmt->canIonOsr = stmt->stackDepth == loopSlots;
-    }
-}
-
-JSObject*
-BytecodeEmitter::innermostStaticScope() const
-{
-    if (StmtInfoBCE* stmt = innermostScopeStmt())
-        return stmt->staticScope;
-    return sc->staticScope();
-}
-
-#ifdef DEBUG
-static bool
-AllLocalsAliased(StaticBlockScope& obj)
-{
-    for (unsigned i = 0; i < obj.numVariables(); i++)
-        if (!obj.isAliased(i))
-            return false;
-    return true;
-}
-#endif
-
-bool
-BytecodeEmitter::computeAliasedSlots(Handle<StaticBlockScope*> blockScope)
-{
-    uint32_t numAliased = script->bindings.numAliasedBodyLevelLocals();
-
-    for (unsigned i = 0; i < blockScope->numVariables(); i++) {
-        Definition* dn = blockScope->definitionParseNode(i);
-
-        MOZ_ASSERT(dn->isDefn());
-
-        uint32_t index = dn->pn_scopecoord.slot();
-        uint32_t slot;
-
-        if (isAliasedName(this, dn)) {
-            slot = blockScope->blockIndexToSlot(index);
-            blockScope->setAliased(i, true);
-        } else {
-            // blockIndexToLocalIndex returns the frame slot following the
-            // unaliased locals. We add numAliased so that the slot value
-            // comes after all (aliased and unaliased) body level locals.
-            slot = numAliased + blockScope->blockIndexToLocalIndex(index);
-            blockScope->setAliased(i, false);
-        }
-
-        if (!dn->pn_scopecoord.setSlot(parser->tokenStream, slot))
-            return false;
-
-#ifdef DEBUG
-        for (ParseNode* pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
-            MOZ_ASSERT(pnu->pn_lexdef == dn);
-            MOZ_ASSERT(!(pnu->pn_dflags & PND_BOUND));
-            MOZ_ASSERT(pnu->pn_scopecoord.isFree());
-        }
-#endif
-
-    }
-
-    MOZ_ASSERT_IF(sc->allLocalsAliased(), AllLocalsAliased(*blockScope));
-
-    return true;
-}
-
-void
-BytecodeEmitter::computeLocalOffset(Handle<StaticBlockScope*> blockScope)
-{
-    unsigned nbodyfixed = !sc->isGlobalContext()
-                          ? script->bindings.numUnaliasedBodyLevelLocals()
-                          : 0;
-    unsigned localOffset = nbodyfixed;
-
-    if (StmtInfoBCE* stmt = innermostScopeStmt()) {
-        Rooted<NestedStaticScope*> outer(cx, stmt->staticScope);
-        for (; outer; outer = outer->enclosingNestedScope()) {
-            if (outer->is<StaticBlockScope>() && !IsStaticGlobalLexicalScope(outer)) {
-                StaticBlockScope& outerBlock = outer->as<StaticBlockScope>();
-                localOffset = outerBlock.localOffset() + outerBlock.numVariables();
-                break;
-            }
-        }
-    }
-
-    MOZ_ASSERT(localOffset + blockScope->numVariables()
-               <= nbodyfixed + script->bindings.numBlockScoped());
-
-    blockScope->setLocalOffset(localOffset);
-}
-
-// ~ Nested Scopes ~
-//
-// A nested scope is a region of a compilation unit (function, script, or eval
-// code) with an additional node on the scope chain.  This node may either be a
-// "with" object or a "block" object.  "With" objects represent "with" scopes.
-// Block objects represent lexical scopes, and contain named block-scoped
-// bindings, for example "let" bindings or the exception in a catch block.
-// Those variables may be local and thus accessible directly from the stack, or
-// "aliased" (accessed by name from nested functions, or dynamically via nested
-// "eval" or "with") and only accessible through the scope chain.
-//
-// All nested scopes are present on the "static scope chain".  A nested scope
-// that is a "with" scope will be present on the scope chain at run-time as
-// well.  A block scope may or may not have a corresponding link on the run-time
-// scope chain; if no variable declared in the block scope is "aliased", then no
-// scope chain node is allocated.
-//
-// To help debuggers, the bytecode emitter arranges to record the PC ranges
-// comprehended by a nested scope, and ultimately attach them to the JSScript.
-// An element in the "block scope array" specifies the PC range, and links to a
-// NestedStaticScope in the object list of the script.  That scope object
-// is linked to the previous link in the static scope chain, if any.  The
-// static scope chain at any pre-retire PC can be retrieved using
-// JSScript::getStaticScope(jsbytecode* pc).
-//
-// Block scopes store their locals in the fixed part of a stack frame, after the
-// "fixed var" bindings.  A fixed var binding is a "var" or legacy "const"
-// binding that occurs in a function (as opposed to a script or in eval code).
-// Only functions have fixed var bindings.
-//
-// To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a
-// block scope, if the block has no aliased locals.  This allows DebugScopes
-// to invalidate any association between a debugger scope object, which can
-// proxy access to unaliased stack locals, and the actual live frame.  In
-// normal, non-debug mode, this opcode does not cause any baseline code to be
-// emitted.
-//
-// If the block has aliased locals, no DEBUGLEAVEBLOCK is emitted, and
-// POPBLOCKSCOPE itself balances the debug scope mapping. This gets around a
-// comedic situation where DEBUGLEAVEBLOCK may remove a block scope from the
-// debug scope map, but the immediate following POPBLOCKSCOPE adds it back due
-// to an onStep hook.
-//
-// Enter a nested scope with enterNestedScope.  It will emit
-// PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of
-// the scope.  Leave a nested scope with leaveNestedScope, which, for blocks,
-// will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE.  (For "with" scopes it
-// emits LEAVEWITH, of course.)  Pass enterNestedScope a fresh StmtInfoBCE
-// object, and pass that same object to the corresponding leaveNestedScope.  If
-// the statement is a block scope, pass StmtType::BLOCK as stmtType; otherwise for
-// with scopes pass StmtType::WITH.
-//
-bool
-BytecodeEmitter::enterNestedScope(StmtInfoBCE* stmt, ObjectBox* objbox, StmtType stmtType)
-{
-    Rooted<NestedStaticScope*> scope(cx, &objbox->object->as<NestedStaticScope>());
-    uint32_t scopeObjectIndex = objectList.add(objbox);
-
-    switch (stmtType) {
-      case StmtType::BLOCK: {
-        Rooted<StaticBlockScope*> blockScope(cx, &scope->as<StaticBlockScope>());
-
-        computeLocalOffset(blockScope);
-
-        if (!computeAliasedSlots(blockScope))
-            return false;
-
-        if (blockScope->needsClone()) {
-            if (!emitInternedObjectOp(scopeObjectIndex, JSOP_PUSHBLOCKSCOPE))
-                return false;
-        }
-
-        // Non-global block scopes are non-extensible. At this point the
-        // Parser has added all bindings to the StaticBlockScope, so we make
-        // it non-extensible.
-        if (!blockScope->makeNonExtensible(cx))
-            return false;
-        break;
-      }
-      case StmtType::WITH:
-        MOZ_ASSERT(scope->is<StaticWithScope>());
-        if (!emitInternedObjectOp(scopeObjectIndex, JSOP_ENTERWITH))
-            return false;
-        break;
-      default:
-        MOZ_CRASH("Unexpected scope statement");
-    }
-
-    uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
-    if (StmtInfoBCE* stmt = innermostScopeStmt())
-        parent = stmt->blockScopeIndex;
-
-    stmt->blockScopeIndex = blockScopeList.length();
-    if (!blockScopeList.append(scopeObjectIndex, offset(), inPrologue(), parent))
-        return false;
-
-    JumpTarget top{ offset() };
-    pushStatement(stmt, stmtType, top);
-    scope->initEnclosingScope(innermostStaticScope());
-    stmtStack.linkAsInnermostScopeStmt(stmt, *scope);
-    MOZ_ASSERT(stmt->linksScope());
-    stmt->isBlockScope = (stmtType == StmtType::BLOCK);
-
-    return true;
-}
-
-// Patches |breaks| and |continues| unless the top statement info record
-// represents a try-catch-finally suite.
-bool
-BytecodeEmitter::popStatement()
-{
-    if (!innermostStmt()->isTrying()) {
-        if (!emitJumpTargetAndPatch(innermostStmt()->breaks))
-            return false;
-        patchJumpsToTarget(innermostStmt()->continues, innermostStmt()->update);
-    }
-
-    stmtStack.pop();
-    return true;
-}
-
-bool
-BytecodeEmitter::leaveNestedScope(StmtInfoBCE* stmt)
-{
-    MOZ_ASSERT(stmt == innermostScopeStmt());
-    MOZ_ASSERT(stmt->isBlockScope == !(stmt->type == StmtType::WITH));
-    uint32_t blockScopeIndex = stmt->blockScopeIndex;
-
-#ifdef DEBUG
-    MOZ_ASSERT(blockScopeList.list[blockScopeIndex].length == 0);
-    uint32_t blockIndex = blockScopeList.list[blockScopeIndex].index;
-    ObjectBox* blockObjBox = objectList.find(blockIndex);
-    NestedStaticScope* staticScope = &blockObjBox->object->as<NestedStaticScope>();
-    MOZ_ASSERT(stmt->staticScope == staticScope);
-    MOZ_ASSERT_IF(!stmt->isBlockScope, staticScope->is<StaticWithScope>());
-#endif
-
-    if (!popStatement())
-        return false;
-
-    if (stmt->isBlockScope) {
-        if (stmt->staticScope->as<StaticBlockScope>().needsClone()) {
-            if (!emit1(JSOP_POPBLOCKSCOPE))
-                return false;
-        } else {
-            if (!emit1(JSOP_DEBUGLEAVEBLOCK))
-                return false;
-        }
-    } else {
-        if (!emit1(JSOP_LEAVEWITH))
-            return false;
-    }
-
-    blockScopeList.recordEnd(blockScopeIndex, offset(), inPrologue());
-
-    return true;
+Scope*
+BytecodeEmitter::innermostScope() const
+{
+    return innermostEmitterScope->scope(this);
 }
 
 bool
 BytecodeEmitter::emitIndex32(JSOp op, uint32_t index)
 {
     MOZ_ASSERT(checkStrictOrSloppy(op));
 
     const size_t len = 1 + UINT32_INDEX_LEN;
@@ -1090,18 +2179,18 @@ BytecodeEmitter::emitIndex32(JSOp op, ui
 
     ptrdiff_t offset;
     if (!emitCheck(len, &offset))
         return false;
 
     jsbytecode* code = this->code(offset);
     code[0] = jsbytecode(op);
     SET_UINT32_INDEX(code, index);
+    checkTypeSet(op);
     updateDepth(offset);
-    checkTypeSet(op);
     return true;
 }
 
 bool
 BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index)
 {
     MOZ_ASSERT(checkStrictOrSloppy(op));
 
@@ -1110,54 +2199,62 @@ BytecodeEmitter::emitIndexOp(JSOp op, ui
 
     ptrdiff_t offset;
     if (!emitCheck(len, &offset))
         return false;
 
     jsbytecode* code = this->code(offset);
     code[0] = jsbytecode(op);
     SET_UINT32_INDEX(code, index);
+    checkTypeSet(op);
     updateDepth(offset);
-    checkTypeSet(op);
     return true;
 }
 
 bool
 BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
 {
     MOZ_ASSERT(atom);
     MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
 
     // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
     // JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
     // It's safe to emit .this lookups though because |with| objects skip
     // those.
     MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME,
-                  !sc->isDotVariable(atom) || atom == cx->names().dotThis);
+                  atom != cx->names().dotGenerator);
 
     if (op == JSOP_GETPROP && atom == cx->names().length) {
         /* Specialize length accesses for the interpreter. */
         op = JSOP_LENGTH;
     }
 
-    jsatomid index;
+    uint32_t index;
     if (!makeAtomIndex(atom, &index))
         return false;
 
     return emitIndexOp(op, index);
 }
 
 bool
 BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op)
 {
     MOZ_ASSERT(pn->pn_atom != nullptr);
     return emitAtomOp(pn->pn_atom, op);
 }
 
 bool
+BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op)
+{
+    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
+    MOZ_ASSERT(index < scopeList.length());
+    return emitIndex32(op, index);
+}
+
+bool
 BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op)
 {
     MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
     MOZ_ASSERT(index < objectList.length);
     return emitIndex32(op, index);
 }
 
 bool
@@ -1178,425 +2275,69 @@ bool
 BytecodeEmitter::emitRegExp(uint32_t index)
 {
     return emitIndex32(JSOP_REGEXP, index);
 }
 
 bool
 BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot)
 {
-    MOZ_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD);
+    MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
     MOZ_ASSERT(IsLocalOp(op));
 
     ptrdiff_t off;
     if (!emitN(op, LOCALNO_LEN, &off))
         return false;
 
     SET_LOCALNO(code(off), slot);
     return true;
 }
 
 bool
-BytecodeEmitter::emitUnaliasedVarOp(JSOp op, uint32_t slot, MaybeCheckLexical checkLexical)
-{
-    MOZ_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD);
-
-    if (IsLocalOp(op)) {
-        // Only unaliased locals have stack slots assigned to them. Convert the
-        // var index (which includes unaliased and aliased locals) to the stack
-        // slot index.
-        MOZ_ASSERT(localsToFrameSlots_[slot] <= slot);
-        slot = localsToFrameSlots_[slot];
-
-        if (checkLexical) {
-            MOZ_ASSERT(op != JSOP_INITLEXICAL);
-            if (!emitLocalOp(JSOP_CHECKLEXICAL, slot))
-                return false;
-        }
-
-        return emitLocalOp(op, slot);
-    }
-
+BytecodeEmitter::emitArgOp(JSOp op, uint16_t slot)
+{
     MOZ_ASSERT(IsArgOp(op));
     ptrdiff_t off;
     if (!emitN(op, ARGNO_LEN, &off))
         return false;
 
     SET_ARGNO(code(off), slot);
     return true;
 }
 
 bool
-BytecodeEmitter::emitScopeCoordOp(JSOp op, ScopeCoordinate sc)
-{
-    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPECOORD);
-
-    unsigned n = SCOPECOORD_HOPS_LEN + SCOPECOORD_SLOT_LEN;
+BytecodeEmitter::emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec)
+{
+    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ENVCOORD);
+
+    unsigned n = ENVCOORD_HOPS_LEN + ENVCOORD_SLOT_LEN;
     MOZ_ASSERT(int(n) + 1 /* op */ == CodeSpec[op].length);
 
     ptrdiff_t off;
     if (!emitN(op, n, &off))
         return false;
 
     jsbytecode* pc = code(off);
-    SET_SCOPECOORD_HOPS(pc, sc.hops());
-    pc += SCOPECOORD_HOPS_LEN;
-    SET_SCOPECOORD_SLOT(pc, sc.slot());
-    pc += SCOPECOORD_SLOT_LEN;
+    SET_ENVCOORD_HOPS(pc, ec.hops());
+    pc += ENVCOORD_HOPS_LEN;
+    SET_ENVCOORD_SLOT(pc, ec.slot());
+    pc += ENVCOORD_SLOT_LEN;
     checkTypeSet(op);
     return true;
 }
 
-bool
-BytecodeEmitter::emitAliasedVarOp(JSOp op, ScopeCoordinate sc, MaybeCheckLexical checkLexical)
-{
-    if (checkLexical) {
-        MOZ_ASSERT(op != JSOP_INITALIASEDLEXICAL);
-        if (!emitScopeCoordOp(JSOP_CHECKALIASEDLEXICAL, sc))
-            return false;
-    }
-
-    return emitScopeCoordOp(op, sc);
-}
-
-bool
-BytecodeEmitter::lookupAliasedName(HandleScript script, PropertyName* name, uint32_t* pslot,
-                                   ParseNode* pn)
-{
-    LazyScript::FreeVariable* freeVariables = nullptr;
-    uint32_t lexicalBegin = 0;
-    uint32_t numFreeVariables = 0;
-    if (emitterMode == BytecodeEmitter::LazyFunction) {
-        freeVariables = lazyScript->freeVariables();
-        lexicalBegin = script->bindings.lexicalBegin();
-        numFreeVariables = lazyScript->numFreeVariables();
-    }
-
-    /*
-     * Beware: BindingIter may contain more than one Binding for a given name
-     * (in the case of |function f(x,x) {}|) but only one will be aliased.
-     */
-    uint32_t bindingIndex = 0;
-    uint32_t slot = CallObject::RESERVED_SLOTS;
-    for (BindingIter bi(script); !bi.done(); bi++) {
-        if (bi->aliased()) {
-            if (bi->name() == name) {
-                // Check if the free variable from a lazy script was marked as
-                // a possible hoisted use and is a lexical binding. If so,
-                // mark it as such so we emit a dead zone check.
-                if (freeVariables) {
-                    for (uint32_t i = 0; i < numFreeVariables; i++) {
-                        if (freeVariables[i].atom() == name) {
-                            if (freeVariables[i].isHoistedUse() && bindingIndex >= lexicalBegin) {
-                                MOZ_ASSERT(pn);
-                                MOZ_ASSERT(pn->isUsed());
-                                pn->pn_dflags |= PND_LEXICAL;
-                            }
-
-                            break;
-                        }
-                    }
-                }
-
-                *pslot = slot;
-                return true;
-            }
-            slot++;
-        }
-        bindingIndex++;
-    }
-    return false;
-}
-
-bool
-BytecodeEmitter::lookupAliasedNameSlot(PropertyName* name, ScopeCoordinate* sc)
-{
-    uint32_t slot;
-    if (!lookupAliasedName(script, name, &slot))
-        return false;
-
-    sc->setSlot(slot);
-    return true;
-}
-
-static inline MaybeCheckLexical
-NodeNeedsCheckLexical(ParseNode* pn)
-{
-    return pn->isHoistedLexicalUse() ? CheckLexical : DontCheckLexical;
-}
-
-static inline JSOp
-UnaliasedVarOpToAliasedVarOp(JSOp op)
-{
-    switch (op) {
-      case JSOP_GETARG: case JSOP_GETLOCAL: return JSOP_GETALIASEDVAR;
-      case JSOP_SETARG: case JSOP_SETLOCAL: return JSOP_SETALIASEDVAR;
-      case JSOP_INITLEXICAL: return JSOP_INITALIASEDLEXICAL;
-      default: MOZ_CRASH("unexpected var op");
-    }
-}
-
-static inline JSOp
-CheckSetConstOp(JSOp op, ParseNode* pn)
-{
-    if (pn->resolve()->isConst()) {
-        switch (op) {
-          case JSOP_GETLOCAL: case JSOP_GETALIASEDVAR: break;
-          case JSOP_INITLEXICAL: case JSOP_INITALIASEDLEXICAL: break;
-          case JSOP_SETLOCAL: return JSOP_THROWSETCONST;
-          case JSOP_SETALIASEDVAR: return JSOP_THROWSETALIASEDCONST;
-          default: MOZ_CRASH("unexpected set var op");
-        }
-    }
-    return op;
-}
-
-bool
-BytecodeEmitter::emitVarOp(ParseNode* pn, JSOp op)
-{
-    MOZ_ASSERT(pn->isKind(PNK_FUNCTION) || pn->isKind(PNK_NAME));
-    MOZ_ASSERT(!pn->pn_scopecoord.isFree());
-
-    if (pn->isDefn()) {
-        // The hop count needs to be computed even for definitions, due to
-        // cases like
-        //
-        // function outer() {
-        //   function inner() { x = 42; }
-        //   try {
-        //   } catch (e) {
-        //     G = function () { e = 43; };
-        //     var x;
-        //   }
-        // }
-        //
-        // The definition for x is emitted when the block scope for the catch
-        // is innermost. Moreover, that block scope has aliased bindings, so
-        // there is a non-0 hop count.
-        if (pn->pn_scopecoord.isHopsUnknown()) {
-            BytecodeEmitter* bceOfDef;
-            uint32_t hops = computeHops(pn, &bceOfDef);
-            MOZ_ASSERT(bceOfDef == this);
-            if (!pn->pn_scopecoord.setHops(parser->tokenStream, hops))
-                return false;
-        }
-
-#ifdef DEBUG
-        BytecodeEmitter* bceOfDef;
-        uint32_t hops = computeHops(pn, &bceOfDef);
-        MOZ_ASSERT(bceOfDef == this);
-        MOZ_ASSERT(hops == pn->pn_scopecoord.hops());
-#endif
-
-        if (!computeDefinitionIsAliased(this, pn->resolve(), &op))
-            return false;
-    }
-
-    // Aliased names had their JSOp changed by bindNameToSlot or above.
-    if (IsAliasedVarOp(op)) {
-        ScopeCoordinate sc;
-        sc.setHops(pn->pn_scopecoord.hops());
-        sc.setSlot(pn->pn_scopecoord.slot());
-        return emitAliasedVarOp(CheckSetConstOp(op, pn), sc, NodeNeedsCheckLexical(pn));
-    }
-
-#ifdef DEBUG
-    BytecodeEmitter* bceOfDef;
-    // Call computeHops to get bceOfDef.
-    (void) computeHops(pn, &bceOfDef);
-    MOZ_ASSERT(!isAliasedName(bceOfDef, pn));
-#endif
-    MOZ_ASSERT_IF(pn->isKind(PNK_NAME), IsArgOp(op) || IsLocalOp(op));
-    MOZ_ASSERT(pn->isUsed() || pn->isDefn());
-    return emitUnaliasedVarOp(CheckSetConstOp(op, pn), pn->pn_scopecoord.slot(),
-                              NodeNeedsCheckLexical(pn));
-}
-
 static JSOp
 GetIncDecInfo(ParseNodeKind kind, bool* post)
 {
     MOZ_ASSERT(kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT ||
                kind == PNK_POSTDECREMENT || kind == PNK_PREDECREMENT);
     *post = kind == PNK_POSTINCREMENT || kind == PNK_POSTDECREMENT;
     return (kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT) ? JSOP_ADD : JSOP_SUB;
 }
 
-bool
-BytecodeEmitter::emitVarIncDec(ParseNode* pn)
-{
-    JSOp op = pn->pn_kid->getOp();
-    MOZ_ASSERT(IsArgOp(op) || IsLocalOp(op) || IsAliasedVarOp(op));
-    MOZ_ASSERT(pn->pn_kid->isKind(PNK_NAME));
-    MOZ_ASSERT(!pn->pn_kid->pn_scopecoord.isFree());
-
-    bool post;
-    JSOp binop = GetIncDecInfo(pn->getKind(), &post);
-
-    JSOp getOp, setOp;
-    if (IsLocalOp(op)) {
-        getOp = JSOP_GETLOCAL;
-        setOp = JSOP_SETLOCAL;
-    } else if (IsArgOp(op)) {
-        getOp = JSOP_GETARG;
-        setOp = JSOP_SETARG;
-    } else {
-        getOp = JSOP_GETALIASEDVAR;
-        setOp = JSOP_SETALIASEDVAR;
-    }
-
-    if (!emitVarOp(pn->pn_kid, getOp))                       // V
-        return false;
-    if (!emit1(JSOP_POS))                                    // N
-        return false;
-    if (post && !emit1(JSOP_DUP))                            // N? N
-        return false;
-    if (!emit1(JSOP_ONE))                                    // N? N 1
-        return false;
-    if (!emit1(binop))                                       // N? N+1
-        return false;
-    if (!emitVarOp(pn->pn_kid, setOp))                       // N? N+1
-        return false;
-    if (post && !emit1(JSOP_POP))                            // RESULT
-        return false;
-
-    return true;
-}
-
-bool
-BytecodeEmitter::atBodyLevel(StmtInfoBCE* stmt) const
-{
-    // 'eval' and non-syntactic scripts are always under an invisible lexical
-    // scope, but since it is not syntactic, it should still be considered at
-    // body level.
-    if (sc->staticScope()->is<StaticEvalScope>()) {
-        bool bl = !stmt->enclosing;
-        MOZ_ASSERT_IF(bl, stmt->type == StmtType::BLOCK);
-        MOZ_ASSERT_IF(bl, stmt->staticScope
-                              ->as<StaticBlockScope>()
-                              .enclosingStaticScope() == sc->staticScope());
-        return bl;
-    }
-    return !stmt;
-}
-
-uint32_t
-BytecodeEmitter::computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut)
-{
-    Definition* dn = pn->resolve();
-    MOZ_ASSERT(dn->isDefn());
-    MOZ_ASSERT(!dn->isPlaceholder());
-    MOZ_ASSERT(dn->isBound());
-
-    uint32_t hops = 0;
-    BytecodeEmitter* bceOfDef = this;
-    StaticScopeIter<NoGC> ssi(innermostStaticScope());
-    JSObject* defScope = blockScopeOfDef(dn);
-    while (ssi.staticScope() != defScope) {
-        if (ssi.hasSyntacticDynamicScopeObject())
-            hops++;
-        if (ssi.type() == StaticScopeIter<NoGC>::Function) {
-            MOZ_ASSERT(dn->isClosed());
-            bceOfDef = bceOfDef->parent;
-        }
-        ssi++;
-    }
-
-    *bceOfDefOut = bceOfDef;
-    return hops;
-}
-
-uint32_t
-BytecodeEmitter::computeHopsToEnclosingFunction()
-{
-    StaticScopeIter<NoGC> ssi(innermostStaticScope());
-
-    uint32_t hops = 0;
-    while (ssi.type() != StaticScopeIter<NoGC>::Function) {
-        if (ssi.hasSyntacticDynamicScopeObject())
-            hops++;
-        ssi++;
-    }
-
-    return hops;
-}
-
-bool
-BytecodeEmitter::isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn)
-{
-    // If the definition is in another function, it's definitely aliased.
-    if (bceOfDef != this)
-        return true;
-
-    Definition* dn = pn->resolve();
-    switch (dn->kind()) {
-      case Definition::LET:
-      case Definition::CONSTANT:
-        /*
-         * There are two ways to alias a let variable: nested functions and
-         * dynamic scope operations. (This is overly conservative since the
-         * bindingsAccessedDynamically flag, checked by allLocalsAliased, is
-         * function-wide.)
-         *
-         * In addition all locals in generators are marked as aliased, to ensure
-         * that they are allocated on scope chains instead of on the stack.  See
-         * the definition of SharedContext::allLocalsAliased.
-         */
-        return dn->isClosed() || sc->allLocalsAliased();
-      case Definition::ARG:
-        /*
-         * Consult the bindings, since they already record aliasing. We might
-         * be tempted to use the same definition as VAR/CONST/LET, but there is
-         * a problem caused by duplicate arguments: only the last argument with
-         * a given name is aliased. This is necessary to avoid generating a
-         * shape for the call object with with more than one name for a given
-         * slot (which violates internal engine invariants). All this means that
-         * the '|| sc->allLocalsAliased()' disjunct is incorrect since it will
-         * mark both parameters in function(x,x) as aliased.
-         */
-        return script->formalIsAliased(pn->pn_scopecoord.slot());
-      case Definition::VAR:
-        MOZ_ASSERT_IF(sc->allLocalsAliased(), script->localIsAliased(pn->pn_scopecoord.slot()));
-        return script->localIsAliased(pn->pn_scopecoord.slot());
-      case Definition::PLACEHOLDER:
-      case Definition::NAMED_LAMBDA:
-      case Definition::MISSING:
-      case Definition::IMPORT:
-        MOZ_CRASH("unexpected dn->kind");
-    }
-    return false;
-}
-
-bool
-BytecodeEmitter::computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op)
-{
-    if (dn->isKnownAliased()) {
-        *op = UnaliasedVarOpToAliasedVarOp(*op);
-    } else if (isAliasedName(bceOfDef, dn)) {
-        // Translate the frame slot to a slot on the dynamic scope
-        // object. Aliased block bindings do not need adjusting; see
-        // computeAliasedSlots.
-        uint32_t slot = dn->pn_scopecoord.slot();
-        if (blockScopeOfDef(dn)->is<JSFunction>() ||
-            blockScopeOfDef(dn)->is<ModuleObject>())
-        {
-            MOZ_ASSERT(IsArgOp(*op) || slot < bceOfDef->script->bindings.numBodyLevelLocals());
-            MOZ_ALWAYS_TRUE(bceOfDef->lookupAliasedName(bceOfDef->script, dn->name(), &slot));
-        }
-        if (!dn->pn_scopecoord.setSlot(parser->tokenStream, slot))
-            return false;
-
-        *op = UnaliasedVarOpToAliasedVarOp(*op);
-
-        // Mark the definition as having already computed alias information.
-        dn->pn_dflags |= PND_KNOWNALIASED;
-    }
-
-    return true;
-}
-
 JSOp
 BytecodeEmitter::strictifySetNameOp(JSOp op)
 {
     switch (op) {
       case JSOP_SETNAME:
         if (sc->strict())
             op = JSOP_STRICTSETNAME;
         break;
@@ -1604,468 +2345,16 @@ BytecodeEmitter::strictifySetNameOp(JSOp
         if (sc->strict())
             op = JSOP_STRICTSETGNAME;
         break;
         default:;
     }
     return op;
 }
 
-void
-BytecodeEmitter::strictifySetNameNode(ParseNode* pn)
-{
-    pn->setOp(strictifySetNameOp(pn->getOp()));
-}
-
-/*
- * Try to convert a *NAME op with a free name to a more specialized GNAME,
- * INTRINSIC or ALIASEDVAR op, which optimize accesses on that name.
- * Return true if a conversion was made.
- */
-bool
-BytecodeEmitter::tryConvertFreeName(ParseNode* pn)
-{
-    /*
-     * In self-hosting mode, JSOP_*NAME is unconditionally converted to
-     * JSOP_*INTRINSIC. This causes lookups to be redirected to the special
-     * intrinsics holder in the global object, into which any missing values are
-     * cloned lazily upon first access.
-     */
-    if (emitterMode == BytecodeEmitter::SelfHosting) {
-        JSOp op;
-        switch (pn->getOp()) {
-          case JSOP_GETNAME:  op = JSOP_GETINTRINSIC; break;
-          case JSOP_SETNAME:  op = JSOP_SETINTRINSIC; break;
-          /* Other *NAME ops aren't (yet) supported in self-hosted code. */
-          default: MOZ_CRASH("intrinsic");
-        }
-        pn->setOp(op);
-        return true;
-    }
-
-    /*
-     * When parsing inner functions lazily, parse nodes for outer functions no
-     * longer exist and only the function's scope chain is available for
-     * resolving upvar accesses within the inner function.
-     */
-    if (emitterMode == BytecodeEmitter::LazyFunction) {
-        // The only statements within a lazy function which can push lexical
-        // scopes are try/catch blocks. Use generic ops in this case.
-        for (StmtInfoBCE* stmt = innermostStmt(); stmt; stmt = stmt->enclosing) {
-            if (stmt->type == StmtType::CATCH)
-                return true;
-        }
-
-        // Walk the static scope chain and look for an aliased binding with
-        // the name pn->pn_atom.
-        uint32_t hops = 0;
-        Maybe<uint32_t> slot;
-        FunctionBox* funbox = sc->asFunctionBox();
-        PropertyName* name = pn->pn_atom->asPropertyName();
-        for (StaticScopeIter<NoGC> ssi(funbox->staticScope()); !ssi.done(); ssi++) {
-            // Don't optimize names through non-global eval. For global eval
-            // we can use GNAME ops.
-            if (ssi.type() == StaticScopeIter<NoGC>::Eval) {
-                if (ssi.eval().isNonGlobal())
-                    return false;
-                MOZ_ASSERT(!slot.isSome());
-                break;
-            }
-
-            if (!ssi.hasSyntacticDynamicScopeObject())
-                continue;
-
-            // Look up for name in function and block scopes.
-            if (ssi.type() == StaticScopeIter<NoGC>::Function) {
-                RootedScript funScript(cx, ssi.funScript());
-                if (funScript->funHasExtensibleScope() || ssi.fun().name() == pn->pn_atom)
-                    return false;
-
-                // Skip the current function, since we're trying to convert a
-                // free name.
-                if (script != funScript) {
-                    uint32_t slot_;
-                    if (lookupAliasedName(funScript, name, &slot_, pn)) {
-                        slot = Some(slot_);
-                        break;
-                    }
-                }
-            } else if (ssi.type() == StaticScopeIter<NoGC>::Module) {
-                RootedScript moduleScript(cx, ssi.moduleScript());
-                uint32_t slot_;
-                if (lookupAliasedName(moduleScript, name, &slot_, pn)) {
-                    slot = Some(slot_);
-                    break;
-                }
-
-                // Convert module import accesses to use JSOP_GETIMPORT.
-                RootedModuleEnvironmentObject env(cx, &ssi.module().initialEnvironment());
-                RootedPropertyName propName(cx, name);
-                MOZ_ASSERT(env);
-                if (env->hasImportBinding(propName)) {
-                    if (pn->getOp() == JSOP_GETNAME) {
-                        pn->setOp(JSOP_GETIMPORT);
-                        return true;
-                    }
-                    return false;
-                }
-            } else if (ssi.type() == StaticScopeIter<NoGC>::Block) {
-                RootedShape shape(cx, ssi.block().lookupAliasedName(name));
-                if (shape) {
-                    // Don't optimize setting a 'const' binding. Let the slow
-                    // path do the error checking.
-                    if (!shape->writable() && pn->getOp() == JSOP_SETNAME)
-                        return false;
-                    slot = Some(shape->slot());
-                    pn->pn_dflags |= PND_LEXICAL;
-                    break;
-                }
-            } else {
-                MOZ_ASSERT(ssi.type() != StaticScopeIter<NoGC>::With);
-            }
-
-            hops++;
-        }
-
-        // If we found a scope binding name, convert the name op to an aliased
-        // var op.
-        if (slot.isSome()) {
-            JSOp op;
-            switch (pn->getOp()) {
-              case JSOP_GETNAME: op = JSOP_GETALIASEDVAR; break;
-              case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
-              default: return false;
-            }
-            pn->setOp(op);
-            MOZ_ALWAYS_TRUE(pn->pn_scopecoord.set(parser->tokenStream, hops, *slot));
-            return true;
-        }
-    }
-
-    // Unbound names aren't recognizable global-property references if the
-    // script is inside a non-global eval call.
-    if (insideNonGlobalEval)
-        return false;
-
-    // If we are inside a module then unbound names in a function may refer to
-    // imports, so we can't use GNAME ops here.
-    if (insideModule)
-        return false;
-
-    // Skip trying to use GNAME ops if we know our script has a non-syntactic
-    // scope, since they'll just get treated as NAME ops anyway.
-    if (script->hasNonSyntacticScope())
-        return false;
-
-    // Deoptimized names also aren't necessarily globals.
-    if (pn->isDeoptimized())
-        return false;
-
-    if (sc->isFunctionBox()) {
-        // Unbound names in function code may not be globals if new locals can
-        // be added to this function (or an enclosing one) to alias a global
-        // reference.
-        FunctionBox* funbox = sc->asFunctionBox();
-        if (funbox->mightAliasLocals())
-            return false;
-    }
-
-    // If this is eval code, being evaluated inside strict mode eval code,
-    // an "unbound" name might be a binding local to that outer eval:
-    //
-    //   var x = "GLOBAL";
-    //   eval('"use strict"; ' +
-    //        'var x; ' +
-    //        'eval("print(x)");'); // "undefined", not "GLOBAL"
-    //
-    // Given the enclosing eval code's strictness and its bindings (neither is
-    // readily available now), we could exactly check global-ness, but it's not
-    // worth the trouble for doubly-nested eval code.  So we conservatively
-    // approximate.  If the outer eval code is strict, then this eval code will
-    // be: thus, don't optimize if we're compiling strict code inside an eval.
-    //
-    // Though actually, we don't even need an inner eval.  We could just as well
-    // have a lambda inside that outer strict mode eval and it would run into
-    // the same issue.
-    if (insideEval && sc->strict())
-        return false;
-
-    JSOp op;
-    switch (pn->getOp()) {
-      case JSOP_GETNAME: op = JSOP_GETGNAME; break;
-      case JSOP_SETNAME: op = strictifySetNameOp(JSOP_SETGNAME); break;
-      default: MOZ_CRASH("gname");
-    }
-    pn->setOp(op);
-    MOZ_ASSERT_IF(op == JSOP_INITGLEXICAL,
-                  IsStaticGlobalLexicalScope(blockScopeOfDef(pn->resolve())));
-    return true;
-}
-
-/*
- * BindNameToSlotHelper attempts to optimize name gets and sets to stack slot
- * loads and stores, given the compile-time information in |this| and a PNK_NAME
- * node pn.  It returns false on error, true on success.
- *
- * The caller can test pn->pn_scopecoord.isFree() to tell whether optimization
- * occurred, in which case bindNameToSlotHelper also updated pn->pn_op.  If
- * pn->pn_cookie.isFree() is still true on return, pn->pn_op still may have
- * been optimized, e.g., from JSOP_GETNAME to JSOP_CALLEE.  Whether or not
- * pn->pn_op was modified, if this function finds an argument or local variable
- * name, PND_CONST will be set in pn_dflags for read-only properties after a
- * successful return.
- *
- * NB: if you add more opcodes specialized from JSOP_GETNAME, etc., don't forget
- * to update the special cases in EmitFor (for-in) and emitAssignment (= and
- * op=, e.g. +=).
- */
-bool
-BytecodeEmitter::bindNameToSlotHelper(ParseNode* pn)
-{
-    MOZ_ASSERT(pn->isKind(PNK_NAME));
-
-    /* Don't attempt if 'pn' is already bound or deoptimized or a function. */
-    if (pn->isBound() || pn->isDeoptimized())
-        return true;
-
-    /* JSOP_CALLEE is pre-bound by definition. */
-    JSOp op = pn->getOp();
-    MOZ_ASSERT(op != JSOP_CALLEE);
-    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
-
-    /*
-     * The parser already linked name uses to definitions when (where not
-     * prevented by non-lexical constructs like 'with' and 'eval').
-     */
-    Definition* dn;
-    if (pn->isUsed()) {
-        MOZ_ASSERT(pn->pn_scopecoord.isFree());
-        dn = pn->pn_lexdef;
-        MOZ_ASSERT(dn->isDefn());
-        pn->pn_dflags |= (dn->pn_dflags & PND_CONST);
-    } else if (pn->isDefn()) {
-        dn = &pn->as<Definition>();
-    } else {
-        return true;
-    }
-
-    if (dn->pn_scopecoord.isFree()) {
-        if (evalCaller) {
-            MOZ_ASSERT(script->treatAsRunOnce() || sc->isFunctionBox());
-
-            /*
-             * Don't generate upvars on the left side of a for loop. See
-             * bug 470758.
-             */
-            if (emittingForInit)
-                return true;
-
-            /*
-             * If this is an eval in the global scope, then unbound variables
-             * must be globals, so try to use GNAME ops.
-             */
-            if (!evalCaller->functionOrCallerFunction() && tryConvertFreeName(pn)) {
-                pn->pn_dflags |= PND_BOUND;
-                return true;
-            }
-
-            /*
-             * Out of tricks, so we must rely on PICs to optimize named
-             * accesses from direct eval called from function code.
-             */
-            return true;
-        }
-
-        /* Optimize accesses to undeclared globals. */
-        if (!tryConvertFreeName(pn))
-            return true;
-
-        pn->pn_dflags |= PND_BOUND;
-        return true;
-    }
-
-    /*
-     * At this point, we are only dealing with uses that have already been
-     * bound to definitions via pn_lexdef. The rest of this routine converts
-     * the parse node of the use from its initial JSOP_*NAME* op to a
-     * LOCAL/ARG op. This requires setting the node's pn_scopecoord with a
-     * pair (hops, slot) where 'hops' is the number of dynamic scopes between
-     * the use and the def and 'slot' is the index to emit as the immediate of
-     * the ARG/LOCAL op. For example, in this code:
-     *
-     *   function(a,b,x) { return x }
-     *   function(y) { function() { return y } }
-     *
-     * x will get (hops = 0, slot = 2) and y will get (hops = 1, slot = 0).
-     */
-    MOZ_ASSERT(!pn->isDefn());
-    MOZ_ASSERT(pn->isUsed());
-    MOZ_ASSERT(pn->pn_lexdef);
-    MOZ_ASSERT(pn->pn_scopecoord.isFree());
-
-    /*
-     * We are compiling a function body and may be able to optimize name
-     * to stack slot. Look for an argument or variable in the function and
-     * rewrite pn_op and update pn accordingly.
-     */
-    switch (dn->kind()) {
-      case Definition::ARG:
-        switch (op) {
-          case JSOP_GETNAME:
-            op = JSOP_GETARG; break;
-          case JSOP_SETNAME:
-          case JSOP_STRICTSETNAME:
-            op = JSOP_SETARG; break;
-          default: MOZ_CRASH("arg");
-        }
-        MOZ_ASSERT(!pn->isConst());
-        break;
-
-      case Definition::VAR:
-      case Definition::CONSTANT:
-      case Definition::LET:
-        switch (op) {
-          case JSOP_GETNAME:
-            op = JSOP_GETLOCAL; break;
-          case JSOP_SETNAME:
-          case JSOP_STRICTSETNAME:
-            op = JSOP_SETLOCAL; break;
-          default: MOZ_CRASH("local");
-        }
-        break;
-
-      case Definition::NAMED_LAMBDA: {
-        MOZ_ASSERT(dn->isOp(JSOP_CALLEE));
-        MOZ_ASSERT(op != JSOP_CALLEE);
-
-        /*
-         * Currently, the ALIASEDVAR ops do not support accessing the
-         * callee of a DeclEnvObject, so use NAME.
-         */
-        JSFunction* fun = sc->asFunctionBox()->function();
-        if (blockScopeOfDef(dn) != fun)
-            return true;
-
-        MOZ_ASSERT(fun->isLambda());
-        MOZ_ASSERT(pn->pn_atom == fun->name());
-
-        /*
-         * Leave pn->isOp(JSOP_GETNAME) if this->fun needs a CallObject to
-         * address two cases: a new binding introduced by eval, and
-         * assignment to the name in strict mode.
-         *
-         *   var fun = (function f(s) { eval(s); return f; });
-         *   assertEq(fun("var f = 42"), 42);
-         *
-         * ECMAScript specifies that a function expression's name is bound
-         * in a lexical environment distinct from that used to bind its
-         * named parameters, the arguments object, and its variables.  The
-         * new binding for "var f = 42" shadows the binding for the
-         * function itself, so the name of the function will not refer to
-         * the function.
-         *
-         *    (function f() { "use strict"; f = 12; })();
-         *
-         * Outside strict mode, assignment to a function expression's name
-         * has no effect.  But in strict mode, this attempt to mutate an
-         * immutable binding must throw a TypeError.  We implement this by
-         * not optimizing such assignments and by marking such functions as
-         * needsCallObject, ensuring that the function name is represented in
-         * the scope chain so that assignment will throw a TypeError.
-         */
-        if (!sc->asFunctionBox()->needsCallObject()) {
-            op = JSOP_CALLEE;
-            pn->pn_dflags |= PND_CONST;
-        }
-
-        pn->setOp(op);
-        pn->pn_dflags |= PND_BOUND;
-        return true;
-      }
-
-      case Definition::PLACEHOLDER:
-        return true;
-
-      case Definition::IMPORT:
-        if (op == JSOP_GETNAME)
-            pn->setOp(JSOP_GETIMPORT);
-        return true;
-
-      case Definition::MISSING:
-        MOZ_CRASH("unexpected definition kind");
-    }
-
-    // The hop count is the number of dynamic scopes during execution that must
-    // be skipped to access the binding.
-    BytecodeEmitter* bceOfDef;
-    uint32_t slot = dn->pn_scopecoord.slot();
-    uint32_t hops = computeHops(pn, &bceOfDef);
-
-    /*
-     * Explicitly disallow accessing var/let bindings in global scope from
-     * nested functions. The reason for this limitation is that, since the
-     * global script is not included in the static scope chain (1. because it
-     * has no object to stand in the static scope chain, 2. to minimize memory
-     * bloat where a single live function keeps its whole global script
-     * alive.), ScopeCoordinateToTypeSet is not able to find the var/let's
-     * associated TypeSet.
-     *
-     * Note the following does not prevent us from optimizing block scopes at
-     * global level, e.g.,
-     *
-     *   { let x; function f() { x = 42; } }
-     */
-    if (dn->kind() == Definition::LET || dn->kind() == Definition::CONSTANT) {
-        if (IsStaticGlobalLexicalScope(blockScopeOfDef(dn)))
-            return true;
-    } else if (bceOfDef != this && bceOfDef->sc->isGlobalContext()) {
-        return true;
-    }
-
-    if (!pn->pn_scopecoord.set(parser->tokenStream, hops, slot))
-        return false;
-
-    if (!computeDefinitionIsAliased(bceOfDef, dn, &op))
-        return false;
-
-    // Re-set the slot on if it is aliased, since the slot would have been
-    // translated on dn.
-    if (IsAliasedVarOp(op)) {
-        MOZ_ASSERT(dn->isKnownAliased());
-        if (!pn->pn_scopecoord.setSlot(parser->tokenStream, dn->pn_scopecoord.slot()))
-            return false;
-    }
-
-    MOZ_ASSERT(!pn->isOp(op));
-    pn->setOp(op);
-    pn->pn_dflags |= PND_BOUND;
-    return true;
-}
-
-/*
- * Attempts to bind the name, then checks that no dynamic scope lookup ops are
- * emitted in self-hosting mode. NAME ops do lookups off current scope chain,
- * and we do not want to allow self-hosted code to use the dynamic scope.
- */
-bool
-BytecodeEmitter::bindNameToSlot(ParseNode* pn)
-{
-    if (!bindNameToSlotHelper(pn))
-        return false;
-
-    strictifySetNameNode(pn);
-
-    if (emitterMode == BytecodeEmitter::SelfHosting && !pn->isBound()) {
-        reportError(pn, JSMSG_SELFHOSTED_UNBOUND_NAME);
-        return false;
-    }
-
-    return true;
-}
-
 bool
 BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
 {
     JS_CHECK_RECURSION(cx, return false);
 
  restart:
 
     switch (pn->getKind()) {
@@ -2441,27 +2730,29 @@ BytecodeEmitter::checkSideEffects(ParseN
             if (!checkSideEffects(cond, answer))
                 return false;
             if (*answer)
                 return true;
         }
         return checkSideEffects(pn->pn_kid3, answer);
 
       case PNK_SWITCH:
-      case PNK_LETBLOCK:
         MOZ_ASSERT(pn->isArity(PN_BINARY));
         if (!checkSideEffects(pn->pn_left, answer))
             return false;
         return *answer || checkSideEffects(pn->pn_right, answer);
 
       case PNK_LABEL:
-      case PNK_LEXICALSCOPE:
         MOZ_ASSERT(pn->isArity(PN_NAME));
         return checkSideEffects(pn->expr(), answer);
 
+      case PNK_LEXICALSCOPE:
+        MOZ_ASSERT(pn->isArity(PN_SCOPE));
+        return checkSideEffects(pn->scopeBody(), answer);
+
       // We could methodically check every interpolated expression, but it's
       // probably not worth the trouble.  Treat template strings as effect-free
       // only if they don't contain any substitutions.
       case PNK_TEMPLATE_STRING_LIST:
         MOZ_ASSERT(pn->isArity(PN_LIST));
         MOZ_ASSERT(pn->pn_count > 0);
         MOZ_ASSERT((pn->pn_count % 2) == 1,
                    "template strings must alternate template and substitution "
@@ -2469,33 +2760,18 @@ BytecodeEmitter::checkSideEffects(ParseN
         *answer = pn->pn_count > 1;
         return true;
 
       case PNK_ARRAYCOMP:
         MOZ_ASSERT(pn->isArity(PN_LIST));
         MOZ_ASSERT(pn->pn_count == 1);
         return checkSideEffects(pn->pn_head, answer);
 
-      case PNK_ANNEXB_FUNCTION:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-
-        // XXXshu NOP check used only for phasing in block-scope function
-        // XXXshu early errors.
-        // XXXshu
-        // XXXshu Back out when major version >= 50. See [1].
-        // XXXshu
-        // XXXshu [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1235590#c10
-        if (pn->pn_left->isKind(PNK_NOP)) {
-            *answer = false;
-            return true;
-        }
-
-        return checkSideEffects(pn->pn_left, answer);
-
-      case PNK_ARGSBODY:
+      // This should be unreachable but is left as-is for now.
+      case PNK_PARAMSBODY:
         *answer = true;
         return true;
 
       case PNK_FORIN:           // by PNK_FOR/PNK_COMPREHENSIONFOR
       case PNK_FOROF:           // by PNK_FOR/PNK_COMPREHENSIONFOR
       case PNK_FORHEAD:         // by PNK_FOR/PNK_COMPREHENSIONFOR
       case PNK_CLASSMETHOD:     // by PNK_CLASS
       case PNK_CLASSNAMES:      // by PNK_CLASS
@@ -2516,21 +2792,17 @@ BytecodeEmitter::checkSideEffects(ParseN
 
     MOZ_CRASH("invalid, unenumerated ParseNodeKind value encountered in "
               "BytecodeEmitter::checkSideEffects");
 }
 
 bool
 BytecodeEmitter::isInLoop()
 {
-    for (StmtInfoBCE* stmt = innermostStmt(); stmt; stmt = stmt->enclosing) {
-        if (stmt->isLoop())
-            return true;
-    }
-    return false;
+    return findInnermostNestableControl<LoopControl>();
 }
 
 bool
 BytecodeEmitter::checkSingletonContext()
 {
     if (!script->treatAsRunOnce() || sc->isFunctionBox() || isInLoop())
         return false;
     hasSingletons = true;
@@ -2541,29 +2813,67 @@ bool
 BytecodeEmitter::checkRunOnceContext()
 {
     return checkSingletonContext() || (!isInLoop() && isRunOnceLambda());
 }
 
 bool
 BytecodeEmitter::needsImplicitThis()
 {
-    // Short-circuit if there is an enclosing 'with' static scope.
+    // Short-circuit if there is an enclosing 'with' scope.
     if (sc->inWith())
         return true;
 
-    // Otherwise walk the statement stack.
-    for (StmtInfoBCE* stmt = innermostStmt(); stmt; stmt = stmt->enclosing) {
-        if (stmt->type == StmtType::WITH)
+    // Otherwise see if the current point is under a 'with'.
+    for (EmitterScope* es = innermostEmitterScope; es; es = es->enclosingInFrame()) {
+        if (es->scope(this)->kind() == ScopeKind::With)
             return true;
     }
 
     return false;
 }
 
+bool
+BytecodeEmitter::maybeSetDisplayURL()
+{
+    if (tokenStream()->hasDisplayURL()) {
+        if (!parser->ss->setDisplayURL(cx, tokenStream()->displayURL()))
+            return false;
+    }
+    return true;
+}
+
+bool
+BytecodeEmitter::maybeSetSourceMap()
+{
+    if (tokenStream()->hasSourceMapURL()) {
+        MOZ_ASSERT(!parser->ss->hasSourceMapURL());
+        if (!parser->ss->setSourceMapURL(cx, tokenStream()->sourceMapURL()))
+            return false;
+    }
+
+    /*
+     * Source map URLs passed as a compile option (usually via a HTTP source map
+     * header) override any source map urls passed as comment pragmas.
+     */
+    if (parser->options().sourceMapURL()) {
+        // Warn about the replacement, but use the new one.
+        if (parser->ss->hasSourceMapURL()) {
+            if(!parser->report(ParseWarning, false, nullptr, JSMSG_ALREADY_HAS_PRAGMA,
+                               parser->ss->filename(), "//# sourceMappingURL"))
+                return false;
+        }
+
+        if (!parser->ss->setSourceMapURL(cx, parser->options().sourceMapURL()))
+            return false;
+    }
+
+    return true;
+}
+
 void
 BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx)
 {
     // Note: when parsing off thread the resulting scripts need to be handed to
     // the debugger after rejoining to the main thread.
     if (!cx->isJSContext())
         return;
 
@@ -2627,18 +2937,18 @@ BytecodeEmitter::emitNewInit(JSProtoKey 
         return false;
 
     jsbytecode* code = this->code(offset);
     code[0] = JSOP_NEWINIT;
     code[1] = jsbytecode(key);
     code[2] = 0;
     code[3] = 0;
     code[4] = 0;
+    checkTypeSet(JSOP_NEWINIT);
     updateDepth(offset);
-    checkTypeSet(JSOP_NEWINIT);
     return true;
 }
 
 bool
 BytecodeEmitter::iteratorResultShape(unsigned* shape)
 {
     // No need to do any guessing for the object kind, since we know exactly how
     // many properties we plan to have.
@@ -2676,89 +2986,323 @@ BytecodeEmitter::emitPrepareIteratorResu
     if (!iteratorResultShape(&shape))
         return false;
     return emitIndex32(JSOP_NEWOBJECT, shape);
 }
 
 bool
 BytecodeEmitter::emitFinishIteratorResult(bool done)
 {
-    jsatomid value_id;
+    uint32_t value_id;
     if (!makeAtomIndex(cx->names().value, &value_id))
         return false;
-    jsatomid done_id;
+    uint32_t done_id;
     if (!makeAtomIndex(cx->names().done, &done_id))
         return false;
 
     if (!emitIndex32(JSOP_INITPROP, value_id))
         return false;
     if (!emit1(done ? JSOP_TRUE : JSOP_FALSE))
         return false;
     if (!emitIndex32(JSOP_INITPROP, done_id))
         return false;
     return true;
 }
 
 bool
-BytecodeEmitter::emitNameOp(ParseNode* pn, bool callContext)
-{
-    if (!bindNameToSlot(pn))
-        return false;
-
-    JSOp op = pn->getOp();
-
-    if (op == JSOP_CALLEE) {
-        if (!emit1(op))
-            return false;
-    } else {
-        if (!pn->pn_scopecoord.isFree()) {
-            MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ATOM);
-            if (!emitVarOp(pn, op))
+BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext)
+{
+    switch (loc.kind()) {
+      case NameLocation::Kind::Dynamic:
+        if (!emitAtomOp(name, JSOP_GETNAME))
+            return false;
+        break;
+
+      case NameLocation::Kind::Global:
+        if (!emitAtomOp(name, JSOP_GETGNAME))
+            return false;
+        break;
+
+      case NameLocation::Kind::Intrinsic:
+        if (!emitAtomOp(name, JSOP_GETINTRINSIC))
+            return false;
+        break;
+
+      case NameLocation::Kind::NamedLambdaCallee:
+        if (!emit1(JSOP_CALLEE))
+            return false;
+        break;
+
+      case NameLocation::Kind::Import:
+        if (!emitAtomOp(name, JSOP_GETIMPORT))
+            return false;
+        break;
+
+      case NameLocation::Kind::ArgumentSlot:
+        if (!emitArgOp(JSOP_GETARG, loc.argumentSlot()))
+            return false;
+        break;
+
+      case NameLocation::Kind::FrameSlot:
+        if (loc.isLexical()) {
+            if (!emitTDZCheckIfNeeded(name, loc))
+                return false;
+        }
+        if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot()))
+            return false;
+        break;
+
+      case NameLocation::Kind::EnvironmentCoordinate:
+        if (loc.isLexical()) {
+            if (!emitTDZCheckIfNeeded(name, loc))
+                return false;
+        }
+        if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate()))
+            return false;
+        break;
+
+      case NameLocation::Kind::DynamicAnnexBVar:
+        MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+    }
+
+    // Need to provide |this| value for call.
+    if (callContext) {
+        switch (loc.kind()) {
+          case NameLocation::Kind::Dynamic: {
+            JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
+            if (!emitAtomOp(name, thisOp))
+                return false;
+            break;
+          }
+
+          case NameLocation::Kind::Global:
+            if (!emitAtomOp(name, JSOP_GIMPLICITTHIS))
+                return false;
+            break;
+
+          case NameLocation::Kind::Intrinsic:
+          case NameLocation::Kind::NamedLambdaCallee:
+          case NameLocation::Kind::Import:
+          case NameLocation::Kind::ArgumentSlot:
+          case NameLocation::Kind::FrameSlot:
+          case NameLocation::Kind::EnvironmentCoordinate:
+            if (!emit1(JSOP_UNDEFINED))
+                return false;
+            break;
+
+          case NameLocation::Kind::DynamicAnnexBVar:
+            MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+        }
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext)
+{
+    return emitGetName(pn->name(), callContext);
+}
+
+template <typename RHSEmitter>
+bool
+BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
+                                                   RHSEmitter emitRhs, bool initialize)
+{
+    bool emittedBindOp = false;
+
+    switch (loc.kind()) {
+      case NameLocation::Kind::Dynamic:
+      case NameLocation::Kind::Import:
+      case NameLocation::Kind::DynamicAnnexBVar: {
+        uint32_t atomIndex;
+        if (!makeAtomIndex(name, &atomIndex))
+            return false;
+        if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) {
+            // Annex B vars always go on the nearest variable environment,
+            // even if lexical environments in between contain same-named
+            // bindings.
+            if (!emit1(JSOP_BINDVAR))
                 return false;
         } else {
-            if (!emitAtomOp(pn, op))
-                return false;
-        }
-    }
-
-    /* Need to provide |this| value for call */
-    if (callContext) {
-        if (op == JSOP_GETNAME || op == JSOP_GETGNAME) {
-            JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
-            if (!emitAtomOp(pn, thisOp))
+            if (!emitIndexOp(JSOP_BINDNAME, atomIndex))
+                return false;
+        }
+        emittedBindOp = true;
+        if (!emitRhs(this, loc, emittedBindOp))
+            return false;
+        if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex))
+            return false;
+        break;
+      }
+
+      case NameLocation::Kind::Global: {
+        JSOp op;
+        uint32_t atomIndex;
+        if (!makeAtomIndex(name, &atomIndex))
+            return false;
+        if (loc.isLexical() && initialize) {
+            // INITGLEXICAL always gets the global lexical scope. It doesn't
+            // need a BINDGNAME.
+            MOZ_ASSERT(innermostScope()->is<GlobalScope>());
+            op = JSOP_INITGLEXICAL;
+        } else {
+            if (!emitIndexOp(JSOP_BINDGNAME, atomIndex))
+                return false;
+            emittedBindOp = true;
+            op = strictifySetNameOp(JSOP_SETGNAME);
+        }
+        if (!emitRhs(this, loc, emittedBindOp))
+            return false;
+        if (!emitIndexOp(op, atomIndex))
+            return false;
+        break;
+      }
+
+      case NameLocation::Kind::Intrinsic:
+        if (!emitRhs(this, loc, emittedBindOp))
+            return false;
+        if (!emitAtomOp(name, JSOP_SETINTRINSIC))
+            return false;
+        break;
+
+      case NameLocation::Kind::NamedLambdaCallee:
+        if (!emitRhs(this, loc, emittedBindOp))
+            return false;
+        // Assigning to the named lambda is a no-op in sloppy mode but
+        // throws in strict mode.
+        if (sc->strict() && !emit1(JSOP_THROWSETCALLEE))
+            return false;
+        break;
+
+      case NameLocation::Kind::ArgumentSlot: {
+        // If we assign to a positional formal parameter and the arguments
+        // object is unmapped (strict mode or function with
+        // default/rest/destructing args), parameters do not alias
+        // arguments[i], and to make the arguments object reflect initial
+        // parameter values prior to any mutation we create it eagerly
+        // whenever parameters are (or might, in the case of calls to eval)
+        // assigned.
+        FunctionBox* funbox = sc->asFunctionBox();
+        if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj())
+            funbox->setDefinitelyNeedsArgsObj();
+
+        if (!emitRhs(this, loc, emittedBindOp))
+            return false;
+        if (!emitArgOp(JSOP_SETARG, loc.argumentSlot()))
+            return false;
+        break;
+      }
+
+      case NameLocation::Kind::FrameSlot: {
+        JSOp op = JSOP_SETLOCAL;
+        if (!emitRhs(this, loc, emittedBindOp))
+            return false;
+        if (loc.isLexical()) {
+            if (initialize) {
+                op = JSOP_INITLEXICAL;
+            } else {
+                if (loc.isConst())
+                    op = JSOP_THROWSETCONST;
+
+                if (!emitTDZCheckIfNeeded(name, loc))
+                    return false;
+            }
+        }
+        if (!emitLocalOp(op, loc.frameSlot()))
+            return false;
+        if (op == JSOP_INITLEXICAL) {
+            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
+                return false;
+        }
+        break;
+      }
+
+      case NameLocation::Kind::EnvironmentCoordinate: {
+        JSOp op = JSOP_SETALIASEDVAR;
+        if (!emitRhs(this, loc, emittedBindOp))
+            return false;
+        if (loc.isLexical()) {
+            if (initialize) {
+                op = JSOP_INITALIASEDLEXICAL;
+            } else {
+                if (loc.isConst())
+                    op = JSOP_THROWSETALIASEDCONST;
+
+                if (!emitTDZCheckIfNeeded(name, loc))
+                    return false;
+            }
+        }
+        if (loc.bindingKind() == BindingKind::NamedLambdaCallee) {
+            // Assigning to the named lambda is a no-op in sloppy mode and throws
+            // in strict mode.
+            op = JSOP_THROWSETALIASEDCONST;
+            if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate()))
                 return false;
         } else {
-            if (!emit1(JSOP_UNDEFINED))
-                return false;
-        }
-    }
-
-    return true;
+            if (!emitEnvCoordOp(op, loc.environmentCoordinate()))
+                return false;
+        }
+        if (op == JSOP_INITALIASEDLEXICAL) {
+            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ))
+                return false;
+        }
+        break;
+      }
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
+{
+    // Dynamic accesses have TDZ checks built into their VM code and should
+    // never emit explicit TDZ checks.
+    MOZ_ASSERT(loc.hasKnownSlot());
+    MOZ_ASSERT(loc.isLexical());
+
+    Maybe<MaybeCheckTDZ> check = innermostTDZCheckCache->needsTDZCheck(this, name);
+    if (!check)
+        return false;
+
+    // We've already emitted a check in this basic block.
+    if (*check == DontCheckTDZ)
+        return true;
+
+    if (loc.kind() == NameLocation::Kind::FrameSlot) {
+        if (!emitLocalOp(JSOP_CHECKLEXICAL, loc.frameSlot()))
+            return false;
+    } else {
+        if (!emitEnvCoordOp(JSOP_CHECKALIASEDLEXICAL, loc.environmentCoordinate()))
+            return false;
+    }
+
+    return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ);
 }
 
 bool
 BytecodeEmitter::emitPropLHS(ParseNode* pn)
 {
     MOZ_ASSERT(pn->isKind(PNK_DOT));
     MOZ_ASSERT(!pn->as<PropertyAccess>().isSuper());
 
-    ParseNode* pn2 = pn->maybeExpr();
+    ParseNode* pn2 = pn->pn_expr;
 
     /*
      * If the object operand is also a dotted property reference, reverse the
      * list linked via pn_expr temporarily so we can iterate over it from the
      * bottom up (reversing again as we go), to avoid excessive recursion.
      */
     if (pn2->isKind(PNK_DOT) && !pn2->as<PropertyAccess>().isSuper()) {
         ParseNode* pndot = pn2;
         ParseNode* pnup = nullptr;
         ParseNode* pndown;
         for (;;) {
             /* Reverse pndot->pn_expr to point up, not down. */
-            MOZ_ASSERT(!pndot->isUsed());
             pndown = pndot->pn_expr;
             pndot->pn_expr = pnup;
             if (!pndown->isKind(PNK_DOT) || pndown->as<PropertyAccess>().isSuper())
                 break;
             pnup = pndot;
             pndot = pndown;
         }
 
@@ -2884,46 +3428,48 @@ BytecodeEmitter::emitPropIncDec(ParseNod
         return false;
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitNameIncDec(ParseNode* pn)
 {
-    const JSCodeSpec* cs = &CodeSpec[pn->pn_kid->getOp()];
-
-    bool global = (cs->format & JOF_GNAME);
     bool post;
     JSOp binop = GetIncDecInfo(pn->getKind(), &post);
 
-    if (!emitAtomOp(pn->pn_kid, global ? JSOP_BINDGNAME : JSOP_BINDNAME))  // OBJ
-        return false;
-    if (!emitAtomOp(pn->pn_kid, global ? JSOP_GETGNAME : JSOP_GETNAME))    // OBJ V
-        return false;
-    if (!emit1(JSOP_POS))                      // OBJ N
-        return false;
-    if (post && !emit1(JSOP_DUP))              // OBJ N? N
-        return false;
-    if (!emit1(JSOP_ONE))                      // OBJ N? N 1
-        return false;
-    if (!emit1(binop))                         // OBJ N? N+1
-        return false;
-
-    if (post) {
-        if (!emit2(JSOP_PICK, 2))              // N? N+1 OBJ
-            return false;
-        if (!emit1(JSOP_SWAP))                 // N? OBJ N+1
-            return false;
-    }
-
-    JSOp setOp = strictifySetNameOp(global ? JSOP_SETGNAME : JSOP_SETNAME);
-    if (!emitAtomOp(pn->pn_kid, setOp))        // N? N+1
-        return false;
-    if (post && !emit1(JSOP_POP))              // RESULT
+    auto emitRhs = [pn, post, binop](BytecodeEmitter* bce, const NameLocation& loc,
+                                     bool emittedBindOp)
+    {
+        JSAtom* name = pn->pn_kid->name();
+        if (!bce->emitGetNameAtLocation(name, loc, false)) // SCOPE? V
+            return false;
+        if (!bce->emit1(JSOP_POS))                         // SCOPE? N
+            return false;
+        if (post && !bce->emit1(JSOP_DUP))                 // SCOPE? N? N
+            return false;
+        if (!bce->emit1(JSOP_ONE))                         // SCOPE? N? N 1
+            return false;
+        if (!bce->emit1(binop))                            // SCOPE? N? N+1
+            return false;
+
+        if (post && emittedBindOp) {
+            if (!bce->emit2(JSOP_PICK, 2))                 // N? N+1 SCOPE?
+                return false;
+            if (!bce->emit1(JSOP_SWAP))                    // N? SCOPE? N+1
+                return false;
+        }
+
+        return true;
+    };
+
+    if (!emitSetName(pn->pn_kid, emitRhs))
+        return false;
+
+    if (post && !emit1(JSOP_POP))
         return false;
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts)
 {
@@ -3141,131 +3687,64 @@ BytecodeEmitter::emitNumberOp(double dva
     }
 
     if (!constList.append(DoubleValue(dval)))
         return false;
 
     return emitIndex32(JSOP_DOUBLE, constList.length() - 1);
 }
 
-bool
-BytecodeEmitter::pushInitialConstants(JSOp op, unsigned n)
-{
-    MOZ_ASSERT(op == JSOP_UNDEFINED || op == JSOP_UNINITIALIZED);
-
-    for (unsigned i = 0; i < n; ++i) {
-        if (!emit1(op))
-            return false;
-    }
-
-    return true;
-}
-
-bool
-BytecodeEmitter::initializeBlockScopedLocalsFromStack(Handle<StaticBlockScope*> blockScope)
-{
-    for (unsigned i = blockScope->numVariables(); i > 0; --i) {
-        if (blockScope->isAliased(i - 1)) {
-            ScopeCoordinate sc;
-            sc.setHops(0);
-            sc.setSlot(ClonedBlockObject::RESERVED_SLOTS + i - 1);
-            if (!emitAliasedVarOp(JSOP_INITALIASEDLEXICAL, sc, DontCheckLexical))
-                return false;
-        } else {
-            // blockIndexToLocalIndex returns the slot index after the unaliased
-            // locals stored in the frame. EmitUnaliasedVarOp expects the slot index
-            // to include both unaliased and aliased locals, so we have to add the
-            // number of aliased locals.
-            uint32_t numAliased = script->bindings.numAliasedBodyLevelLocals();
-            unsigned local = blockScope->blockIndexToLocalIndex(i - 1) + numAliased;
-            if (!emitUnaliasedVarOp(JSOP_INITLEXICAL, local, DontCheckLexical))
-                return false;
-        }
-        if (!emit1(JSOP_POP))
-            return false;
-    }
-    return true;
-}
-
-bool
-BytecodeEmitter::enterBlockScope(StmtInfoBCE* stmtInfo, ObjectBox* objbox, JSOp initialValueOp,
-                                 unsigned alreadyPushed)
-{
-    // This is so terrible. The eval body-level lexical scope needs to be
-    // emitted in the prologue so DEFFUN can pick up the right scope chain.
-    bool isEvalBodyLexicalScope = sc->staticScope()->is<StaticEvalScope>() &&
-                                  !innermostStmt();
-    if (isEvalBodyLexicalScope) {
-        MOZ_ASSERT(code().length() == 0);
-        switchToPrologue();
-    }
-
-    if (!enterNestedScope(stmtInfo, objbox, StmtType::BLOCK))
-        return false;
-
-    // Initial values for block-scoped locals. Whether it is undefined or the
-    // JS_UNINITIALIZED_LEXICAL magic value depends on the context. The
-    // current way we emit for-in and for-of heads means its let bindings will
-    // always be initialized, so we can initialize them to undefined.
-    Rooted<StaticBlockScope*> blockScope(cx, &objbox->object->as<StaticBlockScope>());
-    if (!pushInitialConstants(initialValueOp, blockScope->numVariables() - alreadyPushed))
-        return false;
-
-    if (!initializeBlockScopedLocalsFromStack(blockScope))
-        return false;
-
-    if (isEvalBodyLexicalScope)
-        switchToMain();
-
-    return true;
-}
-
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
  * LLVM is deciding to inline this function which uses a lot of stack space
  * into emitTree which is recursive and uses relatively little stack space.
  */
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitSwitch(ParseNode* pn)
 {
     ParseNode* cases = pn->pn_right;
     MOZ_ASSERT(cases->isKind(PNK_LEXICALSCOPE) || cases->isKind(PNK_STATEMENTLIST));
 
     // Emit code for the discriminant.
     if (!emitTree(pn->pn_left))
         return false;
 
-    StmtInfoBCE stmtInfo(cx);
-    JumpTarget top;
+    // Enter the scope before pushing the switch BreakableControl since all
+    // breaks are under this scope.
+    Maybe<TDZCheckCache> tdzCache;
+    Maybe<EmitterScope> emitterScope;
     if (cases->isKind(PNK_LEXICALSCOPE)) {
-        if (!enterBlockScope(&stmtInfo, cases->pn_objbox, JSOP_UNINITIALIZED, 0))
-            return false;
+        if (!cases->isEmptyScope()) {
+            tdzCache.emplace(this);
+            emitterScope.emplace(this);
+            if (!emitterScope->enterLexical(this, ScopeKind::Lexical, cases->scopeBindings()))
+                return false;
+        }
 
         // Advance |cases| to refer to the switch case list.
-        cases = cases->expr();
+        cases = cases->scopeBody();
 
         // A switch statement may contain hoisted functions inside its
         // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
         // bodies of the cases to the case list.
         if (cases->pn_xflags & PNX_FUNCDEFS) {
+            MOZ_ASSERT(emitterScope);
             for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
                 if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
                     if (!emitHoistedFunctionsInList(caseNode->pn_right))
                         return false;
                 }
             }
         }
-
-        stmtInfo.type = StmtType::SWITCH;
-        stmtInfo.update = top = { offset() };
-    } else {
-        MOZ_ASSERT(cases->isKind(PNK_STATEMENTLIST));
-        top = { offset() };
-        pushStatement(&stmtInfo, StmtType::SWITCH, top);
-    }
+    }
+
+    // After entering the scope, push the switch control.
+    BreakableControl controlInfo(this, StatementKind::Switch);
+
+    ptrdiff_t top = offset();
 
     // Switch bytecodes run from here till end of final case.
     uint32_t caseCount = cases->pn_count;
     if (caseCount > JS_BIT(16)) {
         parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES);
         return false;
     }
 
@@ -3363,28 +3842,32 @@ BytecodeEmitter::emitSwitch(ParseNode* p
 
         // 3 offsets (len, low, high) before the table, 1 per entry.
         switchSize = size_t(JUMP_OFFSET_LEN * (3 + tableLength));
         if (!newSrcNote2(SRC_TABLESWITCH, 0, &noteIndex))
             return false;
     }
 
     // Emit switchOp followed by switchSize bytes of jump or lookup table.
-    MOZ_ASSERT(top.offset == offset());
+    MOZ_ASSERT(top == offset());
     if (!emitN(switchOp, switchSize))
         return false;
 
     Vector<CaseClause*, 32, SystemAllocPolicy> table;
 
     JumpList condSwitchDefaultOff;
     if (switchOp == JSOP_CONDSWITCH) {
         unsigned caseNoteIndex;
         bool beforeCases = true;
         ptrdiff_t lastCaseOffset = -1;
 
+        // The case conditions need their own TDZ cache since they might not
+        // all execute.
+        TDZCheckCache tdzCache(this);
+
         // Emit code for evaluating cases and jumping to case statements.
         for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
             ParseNode* caseValue = caseNode->caseExpression();
 
             // If the expression is a literal, suppress line number emission so
             // that debugging works more naturally.
             if (caseValue) {
                 if (!emitTree(caseValue,
@@ -3414,17 +3897,17 @@ BytecodeEmitter::emitSwitch(ParseNode* p
             if (!emitJump(JSOP_CASE, &caseJump))
                 return false;
             caseNode->setOffset(caseJump.offset);
             lastCaseOffset = caseJump.offset;
 
             if (beforeCases) {
                 // Switch note's second offset is to first JSOP_CASE.
                 unsigned noteCount = notes().length();
-                if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top.offset))
+                if (!setSrcNoteOffset(noteIndex, 1, lastCaseOffset - top))
                     return false;
                 unsigned noteCountDelta = notes().length() - noteCount;
                 if (noteCountDelta != 0)
                     caseNoteIndex += noteCountDelta;
                 beforeCases = false;
             }
         }
 
@@ -3441,17 +3924,17 @@ BytecodeEmitter::emitSwitch(ParseNode* p
 
         // Emit default even if no explicit default statement.
         if (!emitJump(JSOP_DEFAULT, &condSwitchDefaultOff))
             return false;
     } else {
         MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
 
         // skip default offset.
-        jsbytecode* pc = code(top.offset + JUMP_OFFSET_LEN);
+        jsbytecode* pc = code(top + JUMP_OFFSET_LEN);
 
         // Fill in switch bounds, which we know fit in 16-bit offsets.
         SET_JUMP_OFFSET(pc, low);
         pc += JUMP_OFFSET_LEN;
         SET_JUMP_OFFSET(pc, high);
         pc += JUMP_OFFSET_LEN;
 
         if (tableLength != 0) {
@@ -3494,16 +3977,18 @@ BytecodeEmitter::emitSwitch(ParseNode* p
             defaultOffset = here;
 
         // If this is emitted as a TABLESWITCH, we'll need to know this case's
         // offset later when emitting the table. Store it in the node's
         // pn_offset (giving the field a different meaning vs. how we used it
         // on the immediately preceding line of code).
         caseNode->setOffset(here.offset);
 
+        TDZCheckCache tdzCache(this);
+
         if (!emitTree(caseNode->statementList()))
             return false;
     }
 
     if (!hasDefault) {
         // If no default case, offset for default is to end of switch.
         if (!emitJumpTarget(&defaultOffset))
             return false;
@@ -3512,45 +3997,45 @@ BytecodeEmitter::emitSwitch(ParseNode* p
 
     // Set the default offset (to end of switch if no default).
     jsbytecode* pc;
     if (switchOp == JSOP_CONDSWITCH) {
         pc = nullptr;
         patchJumpsToTarget(condSwitchDefaultOff, defaultOffset);
     } else {
         MOZ_ASSERT(switchOp == JSOP_TABLESWITCH);
-        pc = code(top.offset);
-        SET_JUMP_OFFSET(pc, defaultOffset.offset - top.offset);
+        pc = code(top);
+        SET_JUMP_OFFSET(pc, defaultOffset.offset - top);
         pc += JUMP_OFFSET_LEN;
     }
 
     // Set the SRC_SWITCH note's offset operand to tell end of switch.
-    if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top.offset))
+    if (!setSrcNoteOffset(noteIndex, 0, lastNonJumpTargetOffset() - top))
         return false;
 
     if (switchOp == JSOP_TABLESWITCH) {
         // Skip over the already-initialized switch bounds.
         pc += 2 * JUMP_OFFSET_LEN;
 
         // Fill in the jump table, if there is one.
         for (uint32_t i = 0; i < tableLength; i++) {
             CaseClause* caseNode = table[i];
-            ptrdiff_t off = caseNode ? caseNode->offset() - top.offset : 0;
+            ptrdiff_t off = caseNode ? caseNode->offset() - top : 0;
             SET_JUMP_OFFSET(pc, off);
             pc += JUMP_OFFSET_LEN;
         }
     }