Bug 1592102 - Defer allocation of Scopes r=tcampbell
authorMatthew Gaudet <mgaudet@mozilla.com>
Mon, 06 Jan 2020 17:02:14 +0000
changeset 508936 47592189b30514c1c224ac1af185792c9d73c312
parent 508935 1410db0871ba5a00a43906437e0050b19bc363bc
child 508937 2a39a8e425b0824c670ba03452c437c3748c990b
push id36986
push usernerli@mozilla.com
push dateMon, 06 Jan 2020 21:54:03 +0000
treeherdermozilla-central@e6427fac5ee8 [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 - Defer allocation of Scopes r=tcampbell Defer the allocation of Scopes by implementing ScopeCreationData and family. The idea is that we do not reify a GC allocated scope until JSScript::fullyInitFromEmitter (the goal being to eventually move that onto main thread). Differential Revision: https://phabricator.services.mozilla.com/D51912
js/src/frontend/AbstractScope.cpp
js/src/frontend/AbstractScope.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeSection.cpp
js/src/frontend/BytecodeSection.h
js/src/frontend/EmitterScope.cpp
js/src/frontend/EmitterScope.h
js/src/frontend/ParseInfo.h
js/src/frontend/SharedContext.cpp
js/src/frontend/SharedContext.h
js/src/frontend/Stencil.cpp
js/src/frontend/Stencil.h
js/src/frontend/TypedIndex.h
js/src/frontend/moz.build
js/src/vm/Scope.cpp
js/src/vm/Scope.h
--- a/js/src/frontend/AbstractScope.cpp
+++ b/js/src/frontend/AbstractScope.cpp
@@ -3,46 +3,149 @@
  * 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 "frontend/ParseInfo.h"
+#include "frontend/Stencil.h"
 #include "js/GCPolicyAPI.h"
-#include "vm/JSFunction.h"
-#include "vm/Scope.h"
+#include "js/GCVariant.h"
 
 using namespace js;
 using namespace js::frontend;
 
-Scope* AbstractScope::maybeScope() const { return scope_; }
+MutableHandle<ScopeCreationData> AbstractScope::scopeCreationData() const {
+  const Deferred& data = scope_.as<Deferred>();
+  return data.parseInfo.scopeCreationData[data.index.index];
+}
+
+// This is used during allocation of the scopes to ensure that we only
+// allocate GC scopes with GC-enclosing scopes. This can recurse through
+// the scope chain.
+//
+// Once all ScopeCreation for a compilation tree is centralized, this
+// will go away, to be replaced with a single top down GC scope allocation.
+//
+// This uses an outparam to disambiguate between the case where we have a
+// real nullptr scope and we failed to allocate a new scope because of OOM.
+bool AbstractScope::getOrCreateScope(JSContext* cx, MutableHandleScope scope) {
+  if (isScopeCreationData()) {
+    MutableHandle<ScopeCreationData> scd = scopeCreationData();
+    if (scd.get().hasScope()) {
+      scope.set(scd.get().getScope());
+      return true;
+    }
+
+    scope.set(scd.get().createScope(cx));
+    return scope;
+  }
+
+  scope.set(this->scope());
+  return true;
+}
+
+Scope* AbstractScope::getExistingScope() const {
+  if (scope_.is<HeapPtrScope>()) {
+    return scope_.as<HeapPtrScope>();
+  }
+  MOZ_ASSERT(isScopeCreationData());
+  // This should only be called post-reification, as it needs to return a real
+  // Scope* unless it represents nullptr (in which case the variant should be
+  // in HeapPtrScope and handled above)
+  MOZ_ASSERT(scopeCreationData().get().getScope());
+  return scopeCreationData().get().getScope();
+}
 
 ScopeKind AbstractScope::kind() const {
-  MOZ_ASSERT(maybeScope());
-  return maybeScope()->kind();
+  MOZ_ASSERT(!isNullptr());
+  if (isScopeCreationData()) {
+    return scopeCreationData().get().kind();
+  }
+  return scope()->kind();
 }
 
 AbstractScope AbstractScope::enclosing() const {
-  MOZ_ASSERT(maybeScope());
-  return AbstractScope(maybeScope()->enclosing());
+  MOZ_ASSERT(!isNullptr());
+  if (isScopeCreationData()) {
+    return scopeCreationData().get().enclosing();
+  }
+  return AbstractScope(scope()->enclosing());
 }
 
 bool AbstractScope::hasEnvironment() const {
-  MOZ_ASSERT(maybeScope());
-  return maybeScope()->hasEnvironment();
+  MOZ_ASSERT(!isNullptr());
+  if (isScopeCreationData()) {
+    return scopeCreationData().get().hasEnvironment();
+  }
+  return scope()->hasEnvironment();
+}
+
+bool AbstractScope::isArrow() const {
+  // nullptr will also fail the below assert, so effectively also checking
+  // !isNullptr()
+  MOZ_ASSERT(is<FunctionScope>());
+  if (isScopeCreationData()) {
+    return scopeCreationData().get().isArrow();
+  }
+  return scope()->as<FunctionScope>().canonicalFunction()->isArrow();
+}
+
+JSFunction* AbstractScope::canonicalFunction() const {
+  // nullptr will also fail the below assert, so effectively also checking
+  // !isNullptr()
+  MOZ_ASSERT(is<FunctionScope>());
+  if (isScopeCreationData()) {
+    return scopeCreationData().get().canonicalFunction();
+  }
+  return scope()->as<FunctionScope>().canonicalFunction();
 }
 
-bool AbstractScope::isArrow() const { return canonicalFunction()->isArrow(); }
+uint32_t AbstractScope::nextFrameSlot() const {
+  if (isScopeCreationData()) {
+    return scopeCreationData().get().nextFrameSlot();
+  }
 
-JSFunction* AbstractScope::canonicalFunction() const {
-  MOZ_ASSERT(is<FunctionScope>());
-  MOZ_ASSERT(maybeScope());
-  return maybeScope()->as<FunctionScope>().canonicalFunction();
+  switch (kind()) {
+    case ScopeKind::Function:
+      return scope()->as<FunctionScope>().nextFrameSlot();
+    case ScopeKind::FunctionBodyVar:
+    case ScopeKind::ParameterExpressionVar:
+      return scope()->as<VarScope>().nextFrameSlot();
+    case ScopeKind::Lexical:
+    case ScopeKind::SimpleCatch:
+    case ScopeKind::Catch:
+    case ScopeKind::FunctionLexical:
+      return scope()->as<LexicalScope>().nextFrameSlot();
+    case ScopeKind::NamedLambda:
+    case ScopeKind::StrictNamedLambda:
+      // Named lambda scopes cannot have frame slots.
+      return 0;
+    case ScopeKind::Eval:
+    case ScopeKind::StrictEval:
+      return scope()->as<EvalScope>().nextFrameSlot();
+    case ScopeKind::Global:
+    case ScopeKind::NonSyntactic:
+      return 0;
+    case ScopeKind::Module:
+      return scope()->as<ModuleScope>().nextFrameSlot();
+    case ScopeKind::WasmInstance:
+      MOZ_CRASH("WasmInstanceScope doesn't have nextFrameSlot()");
+      return 0;
+    case ScopeKind::WasmFunction:
+      MOZ_CRASH("WasmFunctionScope doesn't have nextFrameSlot()");
+      return 0;
+    case ScopeKind::With:
+      MOZ_CRASH("With Scopes don't get nextFrameSlot()");
+      return 0;
+  }
+  MOZ_CRASH("Not an enclosing intra-frame scope");
 }
 
 void AbstractScope::trace(JSTracer* trc) {
   JS::GCPolicy<ScopeType>::trace(trc, &scope_, "AbstractScope");
 }
 
 bool AbstractScopeIter::hasSyntacticEnvironment() const {
   return abstractScope().hasEnvironment() &&
--- a/js/src/frontend/AbstractScope.h
+++ b/js/src/frontend/AbstractScope.h
@@ -4,72 +4,121 @@
  * 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 "frontend/TypedIndex.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;
+class ScopeCreationData;
+
+namespace frontend {
+struct ParseInfo;
+class FunctionBox;
+}  // namespace frontend
+
+using ScopeIndex = frontend::TypedIndex<Scope>;
 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)
+//
+// Because a AbstractScope may hold onto a Scope, it must be rooted if a GC may
+// occur to ensure that the scope is traced.
 class AbstractScope {
  public:
-  using ScopeType = HeapPtrScope;
+  // Used to hold index and the parseInfo together to avoid having a
+  // potentially nullable parseInfo.
+  struct Deferred {
+    ScopeIndex index;
+    frontend::ParseInfo& parseInfo;
+  };
+
+  // To make writing code and managing invariants easier, we require that
+  // any nullptr scopes be stored on the HeapPtrScope arm of the variant.
+  using ScopeType = mozilla::Variant<HeapPtrScope, Deferred>;
 
  private:
-  ScopeType scope_ = {};
+  ScopeType scope_ = ScopeType(HeapPtrScope());
+
+  // Extract the Scope* represented by this; may be nullptr, and will
+  // forward through to the ScopeCreationData if it has a Scope*
+  //
+  // Should only be used after getOrCreate() has been used to reify this into a
+  // Scope.
+  Scope* getExistingScope() const;
 
  public:
   friend class js::Scope;
+  friend class js::frontend::FunctionBox;
 
-  AbstractScope() {}
+  AbstractScope() = default;
+
+  explicit AbstractScope(Scope* scope) : scope_(HeapPtrScope(scope)) {}
+
+  AbstractScope(frontend::ParseInfo& parseInfo, ScopeIndex scope)
+      : scope_(Deferred{scope, parseInfo}) {}
 
-  explicit AbstractScope(Scope* scope) : scope_(scope) {}
+  bool isNullptr() const {
+    if (isScopeCreationData()) {
+      return false;
+    }
+    return scope_.as<HeapPtrScope>() == nullptr;
+  }
 
   // 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(); }
+  explicit operator bool() const { return !isNullptr(); }
+
+  bool isScopeCreationData() const { return scope_.is<Deferred>(); }
+
+  // Note: this handle is rooted in the ParseInfo.
+  MutableHandle<ScopeCreationData> scopeCreationData() const;
 
-  Scope* maybeScope() const;
+  Scope* scope() const { return scope_.as<HeapPtrScope>(); }
 
-  // This allows us to check whether or not this abstract scope wraps
+  // Get a Scope*, creating it from a ScopeCreationData if required.
+  // Used to allow us to ensure that Scopes are always allocated with
+  // real GC allocated Enclosing scopes.
+  bool getOrCreateScope(JSContext* cx, MutableHandleScope scope);
+
+  // This allows us to check whether or not this provider 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()) {
+    if (isNullptr()) {
       return false;
     }
     return kind() == T::classScopeKind_;
   }
 
   ScopeKind kind() const;
   AbstractScope enclosing() const;
   bool hasEnvironment() const;
+  uint32_t nextFrameSlot() 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;
@@ -79,23 +128,23 @@ class AbstractScope {
   }
 
   void trace(JSTracer* trc);
 };
 
 // Specializations of AbstractScope::is
 template <>
 inline bool AbstractScope::is<GlobalScope>() const {
-  return maybeScope() &&
+  return !isNullptr() &&
          (kind() == ScopeKind::Global || kind() == ScopeKind::NonSyntactic);
 }
 
 template <>
 inline bool AbstractScope::is<EvalScope>() const {
-  return maybeScope() &&
+  return !isNullptr() &&
          (kind() == ScopeKind::Eval || kind() == ScopeKind::StrictEval);
 }
 
 // Iterate over abstract scopes rather than scopes.
 class AbstractScopeIter {
   AbstractScope scope_;
 
  public:
@@ -126,9 +175,15 @@ class AbstractScopeIter {
     if (scope_) {
       scope_.trace(trc);
     }
   };
 };
 
 }  // namespace js
 
