Bug 1592102 - Introduce AbstractScope r=tcampbell
authorMatthew Gaudet <mgaudet@mozilla.com>
Fri, 06 Dec 2019 16:25:29 +0000
changeset 505850 0f59860a21ef5cd460d9850bcb7719de85c5eca3
parent 505849 351c88e680a33ea769a863b67800afedc75319fb
child 505851 69d620fead696800ce4199ce6aefaa87085923da
push id102528
push usermgaudet@mozilla.com
push dateFri, 06 Dec 2019 16:34:45 +0000
treeherderautoland@0f59860a21ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1592102
milestone73.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 1592102 - Introduce AbstractScope r=tcampbell In preparation for deferring the allocation of Scopes to the end of bytecode emission, we introduce AbstractScope, which is a facade class for use within the BytecodeEmitter. This class allows asking the same set of queries that are asked of Scopes, but when we defer the allocation of Scopes, we may not choose to answer the queries with a Scope, instead using a (to be implemented) ScopeCreationData. Differential Revision: https://phabricator.services.mozilla.com/D51911
js/src/frontend/AbstractScope.cpp
js/src/frontend/AbstractScope.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/BytecodeSection.h
js/src/frontend/CForEmitter.cpp
js/src/frontend/EmitterScope.cpp
js/src/frontend/EmitterScope.h
js/src/frontend/ForInEmitter.cpp
js/src/frontend/ForOfEmitter.cpp
js/src/frontend/NameOpEmitter.cpp
js/src/frontend/ParseNode.cpp
js/src/frontend/SharedContext.cpp
js/src/frontend/SharedContext.h
js/src/frontend/moz.build
js/src/vm/JSScript.cpp
js/src/vm/Scope.cpp
js/src/vm/Scope.h
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/AbstractScope.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/AbstractScope.h"
+
+#include "mozilla/Maybe.h"
+
+#include "js/GCPolicyAPI.h"
+#include "vm/JSFunction.h"
+#include "vm/Scope.h"
+
+using namespace js;
+using namespace js::frontend;
+
+Scope* AbstractScope::maybeScope() const { return scope_; }
+
+ScopeKind AbstractScope::kind() const {
+  MOZ_ASSERT(maybeScope());
+  return maybeScope()->kind();
+}
+
+AbstractScope AbstractScope::enclosing() const {
+  MOZ_ASSERT(maybeScope());
+  return AbstractScope(maybeScope()->enclosing());
+}
+
+bool AbstractScope::hasEnvironment() const {
+  MOZ_ASSERT(maybeScope());
+  return maybeScope()->hasEnvironment();
+}
+
+bool AbstractScope::isArrow() const { return canonicalFunction()->isArrow(); }
+
+JSFunction* AbstractScope::canonicalFunction() const {
+  MOZ_ASSERT(is<FunctionScope>());
+  MOZ_ASSERT(maybeScope());
+  return maybeScope()->as<FunctionScope>().canonicalFunction();
+}
+
+void AbstractScope::trace(JSTracer* trc) {
+  JS::GCPolicy<ScopeType>::trace(trc, &scope_, "AbstractScope");
+}
+
+bool AbstractScopeIter::hasSyntacticEnvironment() const {
+  return abstractScope().hasEnvironment() &&
+         abstractScope().kind() != ScopeKind::NonSyntactic;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/AbstractScope.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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 frontend_AbstractScope_h
+#define frontend_AbstractScope_h
+
+#include "mozilla/Variant.h"
+
+#include "gc/Barrier.h"
+#include "gc/Rooting.h"
+#include "gc/Tracer.h"
+#include "vm/Scope.h"
+
+#include "vm/ScopeKind.h"  // For ScopeKind
+
+namespace js {
+class Scope;
+class GlobalScope;
+class EvalScope;
+class GCMarker;
+using HeapPtrScope = HeapPtr<Scope*>;
+
+// An interface class to support Scope queries in the frontend without requiring
+// a GC Allocated scope to necessarily exist.
+//
+// This abstracts Scope* (and a future ScopeCreationData type used within the
+// frontend before the Scope is allocated)
+class AbstractScope {
+ public:
+  using ScopeType = HeapPtrScope;
+
+ private:
+  ScopeType scope_ = {};
+
+ public:
+  friend class js::Scope;
+
+  AbstractScope() {}
+
+  explicit AbstractScope(Scope* scope) : scope_(scope) {}
+
+  // Return true if this AbstractScope represents a Scope, either existant
+  // or to be reified. This indicates that queries can be executed on this
+  // scope data. Returning false is the equivalent to a nullptr, and usually
+  // indicates the end of the scope chain.
+  explicit operator bool() const { return maybeScope(); }
+
+  Scope* maybeScope() const;
+
+  // This allows us to check whether or not this abstract scope wraps
+  // or otherwise would reify to a particular scope type.
+  template <typename T>
+  bool is() const {
+    static_assert(std::is_base_of<Scope, T>::value,
+                  "Trying to ask about non-Scope type");
+    if (!maybeScope()) {
+      return false;
+    }
+    return kind() == T::classScopeKind_;
+  }
+
+  ScopeKind kind() const;
+  AbstractScope enclosing() const;
+  bool hasEnvironment() const;
+  // Valid iff is<FunctionScope>
+  bool isArrow() const;
+  JSFunction* canonicalFunction() const;
+
+  bool hasOnChain(ScopeKind kind) const {
+    for (AbstractScope it = *this; it; it = it.enclosing()) {
+      if (it.kind() == kind) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void trace(JSTracer* trc);
+};
+
+// Specializations of AbstractScope::is
+template <>
+inline bool AbstractScope::is<GlobalScope>() const {
+  return maybeScope() &&
+         (kind() == ScopeKind::Global || kind() == ScopeKind::NonSyntactic);
+}
+
+template <>
+inline bool AbstractScope::is<EvalScope>() const {
+  return maybeScope() &&
+         (kind() == ScopeKind::Eval || kind() == ScopeKind::StrictEval);
+}
+
+// Iterate over abstract scopes rather than scopes.
+class AbstractScopeIter {
+  AbstractScope scope_;
+
+ public:
+  explicit AbstractScopeIter(const AbstractScope& f) : scope_(f) {}
+  explicit operator bool() const { return !done(); }
+
+  bool done() const { return !scope_; }
+
+  ScopeKind kind() const {
+    MOZ_ASSERT(!done());
+    MOZ_ASSERT(scope_);
+    return scope_.kind();
+  }
+
+  AbstractScope abstractScope() const { return scope_; }
+
+  void operator++(int) {
+    MOZ_ASSERT(!done());
+    scope_ = scope_.enclosing();
+  }
+
+  // Returns whether this scope has a syntactic environment (i.e., an
+  // Environment that isn't a non-syntactic With or NonSyntacticVariables)
+  // on the environment chain.
+  bool hasSyntacticEnvironment() const;
+
+  void trace(JSTracer* trc) {
+    if (scope_) {
+      scope_.trace(trc);
+    }
+  };
+};
+
+}  // namespace js
+
+#endif  // frontend_AbstractScope_h
\ No newline at end of file
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -21,17 +21,18 @@
 #include "mozilla/Variant.h"        // mozilla::AsVariant
 
 #include <algorithm>
 #include <string.h>
 
 #include "jsnum.h"    // NumberToAtom
 #include "jstypes.h"  // JS_BIT
 
-#include "ds/Nestable.h"                         // Nestable
+#include "ds/Nestable.h"  // Nestable
+#include "frontend/AbstractScope.h"
 #include "frontend/BytecodeControlStructures.h"  // NestableControl, BreakableControl, LabelControl, LoopControl, TryFinallyControl
 #include "frontend/CallOrNewEmitter.h"           // CallOrNewEmitter
 #include "frontend/CForEmitter.h"                // CForEmitter
 #include "frontend/DefaultEmitter.h"             // DefaultEmitter
 #include "frontend/DoWhileEmitter.h"             // DoWhileEmitter
 #include "frontend/ElemOpEmitter.h"              // ElemOpEmitter
 #include "frontend/EmitterScope.h"               // EmitterScope
 #include "frontend/ExpressionStatementEmitter.h"  // ExpressionStatementEmitter
@@ -175,17 +176,17 @@ NameLocation BytecodeEmitter::lookupName
 Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope(
     JSAtom* name, EmitterScope* target) {
   return innermostEmitterScope()->locationBoundInScope(name, target);
 }
 
 Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInFunctionScope(
     JSAtom* name, EmitterScope* source) {
   EmitterScope* funScope = source;
-  while (!funScope->scope(this)->is<FunctionScope>()) {
+  while (!funScope->scope(this).is<FunctionScope>()) {
     funScope = funScope->enclosingInFrame();
   }
   return source->locationBoundInScope(name, funScope);
 }
 
 bool BytecodeEmitter::markStepBreakpoint() {
   if (inPrologue()) {
     return true;
@@ -864,17 +865,17 @@ bool BytecodeEmitter::emitGoto(NestableC
 
   if (!nle.prepareForNonLocalJump(target)) {
     return false;
   }
 
   return emitJump(JSOP_GOTO, jumplist);
 }
 
-Scope* BytecodeEmitter::innermostScope() const {
+AbstractScope 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;
   MOZ_ASSERT(len == size_t(CodeSpec[op].length));
@@ -1557,17 +1558,17 @@ bool BytecodeEmitter::needsImplicitThis(
   // Short-circuit if there is an enclosing 'with' scope.
   if (sc->inWith()) {
     return true;
   }
 
   // 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) {
+    if (es->scope(this).kind() == ScopeKind::With) {
       return true;
     }
   }
 
   return false;
 }
 
 bool BytecodeEmitter::emitThisEnvironmentCallee() {
@@ -1575,24 +1576,24 @@ bool BytecodeEmitter::emitThisEnvironmen
 
   // Directly load callee from the frame if possible.
   if (sc->isFunctionBox() && !sc->asFunctionBox()->isArrow()) {
     return emit1(JSOP_CALLEE);
   }
 
   // We have to load the callee from the environment chain.
   unsigned numHops = 0;
-  for (ScopeIter si(innermostScope()); si; si++) {
-    if (si.hasSyntacticEnvironment() && si.scope()->is<FunctionScope>()) {
-      JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
-      if (!fun->isArrow()) {
+  for (AbstractScopeIter si(innermostScope()); si; si++) {
+    if (si.hasSyntacticEnvironment() &&
+        si.abstractScope().is<FunctionScope>()) {
+      if (!si.abstractScope().isArrow()) {
         break;
       }
     }
-    if (si.scope()->hasEnvironment()) {
+    if (si.abstractScope().hasEnvironment()) {
       numHops++;
     }
   }
 
   static_assert(ENVCOORD_HOPS_LIMIT - 1 <= UINT8_MAX,
                 "JSOP_ENVCALLEE operand size should match ENVCOORD_HOPS_LIMIT");
 
   // Note: we need to check numHops here because we don't call
@@ -8359,19 +8360,19 @@ const FieldInitializers& BytecodeEmitter
         const FieldInitializers& fieldInitializers =
             current->getFieldInitializers();
         MOZ_ASSERT(fieldInitializers.valid);
         return fieldInitializers;
       }
     }
   }
 
-  for (ScopeIter si(innermostScope()); si; si++) {
-    if (si.scope()->is<FunctionScope>()) {
-      JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
+  for (AbstractScopeIter si(innermostScope()); si; si++) {
+    if (si.abstractScope().is<FunctionScope>()) {
+      JSFunction* fun = si.abstractScope().canonicalFunction();
       if (fun->kind() == FunctionFlags::FunctionKind::ClassConstructor) {
         const FieldInitializers& fieldInitializers =
             fun->isInterpretedLazy()
                 ? fun->lazyScript()->getFieldInitializers()
                 : fun->nonLazyScript()->getFieldInitializers();
         MOZ_ASSERT(fieldInitializers.valid);
         return fieldInitializers;
       }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -15,16 +15,17 @@
 #include "mozilla/Span.h"    // mozilla::Span
 #include "mozilla/Vector.h"  // mozilla::Vector
 
 #include <stddef.h>  // ptrdiff_t
 #include <stdint.h>  // uint16_t, uint32_t
 
 #include "jsapi.h"  // CompletionKind
 
+#include "frontend/AbstractScope.h"
 #include "frontend/BCEParserHandle.h"            // BCEParserHandle
 #include "frontend/BytecodeControlStructures.h"  // NestableControl
 #include "frontend/BytecodeOffset.h"             // BytecodeOffset
 #include "frontend/BytecodeSection.h"  // BytecodeSection, PerScriptData, CGScopeList
 #include "frontend/DestructuringFlavor.h"  // DestructuringFlavor
 #include "frontend/EitherParser.h"         // EitherParser
 #include "frontend/ErrorReporter.h"        // ErrorReporter
 #include "frontend/FullParseHandler.h"     // FullParseHandler
@@ -250,21 +251,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   }
 
   void setVarEmitterScope(EmitterScope* emitterScope) {
     MOZ_ASSERT(emitterScope);
     MOZ_ASSERT(!varEmitterScope);
     varEmitterScope = emitterScope;
   }
 
-  Scope* outermostScope() const {
+  AbstractScope outermostScope() const {
     return perScriptData().gcThingList().firstScope();
   }
-  Scope* innermostScope() const;
-  Scope* bodyScope() const {
+  AbstractScope innermostScope() const;
+  AbstractScope bodyScope() const {
     return perScriptData().gcThingList().getScope(bodyScopeIndex);
   }
 
   MOZ_ALWAYS_INLINE
   MOZ_MUST_USE bool makeAtomIndex(JSAtom* atom, uint32_t* indexp) {
     MOZ_ASSERT(perScriptData().atomIndices());
     AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom);
     if (p) {
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -9,18 +9,20 @@
 
 #include "mozilla/Attributes.h"  // MOZ_MUST_USE, MOZ_STACK_CLASS
 #include "mozilla/Maybe.h"       // mozilla::Maybe
 #include "mozilla/Span.h"        // mozilla::Span
 
 #include <stddef.h>  // ptrdiff_t, size_t
 #include <stdint.h>  // uint16_t, int32_t, uint32_t
 
-#include "jstypes.h"                   // JS_PUBLIC_API
-#include "NamespaceImports.h"          // ValueVector
+#include "jstypes.h"           // JS_PUBLIC_API
+#include "NamespaceImports.h"  // ValueVector
+
+#include "frontend/AbstractScope.h"    // AbstractScope
 #include "frontend/BytecodeOffset.h"   // BytecodeOffset
 #include "frontend/JumpList.h"         // JumpTarget
 #include "frontend/NameCollections.h"  // AtomIndexMap, PooledMapPtr
 #include "frontend/ObjLiteral.h"       // ObjLiteralCreationData
 #include "frontend/ParseNode.h"        // BigIntLiteral
 #include "frontend/SourceNotes.h"      // jssrcnote
 #include "gc/Barrier.h"                // GCPtrObject, GCPtrScope, GCPtrValue
 #include "gc/Rooting.h"                // JS::Rooted
@@ -89,21 +91,22 @@ struct MOZ_STACK_CLASS GCThingList {
     return vector.append(mozilla::AsVariant(std::move(objlit)));
   }
   MOZ_MUST_USE bool append(ObjectBox* obj, uint32_t* index);
 
   uint32_t length() const { return vector.length(); }
   MOZ_MUST_USE bool finish(JSContext* cx, mozilla::Span<JS::GCCellPtr> array);
   void finishInnerFunctions();
 
-  Scope* getScope(size_t index) const {
-    return &vector[index].get().as<StackGCCellPtr>().get().as<Scope>();
+  AbstractScope getScope(size_t index) const {
+    auto& elem = vector[index].get();
+    return AbstractScope(&elem.as<StackGCCellPtr>().get().as<Scope>());
   }
 
-  Scope* firstScope() const {
+  AbstractScope firstScope() const {
     MOZ_ASSERT(firstScopeIndex.isSome());
     return getScope(*firstScopeIndex);
   }
 };
 
 struct CGTryNoteList {
   Vector<JSTryNote> list;
   explicit CGTryNoteList(JSContext* cx) : list(cx) {}
--- a/js/src/frontend/CForEmitter.cpp
+++ b/js/src/frontend/CForEmitter.cpp
@@ -52,17 +52,17 @@ bool CForEmitter::emitCond(const Maybe<u
   if (headLexicalEmitterScopeForLet_) {
     // The environment chain only includes an environment for the
     // for(;;) loop head's let-declaration *if* a scope binding is
     // captured, thus requiring a fresh environment each iteration. If
     // a lexical scope exists for the head, it must be the innermost
     // one. If that scope has closed-over bindings inducing an
     // environment, recreate the current environment.
     MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope());
-    MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_)->kind() ==
+    MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_).kind() ==
                ScopeKind::Lexical);
 
     if (headLexicalEmitterScopeForLet_->hasEnvironment()) {
       if (!bce_->emit1(JSOP_FRESHENLEXICALENV)) {
         return false;
       }
     }
   }
@@ -110,17 +110,17 @@ bool CForEmitter::emitUpdate(Update upda
   // refresh the block.
   if (!loopInfo_->emitContinueTarget(bce_)) {
     return false;
   }
 
   // ES 13.7.4.8 step 3.e. The per-iteration freshening.
   if (headLexicalEmitterScopeForLet_) {
     MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope());
-    MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_)->kind() ==
+    MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_).kind() ==
                ScopeKind::Lexical);
 
     if (headLexicalEmitterScopeForLet_->hasEnvironment()) {
       if (!bce_->emit1(JSOP_FRESHENLEXICALENV)) {
         return false;
       }
     }
   }
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -1,20 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * 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/EmitterScope.h"
 
+#include "frontend/AbstractScope.h"
 #include "frontend/BytecodeEmitter.h"
 #include "frontend/ModuleSharedContext.h"
 #include "frontend/TDZCheckCache.h"
-
 #include "vm/GlobalObject.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
 using mozilla::Nothing;
@@ -113,24 +113,24 @@ EmitterScope* EmitterScope::enclosing(By
   if ((*bce)->parent) {
     *bce = (*bce)->parent;
     return (*bce)->innermostEmitterScopeNoCheck();
   }
 
   return nullptr;
 }
 
-Scope* EmitterScope::enclosingScope(BytecodeEmitter* bce) const {
+AbstractScope EmitterScope::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();
+  return AbstractScope(bce->sc->compilationEnclosingScope());
 }
 
 /* static */
 bool EmitterScope::nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) {
   // '.generator' cannot be accessed by name.
   return name != bce->cx->names().dotGenerator;
 }
 
@@ -331,17 +331,17 @@ NameLocation EmitterScope::searchAndCach
     bce->cx->recoverFromOutOfMemory();
   }
 
   return *loc;
 }
 
 template <typename ScopeCreator>
 bool EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) {
-  RootedScope enclosing(bce->cx, enclosingScope(bce));
+  RootedScope enclosing(bce->cx, enclosingScope(bce).maybeScope());
   Scope* scope = createScope(bce->cx, enclosing);
   if (!scope) {
     return false;
   }
   hasEnvironment_ = scope->hasEnvironment();
   return bce->perScriptData().gcThingList().append(scope, &scopeIndex_);
 }
 
@@ -350,17 +350,17 @@ bool EmitterScope::internBodyScope(Bytec
                                    ScopeCreator createScope) {
   MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX,
              "There can be only one body scope");
   bce->bodyScopeIndex = bce->perScriptData().gcThingList().length();
   return internScope(bce, createScope);
 }
 
 bool EmitterScope::appendScopeNote(BytecodeEmitter* bce) {
-  MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(),
+  MOZ_ASSERT(ScopeKindIsInBody(scope(bce).kind()) && enclosingInFrame(),
              "Scope notes are not needed for body-level scopes.");
   noteIndex_ = bce->bytecodeSection().scopeNoteList().length();
   return bce->bytecodeSection().scopeNoteList().append(
       index(), bce->bytecodeSection().offset(),
       enclosingInFrame() ? enclosingInFrame()->noteIndex()
                          : ScopeNote::NoScopeNoteIndex);
 }
 
@@ -387,17 +387,17 @@ bool EmitterScope::deadZoneFrameSlotRang
       return false;
     }
   }
 
   return true;
 }
 
 void EmitterScope::dump(BytecodeEmitter* bce) {
-  fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce)->kind()),
+  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();
 
     UniqueChars bytes = AtomToPrintableString(bce->cx, r.front().key());
     if (!bytes) {
       return;
@@ -894,17 +894,17 @@ bool EmitterScope::enterEval(BytecodeEmi
           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>()) {
+    if (scope(bce).enclosing().is<GlobalScope>()) {
       fallbackFreeNameLocation_ = Some(NameLocation::Global(BindingKind::Var));
     }
   }
 
   return true;
 }
 
 bool EmitterScope::enterModule(BytecodeEmitter* bce,
@@ -1004,17 +1004,17 @@ bool EmitterScope::deadZoneFrameSlots(By
   return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
 }
 
 bool 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->innermostEmitterScopeNoCheck());
 
-  ScopeKind kind = scope(bce)->kind();
+  ScopeKind kind = scope(bce).kind();
   switch (kind) {
     case ScopeKind::Lexical:
     case ScopeKind::SimpleCatch:
     case ScopeKind::Catch:
     case ScopeKind::FunctionLexical:
       if (!bce->emit1(hasEnvironment() ? JSOP_POPLEXICALENV
                                        : JSOP_DEBUGLEAVELEXICALENV)) {
         return false;
@@ -1065,17 +1065,17 @@ bool EmitterScope::leave(BytecodeEmitter
             noteIndex_, bce->bytecodeSection().offset());
       }
     }
   }
 
   return true;
 }
 
