Bug 1535154 - Merge PrivateScriptData scopes/objects/bigints arrays into a single array of GC things. r=tcampbell,jonco
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 20 Jun 2019 03:02:35 +0000
changeset 479284 5dec6c60fb6f375cd8d65d63d82bc53e0efd295a
parent 479283 1e2f7d1f21eace810cb18b8337919e57e4b4ea17
child 479285 4c6d64a741058690a8799c378384388f6c5a0124
push id36177
push userrmaries@mozilla.com
push dateThu, 20 Jun 2019 09:46:31 +0000
treeherdermozilla-central@a440f0629814 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell, jonco
bugs1535154
milestone69.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 1535154 - Merge PrivateScriptData scopes/objects/bigints arrays into a single array of GC things. r=tcampbell,jonco Once the other data is moved out of PrivateScriptData, this GC-thing array will be stored at a fixed offset. At that point we can simplify PrivateScriptData and get fast indexing into this array, important for the Baseline Interpreter. Differential Revision: https://phabricator.services.mozilla.com/D34902
js/public/HeapAPI.h
js/src/builtin/Eval.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/BytecodeSection.cpp
js/src/frontend/BytecodeSection.h
js/src/frontend/EmitterScope.cpp
js/src/frontend/FunctionEmitter.cpp
js/src/frontend/ParseNode.h
js/src/gc/Barrier.h
js/src/gc/Marking.cpp
js/src/shell/js.cpp
js/src/vm/BytecodeUtil.cpp
js/src/vm/Debugger.cpp
js/src/vm/EnvironmentObject.cpp
js/src/vm/JSScript.cpp
js/src/vm/JSScript.h
js/src/vm/Realm.cpp
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -242,16 +242,18 @@ struct Symbol {
  * A GC pointer, tagged with the trace kind.
  *
  * In general, a GC pointer should be stored with an exact type. This class
  * is for use when that is not possible because a single pointer must point
  * to several kinds of GC thing.
  */
 class JS_FRIEND_API GCCellPtr {
  public:
+  GCCellPtr() : GCCellPtr(nullptr) {}
+
   // Construction from a void* and trace kind.
   GCCellPtr(void* gcthing, JS::TraceKind traceKind)
       : ptr(checkedCast(gcthing, traceKind)) {}
 
   // Automatically construct a null GCCellPtr from nullptr.
   MOZ_IMPLICIT GCCellPtr(decltype(nullptr))
       : ptr(checkedCast(nullptr, JS::TraceKind::Null)) {}
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -38,20 +38,33 @@ static void AssertInnerizedEnvironmentCh
   RootedObject obj(cx);
   for (obj = &env; obj; obj = obj->enclosingEnvironment()) {
     MOZ_ASSERT(!IsWindowProxy(obj));
   }
 #endif
 }
 
 static bool IsEvalCacheCandidate(JSScript* script) {
+  if (!script->isDirectEvalInFunction()) {
+    return false;
+  }
+
   // Make sure there are no inner objects which might use the wrong parent
   // and/or call scope by reusing the previous eval's script.
-  return script->isDirectEvalInFunction() && !script->hasSingletons() &&
-         !script->hasObjects();
+  if (script->hasSingletons()) {
+    return false;
+  }
+
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (gcThing.is<JSObject>()) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 /* static */
 HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) {
   AutoCheckCannotGC nogc;
   uint32_t hash = l.str->hasLatin1Chars()
                       ? HashString(l.str->latin1Chars(nogc), l.str->length())
                       : HashString(l.str->twoByteChars(nogc), l.str->length());
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -918,35 +918,44 @@ bool BytecodeEmitter::emitAtomOp(JSAtom*
 bool BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
 
   return emitIndexOp(op, atomIndex);
 }
 
 bool BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
-  MOZ_ASSERT(index < perScriptData().scopeList().length());
+  MOZ_ASSERT(index < perScriptData().gcThingList().length());
   return emitIndex32(op, index);
 }
 
 bool BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
-  MOZ_ASSERT(index < perScriptData().objectList().length);
+  MOZ_ASSERT(index < perScriptData().gcThingList().length());
   return emitIndex32(op, index);
 }
 
 bool BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op) {
-  return emitInternedObjectOp(perScriptData().objectList().add(objbox), op);
+  uint32_t index;
+  if (!perScriptData().gcThingList().append(objbox, &index)) {
+    return false;
+  }
+
+  return emitInternedObjectOp(index, op);
 }
 
 bool BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2,
                                        JSOp op) {
-  uint32_t index = perScriptData().objectList().add(objbox1);
-  perScriptData().objectList().add(objbox2);
-  return emitInternedObjectOp(index, op);
+  uint32_t index1, index2;
+  if (!perScriptData().gcThingList().append(objbox1, &index1) ||
+      !perScriptData().gcThingList().append(objbox2, &index2)) {
+    return false;
+  }
+
+  return emitInternedObjectOp(index1, op);
 }
 
 bool BytecodeEmitter::emitRegExp(uint32_t index) {
   return emitIndex32(JSOP_REGEXP, index);
 }
 
 bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) {
   MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
@@ -1673,17 +1682,17 @@ bool BytecodeEmitter::emitNewInit() {
   code[1] = 0;
   code[2] = 0;
   code[3] = 0;
   code[4] = 0;
   bytecodeSection().updateDepth(offset);
   return true;
 }
 
-bool BytecodeEmitter::iteratorResultShape(unsigned* shape) {
+bool BytecodeEmitter::iteratorResultShape(uint32_t* shape) {
   // No need to do any guessing for the object kind, since we know exactly how
   // many properties we plan to have.
   gc::AllocKind kind = gc::GetGCObjectKind(2);
   RootedPlainObject obj(
       cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
   if (!obj) {
     return false;
   }
@@ -1699,23 +1708,21 @@ bool BytecodeEmitter::iteratorResultShap
     return false;
   }
 
   ObjectBox* objbox = parser->newObjectBox(obj);
   if (!objbox) {
     return false;
   }
 
-  *shape = perScriptData().objectList().add(objbox);
-
-  return true;
+  return perScriptData().gcThingList().append(objbox, shape);
 }
 
 bool BytecodeEmitter::emitPrepareIteratorResult() {
-  unsigned shape;
+  uint32_t shape;
   if (!iteratorResultShape(&shape)) {
     return false;
   }
   return emitIndex32(JSOP_NEWOBJECT, shape);
 }
 
 bool BytecodeEmitter::emitFinishIteratorResult(bool done) {
   uint32_t value_id;
@@ -5001,20 +5008,21 @@ bool BytecodeEmitter::emitCopyDataProper
     return false;
   }
 
   MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth());
   return true;
 }
 
 bool BytecodeEmitter::emitBigIntOp(BigInt* bigint) {
-  if (!perScriptData().bigIntList().append(bigint)) {
-    return false;
-  }
-  return emitIndex32(JSOP_BIGINT, perScriptData().bigIntList().length() - 1);
+  uint32_t index;
+  if (!perScriptData().gcThingList().append(bigint, &index)) {
+    return false;
+  }
+  return emitIndex32(JSOP_BIGINT, index);
 }
 
 bool BytecodeEmitter::emitIterator() {
   // Convert iterable to iterator.
   if (!emit1(JSOP_DUP)) {
     //              [stack] OBJ OBJ
     return false;
   }
@@ -8241,17 +8249,21 @@ bool BytecodeEmitter::replaceNewInitWith
   if (!objbox) {
     return false;
   }
 
   static_assert(
       JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH,
       "newinit and newobject must have equal length to edit in-place");
 
-  uint32_t index = perScriptData().objectList().add(objbox);
+  uint32_t index;
+  if (!perScriptData().gcThingList().append(objbox, &index)) {
+    return false;
+  }
+
   jsbytecode* code = bytecodeSection().code(offset);
 
   MOZ_ASSERT(code[0] == JSOP_NEWINIT);
   code[0] = JSOP_NEWOBJECT;
   SET_UINT32(code, index);
 
   return true;
 }