-#endif  // frontend_AbstractScope_h
\ No newline at end of file
+namespace JS {
+template <>
+struct GCPolicy<js::AbstractScope::Deferred>
+    : JS::IgnoreGCPolicy<js::AbstractScope::Deferred> {};
+}  // namespace JS
+
+#endif  // frontend_AbstractScope_h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -100,17 +100,17 @@ BytecodeEmitter::BytecodeEmitter(
     ParseInfo& parseInfo, EmitterMode emitterMode,
     FieldInitializers fieldInitializers /* = FieldInitializers::Invalid() */)
     : sc(sc),
       cx(sc->cx_),
       parent(parent),
       script(cx, script),
       lazyScript(cx, lazyScript),
       bytecodeSection_(cx, line),
-      perScriptData_(cx),
+      perScriptData_(cx, parseInfo),
       fieldInitializers_(fieldInitializers),
       parseInfo(parseInfo),
       firstLine(line),
       firstColumn(column),
       emitterMode(emitterMode) {
   MOZ_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
 
   if (sc->isFunctionBox()) {
--- a/js/src/frontend/BytecodeSection.cpp
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -8,16 +8,17 @@
 
 #include "mozilla/Assertions.h"       // MOZ_ASSERT
 #include "mozilla/PodOperations.h"    // PodZero
 #include "mozilla/ReverseIterator.h"  // mozilla::Reversed
 
 #include "frontend/ParseInfo.h"
 #include "frontend/ParseNode.h"      // ObjectBox
 #include "frontend/SharedContext.h"  // FunctionBox
+#include "frontend/Stencil.h"        // ScopeCreationData
 #include "vm/BytecodeUtil.h"         // INDEX_LIMIT, StackUses, StackDefs
 #include "vm/JSContext.h"            // JSContext
 #include "vm/RegExpObject.h"         // RegexpObject
 
 using namespace js;
 using namespace js::frontend;
 
 bool GCThingList::append(ObjectBox* objbox, uint32_t* index) {
@@ -82,16 +83,28 @@ bool GCThingList::finish(JSContext* cx, 
     bool operator()(ObjLiteralCreationData& data) {
       JSObject* obj = data.create(cx);
       if (!obj) {
         return false;
       }
       array[i] = JS::GCCellPtr(obj);
       return true;
     }
+
+    bool operator()(ScopeIndex& index) {
+      MutableHandle<ScopeCreationData> data =
+          parseInfo.scopeCreationData[index];
+      Scope* scope = data.get().createScope(cx);
+      if (!scope) {
+        return false;
+      }
+
+      array[i] = JS::GCCellPtr(scope);
+      return true;
+    }
   };
 
   for (uint32_t i = 0; i < length(); i++) {
     Matcher m{cx, parseInfo, i, array};
     if (!vector[i].get().match(m)) {
       return false;
     }
   }
@@ -192,12 +205,12 @@ void BytecodeSection::updateDepth(Byteco
   MOZ_ASSERT(stackDepth_ >= 0);
   stackDepth_ += ndefs;
 
   if (uint32_t(stackDepth_) > maxStackDepth_) {
     maxStackDepth_ = stackDepth_;
   }
 }
 
-PerScriptData::PerScriptData(JSContext* cx)
-    : gcThingList_(cx), atomIndices_(cx->frontendCollectionPool()) {}
+PerScriptData::PerScriptData(JSContext* cx, frontend::ParseInfo& parseInfo)
+    : gcThingList_(cx, parseInfo), atomIndices_(cx->frontendCollectionPool()) {}
 
 bool PerScriptData::init(JSContext* cx) { return atomIndices_.acquire(cx); }
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -17,17 +17,16 @@
 #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/ParseInfo.h"        // ParseInfo
 #include "frontend/ParseNode.h"        // BigIntLiteral
 #include "frontend/SourceNotes.h"      // jssrcnote
 #include "frontend/Stencil.h"          // Stencils
 #include "gc/Barrier.h"                // GCPtrObject, GCPtrScope, GCPtrValue
 #include "gc/Rooting.h"                // JS::Rooted
 #include "js/GCVariant.h"              // GCPolicy<mozilla::Variant>
 #include "js/GCVector.h"               // GCVector
 #include "js/TypeDecls.h"              // jsbytecode, JSContext
@@ -43,28 +42,41 @@ class Scope;
 using BigIntVector = JS::GCVector<js::BigInt*>;
 
 namespace frontend {
 
 class BigIntLiteral;
 class ObjectBox;
 
 struct MOZ_STACK_CLASS GCThingList {
-  using ListType = mozilla::Variant<JS::GCCellPtr, BigIntIndex,
-                                    ObjLiteralCreationData, RegExpIndex>;
+  using ListType =
+      mozilla::Variant<JS::GCCellPtr, BigIntIndex, ObjLiteralCreationData,
+                       RegExpIndex, ScopeIndex>;
+  ParseInfo& parseInfo;
   JS::RootedVector<ListType> vector;
 
   // Last emitted object.
   ObjectBox* lastbox = nullptr;
 
   // Index of the first scope in the vector.
   mozilla::Maybe<uint32_t> firstScopeIndex;
 
-  explicit GCThingList(JSContext* cx) : vector(cx) {}
+  explicit GCThingList(JSContext* cx, ParseInfo& parseInfo)
+      : parseInfo(parseInfo), vector(cx) {}
 
+  MOZ_MUST_USE bool append(ScopeIndex scope, uint32_t* index) {
+    *index = vector.length();
+    if (!vector.append(mozilla::AsVariant(scope))) {
+      return false;
+    }
+    if (!firstScopeIndex) {
+      firstScopeIndex.emplace(*index);
+    }
+    return true;
+  }
   MOZ_MUST_USE bool append(Scope* scope, uint32_t* index) {
     *index = vector.length();
     if (!vector.append(mozilla::AsVariant(JS::GCCellPtr(scope)))) {
       return false;
     }
     if (!firstScopeIndex) {
       firstScopeIndex.emplace(*index);
     }
@@ -93,17 +105,20 @@ struct MOZ_STACK_CLASS GCThingList {
 
   uint32_t length() const { return vector.length(); }
   MOZ_MUST_USE bool finish(JSContext* cx, ParseInfo& parseInfo,
                            mozilla::Span<JS::GCCellPtr> array);
   void finishInnerFunctions();
 
   AbstractScope getScope(size_t index) const {
     auto& elem = vector[index].get();
-    return AbstractScope(&elem.as<JS::GCCellPtr>().as<Scope>());
+    if (elem.is<JS::GCCellPtr>()) {
+      return AbstractScope(&elem.as<JS::GCCellPtr>().as<Scope>());
+    }
+    return AbstractScope(parseInfo, elem.as<ScopeIndex>());
   }
 
   AbstractScope firstScope() const {
     MOZ_ASSERT(firstScopeIndex.isSome());
     return getScope(*firstScopeIndex);
   }
 };
 
@@ -368,17 +383,17 @@ class BytecodeSection {
   // Number of JOF_TYPESET opcodes generated.
   uint32_t numTypeSets_ = 0;
 };
 
 // Data that is not directly associated with specific opcode/index inside
 // bytecode, but referred from bytecode is stored in this class.
 class PerScriptData {
  public:
-  explicit PerScriptData(JSContext* cx);
+  explicit PerScriptData(JSContext* cx, frontend::ParseInfo& parseInfo);
 
   MOZ_MUST_USE bool init(JSContext* cx);
 
   GCThingList& gcThingList() { return gcThingList_; }
   const GCThingList& gcThingList() const { return gcThingList_; }
 
   PooledMapPtr<AtomIndexMap>& atomIndices() { return atomIndices_; }
   const PooledMapPtr<AtomIndexMap>& atomIndices() const { return atomIndices_; }
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -331,34 +331,61 @@ NameLocation EmitterScope::searchAndCach
     bce->cx->recoverFromOutOfMemory();
   }
 
   return *loc;
 }
 
 template <typename ScopeCreator>
 bool EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) {
-  RootedScope enclosing(bce->cx, enclosingScope(bce).maybeScope());
+  RootedScope enclosing(bce->cx);
+  if (!enclosingScope(bce).getOrCreateScope(bce->cx, &enclosing)) {
+    return false;
+  }
+
   Scope* scope = createScope(bce->cx, enclosing);
   if (!scope) {
     return false;
   }
   hasEnvironment_ = scope->hasEnvironment();
+
   return bce->perScriptData().gcThingList().append(scope, &scopeIndex_);
 }
 
 template <typename ScopeCreator>
+bool EmitterScope::internScopeCreationData(BytecodeEmitter* bce,
+                                           ScopeCreator createScope) {
+  Rooted<AbstractScope> enclosing(bce->cx, enclosingScope(bce));
+  ScopeIndex index;
+  if (!createScope(bce->cx, enclosing, &index)) {
+    return false;
+  }
+  auto scope = bce->parseInfo.scopeCreationData[index.index];
+  hasEnvironment_ = scope.get().hasEnvironment();
+  return bce->perScriptData().gcThingList().append(index, &scopeIndex_);
+}
+
+template <typename ScopeCreator>
 bool EmitterScope::internBodyScope(BytecodeEmitter* bce,
                                    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);
 }
 
+template <typename ScopeCreator>
+bool EmitterScope::internBodyScopeCreationData(BytecodeEmitter* bce,
+                                               ScopeCreator createScope) {
+  MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX,
+             "There can be only one body scope");
+  bce->bodyScopeIndex = bce->perScriptData().gcThingList().length();
+  return internScopeCreationData(bce, createScope);
+}
+
 bool EmitterScope::appendScopeNote(BytecodeEmitter* bce) {
   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);
@@ -485,23 +512,37 @@ bool EmitterScope::enterLexical(Bytecode
 
     if (!tdzCache->noteTDZCheck(bce, bi.name(), CheckTDZ)) {
       return false;
     }
   }
 
   updateFrameFixedSlots(bce, bi);
 
-  // Create and intern the VM scope.
-  auto createScope = [kind, bindings, firstFrameSlot](JSContext* cx,
-                                                      HandleScope enclosing) {
-    return LexicalScope::create(cx, kind, bindings, firstFrameSlot, enclosing);
-  };
-  if (!internScope(bce, createScope)) {
-    return false;
+  if (bce->parseInfo.isDeferred()) {
+    auto createScope = [kind, bindings, firstFrameSlot, bce](
+                           JSContext* cx, Handle<AbstractScope> enclosing,
+                           ScopeIndex* index) {
+      return ScopeCreationData::create(cx, bce->parseInfo, kind, bindings,
+                                       firstFrameSlot, enclosing, index);
+    };
+    if (!internScopeCreationData(bce, createScope)) {
+      return false;
+    }
+
+  } else {
+    // Create and intern the VM scope.
+    auto createScope = [kind, bindings, firstFrameSlot](JSContext* 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;
     }
   }
@@ -545,24 +586,38 @@ bool EmitterScope::enterNamedLambda(Byte
   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](JSContext* 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;
+  ScopeKind scopeKind =
+      funbox->strict() ? ScopeKind::StrictNamedLambda : ScopeKind::NamedLambda;
+  if (bce->parseInfo.isDeferred()) {
+    auto createScope = [funbox, scopeKind, bce](JSContext* cx,
+                                                Handle<AbstractScope> enclosing,
+                                                ScopeIndex* index) {
+      return ScopeCreationData::create(cx, bce->parseInfo, scopeKind,
+                                       funbox->namedLambdaBindings(),
+                                       LOCALNO_LIMIT, enclosing, index);
+    };
+    if (!internScopeCreationData(bce, createScope)) {
+      return false;
+    }
+  } else {
+    auto createScope = [funbox, scopeKind](JSContext* cx,
+                                           HandleScope enclosing) {
+      return LexicalScope::create(cx, scopeKind, funbox->namedLambdaBindings(),
+                                  LOCALNO_LIMIT, enclosing);
+    };
+    if (!internScope(bce, createScope)) {
+      return false;
+    }
   }
 
   return checkEnvironmentChainLength(bce);
 }
 
 bool EmitterScope::enterFunction(BytecodeEmitter* bce, FunctionBox* funbox) {
   MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
 
@@ -636,25 +691,40 @@ bool EmitterScope::enterFunction(Bytecod
       }
     }
 
     if (!deadZoneFrameSlotRange(bce, 0, paramFrameSlotEnd)) {
       return false;
     }
   }
 
-  // Create and intern the VM scope.
-  auto createScope = [funbox](JSContext* 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;
+  if (bce->parseInfo.isDeferred()) {
+    auto createScope = [funbox, bce](JSContext* cx,
+                                     Handle<AbstractScope> enclosing,
+                                     ScopeIndex* index) {
+      return ScopeCreationData::create(
+          cx, bce->parseInfo, funbox->functionScopeBindings(),
+          funbox->hasParameterExprs,
+          funbox->needsCallObjectRegardlessOfBindings(), funbox, enclosing,
+          index);
+    };
+    if (!internBodyScopeCreationData(bce, createScope)) {
+      return false;
+    }
+  } else {
+    // Create and intern the VM scope.
+    auto createScope = [funbox](JSContext* 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 EmitterScope::enterFunctionExtraBodyVar(BytecodeEmitter* bce,
                                              FunctionBox* funbox) {
   MOZ_ASSERT(funbox->hasParameterExprs);
@@ -693,26 +763,43 @@ bool EmitterScope::enterFunctionExtraBod
   // 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](JSContext* cx,
-                                              HandleScope enclosing) {
-    return VarScope::create(
-        cx, ScopeKind::FunctionBodyVar, funbox->extraVarScopeBindings(),
-        firstFrameSlot,
-        funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), enclosing);
-  };
-  if (!internScope(bce, createScope)) {
-    return false;
+  if (bce->parseInfo.isDeferred()) {
+    // Create and intern the VM scope.
+    auto createScope = [funbox, firstFrameSlot, bce](
+                           JSContext* cx, Handle<AbstractScope> enclosing,
+                           ScopeIndex* index) {
+      return ScopeCreationData::create(
+          cx, bce->parseInfo, ScopeKind::FunctionBodyVar,
+          funbox->extraVarScopeBindings(), firstFrameSlot,
+          funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(), enclosing,
+          index);
+    };
+    if (!internScopeCreationData(bce, createScope)) {
+      return false;
+    }
+  } else {
+    // Create and intern the VM scope.
+    auto createScope = [funbox, firstFrameSlot](JSContext* 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;
     }
   }
 
@@ -732,23 +819,37 @@ bool EmitterScope::enterParameterExpress
   }
 
   // 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](JSContext* cx, HandleScope enclosing) {
-    return VarScope::create(cx, ScopeKind::ParameterExpressionVar,
-                            /* data = */ nullptr, firstFrameSlot,
-                            /* needsEnvironment = */ true, enclosing);
-  };
-  if (!internScope(bce, createScope)) {
-    return false;
+  if (bce->parseInfo.isDeferred()) {
+    auto createScope = [firstFrameSlot, bce](JSContext* cx,
+                                             Handle<AbstractScope> enclosing,
+                                             ScopeIndex* index) {
+      return ScopeCreationData::create(
+          cx, bce->parseInfo, ScopeKind::ParameterExpressionVar,
+          /* dataArg = */ nullptr, firstFrameSlot,
+          /* needsEnvironment = */ true, enclosing, index);
+    };
+    if (!internScopeCreationData(bce, createScope)) {
+      return false;
+    }
+  } else {
+    auto createScope = [firstFrameSlot](JSContext* 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.
@@ -835,18 +936,29 @@ bool EmitterScope::enterGlobal(BytecodeE
   // 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());
   }
 
+  if (bce->parseInfo.isDeferred()) {
+    auto createScope = [globalsc, bce](JSContext* cx,
+                                       Handle<AbstractScope> enclosing,
+                                       ScopeIndex* index) {
+      MOZ_ASSERT(!enclosing.get());
+      return ScopeCreationData::create(
+          cx, bce->parseInfo, globalsc->scopeKind(), globalsc->bindings, index);
+    };
+    return internBodyScopeCreationData(bce, createScope);
+  }
+
   auto createScope = [globalsc](JSContext* cx, HandleScope enclosing) {
-    MOZ_ASSERT(!enclosing);
+    MOZ_ASSERT(!enclosing.get());
     return GlobalScope::create(cx, globalsc->scopeKind(), globalsc->bindings);
   };
   return internBodyScope(bce, createScope);
 }
 
 bool EmitterScope::enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc) {
   MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
 
@@ -856,25 +968,38 @@ bool EmitterScope::enterEval(BytecodeEmi
     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](JSContext* 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 (bce->parseInfo.isDeferred()) {
+    auto createScope = [evalsc, bce](JSContext* cx,
+                                     Handle<AbstractScope> enclosing,
+                                     ScopeIndex* index) {
+      ScopeKind scopeKind =
+          evalsc->strict() ? ScopeKind::StrictEval : ScopeKind::Eval;
+      return ScopeCreationData::create(cx, bce->parseInfo, scopeKind,
+                                       evalsc->bindings, enclosing, index);
+    };
+    if (!internBodyScopeCreationData(bce, createScope)) {
+      return false;
+    }
+  } else {
+    auto createScope = [evalsc](JSContext* 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
@@ -955,16 +1080,31 @@ bool EmitterScope::enterModule(BytecodeE
   // Put lexical frame slots in TDZ. Environment slots are poisoned during
   // environment creation.
   if (firstLexicalFrameSlot) {
     if (!deadZoneFrameSlotRange(bce, *firstLexicalFrameSlot, frameSlotEnd())) {
       return false;
     }
   }
 
+  if (bce->parseInfo.isDeferred()) {
+    // Create and intern the VM scope creation data.
+    auto createScope = [modulesc, bce](JSContext* cx,
+                                       Handle<AbstractScope> enclosing,
+                                       ScopeIndex* index) {
+      return ScopeCreationData::create(cx, bce->parseInfo, modulesc->bindings,
+                                       modulesc->module(), enclosing, index);
+    };
+    if (!internBodyScopeCreationData(bce, createScope)) {
+      return false;
+    }
+
+    return checkEnvironmentChainLength(bce);
+  }
+
   // Create and intern the VM scope.
   auto createScope = [modulesc](JSContext* cx, HandleScope enclosing) {
     return ModuleScope::create(cx, modulesc->bindings, modulesc->module(),
                                enclosing);
   };
   if (!internBodyScope(bce, createScope)) {
     return false;
   }
@@ -977,21 +1117,31 @@ bool EmitterScope::enterWith(BytecodeEmi
 
   if (!ensureCache(bce)) {
     return false;
   }
 
   // 'with' make all accesses dynamic and unanalyzable.
   fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
 
-  auto createScope = [](JSContext* cx, HandleScope enclosing) {
-    return WithScope::create(cx, enclosing);
-  };
-  if (!internScope(bce, createScope)) {
-    return false;
+  if (bce->parseInfo.isDeferred()) {
+    auto createScope = [bce](JSContext* cx, Handle<AbstractScope> enclosing,
+                             ScopeIndex* index) {
+      return ScopeCreationData::create(cx, bce->parseInfo, enclosing, index);
+    };
+    if (!internScopeCreationData(bce, createScope)) {
+      return false;
+    }
+  } else {
+    auto createScope = [](JSContext* 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;
--- a/js/src/frontend/EmitterScope.h
+++ b/js/src/frontend/EmitterScope.h
@@ -83,18 +83,26 @@ class EmitterScope : public Nestable<Emi
 
   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 internScopeCreationData(BytecodeEmitter* bce,
+                                            ScopeCreator createScope);
+
+  template <typename ScopeCreator>
   MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce,
                                     ScopeCreator createScope);
+
+  template <typename ScopeCreator>
+  MOZ_MUST_USE bool internBodyScopeCreationData(BytecodeEmitter* bce,
+                                                ScopeCreator createScope);
   MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
 
   MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce,
                                            uint32_t slotStart,
                                            uint32_t slotEnd) const;
 
  public:
   explicit EmitterScope(BytecodeEmitter* bce);
--- a/js/src/frontend/ParseInfo.h
+++ b/js/src/frontend/ParseInfo.h
@@ -17,47 +17,57 @@
 #include "js/RealmOptions.h"
 #include "js/Vector.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 
 namespace js {
 namespace frontend {
 
-class ParserBase;
-
 // ParseInfo owns a number of pieces of information about a parse,
 // as well as controls the lifetime of parse nodes and other data
 // by controling the mark and reset of the LifoAlloc.
 struct MOZ_RAII ParseInfo {
   // ParseInfo's mode can be eager or deferred:
   //
-  // - In Eager mode, allocation happens right away and the Function Tree is not
-  //   constructed.
+  // - In Eager mode, allocation happens right away and the Function Tree is
+  //   not constructed.
   // - In Deferred mode, allocation is deferred as late as possible.
   enum Mode { Eager, Deferred };
 
   UsedNameTracker usedNames;
   LifoAllocScope& allocScope;
   FunctionTreeHolder treeHolder;
   Mode mode;
-  // Hold onto the RegExpCreationData and BigIntCreationDatas that are allocated
-  // during parse to ensure correct destruction.
+  // Hold onto the RegExpCreationData and BigIntCreationDatas that are
+  // allocated during parse to ensure correct destruction.
   Vector<RegExpCreationData> regExpData;
   Vector<BigIntCreationData> bigIntData;
 
+  // A rooted list of scopes created during this parse.
+  //
+  // To ensure that ScopeCreationData's destructors fire, and thus our HeapPtr
+  // barriers, we store the scopeCreationData at this level so that they
+  // can be safely destroyed, rather than LifoAllocing them with the rest of
+  // the parser data structures.
+  //
+  // References to scopes are controlled via AbstractScope, which holds onto
+  // an index (and ParseInfo reference).
+  JS::RootedVector<ScopeCreationData> scopeCreationData;
+
   ParseInfo(JSContext* cx, LifoAllocScope& alloc)
       : usedNames(cx),
         allocScope(alloc),
         treeHolder(cx),
         mode(cx->realm()->behaviors().deferredParserAlloc()
                  ? ParseInfo::Mode::Deferred
                  : ParseInfo::Mode::Eager),
         regExpData(cx),
-        bigIntData(cx) {}
+        bigIntData(cx),
+        scopeCreationData(cx) {}
 
   // To avoid any misuses, make sure this is neither copyable,
   // movable or assignable.
   ParseInfo(const ParseInfo&) = delete;
   ParseInfo(ParseInfo&&) = delete;
   ParseInfo& operator=(const ParseInfo&) = delete;
   ParseInfo& operator=(ParseInfo&&) = delete;
 
--- a/js/src/frontend/SharedContext.cpp
+++ b/js/src/frontend/SharedContext.cpp
@@ -314,17 +314,17 @@ void FunctionBox::setEnclosingScopeForIn
   MOZ_ASSERT(!enclosingScope_);
   enclosingScope_ = enclosingScope;
 }
 
 void FunctionBox::finish() {
   if (isInterpretedLazy()) {
     // Lazy inner functions need to record their enclosing scope for when they
     // eventually are compiled.
-    function()->setEnclosingScope(enclosingScope_.maybeScope());
+    function()->setEnclosingScope(enclosingScope_.getExistingScope());
   } else {
     // Non-lazy inner functions don't use the enclosingScope_ field.
     MOZ_ASSERT(!enclosingScope_);
   }
 }
 
 ModuleSharedContext::ModuleSharedContext(JSContext* cx, ModuleObject* module,
                                          Scope* enclosingScope,
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -514,17 +514,17 @@ class FunctionBox : public ObjectBox, pu
     setIsInterpretedLazy(function->isInterpretedLazy());
   }
 
   Scope* compilationEnclosingScope() const override {
     // This is used when emitting code for the current FunctionBox and therefore
     // the enclosingScope_ must have be set correctly during initalization.
 
     MOZ_ASSERT(enclosingScope_);
-    return enclosingScope_.maybeScope();
+    return enclosingScope_.scope();
   }
 
   bool needsCallObjectRegardlessOfBindings() const {
     return hasExtensibleScope() || needsHomeObject() ||
            isDerivedClassConstructor() || isGenerator() || isAsync();
   }
 
   bool hasExtraBodyVarScope() const {
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/Stencil.cpp
@@ -0,0 +1,163 @@
+/* -*- 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/Stencil.h"
+
+#include "frontend/SharedContext.h"
+#include "js/TracingAPI.h"
+#include "vm/EnvironmentObject.h"
+#include "vm/Scope.h"
+
+using namespace js;
+using namespace js::frontend;
+
+Scope* ScopeCreationData::createScope(JSContext* cx) {
+  // If we've already created a scope, best just return it.
+  if (scope_) {
+    return scope_;
+  }
+
+  Scope* scope = nullptr;
+  switch (kind()) {
+    case ScopeKind::Function: {
+      scope = createSpecificScope<FunctionScope>(cx);
+      break;
+    }
+    case ScopeKind::Lexical:
+    case ScopeKind::SimpleCatch:
+    case ScopeKind::Catch:
+    case ScopeKind::NamedLambda:
+    case ScopeKind::StrictNamedLambda:
+    case ScopeKind::FunctionLexical: {
+      scope = createSpecificScope<LexicalScope>(cx);
+      break;
+    }
+    case ScopeKind::FunctionBodyVar:
+    case ScopeKind::ParameterExpressionVar: {
+      scope = createSpecificScope<VarScope>(cx);
+      break;
+    }
+    case ScopeKind::Global:
+    case ScopeKind::NonSyntactic: {
+      scope = createSpecificScope<GlobalScope>(cx);
+      break;
+    }
+    case ScopeKind::Eval:
+    case ScopeKind::StrictEval: {
+      scope = createSpecificScope<EvalScope>(cx);
+      break;
+    }
+    case ScopeKind::Module: {
+      scope = createSpecificScope<ModuleScope>(cx);
+      break;
+    }
+    case ScopeKind::With: {
+      scope = createSpecificScope<WithScope>(cx);
+      break;
+    }
+    default: {
+      MOZ_CRASH("Unexpected deferred type");
+    }
+  }
+  return scope;
+}
+
+void ScopeCreationData::trace(JSTracer* trc) {
+  if (enclosing_) {
+    enclosing_.trace(trc);
+  }
+  if (environmentShape_) {
+    TraceEdge(trc, &environmentShape_, "ScopeCreationData Environment Shape");
+  }
+  if (scope_) {
+    TraceEdge(trc, &scope_, "ScopeCreationData Scope");
+  }
+  if (funbox_) {
+    funbox_->trace(trc);
+  }
+
+  // Trace Datas
+  if (data_) {
+    switch (kind()) {
+      case ScopeKind::Function: {
+        data<FunctionScope>().trace(trc);
+        break;
+      }
+      case ScopeKind::Lexical:
+      case ScopeKind::SimpleCatch:
+      case ScopeKind::Catch:
+      case ScopeKind::NamedLambda:
+      case ScopeKind::StrictNamedLambda:
+      case ScopeKind::FunctionLexical: {
+        data<LexicalScope>().trace(trc);
+        break;
+      }
+      case ScopeKind::FunctionBodyVar:
+      case ScopeKind::ParameterExpressionVar: {
+        data<VarScope>().trace(trc);
+        break;
+      }
+      case ScopeKind::Global:
+      case ScopeKind::NonSyntactic: {
+        data<GlobalScope>().trace(trc);
+        break;
+      }
+      case ScopeKind::Eval:
+      case ScopeKind::StrictEval: {
+        data<EvalScope>().trace(trc);
+        break;
+      }
+      case ScopeKind::Module: {
+        data<ModuleScope>().trace(trc);
+        break;
+      }
+      case ScopeKind::With:
+      default:
+        MOZ_CRASH("Unexpected data type");
+    }
+  }
+}
+
+uint32_t ScopeCreationData::nextFrameSlot() const {
+  switch (kind()) {
+    case ScopeKind::Function:
+      return nextFrameSlot<FunctionScope>();
+    case ScopeKind::FunctionBodyVar:
+    case ScopeKind::ParameterExpressionVar:
+      return nextFrameSlot<VarScope>();
+    case ScopeKind::Lexical:
+    case ScopeKind::SimpleCatch:
+    case ScopeKind::Catch:
+    case ScopeKind::FunctionLexical:
+      return nextFrameSlot<LexicalScope>();
+    case ScopeKind::NamedLambda:
+    case ScopeKind::StrictNamedLambda:
+      // Named lambda scopes cannot have frame slots.
+      return 0;
+    case ScopeKind::Eval:
+    case ScopeKind::StrictEval:
+      return nextFrameSlot<EvalScope>();
+    case ScopeKind::Global:
+    case ScopeKind::NonSyntactic:
+      return 0;
+    case ScopeKind::Module:
+      return nextFrameSlot<ModuleScope>();
+    case ScopeKind::WasmInstance:
+    case ScopeKind::WasmFunction:
+    case ScopeKind::With:
+      MOZ_CRASH(
+          "With, WasmInstance and WasmFunction Scopes don't get "
+          "nextFrameSlot()");
+      return 0;
+  }
+  MOZ_CRASH("Not an enclosing intra-frame scope");
+}
+
+bool ScopeCreationData::isArrow() const { return funbox_->isArrow(); }
+
+JSFunction* ScopeCreationData::canonicalFunction() const {
+  return funbox_->function();
+}
--- a/js/src/frontend/Stencil.h
+++ b/js/src/frontend/Stencil.h
@@ -2,38 +2,29 @@
  * 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_Stencil_h
 #define frontend_Stencil_h
 
+#include "frontend/AbstractScope.h"
+#include "frontend/TypedIndex.h"
 #include "gc/AllocKind.h"
 #include "gc/Rooting.h"
 #include "js/RegExpFlags.h"
 #include "vm/BigIntType.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
+#include "vm/Scope.h"
 
 namespace js {
 namespace frontend {
 
-// TypedIndex allows discrimination in variants between different
-// index types. Used as a typesafe index for various stencil arrays.
-template <typename Tag>
-struct TypedIndex {
-  explicit TypedIndex(uint32_t index) : index(index){};
-
-  uint32_t index = 0;
-
-  // For Vector::operator[]
-  operator size_t() const { return index; }
-};
-
 // [SMDOC] Script Stencil (Frontend Representation)
 //
 // Stencils are GC object free representations of artifacts created during
 // parsing and bytecode emission that are being used as part of Project
 // Stencil (https://bugzilla.mozilla.org/show_bug.cgi?id=stencil) to revamp
 // the frontend.
 //
 // Renaming to use the term stencil more broadly is still in progress.
@@ -185,11 +176,157 @@ class BigIntCreationData {
     mozilla::Range<const char16_t> source(buf_.get(), length_);
     return js::BigIntLiteralIsZero(source);
   }
 };
 
 using BigIntIndex = TypedIndex<BigIntCreationData>;
 
 } /* namespace frontend */
+
+class ScopeCreationData {
+  friend class AbstractScope;
+  friend class GCMarker;
+
+  // The enclosing scope if it exists
+  AbstractScope enclosing_;
+
+  // The kind determines data_.
+  ScopeKind kind_;
+
+  // If there are any aliased bindings, the shape for the
+  // EnvironmentObject. Otherwise nullptr.
+  HeapPtr<Shape*> environmentShape_ = {};
+
+  // Once we've produced a scope from a scope creation data, there may still be
+  // AbstractScopes refering to this ScopeCreationData, and if reification is
+  // requested multiple times, we should return the same scope rather than
+  // creating multiple sopes.
+  //
+  // As well, any queries that require data() to answer must be redirected to
+  // the scope once the scope has been reified, as the ScopeCreationData loses
+  // ownership of the data on reification.
+  HeapPtr<Scope*> scope_ = {};
+
+  // For FunctionScopes we need the funbox; nullptr otherwise.
+  frontend::FunctionBox* funbox_ = nullptr;
+
+  UniquePtr<BaseScopeData> data_;
+
+ public:
+  ScopeCreationData(JSContext* cx, ScopeKind kind,
+                    Handle<AbstractScope> enclosing,
+                    UniquePtr<BaseScopeData> data = {},
+                    Shape* environmentShape = nullptr,
+                    frontend::FunctionBox* funbox = nullptr)
+      : enclosing_(enclosing),
+        kind_(kind),
+        environmentShape_(environmentShape),
+        funbox_(funbox),
+        data_(std::move(data)) {}
+
+  ScopeKind kind() const { return kind_; }
+  AbstractScope enclosing() { return enclosing_; }
+  bool getOrCreateEnclosingScope(JSContext* cx, MutableHandleScope scope) {
+    return enclosing_.getOrCreateScope(cx, scope);
+  }
+
+  // FunctionScope
+  static bool create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                     Handle<FunctionScope::Data*> dataArg,
+                     bool hasParameterExprs, bool needsEnvironment,
+                     frontend::FunctionBox* funbox,
+                     Handle<AbstractScope> enclosing, ScopeIndex* index);
+
+  // LexicalScope
+  static bool create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                     ScopeKind kind, Handle<LexicalScope::Data*> dataArg,
+                     uint32_t firstFrameSlot, Handle<AbstractScope> enclosing,
+                     ScopeIndex* index);
+  // VarScope
+  static bool create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                     ScopeKind kind, Handle<VarScope::Data*> dataArg,
+                     uint32_t firstFrameSlot, bool needsEnvironment,
+                     Handle<AbstractScope> enclosing, ScopeIndex* index);
+
+  // GlobalScope
+  static bool create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                     ScopeKind kind, Handle<GlobalScope::Data*> dataArg,
+                     ScopeIndex* index);
+
+  // EvalScope
+  static bool create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                     ScopeKind kind, Handle<EvalScope::Data*> dataArg,
+                     Handle<AbstractScope> enclosing, ScopeIndex* index);
+
+  // ModuleScope
+  static bool create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                     Handle<ModuleScope::Data*> dataArg,
+                     HandleModuleObject module, Handle<AbstractScope> enclosing,
+                     ScopeIndex* index);
+
+  // WithScope
+  static bool create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                     Handle<AbstractScope> enclosing, ScopeIndex* index);
+
+  bool hasEnvironment() const {
+    return Scope::hasEnvironment(kind(), environmentShape_);
+  }
+
+  // Valid for functions;
+  bool isArrow() const;
+  JSFunction* canonicalFunction() const;
+
+  bool hasScope() const { return scope_ != nullptr; }
+
+  Scope* getScope() const {
+    MOZ_ASSERT(hasScope());
+    return scope_;
+  }
+
+  Scope* createScope(JSContext* cx);
+
+  void trace(JSTracer* trc);
+
+  uint32_t nextFrameSlot() const;
+
+ private:
+  // Non owning reference to data
+  template <typename SpecificScopeType>
+  typename SpecificScopeType::Data& data() const {
+    MOZ_ASSERT(data_.get());
+    return *static_cast<typename SpecificScopeType::Data*>(data_.get());
+  }
+
+  // Transfer ownership into a new UniquePtr.
+  template <typename SpecificScopeType>
+  UniquePtr<typename SpecificScopeType::Data> releaseData() {
+    return UniquePtr<typename SpecificScopeType::Data>(
+        static_cast<typename SpecificScopeType::Data*>(data_.release()));
+  }
+
+  template <typename SpecificScopeType>
+  Scope* createSpecificScope(JSContext* cx);
+
+  template <typename SpecificScopeType>
+  uint32_t nextFrameSlot() const {
+    // If a scope has been allocated for the ScopeCreationData we no longer own
+    // data, so defer to scope
+    if (hasScope()) {
+      return getScope()->template as<SpecificScopeType>().nextFrameSlot();
+    }
+    return data<SpecificScopeType>().nextFrameSlot;
+  }
+};
+
 } /* namespace js */
 
+namespace JS {
+template <>
+struct GCPolicy<js::ScopeCreationData*> {
+  static void trace(JSTracer* trc, js::ScopeCreationData** data,
+                    const char* name) {
+    (*data)->trace(trc);
+  }
+};
+
+}  // namespace JS
 #endif /* frontend_Stencil_h */
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/TypedIndex.h
@@ -0,0 +1,36 @@
+/* -*- 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_TypedIndex_h
+#define frontend_TypedIndex_h
+
+#include <cstdint>
+
+namespace js {
+namespace frontend {
+
+// TypedIndex allows discrimination in variants between different
+// index types. Used as a typesafe index for various stencil arrays.
+template <typename Tag>
+struct TypedIndex {
+  TypedIndex() = default;
+  explicit TypedIndex(uint32_t index) : index(index){};
+
+  uint32_t index = 0;
+
+  // For Vector::operator[]
+  operator size_t() const { return index; }
+
+  TypedIndex& operator=(size_t idx) {
+    index = idx;
+    return *this;
+  }
+};
+
+}  // namespace frontend
+}  // namespace js
+
+#endif
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -47,16 +47,17 @@ UNIFIED_SOURCES += [
     'NameOpEmitter.cpp',
     'ObjectEmitter.cpp',
     'ObjLiteral.cpp',
     'ParseContext.cpp',
     'ParseNode.cpp',
     'ParseNodeVerify.cpp',
     'PropOpEmitter.cpp',
     'SharedContext.cpp',
+    'Stencil.cpp',
     'SwitchEmitter.cpp',
     'TDZCheckCache.cpp',
     'TokenStream.cpp',
     'TryEmitter.cpp',
     'WhileEmitter.cpp',
 ]
 
 # Parser.cpp cannot be built in unified mode because of explicit
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -7,17 +7,19 @@
 #include "vm/Scope.h"
 
 #include "mozilla/ScopeExit.h"
 
 #include <memory>
 #include <new>
 
 #include "builtin/ModuleObject.h"
-#include "frontend/AbstractScope.h"
+#include "frontend/ParseInfo.h"
+#include "frontend/SharedContext.h"
+#include "frontend/Stencil.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"
@@ -506,60 +508,35 @@ uint32_t LexicalScope::firstFrameSlot() 
   }
   return 0;
 }
 
 /* static */
 uint32_t LexicalScope::nextFrameSlot(const AbstractScope& scope) {
   for (AbstractScopeIter si(scope); si; si++) {
     switch (si.kind()) {
+      case ScopeKind::With:
+        continue;
       case ScopeKind::Function:
-        MOZ_ASSERT(si.abstractScope().maybeScope());
-        return si.abstractScope()
-            .maybeScope()
-            ->as<FunctionScope>()
-            .nextFrameSlot();
       case ScopeKind::FunctionBodyVar:
       case ScopeKind::ParameterExpressionVar:
-        MOZ_ASSERT(si.abstractScope().maybeScope());
-        return si.abstractScope().maybeScope()->as<VarScope>().nextFrameSlot();
       case ScopeKind::Lexical:
       case ScopeKind::SimpleCatch:
       case ScopeKind::Catch:
       case ScopeKind::FunctionLexical:
-        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:
-        MOZ_ASSERT(si.abstractScope().maybeScope());
-        return si.abstractScope().maybeScope()->as<EvalScope>().nextFrameSlot();
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
-        return 0;
       case ScopeKind::Module:
-        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;
+        return si.abstractScope().nextFrameSlot();
     }
   }
   MOZ_CRASH("Not an enclosing intra-frame Scope");
 }
 
 /* static */
 LexicalScope* LexicalScope::create(JSContext* cx, ScopeKind kind,
                                    Handle<Data*> data, uint32_t firstFrameSlot,
@@ -574,24 +551,24 @@ LexicalScope* LexicalScope::create(JSCon
     return nullptr;
   }
 
   return createWithData(cx, kind, &copy, firstFrameSlot, enclosing);
 }
 
 bool LexicalScope::prepareForScopeCreation(JSContext* cx, ScopeKind kind,
                                            uint32_t firstFrameSlot,
-                                           HandleScope enclosing,
+                                           Handle<AbstractScope> enclosing,
                                            MutableHandle<UniquePtr<Data>> data,
                                            MutableHandleShape envShape) {
   bool isNamedLambda =
       kind == ScopeKind::NamedLambda || kind == ScopeKind::StrictNamedLambda;
 
   MOZ_ASSERT_IF(!isNamedLambda && firstFrameSlot != 0,
-                firstFrameSlot == nextFrameSlot(AbstractScope(enclosing)));
+                firstFrameSlot == nextFrameSlot(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;
   }
@@ -599,17 +576,19 @@ bool LexicalScope::prepareForScopeCreati
 }
 
 /* static */
 LexicalScope* LexicalScope::createWithData(JSContext* cx, ScopeKind kind,
                                            MutableHandle<UniquePtr<Data>> data,
                                            uint32_t firstFrameSlot,
                                            HandleScope enclosing) {
   RootedShape envShape(cx);
-  if (!prepareForScopeCreation(cx, kind, firstFrameSlot, enclosing, data,
+  Rooted<AbstractScope> abstractScope(cx, enclosing);
+
+  if (!prepareForScopeCreation(cx, kind, firstFrameSlot, abstractScope, data,
                                &envShape)) {
     return nullptr;
   }
 
   auto scope = Scope::create<LexicalScope>(cx, kind, enclosing, envShape, data);
   if (!scope) {
     return nullptr;
   }
@@ -1047,17 +1026,16 @@ GlobalScope* GlobalScope::createWithData
 GlobalScope* GlobalScope::clone(JSContext* cx, Handle<GlobalScope*> scope,
                                 ScopeKind kind) {
   Rooted<Data*> dataOriginal(cx, &scope->as<GlobalScope>().data());
   Rooted<UniquePtr<Data>> dataClone(
       cx, CopyScopeData<GlobalScope>(cx, dataOriginal));
   if (!dataClone) {
     return nullptr;
   }
-
   return Scope::create<GlobalScope>(cx, kind, nullptr, nullptr, &dataClone);
 }
 
 template <XDRMode mode>
 /* static */
 XDRResult GlobalScope::XDR(XDRState<mode>* xdr, ScopeKind kind,
                            MutableHandleScope scope) {
   MOZ_ASSERT((mode == XDR_DECODE) == !scope);
@@ -1110,17 +1088,16 @@ WithScope* WithScope::create(JSContext* 
   return static_cast<WithScope*>(scope);
 }
 
 template <XDRMode mode>
 /* static */
 XDRResult WithScope::XDR(XDRState<mode>* xdr, HandleScope enclosing,
                          MutableHandleScope scope) {
   JSContext* cx = xdr->cx();
-
   if (mode == XDR_DECODE) {
     scope.set(create(cx, enclosing));
     if (!scope) {
       return xdr->fail(JS::TranscodeResult_Throw);
     }
   }
 
   return Ok();
@@ -1231,17 +1208,16 @@ XDRResult EvalScope::XDR(XDRState<mode>*
     }
 
     MOZ_TRY(XDRSizedBindingNames<EvalScope>(xdr, scope.as<EvalScope>(), &data));
 
     if (mode == XDR_DECODE) {
       if (!data->length) {
         MOZ_ASSERT(!data->nextFrameSlot);
       }
-
       scope.set(createWithData(cx, kind, &uniqueData.ref(), enclosing));
       if (!scope) {
         return xdr->fail(JS::TranscodeResult_Throw);
       }
     }
   }
 
   return Ok();
@@ -1463,20 +1439,19 @@ WasmInstanceScope* WasmInstanceScope::cr
   }
 
   MOZ_ASSERT(data->length == namesCount);
 
   data->instance.init(instance);
   data->memoriesStart = 0;
   data->globalsStart = globalsStart;
 
-  Rooted<Scope*> enclosingScope(cx, &cx->global()->emptyGlobalScope());
-
+  RootedScope enclosing(cx, &cx->global()->emptyGlobalScope());
   return Scope::create<WasmInstanceScope>(cx, ScopeKind::WasmInstance,
-                                          enclosingScope,
+                                          enclosing,
                                           /* envShape = */ nullptr, &data);
 }
 
 /* static */
 Shape* WasmInstanceScope::getEmptyEnvironmentShape(JSContext* cx) {
   const JSClass* cls = &WasmInstanceEnvironmentObject::class_;
   return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls),
                                WasmInstanceEnvShapeFlags);
@@ -1824,8 +1799,233 @@ JSAtom* js::FrameSlotName(JSScript* scri
   MOZ_CRASH("Frame slot not found");
 }
 
 JS::ubi::Node::Size JS::ubi::Concrete<Scope>::size(
     mozilla::MallocSizeOf mallocSizeOf) const {
   return js::gc::Arena::thingSize(get().asTenured().getAllocKind()) +
          get().sizeOfExcludingThis(mallocSizeOf);
 }
+
+/* static */
+bool ScopeCreationData::create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                               Handle<FunctionScope::Data*> dataArg,
+                               bool hasParameterExprs, bool needsEnvironment,
+                               frontend::FunctionBox* funbox,
+                               Handle<AbstractScope> enclosing,
+                               ScopeIndex* index) {
+  // The data that's passed in is from the frontend and is LifoAlloc'd.
+  // Copy it now that we're creating a permanent VM scope.
+  Rooted<UniquePtr<FunctionScope::Data>> data(
+      cx, dataArg ? CopyScopeData<FunctionScope>(cx, dataArg)
+                  : NewEmptyScopeData<FunctionScope>(cx));
+  if (!data) {
+    return false;
+  }
+
+  RootedShape envShape(cx);
+  RootedFunction fun(cx, funbox->function());
+  if (!FunctionScope::prepareForScopeCreation(
+          cx, &data, hasParameterExprs,
+          dataArg ? dataArg->isFieldInitializer : IsFieldInitializer::No,
+          needsEnvironment, fun, &envShape)) {
+    return false;
+  }
+
+  *index = parseInfo.scopeCreationData.length();
+  return parseInfo.scopeCreationData.emplaceBack(
+      cx, ScopeKind::Function, enclosing, std::move(data.get()), envShape,
+      funbox);
+}
+
+/* static */
+bool ScopeCreationData::create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                               ScopeKind kind,
+                               Handle<LexicalScope::Data*> dataArg,
+                               uint32_t firstFrameSlot,
+                               Handle<AbstractScope> enclosing,
+                               ScopeIndex* index) {
+  // The data that's passed in is from the frontend and is LifoAlloc'd.
+  // Copy it now that we're creating a permanent VM scope.
+  Rooted<UniquePtr<LexicalScope::Data>> data(
+      cx, CopyScopeData<LexicalScope>(cx, dataArg));
+  if (!data) {
+    return false;
+  }
+
+  RootedShape envShape(cx);
+  if (!LexicalScope::prepareForScopeCreation(cx, kind, firstFrameSlot,
+                                             enclosing, &data, &envShape)) {
+    return false;
+  }
+
+  *index = parseInfo.scopeCreationData.length();
+  return parseInfo.scopeCreationData.emplaceBack(
+      cx, kind, enclosing, std::move(data.get()), envShape);
+}
+
+bool ScopeCreationData::create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                               ScopeKind kind, Handle<VarScope::Data*> dataArg,
+                               uint32_t firstFrameSlot, bool needsEnvironment,
+                               Handle<AbstractScope> enclosing,
+                               ScopeIndex* index) {
+  // The data that's passed in is from the frontend and is LifoAlloc'd.
+  // Copy it now that we're creating a permanent VM scope.
+  Rooted<UniquePtr<VarScope::Data>> data(
+      cx, dataArg ? CopyScopeData<VarScope>(cx, dataArg)
+                  : NewEmptyVarScopeData(cx, firstFrameSlot));
+  if (!data) {
+    return false;
+  }
+
+  RootedShape envShape(cx);
+  if (!VarScope::prepareForScopeCreation(cx, kind, &data, firstFrameSlot,
+                                         needsEnvironment, &envShape)) {
+    return false;
+  }
+
+  *index = parseInfo.scopeCreationData.length();
+  return parseInfo.scopeCreationData.emplaceBack(
+      cx, kind, enclosing, std::move(data.get()), envShape);
+}
+
+/* static */
+bool ScopeCreationData::create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                               ScopeKind kind,
+                               Handle<GlobalScope::Data*> dataArg,
+                               ScopeIndex* index) {
+  // The data that's passed in is from the frontend and is LifoAlloc'd.
+  // Copy it now that we're creating a permanent VM scope.
+  Rooted<UniquePtr<GlobalScope::Data>> data(
+      cx, dataArg ? CopyScopeData<GlobalScope>(cx, dataArg)
+                  : NewEmptyScopeData<GlobalScope>(cx));
+  if (!data) {
+    return false;
+  }
+
+  // The global scope has no environment shape. Its environment is the
+  // global lexical scope and the global object or non-syntactic objects
+  // created by embedding, all of which are not only extensible but may
+  // have names on them deleted.
+  Rooted<AbstractScope> enclosing(cx);
+
+  *index = parseInfo.scopeCreationData.length();
+  return parseInfo.scopeCreationData.emplaceBack(cx, kind, enclosing,
+                                                 std::move(data.get()));
+}
+
+/* static */
+bool ScopeCreationData::create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                               ScopeKind kind, Handle<EvalScope::Data*> dataArg,
+                               Handle<AbstractScope> enclosing,
+                               ScopeIndex* index) {
+  // The data that's passed in is from the frontend and is LifoAlloc'd.
+  // Copy it now that we're creating a permanent VM scope.
+  Rooted<UniquePtr<EvalScope::Data>> data(
+      cx, dataArg ? CopyScopeData<EvalScope>(cx, dataArg)
+                  : NewEmptyScopeData<EvalScope>(cx));
+  if (!data) {
+    return false;
+  }
+
+  RootedShape envShape(cx);
+  if (!EvalScope::prepareForScopeCreation(cx, kind, &data, &envShape)) {
+    return false;
+  }
+
+  *index = parseInfo.scopeCreationData.length();
+  return parseInfo.scopeCreationData.emplaceBack(
+      cx, kind, enclosing, std::move(data.get()), envShape);
+}
+
+/* static */
+bool ScopeCreationData::create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                               Handle<ModuleScope::Data*> dataArg,
+                               HandleModuleObject module,
+                               Handle<AbstractScope> enclosing,
+                               ScopeIndex* index) {
+  // The data that's passed in is from the frontend and is LifoAlloc'd.
+  // Copy it now that we're creating a permanent VM scope.
+  Rooted<UniquePtr<ModuleScope::Data>> data(
+      cx, dataArg ? CopyScopeData<ModuleScope>(cx, dataArg)
+                  : NewEmptyScopeData<ModuleScope>(cx));
+  if (!data) {
+    return false;
+  }
+
+  MOZ_ASSERT(enclosing.get().is<GlobalScope>());
+
+  // The data that's passed in is from the frontend and is LifoAlloc'd.
+  // Copy it now that we're creating a permanent VM scope.
+  RootedShape envShape(cx);
+  if (!ModuleScope::prepareForScopeCreation(cx, &data, module, &envShape)) {
+    return false;
+  }
+
+  *index = parseInfo.scopeCreationData.length();
+  return parseInfo.scopeCreationData.emplaceBack(
+      cx, ScopeKind::Module, enclosing, std::move(data.get()), envShape);
+}
+
+/* static */
+bool ScopeCreationData::create(JSContext* cx, frontend::ParseInfo& parseInfo,
+                               Handle<AbstractScope> enclosing,
+                               ScopeIndex* index) {
+  *index = parseInfo.scopeCreationData.length();
+  return parseInfo.scopeCreationData.emplaceBack(cx, ScopeKind::With,
+                                                 enclosing);
+}
+
+// WithScopes are unique because they don't go through the
+// Scope::create<ConcreteType> path.
+template <>
+Scope* ScopeCreationData::createSpecificScope<WithScope>(JSContext* cx) {
+  RootedScope enclosingScope(cx);
+  if (!getOrCreateEnclosingScope(cx, &enclosingScope)) {
+    return nullptr;
+  }
+
+  WithScope* scope = static_cast<WithScope*>(
+      Scope::create(cx, ScopeKind::With, enclosingScope, nullptr));
+
+  if (!scope) {
+    return nullptr;
+  }
+
+  scope_ = scope;
+
+  return scope;
+}
+
+template <class SpecificScopeType>
+Scope* ScopeCreationData::createSpecificScope(JSContext* cx) {
+  Rooted<UniquePtr<typename SpecificScopeType::Data>> rootedData(
+      cx, releaseData<SpecificScopeType>());
+  RootedShape shape(cx, environmentShape_);
+
+  RootedScope enclosingScope(cx);
+  if (!getOrCreateEnclosingScope(cx, &enclosingScope)) {
+    return nullptr;
+  }
+
+  // Because we already baked the data here, we needn't do it again.
+  SpecificScopeType* scope = Scope::create<SpecificScopeType>(
+      cx, kind(), enclosingScope, shape, &rootedData);
+  if (!scope) {
+    return nullptr;
+  }
+
+  scope_ = scope;
+
+  return scope;
+}
+
+template Scope* ScopeCreationData::createSpecificScope<FunctionScope>(
+    JSContext* cx);
+template Scope* ScopeCreationData::createSpecificScope<LexicalScope>(
+    JSContext* cx);
+template Scope* ScopeCreationData::createSpecificScope<EvalScope>(
+    JSContext* cx);
+template Scope* ScopeCreationData::createSpecificScope<GlobalScope>(
+    JSContext* cx);
+template Scope* ScopeCreationData::createSpecificScope<VarScope>(JSContext* cx);
+template Scope* ScopeCreationData::createSpecificScope<ModuleScope>(
+    JSContext* cx);
\ No newline at end of file
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -22,17 +22,16 @@
 #include "vm/JSObject.h"
 #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,
@@ -235,16 +234,17 @@ class WrappedPtrOperations<Scope*, Wrapp
   }
 };
 
 //
 // The base class of all Scopes.
 //
 class Scope : public js::gc::TenuredCell {
   friend class GCMarker;
+  friend class ScopeCreationData;
 
   // The enclosing scope or nullptr.
   const GCPtrScope enclosing_;
 
   // The kind determines data_.
   const ScopeKind kind_;
 
   // If there are any aliased bindings, the shape for the
@@ -258,36 +258,36 @@ class Scope : public js::gc::TenuredCell
       : enclosing_(enclosing),
         kind_(kind),
         environmentShape_(environmentShape),
         data_(nullptr) {}
 
   static Scope* create(JSContext* cx, ScopeKind kind, HandleScope enclosing,
                        HandleShape envShape);
 
-  template <typename ConcreteScope>
-  static ConcreteScope* create(
-      JSContext* cx, ScopeKind kind, HandleScope enclosing,
-      HandleShape envShape,
-      MutableHandle<UniquePtr<typename ConcreteScope::Data>> data);
-
   template <typename ConcreteScope, XDRMode mode>
   static XDRResult XDRSizedBindingNames(
       XDRState<mode>* xdr, Handle<ConcreteScope*> scope,
       MutableHandle<typename ConcreteScope::Data*> data);
 
   Shape* maybeCloneEnvironmentShape(JSContext* cx);
 
   template <typename ConcreteScope>
   void initData(MutableHandle<UniquePtr<typename ConcreteScope::Data>> data);
 
   template <typename F>
   void applyScopeDataTyped(F&& f);
 
  public:
+  template <typename ConcreteScope>
+  static ConcreteScope* create(
+      JSContext* cx, ScopeKind kind, HandleScope enclosing,
+      HandleShape envShape,
+      MutableHandle<UniquePtr<typename ConcreteScope::Data>> data);
+
   static const JS::TraceKind TraceKind = JS::TraceKind::Scope;
 
   template <typename T>
   bool is() const {
     return kind_ == T::classScopeKind_;
   }
 
   template <typename T>
@@ -303,28 +303,32 @@ class Scope : public js::gc::TenuredCell
   }
 
   ScopeKind kind() const { return kind_; }
 
   Scope* enclosing() const { return enclosing_; }
 
   Shape* environmentShape() const { return environmentShape_; }
 