-Scope* EmitterScope::scope(const BytecodeEmitter* bce) const {
+AbstractScope EmitterScope::scope(const BytecodeEmitter* bce) const {
   return bce->perScriptData().gcThingList().getScope(index());
 }
 
 NameLocation EmitterScope::lookup(BytecodeEmitter* bce, JSAtom* name) {
   if (Maybe<NameLocation> loc = lookupInCache(bce, name)) {
     return *loc;
   }
   return searchAndCache(bce, name);
--- a/js/src/frontend/EmitterScope.h
+++ b/js/src/frontend/EmitterScope.h
@@ -8,16 +8,17 @@
 #define frontend_EmitterScope_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 
 #include <stdint.h>
 
 #include "ds/Nestable.h"
+#include "frontend/AbstractScope.h"
 #include "frontend/NameAnalysisTypes.h"
 #include "frontend/NameCollections.h"
 #include "frontend/ParseContext.h"
 #include "frontend/SharedContext.h"
 #include "js/TypeDecls.h"
 
 namespace js {
 
@@ -71,17 +72,17 @@ class EmitterScope : public Nestable<Emi
   MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name,
                                    NameLocation loc);
 
   mozilla::Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce,
                                              JSAtom* name);
 
   EmitterScope* enclosing(BytecodeEmitter** bce) const;
 
-  Scope* enclosingScope(BytecodeEmitter* bce) const;
+  AbstractScope enclosingScope(BytecodeEmitter* bce) const;
 
   static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name);
 
   static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope,
                                              uint8_t hops);
   NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
 
   template <typename ScopeCreator>