@@ -9334,22 +9346,27 @@ bool BytecodeEmitter::emitTree(
       break;
 
     case ParseNodeKind::BigIntExpr:
       if (!emitBigIntOp(pn->as<BigIntLiteral>().box()->value())) {
         return false;
       }
       break;
 
-    case ParseNodeKind::RegExpExpr:
-      if (!emitRegExp(perScriptData().objectList().add(
-              pn->as<RegExpLiteral>().objbox()))) {
+    case ParseNodeKind::RegExpExpr: {
+      ObjectBox* obj = pn->as<RegExpLiteral>().objbox();
+      uint32_t index;
+      if (!perScriptData().gcThingList().append(obj, &index)) {
+        return false;
+      }
+      if (!emitRegExp(index)) {
         return false;
       }
       break;
+    }
 
     case ParseNodeKind::TrueExpr:
       if (!emit1(JSOP_TRUE)) {
         return false;
       }
       break;
     case ParseNodeKind::FalseExpr:
       if (!emit1(JSOP_FALSE)) {
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -273,22 +273,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
 
   void setVarEmitterScope(EmitterScope* emitterScope) {
     MOZ_ASSERT(emitterScope);
     MOZ_ASSERT(!varEmitterScope);
     varEmitterScope = emitterScope;
   }
 
   Scope* outermostScope() const {
-    return perScriptData().scopeList().vector[0];
+    return perScriptData().gcThingList().firstScope();
   }
   Scope* innermostScope() const;
   Scope* bodyScope() const {
-    MOZ_ASSERT(bodyScopeIndex < perScriptData().scopeList().length());
-    return perScriptData().scopeList().vector[bodyScopeIndex];
+    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) {
       *indexp = p->value();
@@ -541,17 +540,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                       bool* emitSetFunName);
   MOZ_MUST_USE bool emitAssignmentRhs(uint8_t offset);
 
   MOZ_MUST_USE bool emitNewInit();
   MOZ_MUST_USE bool emitSingletonInitialiser(ListNode* objOrArray);
 
   MOZ_MUST_USE bool emitPrepareIteratorResult();
   MOZ_MUST_USE bool emitFinishIteratorResult(bool done);
-  MOZ_MUST_USE bool iteratorResultShape(unsigned* shape);
+  MOZ_MUST_USE bool iteratorResultShape(uint32_t* shape);
 
   MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() {
     return emitGetDotGeneratorInScope(*innermostEmitterScope());
   }
   MOZ_MUST_USE bool emitGetDotGeneratorInScope(EmitterScope& currentScope);
 
   MOZ_MUST_USE bool allocateResumeIndex(BytecodeOffset offset,
                                         uint32_t* resumeIndex);
--- a/js/src/frontend/BytecodeSection.cpp
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -13,69 +13,45 @@
 #include "frontend/ParseNode.h"      // ObjectBox
 #include "frontend/SharedContext.h"  // FunctionBox
 #include "vm/BytecodeUtil.h"         // INDEX_LIMIT, StackUses, StackDefs
 #include "vm/JSContext.h"            // JSContext
 
 using namespace js;
 using namespace js::frontend;
 
-void CGBigIntList::finish(mozilla::Span<GCPtrBigInt> array) {
-  MOZ_ASSERT(length() == array.size());
-
-  for (unsigned i = 0; i < length(); i++) {
-    array[i].init(vector[i]);
-  }
-}
+bool GCThingList::append(ObjectBox* objbox, uint32_t* index) {
+  // Append the object to the vector and return the index in *index. Also add
+  // the ObjectBox to the |lastbox| linked list for finishInnerFunctions below.
 
-/*
- * Find the index of the given object for code generator.
- *
- * Since the emitter refers to each parsed object only once, for the index we
- * use the number of already indexed objects. We also add the object to a list
- * to convert the list to a fixed-size array when we complete code generation,
- * see js::CGObjectList::finish below.
- */
-unsigned CGObjectList::add(ObjectBox* objbox) {
   MOZ_ASSERT(objbox->isObjectBox());
   MOZ_ASSERT(!objbox->emitLink);
   objbox->emitLink = lastbox;
   lastbox = objbox;
-  return length++;
+
+  *index = vector.length();
+  return vector.append(JS::GCCellPtr(objbox->object()));
 }
 
-void CGObjectList::finish(mozilla::Span<GCPtrObject> array) {
-  MOZ_ASSERT(length <= INDEX_LIMIT);
-  MOZ_ASSERT(length == array.size());
-
-  ObjectBox* objbox = lastbox;
-  for (GCPtrObject& obj : mozilla::Reversed(array)) {
-    MOZ_ASSERT(obj == nullptr);
-    MOZ_ASSERT(objbox->object()->isTenured());
-    obj.init(objbox->object());
-    objbox = objbox->emitLink;
-  }
-}
-
-void CGObjectList::finishInnerFunctions() {
+void GCThingList::finishInnerFunctions() {
   ObjectBox* objbox = lastbox;
   while (objbox) {
     if (objbox->isFunctionBox()) {
       objbox->asFunctionBox()->finish();
     }
     objbox = objbox->emitLink;
   }
 }
 
-void CGScopeList::finish(mozilla::Span<GCPtrScope> array) {
+void GCThingList::finish(mozilla::Span<JS::GCCellPtr> array) {
   MOZ_ASSERT(length() <= INDEX_LIMIT);
   MOZ_ASSERT(length() == array.size());
 
   for (uint32_t i = 0; i < length(); i++) {
-    array[i].init(vector[i]);
+    array[i] = vector[i].get().get();
   }
 }
 
 bool CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth,
                            BytecodeOffset start, BytecodeOffset end) {
   MOZ_ASSERT(start <= end);
 
   // Offsets are given relative to sections, but we only expect main-section
@@ -165,13 +141,11 @@ void BytecodeSection::updateDepth(Byteco
   stackDepth_ += ndefs;
 
   if (uint32_t(stackDepth_) > maxStackDepth_) {
     maxStackDepth_ = stackDepth_;
   }
 }
 
 PerScriptData::PerScriptData(JSContext* cx)
-    : scopeList_(cx),
-      bigIntList_(cx),
-      atomIndices_(cx->frontendCollectionPool()) {}
+    : gcThingList_(cx), atomIndices_(cx->frontendCollectionPool()) {}
 
 bool PerScriptData::init(JSContext* cx) { return atomIndices_.acquire(cx); }
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -3,16 +3,17 @@
  * 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_BytecodeSection_h
 #define frontend_BytecodeSection_h
 
 #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 "NamespaceImports.h"          // ValueVector
 #include "frontend/BytecodeOffset.h"   // BytecodeOffset
 #include "frontend/JumpList.h"         // JumpTarget
@@ -32,47 +33,55 @@ namespace js {
 class Scope;
 
 using BigIntVector = JS::GCVector<js::BigInt*>;
 
 namespace frontend {
 
 class ObjectBox;
 
-class CGBigIntList {
-  JS::Rooted<BigIntVector> vector;
+struct MOZ_STACK_CLASS GCThingList {
+  JS::RootedVector<StackGCCellPtr> vector;
 
- public:
-  explicit CGBigIntList(JSContext* cx) : vector(cx, BigIntVector(cx)) {}
-  MOZ_MUST_USE bool append(js::BigInt* bi) { return vector.append(bi); }
-  size_t length() const { return vector.length(); }
-  void finish(mozilla::Span<GCPtrBigInt> array);
-};
+  // Last emitted object.
+  ObjectBox* lastbox = nullptr;
 
-struct CGObjectList {
-  // Number of emitted so far objects.
-  uint32_t length;
-  // Last emitted object.
-  ObjectBox* lastbox;
+  // Index of the first scope in the vector.
+  mozilla::Maybe<uint32_t> firstScopeIndex;
+
+  explicit GCThingList(JSContext* cx) : vector(cx) {}
 
-  CGObjectList() : length(0), lastbox(nullptr) {}
-
-  unsigned add(ObjectBox* objbox);
-  void finish(mozilla::Span<GCPtrObject> array);
-  void finishInnerFunctions();
-};
+  MOZ_MUST_USE bool append(Scope* scope, uint32_t* index) {
+    *index = vector.length();
+    if (!vector.append(JS::GCCellPtr(scope))) {
+      return false;
+    }
+    if (!firstScopeIndex) {
+      firstScopeIndex.emplace(*index);
+    }
+    return true;
+  }
+  MOZ_MUST_USE bool append(BigInt* bi, uint32_t* index) {
+    *index = vector.length();
+    return vector.append(JS::GCCellPtr(bi));
+  }
+  MOZ_MUST_USE bool append(ObjectBox* obj, uint32_t* index);
 
-struct MOZ_STACK_CLASS CGScopeList {
-  JS::Rooted<GCVector<Scope*>> vector;
+  uint32_t length() const { return vector.length(); }
+  void finish(mozilla::Span<JS::GCCellPtr> array);
+  void finishInnerFunctions();
 
-  explicit CGScopeList(JSContext* cx) : vector(cx, GCVector<Scope*>(cx)) {}
+  Scope* getScope(size_t index) const {
+    return &vector[index].get().get().as<Scope>();
+  }
 
-  bool append(Scope* scope) { return vector.append(scope); }
-  uint32_t length() const { return vector.length(); }
-  void finish(mozilla::Span<GCPtrScope> array);
+  Scope* firstScope() const {
+    MOZ_ASSERT(firstScopeIndex.isSome());
+    return getScope(*firstScopeIndex);
+  }
 };
 
 struct CGTryNoteList {
   Vector<JSTryNote> list;
   explicit CGTryNoteList(JSContext* cx) : list(cx) {}
 
   MOZ_MUST_USE bool append(JSTryNoteKind kind, uint32_t stackDepth,
                            BytecodeOffset start, BytecodeOffset end);
@@ -320,45 +329,25 @@ class BytecodeSection {
 // 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);
 
   MOZ_MUST_USE bool init(JSContext* cx);
 
-  // ---- Scope ----
-
-  CGScopeList& scopeList() { return scopeList_; }
-  const CGScopeList& scopeList() const { return scopeList_; }
-
-  // ---- Literals ----
-
-  CGBigIntList& bigIntList() { return bigIntList_; }
-  const CGBigIntList& bigIntList() const { return bigIntList_; }
-
-  CGObjectList& objectList() { return objectList_; }
-  const CGObjectList& objectList() const { return objectList_; }
+  GCThingList& gcThingList() { return gcThingList_; }
+  const GCThingList& gcThingList() const { return gcThingList_; }
 
   PooledMapPtr<AtomIndexMap>& atomIndices() { return atomIndices_; }
   const PooledMapPtr<AtomIndexMap>& atomIndices() const { return atomIndices_; }
 
  private:
-  // ---- Scope ----
-
-  // List of emitted scopes.
-  CGScopeList scopeList_;
-
-  // ---- Literals ----
-
-  // List of bigints used by script.
-  CGBigIntList bigIntList_;
-
-  // List of emitted objects.
-  CGObjectList objectList_;
+  // List of emitted scopes/objects/bigints.
+  GCThingList gcThingList_;
 
   // Map from atom to index.
   PooledMapPtr<AtomIndexMap> atomIndices_;
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -337,26 +337,25 @@ NameLocation EmitterScope::searchAndCach
 template <typename ScopeCreator>
 bool EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) {
   RootedScope enclosing(bce->cx, enclosingScope(bce));
   Scope* scope = createScope(bce->cx, enclosing);
   if (!scope) {
     return false;
   }
   hasEnvironment_ = scope->hasEnvironment();
-  scopeIndex_ = bce->perScriptData().scopeList().length();
-  return bce->perScriptData().scopeList().append(scope);
+  return bce->perScriptData().gcThingList().append(scope, &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().scopeList().length();
+  bce->bodyScopeIndex = bce->perScriptData().gcThingList().length();
   return internScope(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(
@@ -1066,17 +1065,17 @@ bool EmitterScope::leave(BytecodeEmitter
       }
     }
   }
 
   return true;
 }
 
 Scope* EmitterScope::scope(const BytecodeEmitter* bce) const {
-  return bce->perScriptData().scopeList().vector[index()];
+  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/FunctionEmitter.cpp
+++ b/js/src/frontend/FunctionEmitter.cpp
@@ -215,17 +215,20 @@ bool FunctionEmitter::emitAsmJSModule() 
 #ifdef DEBUG
   state_ = State::End;
 #endif
   return true;
 }
 
 bool FunctionEmitter::emitFunction() {
   // Make the function object a literal in the outer script's pool.
-  unsigned index = bce_->perScriptData().objectList().add(funbox_);
+  uint32_t index;
+  if (!bce_->perScriptData().gcThingList().append(funbox_, &index)) {
+    return false;
+  }
 
   //                [stack]
 
   if (isHoisted_ == IsHoisted::No) {
     return emitNonHoisted(index);
     //              [stack] FUN?
   }
 
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -2183,17 +2183,17 @@ class TraceListNode {
 class BigIntBox : public TraceListNode {
  public:
   BigIntBox(BigInt* bi, TraceListNode* link);
   BigInt* value() const { return gcThing->as<BigInt>(); }
 };
 
 class ObjectBox : public TraceListNode {
  protected:
-  friend struct CGObjectList;
+  friend struct GCThingList;
   ObjectBox* emitLink;
 
   ObjectBox(JSFunction* function, TraceListNode* link);
 
  public:
   ObjectBox(JSObject* obj, TraceListNode* link);
 
   JSObject* object() const { return gcThing->as<JSObject>(); }
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -952,16 +952,31 @@ struct WeakHeapPtrHasher {
 
   static HashNumber hash(Lookup obj) { return DefaultHasher<T>::hash(obj); }
   static bool match(const Key& k, Lookup l) { return k.unbarrieredGet() == l; }
   static void rekey(Key& k, const Key& newKey) {
     k.set(newKey.unbarrieredGet());
   }
 };
 
+// Wrapper around GCCellPtr for use with RootedVector<StackGCCellPtr>.
+class MOZ_STACK_CLASS StackGCCellPtr {
+  JS::GCCellPtr ptr_;
+
+ public:
+  MOZ_IMPLICIT StackGCCellPtr(JS::GCCellPtr ptr) : ptr_(ptr) {}
+  StackGCCellPtr() = default;
+
+  void operator=(const StackGCCellPtr& other) { ptr_ = other.ptr_; }
+
+  void trace(JSTracer* trc);
+
+  JS::GCCellPtr get() const { return ptr_; }
+};
+
 }  // namespace js
 
 namespace mozilla {
 
 /* Specialized hashing policy for GCPtrs. */
 template <class T>
 struct DefaultHasher<js::GCPtr<T>> : js::GCPtrHasher<T> {};
 
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -547,16 +547,24 @@ void js::TraceManuallyBarrieredGenericPo
                                   TraceManuallyBarrieredEdge(trc, &t, name);
                                   return t;
                                 });
   if (traced != thing) {
     *thingp = traced;
   }
 }
 
+void StackGCCellPtr::trace(JSTracer* trc) {
+  Cell* thing = ptr_.asCell();
+  TraceGenericPointerRoot(trc, &thing, "stack-gc-cell-ptr");
+  if (thing != ptr_.asCell()) {
+    ptr_ = JS::GCCellPtr(thing, ptr_.kind());
+  }
+}
+
 // This method is responsible for dynamic dispatch to the real tracer
 // implementation. Consider replacing this choke point with virtual dispatch:
 // a sufficiently smart C++ compiler may be able to devirtualize some paths.
 template <typename T>
 void js::gc::TraceEdgeInternal(JSTracer* trc, T* thingp, const char* name) {
 #define IS_SAME_TYPE_OR(name, type, _, _1) mozilla::IsSame<type*, T>::value ||
   static_assert(JS_FOR_EACH_TRACEKIND(IS_SAME_TYPE_OR)
                         mozilla::IsSame<T, JS::Value>::value ||
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3274,18 +3274,23 @@ static MOZ_MUST_USE bool DisassembleScri
   }
   if (!TryNotes(cx, script, sp)) {
     return false;
   }
   if (!ScopeNotes(cx, script, sp)) {
     return false;
   }
 
-  if (recursive && script->hasObjects()) {
-    for (JSObject* obj : script->objects()) {
+  if (recursive) {
+    for (JS::GCCellPtr gcThing : script->gcthings()) {
+      if (!gcThing.is<JSObject>()) {
+        continue;
+      }
+
+      JSObject* obj = &gcThing.as<JSObject>();
       if (obj->is<JSFunction>()) {
         if (!sp->put("\n")) {
           return false;
         }
 
         RootedFunction fun(cx, &obj->as<JSFunction>());
         if (fun->isInterpreted()) {
           RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -2908,22 +2908,24 @@ static bool GenerateLcovInfo(JSContext* 
     if (!script->isTopLevel()) {
       continue;
     }
 
     // Iterate from the last to the first object in order to have
     // the functions them visited in the opposite order when popping
     // elements from the stack of remaining scripts, such that the
     // functions are more-less listed with increasing line numbers.
-    if (!script->hasObjects()) {
-      continue;
-    }
-    auto objects = script->objects();
-    for (JSObject* obj : mozilla::Reversed(objects)) {
+    auto gcthings = script->gcthings();
+    for (JS::GCCellPtr gcThing : mozilla::Reversed(gcthings)) {
+      if (!gcThing.is<JSObject>()) {
+        continue;
+      }
+
       // Only continue on JSFunction objects.
+      JSObject* obj = &gcThing.as<JSObject>();
       if (!obj->is<JSFunction>()) {
         continue;
       }
       fun = &obj->as<JSFunction>();
 
       // Let's skip wasm for now.
       if (!fun->isInterpreted()) {
         continue;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6454,39 +6454,40 @@ static bool DebuggerScript_getChildScrip
   THIS_DEBUGSCRIPT_SCRIPT_DELAZIFY(cx, argc, vp, "getChildScripts", args, obj,
                                    script);
   Debugger* dbg = Debugger::fromChildJSObject(obj);
 
   RootedObject result(cx, NewDenseEmptyArray(cx));
   if (!result) {
     return false;
   }
-  if (script->hasObjects()) {
-    // script->savedCallerFun indicates that this is a direct eval script
-    // and the calling function is stored as script->objects()->vector[0].
-    // It is not really a child script of this script, so skip it using
-    // innerObjectsStart().
-    RootedFunction fun(cx);
-    RootedScript funScript(cx);
-    RootedObject s(cx);
-    for (const GCPtrObject& obj : script->objects()) {
-      if (obj->is<JSFunction>()) {
-        fun = &obj->as<JSFunction>();
-        // The inner function could be an asm.js native.
-        if (!IsInterpretedNonSelfHostedFunction(fun)) {
-          continue;
-        }
-        funScript = GetOrCreateFunctionScript(cx, fun);
-        if (!funScript) {
-          return false;
-        }
-        s = dbg->wrapScript(cx, funScript);
-        if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) {
-          return false;
-        }
+
+  // Wrap and append scripts for the inner functions in script->gcthings().
+  RootedFunction fun(cx);
+  RootedScript funScript(cx);
+  RootedObject s(cx);
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
+    if (obj->is<JSFunction>()) {
+      fun = &obj->as<JSFunction>();
+      // The inner function could be an asm.js native.
+      if (!IsInterpretedNonSelfHostedFunction(fun)) {
+        continue;
+      }
+      funScript = GetOrCreateFunctionScript(cx, fun);
+      if (!funScript) {
+        return false;
+      }
+      s = dbg->wrapScript(cx, funScript);
+      if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) {
+        return false;
       }
     }
   }
   args.rval().setObject(*result);
   return true;
 }
 
 static bool ScriptOffset(JSContext* cx, const Value& v, size_t* offsetp) {
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -3796,30 +3796,33 @@ static bool RemoveReferencedNames(JSCont
         break;
     }
 
     if (name) {
       remainingNames.remove(name);
     }
   }
 
-  if (script->hasObjects()) {
-    RootedFunction fun(cx);
-    RootedScript innerScript(cx);
-    for (JSObject* obj : script->objects()) {
-      if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
-        fun = &obj->as<JSFunction>();
-        innerScript = JSFunction::getOrCreateScript(cx, fun);
-        if (!innerScript) {
-          return false;
-        }
-
-        if (!RemoveReferencedNames(cx, innerScript, remainingNames)) {
-          return false;
-        }
+  RootedFunction fun(cx);
+  RootedScript innerScript(cx);
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
+    if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
+      fun = &obj->as<JSFunction>();
+      innerScript = JSFunction::getOrCreateScript(cx, fun);
+      if (!innerScript) {
+        return false;
+      }
+
+      if (!RemoveReferencedNames(cx, innerScript, remainingNames)) {
+        return false;
       }
     }
   }
 
   return true;
 }
 
 static bool AnalyzeEntrainedVariablesInScript(JSContext* cx,
@@ -3868,27 +3871,30 @@ static bool AnalyzeEntrainedVariablesInS
          r.popFront()) {
       buf.printf(" ");
       buf.putString(r.front());
     }
 
     printf("%s\n", buf.string());
   }
 
-  if (innerScript->hasObjects()) {
-    RootedFunction fun(cx);
-    RootedScript innerInnerScript(cx);
-    for (JSObject* obj : script->objects()) {
-      if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
-        fun = &obj->as<JSFunction>();
-        innerInnerScript = JSFunction::getOrCreateScript(cx, fun);
-        if (!innerInnerScript ||
-            !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) {
-          return false;
-        }
+  RootedFunction fun(cx);
+  RootedScript innerInnerScript(cx);
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
+    if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
+      fun = &obj->as<JSFunction>();
+      innerInnerScript = JSFunction::getOrCreateScript(cx, fun);
+      if (!innerInnerScript ||
+          !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) {
+        return false;
       }
     }
   }
 
   return true;
 }
 
 // Look for local variables in script or any other script inner to it, which are
@@ -3898,23 +3904,24 @@ static bool AnalyzeEntrainedVariablesInS
 // function foo() {
 //   var a, b;
 //   function bar() { return a; }
 //   function baz() { return b; }
 // }
 //
 // |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|.
 bool js::AnalyzeEntrainedVariables(JSContext* cx, HandleScript script) {
-  if (!script->hasObjects()) {
-    return true;
-  }
-
   RootedFunction fun(cx);
   RootedScript innerScript(cx);
-  for (JSObject* obj : script->objects()) {
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
       fun = &obj->as<JSFunction>();
       innerScript = JSFunction::getOrCreateScript(cx, fun);
       if (!innerScript) {
         return false;
       }
 
       if (script->functionDelazifying() &&
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -349,21 +349,21 @@ XDRResult ScopeNote::XDR(XDRState<mode>*
   MOZ_TRY(xdr->codeUint32(&index));
   MOZ_TRY(xdr->codeUint32(&start));
   MOZ_TRY(xdr->codeUint32(&length));
   MOZ_TRY(xdr->codeUint32(&parent));
 
   return Ok();
 }
 
-static inline uint32_t FindScopeIndex(mozilla::Span<const GCPtrScope> scopes,
+static inline uint32_t FindScopeIndex(mozilla::Span<const JS::GCCellPtr> scopes,
                                       Scope& scope) {
   unsigned length = scopes.size();
   for (uint32_t i = 0; i < length; ++i) {
-    if (scopes[i] == &scope) {
+    if (scopes[i].asCell() == &scope) {
       return i;
     }
   }
 
   MOZ_CRASH("Scope not found");
 }
 
 template <XDRMode mode>
@@ -418,23 +418,24 @@ static XDRResult XDRInnerObject(XDRState
         } else if (function->isInterpreted()) {
           funEnclosingScope = function->nonLazyScript()->enclosingScope();
         } else {
           MOZ_ASSERT(function->isAsmJSNative());
           return xdr->fail(JS::TranscodeResult_Failure_AsmJSNotSupported);
         }
 
         funEnclosingScopeIndex =
-            FindScopeIndex(data->scopes(), *funEnclosingScope);
+            FindScopeIndex(data->gcthings(), *funEnclosingScope);
       }
 
       MOZ_TRY(xdr->codeUint32(&funEnclosingScopeIndex));
 
       if (mode == XDR_DECODE) {
-        funEnclosingScope = data->scopes()[funEnclosingScopeIndex];
+        funEnclosingScope =
+            &data->gcthings()[funEnclosingScopeIndex].as<Scope>();
       }
 
       // Code nested function and script.
       RootedFunction tmp(cx);
       if (mode == XDR_ENCODE) {
         tmp = &inner->as<JSFunction>();
       }
       MOZ_TRY(
@@ -467,46 +468,45 @@ static XDRResult XDRInnerObject(XDRState
   }
 
   return Ok();
 }
 
 template <XDRMode mode>
 static XDRResult XDRScope(XDRState<mode>* xdr, js::PrivateScriptData* data,
                           HandleScope scriptEnclosingScope, HandleFunction fun,
-                          uint32_t scopeIndex, MutableHandleScope scope) {
+                          bool isFirstScope, MutableHandleScope scope) {
   JSContext* cx = xdr->cx();
 
   ScopeKind scopeKind;
   RootedScope enclosing(cx);
   uint32_t enclosingIndex = 0;
 
   // The enclosingScope is encoded using an integer index into the scope array.
   // This means that scopes must be topologically sorted.
   if (mode == XDR_ENCODE) {
     scopeKind = scope->kind();
 
-    if (scopeIndex == 0) {
+    if (isFirstScope) {
       enclosingIndex = UINT32_MAX;
     } else {
       MOZ_ASSERT(scope->enclosing());
-      enclosingIndex = FindScopeIndex(data->scopes(), *scope->enclosing());
+      enclosingIndex = FindScopeIndex(data->gcthings(), *scope->enclosing());
     }
   }
 
   MOZ_TRY(xdr->codeEnum32(&scopeKind));
   MOZ_TRY(xdr->codeUint32(&enclosingIndex));
 
   if (mode == XDR_DECODE) {
-    if (scopeIndex == 0) {
+    if (isFirstScope) {
       MOZ_ASSERT(enclosingIndex == UINT32_MAX);
       enclosing = scriptEnclosingScope;
     } else {
-      MOZ_ASSERT(enclosingIndex < scopeIndex);
-      enclosing = data->scopes()[enclosingIndex];
+      enclosing = &data->gcthings()[enclosingIndex].as<Scope>();
     }
   }
 
   switch (scopeKind) {
     case ScopeKind::Function:
       MOZ_TRY(FunctionScope::XDR(xdr, fun, enclosing, scope));
       break;
     case ScopeKind::FunctionBodyVar:
@@ -543,120 +543,136 @@ static XDRResult XDRScope(XDRState<mode>
       MOZ_ASSERT(false, "Bad XDR scope kind");
       return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
   }
 
   return Ok();
 }
 
 template <XDRMode mode>
+static XDRResult XDRScriptGCThing(XDRState<mode>* xdr, PrivateScriptData* data,
+                                  HandleScriptSourceObject sourceObject,
+                                  HandleScope scriptEnclosingScope,
+                                  HandleFunction fun, bool* isFirstScope,
+                                  JS::GCCellPtr* thingp) {
+  JSContext* cx = xdr->cx();
+
+  enum class GCThingTag { Object, Scope, BigInt };
+
+  JS::GCCellPtr thing;
+
+  GCThingTag tag;
+  if (mode == XDR_ENCODE) {
+    thing = *thingp;
+    if (thing.is<JSObject>()) {
+      tag = GCThingTag::Object;
+    } else if (thing.is<Scope>()) {
+      tag = GCThingTag::Scope;
+    } else {
+      MOZ_ASSERT(thing.is<BigInt>());
+      tag = GCThingTag::BigInt;
+    }
+  }
+
+  MOZ_TRY(xdr->codeEnum32(&tag));
+
+  switch (tag) {
+    case GCThingTag::Object: {
+      RootedObject obj(cx);
+      if (mode == XDR_ENCODE) {
+        obj = &thing.as<JSObject>();
+      }
+      MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &obj));
+      if (mode == XDR_DECODE) {
+        *thingp = JS::GCCellPtr(obj.get());
+      }
+      break;
+    }
+    case GCThingTag::Scope: {
+      RootedScope scope(cx);
+      if (mode == XDR_ENCODE) {
+        scope = &thing.as<Scope>();
+      }
+      MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, fun, *isFirstScope,
+                       &scope));
+      if (mode == XDR_DECODE) {
+        *thingp = JS::GCCellPtr(scope.get());
+      }
+      *isFirstScope = false;
+      break;
+    }
+    case GCThingTag::BigInt: {
+      RootedBigInt bi(cx);
+      if (mode == XDR_ENCODE) {
+        bi = &thing.as<BigInt>();
+      }
+      MOZ_TRY(XDRBigInt(xdr, &bi));
+      if (mode == XDR_DECODE) {
+        *thingp = JS::GCCellPtr(bi.get());
+      }
+      break;
+    }
+    default:
+      // Fail in debug, but only soft-fail in release.
+      MOZ_ASSERT(false, "Bad XDR GCThingTag");
+      return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
+  }
+  return Ok();
+}
+
+template <XDRMode mode>
 /* static */
 XDRResult js::PrivateScriptData::XDR(XDRState<mode>* xdr, HandleScript script,
                                      HandleScriptSourceObject sourceObject,
                                      HandleScope scriptEnclosingScope,
                                      HandleFunction fun) {
-  uint32_t nscopes = 0;
-  uint32_t nbigints = 0;
-  uint32_t nobjects = 0;
+  uint32_t ngcthings = 0;
   uint32_t ntrynotes = 0;
   uint32_t nscopenotes = 0;
   uint32_t nresumeoffsets = 0;
 
   JSContext* cx = xdr->cx();
   PrivateScriptData* data = nullptr;
 
   if (mode == XDR_ENCODE) {
     data = script->data_;
 
-    nscopes = data->scopes().size();
-    if (data->hasBigInts()) {
-      nbigints = data->bigints().size();
-    }
-    if (data->hasObjects()) {
-      nobjects = data->objects().size();
-    }
+    ngcthings = data->gcthings().size();
     if (data->hasTryNotes()) {
       ntrynotes = data->tryNotes().size();
     }
     if (data->hasScopeNotes()) {
       nscopenotes = data->scopeNotes().size();
     }
     if (data->hasResumeOffsets()) {
       nresumeoffsets = data->resumeOffsets().size();
     }
   }
 
-  MOZ_TRY(xdr->codeUint32(&nscopes));
-  MOZ_TRY(xdr->codeUint32(&nbigints));
-  MOZ_TRY(xdr->codeUint32(&nobjects));
+  MOZ_TRY(xdr->codeUint32(&ngcthings));
   MOZ_TRY(xdr->codeUint32(&ntrynotes));
   MOZ_TRY(xdr->codeUint32(&nscopenotes));
   MOZ_TRY(xdr->codeUint32(&nresumeoffsets));
 
   if (mode == XDR_DECODE) {
-    if (!JSScript::createPrivateScriptData(cx, script, nscopes, nbigints,
-                                           nobjects, ntrynotes, nscopenotes,
-                                           nresumeoffsets)) {
+    if (!JSScript::createPrivateScriptData(cx, script, ngcthings, ntrynotes,
+                                           nscopenotes, nresumeoffsets)) {
       return xdr->fail(JS::TranscodeResult_Throw);
     }
 
     data = script->data_;
   }
 
-  if (nbigints > 0) {
-    RootedBigInt bi(cx);
-    for (GCPtrBigInt& elem : data->bigints()) {
-      if (mode == XDR_ENCODE) {
-        bi = elem.get();
-      }
-      MOZ_TRY(XDRBigInt(xdr, &bi));
-      if (mode == XDR_DECODE) {
-        elem.init(bi);
-      }
-    }
-  }
-
-  {
-    MOZ_ASSERT(nscopes > 0);
-    GCPtrScope* vector = data->scopes().data();
-    for (uint32_t i = 0; i < nscopes; ++i) {
-      RootedScope scope(cx);
-      if (mode == XDR_ENCODE) {
-        scope = vector[i];
-      }
-      MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, fun, i, &scope));
-      if (mode == XDR_DECODE) {
-        vector[i].init(scope);
-      }
-    }
-
-    // Verify marker to detect data corruption after decoding scope data. A
-    // mismatch here indicates we will almost certainly crash in release.
-    MOZ_TRY(xdr->codeMarker(0x48922BAB));
-  }
-
-  /*
-   * Here looping from 0-to-length to xdr objects is essential to ensure that
-   * all references to enclosing blocks (via FindScopeIndex below) happen
-   * after the enclosing block has been XDR'd.
-   */
-  if (nobjects) {
-    for (GCPtrObject& elem : data->objects()) {
-      RootedObject inner(cx);
-      if (mode == XDR_ENCODE) {
-        inner = elem;
-      }
-      MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &inner));
-      if (mode == XDR_DECODE) {
-        elem.init(inner);
-      }
-    }
-  }
-
-  // Verify marker to detect data corruption after decoding object data. A
+  bool isFirstScope = true;
+  for (JS::GCCellPtr& gcThing : data->gcthings()) {
+    MOZ_TRY(XDRScriptGCThing(xdr, data, sourceObject, scriptEnclosingScope, fun,
+                             &isFirstScope, &gcThing));
+  }
+
+  // Verify marker to detect data corruption after decoding GC things. A
   // mismatch here indicates we will almost certainly crash in release.
   MOZ_TRY(xdr->codeMarker(0xF83B989A));
 
   if (ntrynotes) {
     for (JSTryNote& elem : data->tryNotes()) {
       MOZ_TRY(elem.XDR(xdr));
     }
   }
@@ -3411,46 +3427,33 @@ void js::FreeScriptData(JSRuntime* rt) {
             numLive);
   }
 #endif
 
   table.clear();
 }
 
 /* static */
-size_t PrivateScriptData::AllocationSize(uint32_t nscopes, uint32_t nbigints,
-                                         uint32_t nobjects, uint32_t ntrynotes,
+size_t PrivateScriptData::AllocationSize(uint32_t ngcthings, uint32_t ntrynotes,
                                          uint32_t nscopenotes,
                                          uint32_t nresumeoffsets) {
   size_t size = sizeof(PrivateScriptData);
 
-  if (nbigints) {
-    size += sizeof(PackedSpan);
-  }
-  if (nobjects) {
-    size += sizeof(PackedSpan);
-  }
   if (ntrynotes) {
     size += sizeof(PackedSpan);
   }
   if (nscopenotes) {
     size += sizeof(PackedSpan);
   }
   if (nresumeoffsets) {
     size += sizeof(PackedSpan);
   }
 
-  size += nscopes * sizeof(GCPtrScope);
-
-  if (nbigints) {
-    size += nbigints * sizeof(GCPtrBigInt);
-  }
-  if (nobjects) {
-    size += nobjects * sizeof(GCPtrObject);
-  }
+  size += ngcthings * sizeof(JS::GCCellPtr);
+
   if (ntrynotes) {
     size += ntrynotes * sizeof(JSTryNote);
   }
   if (nscopenotes) {
     size += nscopenotes * sizeof(ScopeNote);
   }
   if (nresumeoffsets) {
     size += nresumeoffsets * sizeof(uint32_t);
@@ -3483,21 +3486,20 @@ void PrivateScriptData::initSpan(size_t*
   // Placement-new the elements
   initElements<T>(*cursor, length);
 
   // Advance cursor
   (*cursor) += length * sizeof(T);
 }
 
 // Initialize PackedSpans and placement-new the trailing arrays.
-PrivateScriptData::PrivateScriptData(uint32_t nscopes_, uint32_t nbigints,
-                                     uint32_t nobjects, uint32_t ntrynotes,
+PrivateScriptData::PrivateScriptData(uint32_t ngcthings, uint32_t ntrynotes,
                                      uint32_t nscopenotes,
                                      uint32_t nresumeoffsets)
-    : nscopes(nscopes_) {
+    : ngcthings(ngcthings) {
   // Convert cursor possition to a packed offset.
   auto ToPackedOffset = [](size_t cursor) {
     MOZ_ASSERT(cursor % PackedOffsets::SCALE == 0);
     return cursor / PackedOffsets::SCALE;
   };
 
   // Helper to allocate a PackedSpan from the variable length data.
   auto TakeSpan = [=](size_t* cursor) {
@@ -3511,150 +3513,119 @@ PrivateScriptData::PrivateScriptData(uin
   // Variable-length data begins immediately after PrivateScriptData itself.
   // NOTE: Alignment is computed using cursor/offset so the alignment of
   // PrivateScriptData must be stricter than any trailing array type.
   size_t cursor = sizeof(*this);
 
   // Layout PackedSpan structures and initialize packedOffsets fields.
   static_assert(alignof(PrivateScriptData) >= alignof(PackedSpan),
                 "Incompatible alignment");
-  if (nbigints) {
-    packedOffsets.bigintsSpanOffset = TakeSpan(&cursor);
-  }
-  if (nobjects) {
-    packedOffsets.objectsSpanOffset = TakeSpan(&cursor);
-  }
   if (ntrynotes) {
     packedOffsets.tryNotesSpanOffset = TakeSpan(&cursor);
   }
   if (nscopenotes) {
     packedOffsets.scopeNotesSpanOffset = TakeSpan(&cursor);
   }
   if (nresumeoffsets) {
     packedOffsets.resumeOffsetsSpanOffset = TakeSpan(&cursor);
   }
 
-  // Layout and initialize the scopes array.
+  // Layout and initialize the gcthings array.
   {
-    MOZ_ASSERT(nscopes > 0);
-
-    static_assert(alignof(PackedSpan) >= alignof(GCPtrScope),
+    MOZ_ASSERT(ngcthings > 0);
+
+    static_assert(alignof(PackedSpan) >= alignof(JS::GCCellPtr),
                   "Incompatible alignment");
-    initElements<GCPtrScope>(cursor, nscopes);
-    packedOffsets.scopesOffset = ToPackedOffset(cursor);
-
-    cursor += nscopes * sizeof(GCPtrScope);
+    initElements<JS::GCCellPtr>(cursor, ngcthings);
+    packedOffsets.gcthingsOffset = ToPackedOffset(cursor);
+
+    cursor += ngcthings * sizeof(JS::GCCellPtr);
   }
 
   // Layout arrays, initialize PackedSpans and placement-new the elements.
-  static_assert(alignof(PrivateScriptData) >= alignof(GCPtrBigInt),
-                "Incompatible alignment");
-  static_assert(alignof(GCPtrScope) >= alignof(GCPtrBigInt),
-                "Incompatible alignment");
-  initSpan<GCPtrBigInt>(&cursor, packedOffsets.bigintsSpanOffset, nbigints);
-  static_assert(alignof(GCPtrBigInt) >= alignof(GCPtrObject),
-                "Incompatible alignment");
-  static_assert(alignof(GCPtrScope) >= alignof(GCPtrObject),
-                "Incompatible alignment");
-  initSpan<GCPtrObject>(&cursor, packedOffsets.objectsSpanOffset, nobjects);
-  static_assert(alignof(GCPtrObject) >= alignof(JSTryNote),
+  static_assert(alignof(JS::GCCellPtr) >= alignof(JSTryNote),
                 "Incompatible alignment");
   initSpan<JSTryNote>(&cursor, packedOffsets.tryNotesSpanOffset, ntrynotes);
   static_assert(alignof(JSTryNote) >= alignof(ScopeNote),
                 "Incompatible alignment");
   initSpan<ScopeNote>(&cursor, packedOffsets.scopeNotesSpanOffset, nscopenotes);
   static_assert(alignof(ScopeNote) >= alignof(uint32_t),
                 "Incompatible alignment");
   initSpan<uint32_t>(&cursor, packedOffsets.resumeOffsetsSpanOffset,
                      nresumeoffsets);
 
   // Sanity check
-  MOZ_ASSERT(AllocationSize(nscopes_, nbigints, nobjects, ntrynotes,
-                            nscopenotes, nresumeoffsets) == cursor);
+  MOZ_ASSERT(AllocationSize(ngcthings, ntrynotes, nscopenotes,
+                            nresumeoffsets) == cursor);
 }
 
 /* static */
-PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t nscopes,
-                                           uint32_t nbigints, uint32_t nobjects,
+PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings,
                                            uint32_t ntrynotes,
                                            uint32_t nscopenotes,
                                            uint32_t nresumeoffsets,
                                            uint32_t* dataSize) {
   // Compute size including trailing arrays
-  size_t size = AllocationSize(nscopes, nbigints, nobjects, ntrynotes,
-                               nscopenotes, nresumeoffsets);
+  size_t size =
+      AllocationSize(ngcthings, ntrynotes, nscopenotes, nresumeoffsets);
 
   // Allocate contiguous raw buffer
   void* raw = cx->pod_malloc<uint8_t>(size);
   MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0);
   if (!raw) {
     return nullptr;
   }
 
   if (dataSize) {
     *dataSize = size;
   }
 
   // Constuct the PrivateScriptData. Trailing arrays are uninitialized but
   // GCPtrs are put into a safe state.
-  return new (raw) PrivateScriptData(nscopes, nbigints, nobjects, ntrynotes,
-                                     nscopenotes, nresumeoffsets);
+  return new (raw)
+      PrivateScriptData(ngcthings, ntrynotes, nscopenotes, nresumeoffsets);
 }
 
 /* static */ bool PrivateScriptData::InitFromEmitter(
     JSContext* cx, js::HandleScript script, frontend::BytecodeEmitter* bce) {
-  uint32_t nscopes = bce->perScriptData().scopeList().length();
-  uint32_t nbigints = bce->perScriptData().bigIntList().length();
-  uint32_t nobjects = bce->perScriptData().objectList().length;
+  uint32_t ngcthings = bce->perScriptData().gcThingList().length();
   uint32_t ntrynotes = bce->bytecodeSection().tryNoteList().length();
   uint32_t nscopenotes = bce->bytecodeSection().scopeNoteList().length();
   uint32_t nresumeoffsets = bce->bytecodeSection().resumeOffsetList().length();
 
   // Create and initialize PrivateScriptData
-  if (!JSScript::createPrivateScriptData(cx, script, nscopes, nbigints,
-                                         nobjects, ntrynotes, nscopenotes,
-                                         nresumeoffsets)) {
+  if (!JSScript::createPrivateScriptData(cx, script, ngcthings, ntrynotes,
+                                         nscopenotes, nresumeoffsets)) {
     return false;
   }
 
   js::PrivateScriptData* data = script->data_;
-  if (nscopes) {
-    bce->perScriptData().scopeList().finish(data->scopes());
-  }
-  if (nbigints) {
-    bce->perScriptData().bigIntList().finish(data->bigints());
-  }
-  if (nobjects) {
-    bce->perScriptData().objectList().finish(data->objects());
+  if (ngcthings) {
+    bce->perScriptData().gcThingList().finish(data->gcthings());
   }
   if (ntrynotes) {
     bce->bytecodeSection().tryNoteList().finish(data->tryNotes());
   }
   if (nscopenotes) {
     bce->bytecodeSection().scopeNoteList().finish(data->scopeNotes());
   }
   if (nresumeoffsets) {
     bce->bytecodeSection().resumeOffsetList().finish(data->resumeOffsets());
   }
 
   return true;
 }
 
 void PrivateScriptData::trace(JSTracer* trc) {
-  auto scopearray = scopes();
-  TraceRange(trc, scopearray.size(), scopearray.data(), "scopes");
-
-  if (hasBigInts()) {
-    auto bigintarray = bigints();
-    TraceRange(trc, bigintarray.size(), bigintarray.data(), "bigints");
-  }
-
-  if (hasObjects()) {
-    auto objarray = objects();
-    TraceRange(trc, objarray.size(), objarray.data(), "objects");
+  for (JS::GCCellPtr& elem : gcthings()) {
+    gc::Cell* thing = elem.asCell();
+    TraceManuallyBarrieredGenericPointerEdge(trc, &thing, "script-gcthing");
+    if (thing != elem.asCell()) {
+      elem = JS::GCCellPtr(thing, elem.kind());
+    }
   }
 }
 
 JSScript::JSScript(JS::Realm* realm, uint8_t* stubEntry,
                    HandleScriptSourceObject sourceObject, uint32_t sourceStart,
                    uint32_t sourceEnd, uint32_t toStringStart,
                    uint32_t toStringEnd)
     :
@@ -3789,62 +3760,58 @@ bool JSScript::initScriptName(JSContext*
     return false;
   }
 
   return true;
 }
 
 /* static */
 bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script,
-                                       uint32_t nscopes, uint32_t nbigints,
-                                       uint32_t nobjects, uint32_t ntrynotes,
+                                       uint32_t ngcthings, uint32_t ntrynotes,
                                        uint32_t nscopenotes,
                                        uint32_t nresumeoffsets) {
   cx->check(script);
   MOZ_ASSERT(!script->data_);
 
   uint32_t dataSize;
 
-  PrivateScriptData* data =
-      PrivateScriptData::new_(cx, nscopes, nbigints, nobjects, ntrynotes,
-                              nscopenotes, nresumeoffsets, &dataSize);
+  PrivateScriptData* data = PrivateScriptData::new_(
+      cx, ngcthings, ntrynotes, nscopenotes, nresumeoffsets, &dataSize);
   if (!data) {
     return false;
   }
 
   script->data_ = data;
   script->dataSize_ = dataSize;
   AddCellMemory(script, dataSize, MemoryUse::ScriptPrivateData);
 
   return true;
 }
 
 /* static */
 bool JSScript::initFunctionPrototype(JSContext* cx, HandleScript script,
                                      HandleFunction functionProto) {
-  uint32_t numScopes = 1;
-  uint32_t numBigInts = 0;
-  uint32_t numObjects = 0;
+  uint32_t numGCThings = 1;
   uint32_t numTryNotes = 0;
   uint32_t numScopeNotes = 0;
   uint32_t nresumeoffsets = 0;
-  if (!createPrivateScriptData(cx, script, numScopes, numBigInts, numObjects,
-                               numTryNotes, numScopeNotes, nresumeoffsets)) {
+  if (!createPrivateScriptData(cx, script, numGCThings, numTryNotes,
+                               numScopeNotes, nresumeoffsets)) {
     return false;
   }
 
   RootedScope enclosing(cx, &cx->global()->emptyGlobalScope());
   Scope* functionProtoScope = FunctionScope::create(cx, nullptr, false, false,
                                                     functionProto, enclosing);
   if (!functionProtoScope) {
     return false;
   }
 
-  mozilla::Span<GCPtrScope> scopes = script->data_->scopes();
-  scopes[0].init(functionProtoScope);
+  mozilla::Span<JS::GCCellPtr> gcthings = script->data_->gcthings();
+  gcthings[0] = JS::GCCellPtr(functionProtoScope);
 
   uint32_t codeLength = 1;
   uint32_t noteLength = 1;
   uint32_t numAtoms = 0;
   if (!script->createSharedScriptData(cx, codeLength, noteLength, numAtoms)) {
     return false;
   }
 
@@ -3922,17 +3889,17 @@ bool JSScript::fullyInitFromEmitter(JSCo
   // If initialization fails, we must call JSScript::freeScriptData in order to
   // neuter the script. Various things that iterate raw scripts in a GC arena
   // use the presense of this data to detect if initialization is complete.
   auto scriptDataGuard =
       mozilla::MakeScopeExit([&] { script->freeScriptData(); });
 
   /* The counts of indexed things must be checked during code generation. */
   MOZ_ASSERT(bce->perScriptData().atomIndices()->count() <= INDEX_LIMIT);
-  MOZ_ASSERT(bce->perScriptData().objectList().length <= INDEX_LIMIT);
+  MOZ_ASSERT(bce->perScriptData().gcThingList().length() <= INDEX_LIMIT);
 
   uint64_t nslots =
       bce->maxFixedSlots +
       static_cast<uint64_t>(bce->bytecodeSection().maxStackDepth());
   if (nslots > UINT32_MAX) {
     bce->reportError(nullptr, JSMSG_NEED_DIET, js_script_str);
     return false;
   }
@@ -3981,17 +3948,17 @@ bool JSScript::fullyInitFromEmitter(JSCo
     } else {
       fun->setScript(script);
     }
   }
 
   // Part of the parse result – the scope containing each inner function – must
   // be stored in the inner function itself. Do this now that compilation is
   // complete and can no longer fail.
-  bce->perScriptData().objectList().finishInnerFunctions();
+  bce->perScriptData().gcThingList().finishInnerFunctions();
 
 #ifdef JS_STRUCTURED_SPEW
   // We want this to happen after line number initialization to allow filtering
   // to work.
   script->setSpewEnabled(cx->spewer().enabled(script));
 #endif
 
 #ifdef DEBUG
@@ -4400,142 +4367,125 @@ static JSObject* CloneInnerInterpretedFu
 
   if (!JSFunction::setTypeForScriptedFunction(cx, clone)) {
     return nullptr;
   }
 
   return clone;
 }
 
+static JSObject* CloneScriptObject(JSContext* cx, PrivateScriptData* srcData,
+                                   HandleObject obj,
+                                   Handle<ScriptSourceObject*> sourceObject,
+                                   JS::HandleVector<StackGCCellPtr> gcThings) {
+  if (obj->is<RegExpObject>()) {
+    return CloneScriptRegExpObject(cx, obj->as<RegExpObject>());
+  }
+
+  if (obj->is<JSFunction>()) {
+    HandleFunction innerFun = obj.as<JSFunction>();
+    if (innerFun->isNative()) {
+      if (cx->realm() != innerFun->realm()) {
+        MOZ_ASSERT(innerFun->isAsmJSNative());
+        JS_ReportErrorASCII(cx, "AsmJS modules do not yet support cloning.");
+        return nullptr;
+      }
+      return innerFun;
+    }
+
+    if (innerFun->isInterpretedLazy()) {
+      AutoRealm ar(cx, innerFun);
+      if (!JSFunction::getOrCreateScript(cx, innerFun)) {
+        return nullptr;
+      }
+    }
+
+    Scope* enclosing = innerFun->nonLazyScript()->enclosingScope();
+    uint32_t scopeIndex = FindScopeIndex(srcData->gcthings(), *enclosing);
+    RootedScope enclosingClone(cx,
+                               &gcThings[scopeIndex].get().get().as<Scope>());
+    return CloneInnerInterpretedFunction(cx, enclosingClone, innerFun,
+                                         sourceObject);
+  }
+
+  return DeepCloneObjectLiteral(cx, obj, TenuredObject);
+}
+
 /* static */
 bool PrivateScriptData::Clone(JSContext* cx, HandleScript src, HandleScript dst,
                               MutableHandle<GCVector<Scope*>> scopes) {
   PrivateScriptData* srcData = src->data_;
-  uint32_t nscopes = srcData->scopes().size();
-  uint32_t nbigints = srcData->hasBigInts() ? srcData->bigints().size() : 0;
-  uint32_t nobjects = srcData->hasObjects() ? srcData->objects().size() : 0;
+  uint32_t ngcthings = srcData->gcthings().size();
   uint32_t ntrynotes = srcData->hasTryNotes() ? srcData->tryNotes().size() : 0;
   uint32_t nscopenotes =
       srcData->hasScopeNotes() ? srcData->scopeNotes().size() : 0;
   uint32_t nresumeoffsets =
       srcData->hasResumeOffsets() ? srcData->resumeOffsets().size() : 0;
 
-  /* Scopes */
-
-  // The passed in scopes vector contains body scopes that needed to be
-  // cloned especially, depending on whether the script is a function or
-  // global scope. Starting at scopes.length() means we only deal with
-  // intra-body scopes.
-  {
-    MOZ_ASSERT(nscopes != 0);
-    MOZ_ASSERT(src->bodyScopeIndex() + 1 == scopes.length());
-    RootedScope original(cx);
-    RootedScope clone(cx);
-    for (const GCPtrScope& elem : srcData->scopes().From(scopes.length())) {
-      original = elem.get();
-      uint32_t scopeIndex =
-          FindScopeIndex(srcData->scopes(), *original->enclosing());
-      clone = Scope::clone(cx, original, scopes[scopeIndex]);
-      if (!clone || !scopes.append(clone)) {
+  // Clone GC things.
+  JS::RootedVector<StackGCCellPtr> gcThings(cx);
+  size_t scopeIndex = 0;
+  Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject());
+  RootedObject obj(cx);
+  RootedScope scope(cx);
+  RootedScope enclosingScope(cx);
+  RootedBigInt bigint(cx);
+  for (JS::GCCellPtr gcThing : srcData->gcthings()) {
+    if (gcThing.is<JSObject>()) {
+      obj = &gcThing.as<JSObject>();
+      JSObject* clone =
+          CloneScriptObject(cx, srcData, obj, sourceObject, gcThings);
+      if (!clone || !gcThings.append(JS::GCCellPtr(clone))) {
         return false;
       }
-    }
-  }
-
-  /* BigInts */
-
-  Rooted<BigIntVector> bigints(cx);
-  if (nbigints != 0) {
-    RootedBigInt clone(cx);
-    for (const GCPtrBigInt& elem : srcData->bigints()) {
-      if (cx->zone() == elem->zone()) {
-        clone = elem;
+    } else if (gcThing.is<Scope>()) {
+      // The passed in scopes vector contains body scopes that needed to be
+      // cloned especially, depending on whether the script is a function or
+      // global scope. Clone all other scopes.
+      if (scopeIndex < scopes.length()) {
+        if (!gcThings.append(JS::GCCellPtr(scopes[scopeIndex].get()))) {
+          return false;
+        }
       } else {
-        RootedBigInt b(cx, elem);
-        clone = BigInt::copy(cx, b);
+        scope = &gcThing.as<Scope>();
+        uint32_t enclosingScopeIndex =
+            FindScopeIndex(srcData->gcthings(), *scope->enclosing());
+        enclosingScope = &gcThings[enclosingScopeIndex].get().get().as<Scope>();
+        Scope* clone = Scope::clone(cx, scope, enclosingScope);
+        if (!clone || !gcThings.append(JS::GCCellPtr(clone))) {
+          return false;
+        }
+      }
+      scopeIndex++;
+    } else {
+      bigint = &gcThing.as<BigInt>();
+      BigInt* clone = bigint;
+      if (cx->zone() != bigint->zone()) {
+        clone = BigInt::copy(cx, bigint);
         if (!clone) {
           return false;
         }
       }
-      if (!bigints.append(clone)) {
-        return false;
-      }
-    }
-  }
-
-  /* Objects */
-
-  RootedObjectVector objects(cx);
-  if (nobjects != 0) {
-    RootedObject obj(cx);
-    RootedObject clone(cx);
-    Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject());
-    for (const GCPtrObject& elem : srcData->objects()) {
-      obj = elem.get();
-      clone = nullptr;
-      if (obj->is<RegExpObject>()) {
-        clone = CloneScriptRegExpObject(cx, obj->as<RegExpObject>());
-      } else if (obj->is<JSFunction>()) {
-        RootedFunction innerFun(cx, &obj->as<JSFunction>());
-        if (innerFun->isNative()) {
-          if (cx->realm() != innerFun->realm()) {
-            MOZ_ASSERT(innerFun->isAsmJSNative());
-            JS_ReportErrorASCII(cx,
-                                "AsmJS modules do not yet support cloning.");
-            return false;
-          }
-          clone = innerFun;
-        } else {
-          if (innerFun->isInterpretedLazy()) {
-            AutoRealm ar(cx, innerFun);
-            if (!JSFunction::getOrCreateScript(cx, innerFun)) {
-              return false;
-            }
-          }
-
-          Scope* enclosing = innerFun->nonLazyScript()->enclosingScope();
-          uint32_t scopeIndex = FindScopeIndex(srcData->scopes(), *enclosing);
-          RootedScope enclosingClone(cx, scopes[scopeIndex]);
-          clone = CloneInnerInterpretedFunction(cx, enclosingClone, innerFun,
-                                                sourceObject);
-        }
-      } else {
-        clone = DeepCloneObjectLiteral(cx, obj, TenuredObject);
-      }
-
-      if (!clone || !objects.append(clone)) {
+      if (!gcThings.append(JS::GCCellPtr(clone))) {
         return false;
       }
     }
   }
 
   // Create the new PrivateScriptData on |dst| and fill it in.
-  if (!JSScript::createPrivateScriptData(cx, dst, nscopes, nbigints, nobjects,
-                                         ntrynotes, nscopenotes,
-                                         nresumeoffsets)) {
+  if (!JSScript::createPrivateScriptData(cx, dst, ngcthings, ntrynotes,
+                                         nscopenotes, nresumeoffsets)) {
     return false;
   }
 
   PrivateScriptData* dstData = dst->data_;
   {
-    auto array = dstData->scopes();
-    for (uint32_t i = 0; i < nscopes; ++i) {
-      array[i].init(scopes[i]);
-    }
-  }
-  if (nbigints) {
-    auto array = dstData->bigints();
-    for (unsigned i = 0; i < nbigints; ++i) {
-      array[i].init(bigints[i]);
-    }
-  }
-  if (nobjects) {
-    auto array = dstData->objects();
-    for (unsigned i = 0; i < nobjects; ++i) {
-      array[i].init(objects[i]);
+    auto array = dstData->gcthings();
+    for (uint32_t i = 0; i < ngcthings; ++i) {
+      array[i] = gcThings[i].get().get();
     }
   }
   if (ntrynotes) {
     std::copy_n(srcData->tryNotes().begin(), ntrynotes,
                 dstData->tryNotes().begin());
   }
   if (nscopenotes) {
     std::copy_n(srcData->scopeNotes().begin(), nscopenotes,
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -30,16 +30,17 @@
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
 #include "js/CompileOptions.h"
 #include "js/UbiNode.h"
 #include "js/UniquePtr.h"
 #include "js/Utility.h"
 #include "util/StructuredSpewer.h"
+#include "vm/BigIntType.h"
 #include "vm/BytecodeIterator.h"
 #include "vm/BytecodeLocation.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSAtom.h"
 #include "vm/NativeObject.h"
 #include "vm/Scope.h"
 #include "vm/Shape.h"
 #include "vm/SharedImmutableStringsCache.h"
@@ -1428,76 +1429,65 @@ XDRResult XDRLazyScript(XDRState<mode>* 
 template <XDRMode mode>
 XDRResult XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp);
 
 // [SMDOC] - JSScript data layout (unshared)
 //
 // PrivateScriptData stores variable-length data associated with a script.
 // Abstractly a PrivateScriptData consists of all these arrays:
 //
-//   * A non-empty array of GCPtrScope in scopes()
-//   * A possibly-empty array of GCPtrBigInt in bigints()
-//   * A possibly-empty array of JSObject* in objects()
+//   * A non-empty array of GCCellPtr in gcthings()
 //   * A possibly-empty array of JSTryNote in tryNotes()
 //   * A possibly-empty array of ScopeNote in scopeNotes()
 //   * A possibly-empty array of uint32_t in resumeOffsets()
 //
 // Accessing any of these arrays just requires calling the appropriate public
 // Span-computing function.
 //
 // Under the hood, PrivateScriptData is a small class followed by a memory
 // layout that compactly encodes all these arrays, in this manner (only
 // explicit padding, "--" separators for readability only):
 //
 //   <PrivateScriptData itself>
 //   --
-//   (OPTIONAL) PackedSpan for bigints()
-//   (OPTIONAL) PackedSpan for objects()
-//   (OPTIONAL) PackedSpan for tryNotes()
-//   (OPTIONAL) PackedSpan for scopeNotes()
-//   (OPTIONAL) PackedSpan for resumeOffsets()
+//   (OPTIONAL) PackedSpan for gcthings()
 //   --
-//   (REQUIRED) All the GCPtrScopes that constitute scopes()
-//   --
-//   (OPTIONAL) All the GCPtrBigInts that constitute bigints()
-//   --
-//   (OPTIONAL) All the GCPtrObjects that constitute objects()
+//   (REQUIRED) All the GCCellPtrs that constitute gcthings()
 //   --
 //   (OPTIONAL) All the JSTryNotes that constitute tryNotes()
 //   --
 //   (OPTIONAL) All the ScopeNotes that constitute scopeNotes()
 //   --
 //   (OPTIONAL) All the uint32_t's that constitute resumeOffsets()
 //
 // The contents of PrivateScriptData indicate which optional items are present.
 // PrivateScriptData::packedOffsets contains bit-fields, one per array.
 // Multiply each packed offset by sizeof(uint32_t) to compute a *real* offset.
 //
-// PrivateScriptData::scopesOffset indicates where scopes() begins. The bound
-// of five PackedSpans ensures we can encode this offset compactly.
-// PrivateScriptData::nscopes indicates the number of GCPtrScopes in scopes().
+// PrivateScriptData::gcthingsOffset indicates where gcthings() begins. The
+// bound of five PackedSpans ensures we can encode this offset compactly.
+// PrivateScriptData::ngcthings indicates the number of GCCellPtrs in
+// gcthings().
 //
 // The other PackedScriptData::*Offset fields indicate where a potential
 // corresponding PackedSpan resides. If the packed offset is 0, there is no
 // PackedSpan, and the array is empty. Otherwise the PackedSpan's uint32_t
 // offset and length fields store: 1) a *non-packed* offset (a literal count of
 // bytes offset from the *start* of PrivateScriptData struct) to the
 // corresponding array, and 2) the number of elements in the array,
 // respectively.
 class alignas(uintptr_t) PrivateScriptData final {
   struct PackedOffsets {
     static constexpr size_t SCALE = sizeof(uint32_t);
     static constexpr size_t MAX_OFFSET = 0b1111;
 
-    // (Scaled) offset to Scopes
-    uint32_t scopesOffset : 8;
+    // (Scaled) offset to GC things.
+    uint32_t gcthingsOffset : 8;
 
     // (Scaled) offset to Spans. These are set to 0 if they don't exist.
-    uint32_t bigintsSpanOffset : 4;
-    uint32_t objectsSpanOffset : 4;
     uint32_t tryNotesSpanOffset : 4;
     uint32_t scopeNotesSpanOffset : 4;
     uint32_t resumeOffsetsSpanOffset : 4;
   };
 
   // Detect accidental size regressions.
   static_assert(sizeof(PackedOffsets) == sizeof(uint32_t),
                 "unexpected bit-field packing");
@@ -1506,17 +1496,17 @@ class alignas(uintptr_t) PrivateScriptDa
   // the private data.
   struct alignas(uintptr_t) PackedSpan {
     uint32_t offset;
     uint32_t length;
   };
 
   // Concrete Fields
   PackedOffsets packedOffsets = {};  // zeroes
-  uint32_t nscopes = 0;
+  uint32_t ngcthings = 0;
 
   js::FieldInitializers fieldInitializers_ = js::FieldInitializers::Invalid();
 
   // Translate an offset into a concrete pointer.
   template <typename T>
   T* offsetToPointer(size_t offset) {
     uintptr_t base = reinterpret_cast<uintptr_t>(this);
     uintptr_t elem = base + offset;
@@ -1541,65 +1531,54 @@ class alignas(uintptr_t) PrivateScriptDa
   // Helpers for creating initializing trailing data
   template <typename T>
   void initSpan(size_t* cursor, uint32_t scaledSpanOffset, size_t length);
 
   template <typename T>
   void initElements(size_t offset, size_t length);
 
   // Size to allocate
-  static size_t AllocationSize(uint32_t nscopes, uint32_t nbigints,
-                               uint32_t nobjects, uint32_t ntrynotes,
+  static size_t AllocationSize(uint32_t ngcthings, uint32_t ntrynotes,
                                uint32_t nscopenotes, uint32_t nresumeoffsets);
 
   // Initialize header and PackedSpans
-  PrivateScriptData(uint32_t nscopes_, uint32_t nbigints, uint32_t nobjects,
-                    uint32_t ntrynotes, uint32_t nscopenotes,
-                    uint32_t nresumeoffsets);
+  PrivateScriptData(uint32_t ngcthings, uint32_t ntrynotes,
+                    uint32_t nscopenotes, uint32_t nresumeoffsets);
 
  public:
   // Accessors for typed array spans.
-  mozilla::Span<GCPtrScope> scopes() {
-    GCPtrScope* base =
-        packedOffsetToPointer<GCPtrScope>(packedOffsets.scopesOffset);
-    return mozilla::MakeSpan(base, nscopes);
-  }
-  mozilla::Span<GCPtrBigInt> bigints() {
-    return packedOffsetToSpan<GCPtrBigInt>(packedOffsets.bigintsSpanOffset);
-  }
-  mozilla::Span<GCPtrObject> objects() {
-    return packedOffsetToSpan<GCPtrObject>(packedOffsets.objectsSpanOffset);
+  mozilla::Span<JS::GCCellPtr> gcthings() {
+    JS::GCCellPtr* base =
+        packedOffsetToPointer<JS::GCCellPtr>(packedOffsets.gcthingsOffset);
+    return mozilla::MakeSpan(base, ngcthings);
   }
   mozilla::Span<JSTryNote> tryNotes() {
     return packedOffsetToSpan<JSTryNote>(packedOffsets.tryNotesSpanOffset);
   }
   mozilla::Span<ScopeNote> scopeNotes() {
     return packedOffsetToSpan<ScopeNote>(packedOffsets.scopeNotesSpanOffset);
   }
   mozilla::Span<uint32_t> resumeOffsets() {
     return packedOffsetToSpan<uint32_t>(packedOffsets.resumeOffsetsSpanOffset);
   }
 
   // Fast tests for if array exists
-  bool hasBigInts() const { return packedOffsets.bigintsSpanOffset != 0; }
-  bool hasObjects() const { return packedOffsets.objectsSpanOffset != 0; }
   bool hasTryNotes() const { return packedOffsets.tryNotesSpanOffset != 0; }
   bool hasScopeNotes() const { return packedOffsets.scopeNotesSpanOffset != 0; }
   bool hasResumeOffsets() const {
     return packedOffsets.resumeOffsetsSpanOffset != 0;
   }
   void setFieldInitializers(FieldInitializers fieldInitializers) {
     fieldInitializers_ = fieldInitializers;
   }
   const FieldInitializers& getFieldInitializers() { return fieldInitializers_; }
 
   // Allocate a new PrivateScriptData. Headers and GCPtrs are initialized.
   // The size of allocation is returned as an out parameter.
-  static PrivateScriptData* new_(JSContext* cx, uint32_t nscopes,
-                                 uint32_t nbigints, uint32_t nobjects,
+  static PrivateScriptData* new_(JSContext* cx, uint32_t ngcthings,
                                  uint32_t ntrynotes, uint32_t nscopenotes,
                                  uint32_t nresumeoffsets, uint32_t* dataSize);
 
   template <XDRMode mode>
   static MOZ_MUST_USE XDRResult XDR(js::XDRState<mode>* xdr,
                                     js::HandleScript script,
                                     js::HandleScriptSourceObject sourceObject,
                                     js::HandleScope scriptEnclosingScope,
@@ -1638,17 +1617,17 @@ class alignas(uintptr_t) SharedScriptDat
   uint32_t mainOffset = 0;
 
   // Fixed frame slots.
   uint32_t nfixed = 0;
 
   // Slots plus maximum stack depth.
   uint32_t nslots = 0;
 
-  // Index into the scopes array of the body scope.
+  // Index into the gcthings array of the body scope.
   uint32_t bodyScopeIndex = 0;
 
   // Number of IC entries to allocate in JitScript for Baseline ICs.
   uint32_t numICEntries = 0;
 
   // ES6 function length.
   uint16_t funLength = 0;
 
@@ -2104,18 +2083,17 @@ class JSScript : public js::gc::TenuredC
                           uint32_t sourceStart, uint32_t sourceEnd,
                           uint32_t toStringStart, uint32_t toStringEnd);
 
   // NOTE: If you use createPrivateScriptData directly instead of via
   // fullyInitFromEmitter, you are responsible for notifying the debugger
   // after successfully creating the script.
   static bool createPrivateScriptData(JSContext* cx,
                                       JS::Handle<JSScript*> script,
-                                      uint32_t nscopes, uint32_t nbigints,
-                                      uint32_t nobjects, uint32_t ntrynotes,
+                                      uint32_t ngcthings, uint32_t ntrynotes,
                                       uint32_t nscopenotes,
                                       uint32_t nresumeoffsets);
 
  private:
   void initFromFunctionBox(js::frontend::FunctionBox* funbox);
 
  public:
   static bool fullyInitFromEmitter(JSContext* cx, js::HandleScript script,
@@ -2706,26 +2684,34 @@ class JSScript : public js::gc::TenuredC
   bool functionHasExtraBodyVarScope() const {
     bool res = hasFlag(ImmutableFlags::FunctionHasExtraBodyVarScope);
     MOZ_ASSERT_IF(res, functionHasParameterExprs());
     return res;
   }
 
   js::VarScope* functionExtraBodyVarScope() const {
     MOZ_ASSERT(functionHasExtraBodyVarScope());
-    for (js::Scope* scope : scopes()) {
+    for (JS::GCCellPtr gcThing : gcthings()) {
+      if (!gcThing.is<js::Scope>()) {
+        continue;
+      }
+      js::Scope* scope = &gcThing.as<js::Scope>();
       if (scope->kind() == js::ScopeKind::FunctionBodyVar) {
         return &scope->as<js::VarScope>();
       }
     }
     MOZ_CRASH("Function extra body var scope not found");
   }
 
   bool needsBodyEnvironment() const {
-    for (js::Scope* scope : scopes()) {
+    for (JS::GCCellPtr gcThing : gcthings()) {
+      if (!gcThing.is<js::Scope>()) {
+        continue;
+      }
+      js::Scope* scope = &gcThing.as<js::Scope>();
       if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) {
         return true;
       }
     }
     return false;
   }
 
   inline js::LexicalScope* maybeNamedLambdaScope() const;
@@ -2812,32 +2798,22 @@ class JSScript : public js::gc::TenuredC
   size_t sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const;
 
   void addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf,
                           size_t* sizeOfJitScript,
                           size_t* sizeOfBaselineFallbackStubs) const;
 
   size_t dataSize() const { return dataSize_; }
 
-  bool hasBigInts() const { return data_->hasBigInts(); }
-  bool hasObjects() const { return data_->hasObjects(); }
   bool hasTrynotes() const { return data_->hasTryNotes(); }
   bool hasScopeNotes() const { return data_->hasScopeNotes(); }
   bool hasResumeOffsets() const { return data_->hasResumeOffsets(); }
 
-  mozilla::Span<const js::GCPtrScope> scopes() const { return data_->scopes(); }
-
-  mozilla::Span<const js::GCPtrBigInt> bigints() const {
-    MOZ_ASSERT(hasBigInts());
-    return data_->bigints();
-  }
-
-  mozilla::Span<const js::GCPtrObject> objects() const {
-    MOZ_ASSERT(hasObjects());
-    return data_->objects();
+  mozilla::Span<const JS::GCCellPtr> gcthings() const {
+    return data_->gcthings();
   }
 
   mozilla::Span<const JSTryNote> trynotes() const {
     MOZ_ASSERT(hasTrynotes());
     return data_->tryNotes();
   }
 
   mozilla::Span<const js::ScopeNote> scopeNotes() const {
@@ -2895,26 +2871,28 @@ class JSScript : public js::gc::TenuredC
     return getAtom(index)->asPropertyName();
   }
 
   js::PropertyName* getName(jsbytecode* pc) const {
     return getAtom(pc)->asPropertyName();
   }
 
   JSObject* getObject(size_t index) {
-    MOZ_ASSERT(objects()[index]->isTenured());
-    return objects()[index];
+    MOZ_ASSERT(gcthings()[index].asCell()->isTenured());
+    return &gcthings()[index].as<JSObject>();
   }
 
   JSObject* getObject(jsbytecode* pc) {
     MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t)));
     return getObject(GET_UINT32_INDEX(pc));
   }
 
-  js::Scope* getScope(size_t index) const { return scopes()[index]; }
+  js::Scope* getScope(size_t index) const {
+    return &gcthings()[index].as<js::Scope>();
+  }
 
   js::Scope* getScope(jsbytecode* pc) const {
     // This method is used to get a scope directly using a JSOp with an
     // index. To search through ScopeNotes to look for a Scope using pc,
     // use lookupScope.
     MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t)));
     MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPE,
                "Did you mean to use lookupScope(pc)?");
@@ -2929,17 +2907,19 @@ class JSScript : public js::gc::TenuredC
       return functionNonDelazifying();
     }
     return nullptr;
   }
 
   inline js::RegExpObject* getRegExp(size_t index);
   inline js::RegExpObject* getRegExp(jsbytecode* pc);
 
-  js::BigInt* getBigInt(size_t index) { return bigints()[index]; }
+  js::BigInt* getBigInt(size_t index) {
+    return &gcthings()[index].as<js::BigInt>();
+  }
 
   js::BigInt* getBigInt(jsbytecode* pc) {
     MOZ_ASSERT(containsPC(pc));
     MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_BIGINT);
     return getBigInt(GET_UINT32_INDEX(pc));
   }
 
   // The following 3 functions find the static scope just before the
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -655,20 +655,22 @@ void Realm::setNewObjectMetadata(JSConte
     if (!objects_.objectMetadataTable->add(cx, obj, metadata)) {
       oomUnsafe.crash("setNewObjectMetadata");
     }
   }
 }
 
 static bool AddInnerLazyFunctionsFromScript(
     JSScript* script, MutableHandleObjectVector lazyFunctions) {
-  if (!script->hasObjects()) {
-    return true;
-  }
-  for (JSObject* obj : script->objects()) {
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) {
       if (!lazyFunctions.append(obj)) {
         return false;
       }
     }
   }
   return true;
 }