-  bool hasEnvironment() const {
-    switch (kind()) {
+  static bool hasEnvironment(ScopeKind kind, Shape* environmentShape) {
+    switch (kind) {
       case ScopeKind::With:
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
         return true;
       default:
         // If there's a shape, an environment must be created for this scope.
-        return environmentShape_ != nullptr;
+        return environmentShape != nullptr;
     }
   }
 
+  bool hasEnvironment() const {
+    return hasEnvironment(kind_, environmentShape_);
+  }
+
   uint32_t chainLength() const;
   uint32_t environmentChainLength() const;
 
   template <typename T>
   bool hasOnChain() const {
     for (const Scope* it = this; it; it = it->enclosing()) {
       if (it->is<T>()) {
         return true;
@@ -384,16 +388,17 @@ inline size_t SizeOfData(uint32_t numBin
 //
 // All kinds of LexicalScopes correspond to LexicalEnvironmentObjects on the
 // environment chain.
 //
 class LexicalScope : public Scope {
   friend class Scope;
   friend class BindingIter;
   friend class GCMarker;
+  friend class ScopeCreationData;
 
  public:
   // Data is public because it is created by the frontend. See
   // Parser<FullParseHandler>::newLexicalScopeData.
   struct Data : public BaseScopeData {
     // Bindings are sorted by kind in both frames and environments.
     //
     //   lets - [0, constStart)
@@ -425,17 +430,17 @@ class LexicalScope : public Scope {
  private:
   static LexicalScope* createWithData(JSContext* cx, ScopeKind kind,
                                       MutableHandle<UniquePtr<Data>> data,
                                       uint32_t firstFrameSlot,
                                       HandleScope enclosing);
 
   static bool prepareForScopeCreation(JSContext* cx, ScopeKind kind,
                                       uint32_t firstFrameSlot,
-                                      HandleScope enclosing,
+                                      Handle<AbstractScope> 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(const AbstractScope& start);
@@ -612,16 +617,17 @@ class FunctionScope : public Scope {
 //     };
 //
 // Corresponds to VarEnvironmentObject on environment chain.
 //
 class VarScope : public Scope {
   friend class GCMarker;
   friend class BindingIter;
   friend class Scope;
+  friend class ScopeCreationData;
 
  public:
   // Data is public because it is created by the
   // frontend. See Parser<FullParseHandler>::newVarScopeData.
   struct Data : public BaseScopeData {
     // All bindings are vars.
     uint32_t length = 0;
 
@@ -786,16 +792,17 @@ class WithScope : public Scope {
 //   A sloppy eval. This is an empty scope, used only in the frontend, to
 //   detect redeclaration errors. It has no Environment. Any `var`s declared
 //   in the eval code are bound on the nearest enclosing var environment.
 //
 class EvalScope : public Scope {
   friend class Scope;
   friend class BindingIter;
   friend class GCMarker;
+  friend class ScopeCreationData;
 
  public:
   // Data is public because it is created by the frontend. See
   // Parser<FullParseHandler>::newEvalScopeData.
   struct Data : public BaseScopeData {
     // All bindings in an eval script are 'var' bindings. The implicit
     // lexical scope around the eval is present regardless of strictness
     // and is its own LexicalScope.
@@ -874,16 +881,17 @@ inline bool Scope::is<EvalScope>() const
 //
 // Corresponds to a ModuleEnvironmentObject on the environment chain.
 //
 class ModuleScope : public Scope {
   friend class GCMarker;
   friend class BindingIter;
   friend class Scope;
   friend class AbstractScope;
+  friend class ScopeCreationData;
   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 = {};