@@ -120,17 +121,17 @@ class EmitterScope : public Nestable<Emi
   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;
+  AbstractScope scope(const BytecodeEmitter* bce) const;
 
   bool hasEnvironment() const { return hasEnvironment_; }
 
   // The first frame slot used.
   uint32_t frameSlotStart() const {
     if (EmitterScope* inFrame = enclosingInFrame()) {
       return inFrame->nextFrameSlot_;
     }
--- a/js/src/frontend/ForInEmitter.cpp
+++ b/js/src/frontend/ForInEmitter.cpp
@@ -70,17 +70,17 @@ bool ForInEmitter::emitInitialize() {
   if (headLexicalEmitterScope_) {
     // The environment chain only includes an environment for the
     // for-in loop head *if* a scope binding is captured, thereby
     // requiring recreation each iteration. If a lexical scope exists
     // for the head, it must be the innermost one. If that scope has
     // closed-over bindings inducing an environment, recreate the
     // current environment.
     MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope());
-    MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_)->kind() ==
+    MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_).kind() ==
                ScopeKind::Lexical);
 
     if (headLexicalEmitterScope_->hasEnvironment()) {
       if (!bce_->emit1(JSOP_RECREATELEXICALENV)) {
         //          [stack] ITER ITERVAL
         return false;
       }
     }
--- a/js/src/frontend/ForOfEmitter.cpp
+++ b/js/src/frontend/ForOfEmitter.cpp
@@ -83,17 +83,17 @@ bool ForOfEmitter::emitInitialize(const 
   // environment with an dead zoned one to implement TDZ semantics.
   if (headLexicalEmitterScope_) {
     // The environment chain only includes an environment for the for-of
     // loop head *if* a scope binding is captured, thereby requiring
     // recreation each iteration. If a lexical scope exists for the head,
     // it must be the innermost one. If that scope has closed-over
     // bindings inducing an environment, recreate the current environment.
     MOZ_ASSERT(headLexicalEmitterScope_ == bce_->innermostEmitterScope());
-    MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_)->kind() ==
+    MOZ_ASSERT(headLexicalEmitterScope_->scope(bce_).kind() ==
                ScopeKind::Lexical);
 
     if (headLexicalEmitterScope_->hasEnvironment()) {
       if (!bce_->emit1(JSOP_RECREATELEXICALENV)) {
         //          [stack] NEXT ITER UNDEF
         return false;
       }
     }
--- a/js/src/frontend/NameOpEmitter.cpp
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * 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/NameOpEmitter.h"
 
+#include "frontend/AbstractScope.h"
 #include "frontend/BytecodeEmitter.h"
 #include "frontend/SharedContext.h"
 #include "frontend/TDZCheckCache.h"
 #include "vm/Opcodes.h"
 #include "vm/Scope.h"
 #include "vm/StringType.h"
 
 using namespace js;
@@ -162,17 +163,17 @@ bool NameOpEmitter::prepareForRhs() {
       break;
     case NameLocation::Kind::Global:
       if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
         return false;
       }
       if (loc_.isLexical() && isInitialize()) {
         // INITGLEXICAL always gets the global lexical scope. It doesn't
         // need a BINDGNAME.
-        MOZ_ASSERT(bce_->innermostScope()->is<GlobalScope>());
+        MOZ_ASSERT(bce_->innermostScope().is<GlobalScope>());
       } else {
         if (!bce_->emitIndexOp(JSOP_BINDGNAME, atomIndex_)) {
           //        [stack] ENV
           return false;
         }
         emittedBindOp_ = true;
       }
       break;
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -438,17 +438,17 @@ void TraceListNode::trace(JSTracer* trc)
   if (gcThing) {
     TraceGenericPointerRoot(trc, &gcThing, "parser.traceListNode");
   }
 }
 
 void FunctionBox::trace(JSTracer* trc) {
   ObjectBox::trace(trc);
   if (enclosingScope_) {
-    TraceRoot(trc, &enclosingScope_, "funbox-enclosingScope");
+    enclosingScope_.trace(trc);
   }
   if (explicitName_) {
     TraceRoot(trc, &explicitName_, "funbox-explicitName");
   }
   if (functionCreationData_) {
     functionCreationData_->trace(trc);
   }
 }
--- a/js/src/frontend/SharedContext.cpp
+++ b/js/src/frontend/SharedContext.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * 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/SharedContext.h"
 
+#include "frontend/AbstractScope.h"
 #include "frontend/ModuleSharedContext.h"
 
 #include "frontend/ParseContext-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 
 namespace js {
 namespace frontend {
 
@@ -116,17 +117,17 @@ bool FunctionBox::atomsAreKept() { retur
 
 FunctionBox::FunctionBox(JSContext* cx, TraceListNode* traceListHead,
                          uint32_t toStringStart, Directives directives,
                          bool extraWarnings, GeneratorKind generatorKind,
                          FunctionAsyncKind asyncKind, JSAtom* explicitName,
                          FunctionFlags flags)
     : ObjectBox(nullptr, traceListHead, TraceListNode::NodeType::Function),
       SharedContext(cx, Kind::FunctionBox, directives, extraWarnings),
-      enclosingScope_(nullptr),
+      enclosingScope_(),
       namedLambdaBindings_(nullptr),
       functionScopeBindings_(nullptr),
       extraVarScopeBindings_(nullptr),
       functionNode(nullptr),
       sourceStart(0),
       sourceEnd(0),
       startLine(1),
       startColumn(0),
@@ -188,40 +189,40 @@ void FunctionBox::initFromLazyFunction(J
   LazyScript* lazy = fun->lazyScript();
   if (lazy->isDerivedClassConstructor()) {
     setDerivedClassConstructor();
   }
   if (lazy->needsHomeObject()) {
     setNeedsHomeObject();
   }
 
-  enclosingScope_ = fun->enclosingScope();
+  enclosingScope_ = AbstractScope(fun->enclosingScope());
 
   if (lazy->bindingsAccessedDynamically()) {
     setBindingsAccessedDynamically();
   }
   if (lazy->hasDirectEval()) {
     setHasDirectEval();
   }
 
   sourceStart = lazy->sourceStart();
   sourceEnd = lazy->sourceEnd();
   toStringStart = lazy->toStringStart();
   toStringEnd = lazy->toStringEnd();
   startLine = lazy->lineno();
   startColumn = lazy->column();
 
-  initWithEnclosingScope(enclosingScope_, fun);
+  initWithEnclosingScope(enclosingScope_.maybeScope(), fun);
 }
 
 void FunctionBox::initStandaloneFunction(Scope* enclosingScope) {
   // Standalone functions are Function or Generator constructors and are
   // always scoped to the global.
   MOZ_ASSERT(enclosingScope->is<GlobalScope>());
-  enclosingScope_ = enclosingScope;
+  enclosingScope_ = AbstractScope(enclosingScope);
   allowNewTarget_ = true;
   thisBinding_ = ThisBinding::Function;
 }
 
 void FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing,
                                                 FunctionSyntaxKind kind,
                                                 bool isArrow,
                                                 bool allowSuperProperty) {
@@ -294,32 +295,33 @@ void FunctionBox::initWithEnclosingScope
   } else {
     computeAllowSyntax(enclosingScope);
     computeThisBinding(enclosingScope);
   }
 
   computeInWith(enclosingScope);
 }
 
-void FunctionBox::setEnclosingScopeForInnerLazyFunction(Scope* enclosingScope) {
+void FunctionBox::setEnclosingScopeForInnerLazyFunction(
+    const AbstractScope& enclosingScope) {
   MOZ_ASSERT(isLazyFunctionWithoutEnclosingScope());
 
   // For lazy functions inside a function which is being compiled, we cache
   // the incomplete scope object while compiling, and store it to the
   // LazyScript once the enclosing script successfully finishes compilation
   // in FunctionBox::finish.
   enclosingScope_ = enclosingScope;
 }
 
 void FunctionBox::finish() {
   if (!isLazyFunctionWithoutEnclosingScope()) {
     return;
   }
   MOZ_ASSERT(enclosingScope_);
-  function()->setEnclosingScope(enclosingScope_);
+  function()->setEnclosingScope(enclosingScope_.maybeScope());
 }
 
 ModuleSharedContext::ModuleSharedContext(JSContext* cx, ModuleObject* module,
                                          Scope* enclosingScope,
                                          ModuleBuilder& builder)
     : SharedContext(cx, Kind::Module, Directives(true), false),
       module_(cx, module),
       enclosingScope_(cx, enclosingScope),
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -6,21 +6,23 @@
 
 #ifndef frontend_SharedContext_h
 #define frontend_SharedContext_h
 
 #include "jspubtd.h"
 #include "jstypes.h"
 
 #include "ds/InlineTable.h"
+#include "frontend/AbstractScope.h"
 #include "frontend/FunctionCreationData.h"
 #include "frontend/ParseNode.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
+#include "vm/Scope.h"
 
 namespace js {
 namespace frontend {
 
 class ParseContext;
 class ParseNode;
 
 enum class StatementKind : uint8_t {
@@ -308,17 +310,17 @@ class FunctionBox : public ObjectBox, pu
   //     holds its enclosing scope, used for compilation.
   //   * If this FunctionBox refers to a lazy child of the function being
   //     compiled, this field holds the child's immediately enclosing scope.
   //     Once compilation succeeds, we will store it in the child's
   //     LazyScript.  (Debugger may become confused if LazyScripts refer to
   //     partially initialized enclosing scopes, so we must avoid storing the
   //     scope in the LazyScript until compilation has completed
   //     successfully.)
-  Scope* enclosingScope_;
+  AbstractScope enclosingScope_;
 
   // Names from the named lambda scope, if a named lambda.
   LexicalScope::Data* namedLambdaBindings_;
 
   // Names from the function scope.
   FunctionScope::Data* functionScopeBindings_;
 
   // Names from the extra 'var' scope of the function, if the parameter list
@@ -504,17 +506,18 @@ class FunctionBox : public ObjectBox, pu
                             HasHeritage hasHeritage);
   void initFieldInitializer(ParseContext* enclosing,
                             Handle<FunctionCreationData> data,
                             HasHeritage hasHeritage);
 
   inline bool isLazyFunctionWithoutEnclosingScope() const {
     return isInterpretedLazy() && !function()->enclosingScope();
   }
-  void setEnclosingScopeForInnerLazyFunction(Scope* enclosingScope);
+  void setEnclosingScopeForInnerLazyFunction(
+      const AbstractScope& enclosingScope);
   void finish();
 
   // Free non-LifoAlloc memory which would otherwise be leaked when
   // the FunctionBox is LifoAlloc destroyed (without calling destructor)
   void cleanupMemory() { clearDeferredAllocationInfo(); }
 
   // Clear any deferred allocation info which will no longer be used.
   void clearDeferredAllocationInfo() {
@@ -542,26 +545,26 @@ class FunctionBox : public ObjectBox, pu
     // a FunctionBox is the outermost SharedContext, it must be a lazy
     // function.
 
     // If the function is lazy and it has enclosing scope, the function is
     // being delazified.  In that case the enclosingScope_ field is copied
     // from the lazy function at the beginning of delazification and should
     // keep pointing the same scope.
     MOZ_ASSERT_IF(isInterpretedLazy() && function()->enclosingScope(),
-                  enclosingScope_ == function()->enclosingScope());
+                  enclosingScope_.maybeScope() == function()->enclosingScope());
 
     // If this FunctionBox is a lazy child of the function we're actually
     // compiling, then it is not the outermost SharedContext, so this
     // method should return nullptr."
     if (isLazyFunctionWithoutEnclosingScope()) {
       return nullptr;
     }
 
-    return enclosingScope_;
+    return enclosingScope_.maybeScope();
   }
 
   bool needsCallObjectRegardlessOfBindings() const {
     return hasExtensibleScope() || needsHomeObject() ||
            isDerivedClassConstructor() || isGenerator() || isAsync();
   }
 
   bool hasExtraBodyVarScope() const {
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -17,16 +17,17 @@ include('../js-cxxflags.mozbuild')
 
 
 # Generate frontend/ReservedWordsGenerated.h from frontend/ReservedWords.h
 GeneratedFile('ReservedWordsGenerated.h', script='GenerateReservedWords.py',
               inputs=['ReservedWords.h'])
 
 
 UNIFIED_SOURCES += [
+    'AbstractScope.cpp',
     'BytecodeCompiler.cpp',
     'BytecodeControlStructures.cpp',
     'BytecodeEmitter.cpp',
     'BytecodeSection.cpp',
     'CallOrNewEmitter.cpp',
     'CForEmitter.cpp',
     'DefaultEmitter.cpp',
     'DoWhileEmitter.cpp',
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -4395,30 +4395,30 @@ static void InitAtomMap(frontend::AtomIn
     uint32_t index = r.front().value();
     MOZ_ASSERT(index < indices.count());
     atoms[index].init(atom);
   }
 }
 
 static bool NeedsFunctionEnvironmentObjects(frontend::BytecodeEmitter* bce) {
   // See JSFunction::needsCallObject()
-  js::Scope* bodyScope = bce->bodyScope();
-  if (bodyScope->kind() == js::ScopeKind::Function) {
-    if (bodyScope->hasEnvironment()) {
+  js::AbstractScope bodyScope = bce->bodyScope();
+  if (bodyScope.kind() == js::ScopeKind::Function) {
+    if (bodyScope.hasEnvironment()) {
       return true;
     }
   }
 
   // See JSScript::maybeNamedLambdaScope()
-  js::Scope* outerScope = bce->outermostScope();
-  if (outerScope->kind() == js::ScopeKind::NamedLambda ||
-      outerScope->kind() == js::ScopeKind::StrictNamedLambda) {
+  js::AbstractScope outerScope = bce->outermostScope();
+  if (outerScope.kind() == js::ScopeKind::NamedLambda ||
+      outerScope.kind() == js::ScopeKind::StrictNamedLambda) {
     MOZ_ASSERT(bce->sc->asFunctionBox()->isNamedLambda());
 
-    if (outerScope->hasEnvironment()) {
+    if (outerScope.hasEnvironment()) {
       return true;
     }
   }
 
   return false;
 }
 
 void JSScript::initFromFunctionBox(frontend::FunctionBox* funbox) {
@@ -4478,17 +4478,17 @@ bool JSScript::fullyInitFromEmitter(JSCo
   script->setFlag(ImmutableFlags::Strict, bce->sc->strict());
   script->setFlag(ImmutableFlags::BindingsAccessedDynamically,
                   bce->sc->bindingsAccessedDynamically());
   script->setFlag(ImmutableFlags::HasCallSiteObj, bce->hasCallSiteObj);
   script->setFlag(ImmutableFlags::IsForEval, bce->sc->isEvalContext());
   script->setFlag(ImmutableFlags::IsModule, bce->sc->isModuleContext());
   script->setFlag(ImmutableFlags::IsFunction, bce->sc->isFunctionBox());
   script->setFlag(ImmutableFlags::HasNonSyntacticScope,
-                  bce->outermostScope()->hasOnChain(ScopeKind::NonSyntactic));
+                  bce->outermostScope().hasOnChain(ScopeKind::NonSyntactic));
   script->setFlag(ImmutableFlags::NeedsFunctionEnvironmentObjects,
                   NeedsFunctionEnvironmentObjects(bce));
 
   // Initialize script flags from FunctionBox
   if (bce->sc->isFunctionBox()) {
     script->initFromFunctionBox(bce->sc->asFunctionBox());
   }
 
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -7,16 +7,17 @@
 #include "vm/Scope.h"
 
 #include "mozilla/ScopeExit.h"
 
 #include <memory>
 #include <new>
 
 #include "builtin/ModuleObject.h"
+#include "frontend/AbstractScope.h"
 #include "gc/Allocator.h"
 #include "util/StringBuffer.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/JSScript.h"
 #include "wasm/WasmInstance.h"
 
 #include "gc/FreeOp-inl.h"
 #include "gc/ObjectKind-inl.h"
@@ -490,56 +491,70 @@ void Scope::dump() {
 
 uint32_t LexicalScope::firstFrameSlot() const {
   switch (kind()) {
     case ScopeKind::Lexical:
     case ScopeKind::SimpleCatch:
     case ScopeKind::Catch:
     case ScopeKind::FunctionLexical:
       // For intra-frame scopes, find the enclosing scope's next frame slot.
-      return nextFrameSlot(enclosing());
+      return nextFrameSlot(AbstractScope(enclosing()));
     case ScopeKind::NamedLambda:
     case ScopeKind::StrictNamedLambda:
       // Named lambda scopes cannot have frame slots.
       return LOCALNO_LIMIT;
     default:
       // Otherwise start at 0.
       break;
   }
   return 0;
 }
 
 /* static */
-uint32_t LexicalScope::nextFrameSlot(Scope* scope) {
-  for (ScopeIter si(scope); si; si++) {
+uint32_t LexicalScope::nextFrameSlot(const AbstractScope& scope) {
+  for (AbstractScopeIter si(scope); si; si++) {
     switch (si.kind()) {
       case ScopeKind::Function:
-        return si.scope()->as<FunctionScope>().nextFrameSlot();
+        MOZ_ASSERT(si.abstractScope().maybeScope());
+        return si.abstractScope()
+            .maybeScope()
+            ->as<FunctionScope>()
+            .nextFrameSlot();
       case ScopeKind::FunctionBodyVar:
       case ScopeKind::ParameterExpressionVar:
-        return si.scope()->as<VarScope>().nextFrameSlot();
+        MOZ_ASSERT(si.abstractScope().maybeScope());
+        return si.abstractScope().maybeScope()->as<VarScope>().nextFrameSlot();
       case ScopeKind::Lexical:
       case ScopeKind::SimpleCatch:
       case ScopeKind::Catch:
       case ScopeKind::FunctionLexical:
-        return si.scope()->as<LexicalScope>().nextFrameSlot();
+        MOZ_ASSERT(si.abstractScope().maybeScope());
+        return si.abstractScope()
+            .maybeScope()
+            ->as<LexicalScope>()
+            .nextFrameSlot();
       case ScopeKind::NamedLambda:
       case ScopeKind::StrictNamedLambda:
         // Named lambda scopes cannot have frame slots.
         return 0;
       case ScopeKind::With:
         continue;
       case ScopeKind::Eval:
       case ScopeKind::StrictEval:
-        return si.scope()->as<EvalScope>().nextFrameSlot();
+        MOZ_ASSERT(si.abstractScope().maybeScope());
+        return si.abstractScope().maybeScope()->as<EvalScope>().nextFrameSlot();
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
         return 0;
       case ScopeKind::Module:
-        return si.scope()->as<ModuleScope>().nextFrameSlot();
+        MOZ_ASSERT(si.abstractScope().maybeScope());
+        return si.abstractScope()
+            .maybeScope()
+            ->as<ModuleScope>()
+            .nextFrameSlot();
       case ScopeKind::WasmInstance:
         // TODO return si.scope()->as<WasmInstanceScope>().nextFrameSlot();
         return 0;
       case ScopeKind::WasmFunction:
         // TODO return si.scope()->as<WasmFunctionScope>().nextFrameSlot();
         return 0;
     }
   }
@@ -567,17 +582,17 @@ bool LexicalScope::prepareForScopeCreati
                                            uint32_t firstFrameSlot,
                                            HandleScope enclosing,
                                            MutableHandle<UniquePtr<Data>> data,
                                            MutableHandleShape envShape) {
   bool isNamedLambda =
       kind == ScopeKind::NamedLambda || kind == ScopeKind::StrictNamedLambda;
 
   MOZ_ASSERT_IF(!isNamedLambda && firstFrameSlot != 0,
-                firstFrameSlot == nextFrameSlot(enclosing));
+                firstFrameSlot == nextFrameSlot(AbstractScope(enclosing)));
   MOZ_ASSERT_IF(isNamedLambda, firstFrameSlot == LOCALNO_LIMIT);
 
   BindingIter bi(*data, firstFrameSlot, isNamedLambda);
   if (!PrepareScopeData<LexicalScope>(
           cx, bi, data, &LexicalEnvironmentObject::class_,
           BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE, envShape)) {
     return false;
   }
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -24,16 +24,17 @@
 #include "vm/ScopeKind.h"
 #include "vm/Xdr.h"
 
 namespace js {
 
 class BaseScopeData;
 class ModuleObject;
 class Scope;
+class AbstractScope;
 
 enum class BindingKind : uint8_t {
   Import,
   FormalParameter,
   Var,
   Let,
   Const,
 
@@ -433,17 +434,17 @@ class LexicalScope : public Scope {
                                       HandleScope enclosing,
                                       MutableHandle<UniquePtr<Data>> data,
                                       MutableHandleShape envShape);
 
   Data& data() { return *static_cast<Data*>(data_); }
 
   const Data& data() const { return *static_cast<Data*>(data_); }
 
-  static uint32_t nextFrameSlot(Scope* start);
+  static uint32_t nextFrameSlot(const AbstractScope& start);
 
  public:
   uint32_t firstFrameSlot() const;
 
   uint32_t nextFrameSlot() const { return data().nextFrameSlot; }
 
   // Returns an empty shape for extensible global and non-syntactic lexical
   // scopes.
@@ -476,16 +477,17 @@ inline bool Scope::is<LexicalScope>() co
 //
 // Corresponds to CallObject on environment chain.
 //
 class FunctionScope : public Scope {
   friend class GCMarker;
   friend class BindingIter;
   friend class PositionalFormalParameterIter;
   friend class Scope;
+  friend class AbstractScope;
   static const ScopeKind classScopeKind_ = ScopeKind::Function;
 
  public:
   // Data is public because it is created by the
   // frontend. See Parser<FullParseHandler>::newFunctionScopeData.
   struct Data : public BaseScopeData {
     // The canonical function of the scope, as during a scope walk we
     // often query properties of the JSFunction (e.g., is the function an
@@ -758,16 +760,17 @@ inline bool Scope::is<GlobalScope>() con
 }
 
 //
 // Scope of a 'with' statement. Has no bindings.
 //
 // Corresponds to a WithEnvironmentObject on the environment chain.
 class WithScope : public Scope {
   friend class Scope;
+  friend class AbstractScope;
   static const ScopeKind classScopeKind_ = ScopeKind::With;
 
  public:
   static WithScope* create(JSContext* cx, HandleScope enclosing);
 
   template <XDRMode mode>
   static XDRResult XDR(XDRState<mode>* xdr, HandleScope enclosing,
                        MutableHandleScope scope);
@@ -871,16 +874,17 @@ inline bool Scope::is<EvalScope>() const
 // the treating of imports and exports requires putting them in one scope.
 //
 // Corresponds to a ModuleEnvironmentObject on the environment chain.
 //
 class ModuleScope : public Scope {
   friend class GCMarker;
   friend class BindingIter;
   friend class Scope;
+  friend class AbstractScope;
   static const ScopeKind classScopeKind_ = ScopeKind::Module;
 
  public:
   // Data is public because it is created by the frontend. See
   // Parser<FullParseHandler>::newModuleScopeData.
   struct Data : BaseScopeData {
     // The module of the scope.
     GCPtr<ModuleObject*> module = {};
@@ -937,16 +941,17 @@ class ModuleScope : public Scope {
 
   static Shape* getEmptyEnvironmentShape(JSContext* cx);
 };
 
 class WasmInstanceScope : public Scope {
   friend class BindingIter;
   friend class Scope;
   friend class GCMarker;
+  friend class AbstractScope;
   static const ScopeKind classScopeKind_ = ScopeKind::WasmInstance;
 
  public:
   struct Data : public BaseScopeData {
     uint32_t memoriesStart = 0;
     uint32_t globalsStart = 0;
     uint32_t length = 0;
     uint32_t nextFrameSlot = 0;
@@ -983,16 +988,17 @@ class WasmInstanceScope : public Scope {
 
 // Scope corresponding to the wasm function. A WasmFunctionScope is used by
 // Debugger only, and not for wasm execution.
 //
 class WasmFunctionScope : public Scope {
   friend class BindingIter;
   friend class Scope;
   friend class GCMarker;
+  friend class AbstractScope;
   static const ScopeKind classScopeKind_ = ScopeKind::WasmFunction;
 
  public:
   struct Data : public BaseScopeData {
     uint32_t length = 0;
     uint32_t nextFrameSlot = 0;
     uint32_t funcIndex = 0;