Bug 1452982 part 6 - Remove ZoneGroup. r=jonco
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 13 Apr 2018 08:45:13 +0200
changeset 466674 73fa6225a03ff9566bb77a140cd9356b861d5b3d
parent 466673 538106142cb2198442edecd824257e87be8f0eed
child 466675 cf543bdd424f24f5320d6284c19cb5c51eda054a
child 466700 cc008b8e4c3c4f2fafa29f7e722e2dd961c26f71
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1452982
milestone61.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 1452982 part 6 - Remove ZoneGroup. r=jonco
dom/base/nsGlobalWindowOuter.cpp
js/src/builtin/ModuleObject.cpp
js/src/builtin/TestingFunctions.cpp
js/src/gc/ArenaList.h
js/src/gc/GC.cpp
js/src/gc/GCRuntime.h
js/src/gc/Nursery.h
js/src/gc/PublicIterators.h
js/src/gc/Verifier.cpp
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/gc/ZoneGroup.cpp
js/src/gc/ZoneGroup.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/shell/js.cpp
js/src/threading/ProtectedData.cpp
js/src/threading/ProtectedData.h
js/src/vm/GlobalObject.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/MemoryMetrics.cpp
js/src/vm/Runtime.cpp
js/src/vm/SelfHosting.cpp
js/src/vm/TypeInference.cpp
js/xpconnect/src/Sandbox.cpp
js/xpconnect/src/XPCShellImpl.cpp
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1567,46 +1567,29 @@ InitializeLegacyNetscapeObject(JSContext
   /* Define PrivilegeManager object with the necessary "static" methods. */
   obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr);
   NS_ENSURE_TRUE(obj, false);
 
   return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec);
 }
 
 static JS::CompartmentCreationOptions&
-SelectZoneGroup(nsGlobalWindowInner* aNewInner,
-                JS::CompartmentCreationOptions& aOptions)
-{
-  JS::CompartmentCreationOptions options;
-
+SelectZone(nsGlobalWindowInner* aNewInner,
+           JS::CompartmentCreationOptions& aOptions)
+{
   if (aNewInner->GetOuterWindow()) {
     nsGlobalWindowOuter *top = aNewInner->GetTopInternal();
 
-    // If we have a top-level window, use its zone (and zone group).
+    // If we have a top-level window, use its zone.
     if (top && top->GetGlobalJSObject()) {
       return aOptions.setExistingZone(top->GetGlobalJSObject());
     }
   }
 
-  // If we're in the parent process, don't bother with zone groups.
-  if (XRE_IsParentProcess()) {
-    return aOptions.setNewZoneInSystemZoneGroup();
-  }
-
-  // Otherwise, find a zone group from the TabGroup. Typically we only have to
-  // go through one iteration of this loop.
-  RefPtr<TabGroup> tabGroup = aNewInner->TabGroup();
-  for (nsPIDOMWindowOuter* outer : tabGroup->GetWindows()) {
-    nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(outer);
-    if (JSObject* global = window->GetGlobalJSObject()) {
-      return aOptions.setNewZoneInExistingZoneGroup(global);
-    }
-  }
-
-  return aOptions.setNewZoneInNewZoneGroup();
+  return aOptions.setNewZone();
 }
 
 /**
  * Create a new global object that will be used for an inner window.
  * Return the native global and an nsISupports 'holder' that can be used
  * to manage the lifetime of it.
  */
 static nsresult
@@ -1623,17 +1606,17 @@ CreateNativeGlobalForInner(JSContext* aC
 
   // DOMWindow with nsEP is not supported, we have to make sure
   // no one creates one accidentally.
   nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(aPrincipal);
   MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");
 
   JS::CompartmentOptions options;
 
-  SelectZoneGroup(aNewInner, options.creationOptions());
+  SelectZone(aNewInner, options.creationOptions());
 
   options.creationOptions().setSecureContext(aIsSecureContext);
 
   xpc::InitGlobalObjectOptions(options, aPrincipal);
 
   // Determine if we need the Components object.
   bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) ||
                         TreatAsRemoteXUL(aPrincipal);
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -344,17 +344,17 @@ IndirectBindingMap::trace(JSTracer* trc)
 bool
 IndirectBindingMap::put(JSContext* cx, HandleId name,
                         HandleModuleEnvironmentObject environment, HandleId localName)
 {
     // This object might have been allocated on the background parsing thread in
     // different zone to the final module. Lazily allocate the map so we don't
     // have to switch its zone when merging compartments.
     if (!map_) {
-        MOZ_ASSERT(!cx->zone()->group()->createdForHelperThread());
+        MOZ_ASSERT(!cx->zone()->createdForHelperThread());
         map_.emplace(cx->zone());
         if (!map_->init()) {
             map_.reset();
             ReportOutOfMemory(cx);
             return false;
         }
     }
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1607,18 +1607,17 @@ ResetOOMFailure(JSContext* cx, unsigned 
     js::oom::ResetSimulatedOOM();
     return true;
 }
 
 static size_t
 CountCompartments(JSContext* cx)
 {
     size_t count = 0;
-    ZoneGroup* group = cx->compartment()->zone()->group();
-    for (auto zone : group->zones())
+    for (auto zone : cx->runtime()->gc.zones())
         count += zone->compartments().length();
     return count;
 }
 
 static bool
 OOMTest(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/gc/ArenaList.h
+++ b/js/src/gc/ArenaList.h
@@ -266,17 +266,17 @@ class ArenaLists
     ZoneGroupData<Arena*> gcScriptArenasToUpdate;
     ZoneGroupData<Arena*> gcObjectGroupArenasToUpdate;
 
     // The list of empty arenas which are collected during sweep phase and released at the end of
     // sweeping every sweep group.
     ZoneGroupData<Arena*> savedEmptyArenas;
 
   public:
-    explicit ArenaLists(JSRuntime* rt, ZoneGroup* group);
+    explicit ArenaLists(JSRuntime* rt, JS::Zone* zone);
     ~ArenaLists();
 
     const void* addressOfFreeList(AllocKind thingKind) const {
         return reinterpret_cast<const void*>(&freeLists_.refNoCheck()[thingKind]);
     }
 
     inline Arena* getFirstArena(AllocKind thingKind) const;
     inline Arena* getFirstArenaToSweep(AllocKind thingKind) const;
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -930,17 +930,16 @@ GCRuntime::releaseArena(Arena* arena, co
     if (isBackgroundSweeping())
         arena->zone->threshold.updateForRemovedArena(tunables);
     return arena->chunk()->releaseArena(rt, arena, lock);
 }
 
 GCRuntime::GCRuntime(JSRuntime* rt) :
     rt(rt),
     systemZone(nullptr),
-    systemZoneGroup(nullptr),
     atomsZone(nullptr),
     stats_(rt),
     marker(rt),
     usage(nullptr),
     nextCellUniqueId_(LargestTaggedNullCellPointer + 1), // Ensure disjoint from null tagged pointers.
     numArenasFreeCommitted(0),
     verifyPreData(nullptr),
     chunkAllocationSinceLastGC(false),
@@ -1267,17 +1266,17 @@ GCRuntime::finish()
         for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
             for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
                 js_delete(comp.get());
             zone->compartments().clear();
             js_delete(zone.get());
         }
     }
 
-    groups().clear();
+    zones().clear();
 
     FreeChunkPool(fullChunks_.ref());
     FreeChunkPool(availableChunks_.ref());
     FreeChunkPool(emptyChunks_.ref());
 
     FinishTrace();
 
     nursery().printTotalProfileTimes();
@@ -2989,29 +2988,29 @@ GCRuntime::releaseHeldRelocatedArenasWit
 {
 #ifdef DEBUG
     unprotectHeldRelocatedArenas();
     releaseRelocatedArenasWithoutUnlocking(relocatedArenasToRelease, lock);
     relocatedArenasToRelease = nullptr;
 #endif
 }
 
-ArenaLists::ArenaLists(JSRuntime* rt, ZoneGroup* group)
+ArenaLists::ArenaLists(JSRuntime* rt, Zone* zone)
   : runtime_(rt),
-    freeLists_(group),
-    arenaLists_(group),
+    freeLists_(zone),
+    arenaLists_(zone),
     backgroundFinalizeState_(),
     arenaListsToSweep_(),
-    incrementalSweptArenaKind(group, AllocKind::LIMIT),
-    incrementalSweptArenas(group),
-    gcShapeArenasToUpdate(group, nullptr),
-    gcAccessorShapeArenasToUpdate(group, nullptr),
-    gcScriptArenasToUpdate(group, nullptr),
-    gcObjectGroupArenasToUpdate(group, nullptr),
-    savedEmptyArenas(group, nullptr)
+    incrementalSweptArenaKind(zone, AllocKind::LIMIT),
+    incrementalSweptArenas(zone),
+    gcShapeArenasToUpdate(zone, nullptr),
+    gcAccessorShapeArenasToUpdate(zone, nullptr),
+    gcScriptArenasToUpdate(zone, nullptr),
+    gcObjectGroupArenasToUpdate(zone, nullptr),
+    savedEmptyArenas(zone, nullptr)
 {
     for (auto i : AllAllocKinds()) {
         freeLists()[i] = &placeholder;
         backgroundFinalizeState(i) = BFS_DONE;
         arenaListsToSweep(i) = nullptr;
     }
 }
 
@@ -3787,22 +3786,43 @@ Zone::sweepCompartments(FreeOp* fop, boo
             foundOne = true;
         }
     }
     compartments().shrinkTo(write - compartments().begin());
     MOZ_ASSERT_IF(keepAtleastOne, !compartments().empty());
 }
 
 void
-GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime)
-{
-    MOZ_ASSERT(!group->zones().empty());
-
-    Zone** read = group->zones().begin();
-    Zone** end = group->zones().end();
+GCRuntime::deleteEmptyZone(Zone* zone)
+{
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+    MOZ_ASSERT(zone->compartments().empty());
+    for (auto& i : zones()) {
+        if (i == zone) {
+            zones().erase(&i);
+            zone->destroy(rt->defaultFreeOp());
+            return;
+        }
+    }
+    MOZ_CRASH("Zone not found");
+}
+
+void
+GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime)
+{
+    MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
+    MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
+
+    if (rt->gc.numActiveZoneIters)
+        return;
+
+    assertBackgroundSweepingFinished();
+
+    Zone** read = zones().begin();
+    Zone** end = zones().end();
     Zone** write = read;
 
     while (read < end) {
         Zone* zone = *read++;
 
         if (zone->wasGCStarted()) {
             MOZ_ASSERT(!zone->isQueuedForBackgroundSweep());
             const bool zoneIsDead = zone->arenas.arenaListsAreEmpty() &&
@@ -3826,46 +3846,17 @@ GCRuntime::sweepZones(FreeOp* fop, ZoneG
                 MOZ_ASSERT_IF(arenasEmptyAtShutdown, zone->typeDescrObjects().empty());
                 zone->destroy(fop);
                 continue;
             }
             zone->sweepCompartments(fop, true, destroyingRuntime);
         }
         *write++ = zone;
     }
-    group->zones().shrinkTo(write - group->zones().begin());
-}
-
-void
-GCRuntime::sweepZoneGroups(FreeOp* fop, bool destroyingRuntime)
-{
-    MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
-    MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
-
-    if (rt->gc.numActiveZoneIters)
-        return;
-
-    assertBackgroundSweepingFinished();
-
-    ZoneGroup** read = groups().begin();
-    ZoneGroup** end = groups().end();
-    ZoneGroup** write = read;
-
-    while (read < end) {
-        ZoneGroup* group = *read++;
-        sweepZones(fop, group, destroyingRuntime);
-
-        if (group->zones().empty()) {
-            MOZ_ASSERT(numActiveZoneIters == 0);
-            fop->delete_(group);
-        } else {
-            *write++ = group;
-        }
-    }
-    groups().shrinkTo(write - groups().begin());
+    zones().shrinkTo(write - zones().begin());
 }
 
 #ifdef DEBUG
 static const char*
 AllocKindToAscii(AllocKind kind)
 {
     switch(kind) {
 #define MAKE_CASE(allocKind, traceKind, type, sizedType, bgFinal, nursery) \
@@ -7061,17 +7052,17 @@ GCRuntime::incrementalCollectSlice(Slice
 
         {
             // Re-sweep the zones list, now that background finalization is
             // finished to actually remove and free dead zones.
             gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP);
             gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::DESTROY);
             AutoSetThreadIsSweeping threadIsSweeping;
             FreeOp fop(rt);
-            sweepZoneGroups(&fop, destroyingRuntime);
+            sweepZones(&fop, destroyingRuntime);
         }
 
         MOZ_ASSERT(!startedCompacting);
         incrementalState = State::Compact;
 
         // Always yield before compacting since it is not incremental.
         if (isCompacting && !budget.isUnlimited())
             break;
@@ -7797,57 +7788,36 @@ AutoPrepareForTracing::AutoPrepareForTra
 
 JSCompartment*
 js::NewCompartment(JSContext* cx, JSPrincipals* principals,
                    const JS::CompartmentOptions& options)
 {
     JSRuntime* rt = cx->runtime();
     JS_AbortIfWrongThread(cx);
 
-    ScopedJSDeletePtr<ZoneGroup> groupHolder;
     ScopedJSDeletePtr<Zone> zoneHolder;
 
     Zone* zone = nullptr;
-    ZoneGroup* group = nullptr;
     JS::ZoneSpecifier zoneSpec = options.creationOptions().zoneSpecifier();
     switch (zoneSpec) {
       case JS::SystemZone:
-        // systemZone and possibly systemZoneGroup might be null here, in which
-        // case we'll make a zone/group and set these fields below.
+        // systemZone might be null here, in which case we'll make a zone and
+        // set this field below.
         zone = rt->gc.systemZone;
-        group = rt->gc.systemZoneGroup;
         break;
       case JS::ExistingZone:
-        zone = static_cast<Zone*>(options.creationOptions().zonePointer());
+        zone = options.creationOptions().zone();
         MOZ_ASSERT(zone);
-        group = zone->group();
-        break;
-      case JS::NewZoneInNewZoneGroup:
-        break;
-      case JS::NewZoneInSystemZoneGroup:
-        // As above, systemZoneGroup might be null here.
-        group = rt->gc.systemZoneGroup;
         break;
-      case JS::NewZoneInExistingZoneGroup:
-        group = static_cast<ZoneGroup*>(options.creationOptions().zonePointer());
-        MOZ_ASSERT(group);
+      case JS::NewZone:
         break;
     }
 
-    if (!group) {
-        MOZ_ASSERT(!zone);
-        group = cx->new_<ZoneGroup>(rt);
-        if (!group)
-            return nullptr;
-
-        groupHolder.reset(group);
-    }
-
     if (!zone) {
-        zone = cx->new_<Zone>(cx->runtime(), group);
+        zone = cx->new_<Zone>(cx->runtime());
         if (!zone)
             return nullptr;
 
         zoneHolder.reset(zone);
 
         const JSPrincipals* trusted = rt->trustedPrincipals();
         bool isSystem = principals && principals == trusted;
         if (!zone->init(isSystem)) {
@@ -7866,44 +7836,30 @@ js::NewCompartment(JSContext* cx, JSPrin
     AutoLockGC lock(rt);
 
     if (!zone->compartments().append(compartment.get())) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
     if (zoneHolder) {
-        if (!group->zones().append(zone)) {
+        if (!rt->gc.zones().append(zone)) {
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
         // Lazily set the runtime's sytem zone.
         if (zoneSpec == JS::SystemZone) {
             MOZ_RELEASE_ASSERT(!rt->gc.systemZone);
             rt->gc.systemZone = zone;
             zone->isSystem = true;
         }
     }
 
-    if (groupHolder) {
-        if (!rt->gc.groups().append(group)) {
-            ReportOutOfMemory(cx);
-            return nullptr;
-        }
-
-        // Lazily set the runtime's system zone group.
-        if (zoneSpec == JS::SystemZone || zoneSpec == JS::NewZoneInSystemZoneGroup) {
-            MOZ_RELEASE_ASSERT(!rt->gc.systemZoneGroup);
-            rt->gc.systemZoneGroup = group;
-        }
-    }
-
     zoneHolder.forget();
-    groupHolder.forget();
     return compartment.forget();
 }
 
 void
 gc::MergeCompartments(JSCompartment* source, JSCompartment* target)
 {
     JSRuntime* rt = source->runtimeFromActiveCooperatingThread();
     rt->gc.mergeCompartments(source, target);
@@ -7917,17 +7873,16 @@ GCRuntime::mergeCompartments(JSCompartme
 {
     // The source compartment must be specifically flagged as mergable.  This
     // also implies that the compartment is not visible to the debugger.
     MOZ_ASSERT(source->creationOptions_.mergeable());
     MOZ_ASSERT(source->creationOptions_.invisibleToDebugger());
 
     MOZ_ASSERT(!source->hasBeenEntered());
     MOZ_ASSERT(source->zone()->compartments().length() == 1);
-    MOZ_ASSERT(source->zone()->group()->zones().length() == 1);
 
     JSContext* cx = rt->mainContextFromOwnThread();
 
     MOZ_ASSERT(!source->zone()->wasGCStarted());
     JS::AutoAssertNoGC nogc(cx);
 
     AutoTraceSession session(rt);
 
@@ -8046,35 +8001,18 @@ GCRuntime::mergeCompartments(JSCompartme
     }
 
     // The source compartment is now completely empty, and is the only
     // compartment in its zone, which is the only zone in its group. Delete
     // compartment, zone and group without waiting for this to be cleaned up by
     // a full GC.
 
     Zone* sourceZone = source->zone();
-    ZoneGroup* sourceGroup = sourceZone->group();
     sourceZone->deleteEmptyCompartment(source);
-    sourceGroup->deleteEmptyZone(sourceZone);
-    deleteEmptyZoneGroup(sourceGroup);
-}
-
-void
-GCRuntime::deleteEmptyZoneGroup(ZoneGroup* group)
-{
-    MOZ_ASSERT(group->zones().empty());
-    MOZ_ASSERT(groups().length() > 1);
-    for (auto& i : groups()) {
-        if (i == group) {
-            groups().erase(&i);
-            js_delete(group);
-            return;
-        }
-    }
-    MOZ_CRASH("ZoneGroup not found");
+    deleteEmptyZone(sourceZone);
 }
 
 void
 GCRuntime::runDebugGC()
 {
 #ifdef JS_GC_ZEAL
     if (rt->mainContextFromOwnThread()->suppressGC)
         return;
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -28,18 +28,18 @@ namespace js {
 
 class AutoLockGC;
 class AutoLockGCBgAlloc;
 class AutoLockHelperThreadState;
 class VerifyPreTracer;
 
 namespace gc {
 
-typedef Vector<ZoneGroup*, 4, SystemAllocPolicy> ZoneGroupVector;
 using BlackGrayEdgeVector = Vector<TenuredCell*, 0, SystemAllocPolicy>;
+using ZoneVector = Vector<JS::Zone*, 4, SystemAllocPolicy>;
 
 class AutoCallGCCallbacks;
 class AutoRunParallelTask;
 class AutoTraceSession;
 class MarkingValidator;
 struct MovingTracer;
 enum class ShouldCheckThresholds;
 class SweepGroupsIter;
@@ -487,18 +487,18 @@ class GCRuntime
 
   private:
     enum IncrementalResult
     {
         Reset = 0,
         Ok
     };
 
-    // Delete an empty zone group after its contents have been merged.
-    void deleteEmptyZoneGroup(ZoneGroup* group);
+    // Delete an empty zone after its contents have been merged.
+    void deleteEmptyZone(Zone* zone);
 
     // For ArenaLists::allocateFromArena()
     friend class ArenaLists;
     Chunk* pickChunk(AutoLockGCBgAlloc& lock);
     Arena* allocateArena(Chunk* chunk, Zone* zone, AllocKind kind,
                          ShouldCheckThresholds checkThresholds, const AutoLockGC& lock);
 
 
@@ -593,18 +593,17 @@ class GCRuntime
     void startSweepingAtomsTable();
     IncrementalProgress sweepAtomsTable(FreeOp* fop, SliceBudget& budget);
     IncrementalProgress sweepWeakCaches(FreeOp* fop, SliceBudget& budget);
     IncrementalProgress finalizeAllocKind(FreeOp* fop, SliceBudget& budget, Zone* zone,
                                           AllocKind kind);
     IncrementalProgress sweepShapeTree(FreeOp* fop, SliceBudget& budget, Zone* zone);
     void endSweepPhase(bool lastGC);
     bool allCCVisibleZonesWereCollected() const;
-    void sweepZones(FreeOp* fop, ZoneGroup* group, bool lastGC);
-    void sweepZoneGroups(FreeOp* fop, bool destroyingRuntime);
+    void sweepZones(FreeOp* fop, bool destroyingRuntime);
     void decommitAllWithoutUnlocking(const AutoLockGC& lock);
     void startDecommit();
     void queueZonesForBackgroundSweep(ZoneList& zones);
     void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks);
     void assertBackgroundSweepingFinished();
     bool shouldCompact();
     void beginCompactPhase();
     IncrementalProgress compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
@@ -637,25 +636,24 @@ class GCRuntime
     void callWeakPointerZonesCallbacks() const;
     void callWeakPointerCompartmentCallbacks(JSCompartment* comp) const;
 
   public:
     JSRuntime* const rt;
 
     /* Embedders can use this zone and group however they wish. */
     UnprotectedData<JS::Zone*> systemZone;
-    UnprotectedData<ZoneGroup*> systemZoneGroup;
 
-    // List of all zone groups (protected by the GC lock).
+    // All zones in the runtime, except the atoms zone.
   private:
-    ActiveThreadOrGCTaskData<ZoneGroupVector> groups_;
+    ActiveThreadOrGCTaskData<ZoneVector> zones_;
   public:
-    ZoneGroupVector& groups() { return groups_.ref(); }
+    ZoneVector& zones() { return zones_.ref(); }
 
-    // The unique atoms zone, which has no zone group.
+    // The unique atoms zone.
     WriteOnceData<Zone*> atomsZone;
 
   private:
     UnprotectedData<gcstats::Statistics> stats_;
   public:
     gcstats::Statistics& stats() { return stats_.ref(); }
 
     GCMarker marker;
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -45,17 +45,16 @@ namespace js {
 
 class AutoLockGCBgAlloc;
 class ObjectElements;
 class PlainObject;
 class NativeObject;
 class Nursery;
 struct NurseryChunk;
 class HeapSlot;
-class ZoneGroup;
 class JSONPrinter;
 
 void SetGCZeal(JSRuntime*, uint8_t, uint32_t);
 
 namespace gc {
 class AutoMaybeStartBackgroundAllocation;
 struct Cell;
 class MinorCollectionTracer;
--- a/js/src/gc/PublicIterators.h
+++ b/js/src/gc/PublicIterators.h
@@ -12,128 +12,64 @@
 #define gc_PublicIterators_h
 
 #include "mozilla/Maybe.h"
 
 #include "gc/Zone.h"
 
 namespace js {
 
-// Iterate over all zone groups except those which may be in use by helper
-// thread parse tasks.
-class ZoneGroupsIter
-{
-    gc::AutoEnterIteration iterMarker;
-    ZoneGroup** it;
-    ZoneGroup** end;
-
-  public:
-    explicit ZoneGroupsIter(JSRuntime* rt) : iterMarker(&rt->gc) {
-        it = rt->gc.groups().begin();
-        end = rt->gc.groups().end();
-
-        if (!done() && (*it)->usedByHelperThread())
-            next();
-    }
-
-    bool done() const { return it == end; }
-
-    void next() {
-        MOZ_ASSERT(!done());
-        do {
-            it++;
-        } while (!done() && (*it)->usedByHelperThread());
-    }
-
-    ZoneGroup* get() const {
-        MOZ_ASSERT(!done());
-        return *it;
-    }
-
-    operator ZoneGroup*() const { return get(); }
-    ZoneGroup* operator->() const { return get(); }
-};
-
 // Using the atoms zone without holding the exclusive access lock is dangerous
 // because worker threads may be using it simultaneously. Therefore, it's
 // better to skip the atoms zone when iterating over zones. If you need to
 // iterate over the atoms zone, consider taking the exclusive access lock first.
 enum ZoneSelector {
     WithAtoms,
     SkipAtoms
 };
 
-// Iterate over all zones in one zone group.
-class ZonesInGroupIter
+// Iterate over all zones in the runtime, except those which may be in use by
+// parse threads.
+class ZonesIter
 {
     gc::AutoEnterIteration iterMarker;
+    JS::Zone* atomsZone;
     JS::Zone** it;
     JS::Zone** end;
 
   public:
-    explicit ZonesInGroupIter(ZoneGroup* group) : iterMarker(&group->runtime->gc) {
-        it = group->zones().begin();
-        end = group->zones().end();
-    }
-
-    bool done() const { return it == end; }
-
-    void next() {
-        MOZ_ASSERT(!done());
-        it++;
-    }
-
-    JS::Zone* get() const {
-        MOZ_ASSERT(!done());
-        return *it;
+    ZonesIter(JSRuntime* rt, ZoneSelector selector)
+      : iterMarker(&rt->gc),
+        atomsZone(selector == WithAtoms ? rt->gc.atomsZone.ref() : nullptr),
+        it(rt->gc.zones().begin()),
+        end(rt->gc.zones().end())
+    {
+        if (!atomsZone)
+            skipHelperThreadZones();
     }
 
-    operator JS::Zone*() const { return get(); }
-    JS::Zone* operator->() const { return get(); }
-};
-
-// Iterate over all zones in the runtime, except those which may be in use by
-// parse threads.
-class ZonesIter
-{
-    ZoneGroupsIter group;
-    mozilla::Maybe<ZonesInGroupIter> zone;
-    JS::Zone* atomsZone;
-
-  public:
-    ZonesIter(JSRuntime* rt, ZoneSelector selector)
-      : group(rt), atomsZone(selector == WithAtoms ? rt->gc.atomsZone.ref() : nullptr)
-    {
-        if (!atomsZone && !done())
-            next();
-    }
-
-    bool done() const { return !atomsZone && group.done(); }
+    bool done() const { return !atomsZone && it == end; }
 
     void next() {
         MOZ_ASSERT(!done());
         if (atomsZone)
             atomsZone = nullptr;
-        while (!group.done()) {
-            if (zone.isSome())
-                zone.ref().next();
-            else
-                zone.emplace(group);
-            if (zone.ref().done()) {
-                zone.reset();
-                group.next();
-            } else {
-                break;
-            }
-        }
+        else
+            it++;
+        skipHelperThreadZones();
+    }
+
+    void skipHelperThreadZones() {
+        while (!done() && get()->usedByHelperThread())
+            it++;
     }
 
     JS::Zone* get() const {
         MOZ_ASSERT(!done());
-        return atomsZone ? atomsZone : zone.ref().get();
+        return atomsZone ? atomsZone : *it;
     }
 
     operator JS::Zone*() const { return get(); }
     JS::Zone* operator->() const { return get(); }
 };
 
 struct CompartmentsInZoneIter
 {
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -530,17 +530,17 @@ HeapCheckTracerBase::onChild(const JS::G
     Zone* zone;
     if (thing.is<JSObject>())
         zone = thing.as<JSObject>().zone();
     else if (thing.is<JSString>())
         zone = thing.as<JSString>().zone();
     else
         zone = cell->asTenured().zone();
 
-    if (zone->group() && zone->group()->usedByHelperThread())
+    if (zone->usedByHelperThread())
         return;
 
     WorkItem item(thing, contextName(), parentIndex);
     if (!stack.append(item))
         oom = true;
 }
 
 bool
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -19,69 +19,72 @@
 #include "gc/Marking-inl.h"
 #include "vm/JSCompartment-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 Zone * const Zone::NotOnList = reinterpret_cast<Zone*>(1);
 
-JS::Zone::Zone(JSRuntime* rt, ZoneGroup* group)
+JS::Zone::Zone(JSRuntime* rt)
   : JS::shadow::Zone(rt, &rt->gc.marker),
-    group_(group),
-    debuggers(group, nullptr),
-    uniqueIds_(group),
-    suppressAllocationMetadataBuilder(group, false),
-    arenas(rt, group),
+    debuggers(this, nullptr),
+    uniqueIds_(this),
+    suppressAllocationMetadataBuilder(this, false),
+    arenas(rt, this),
     types(this),
-    gcWeakMapList_(group),
+    gcWeakMapList_(this),
     compartments_(),
-    gcGrayRoots_(group),
-    gcWeakRefs_(group),
-    weakCaches_(group),
-    gcWeakKeys_(group, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
-    typeDescrObjects_(group, this),
+    gcGrayRoots_(this),
+    gcWeakRefs_(this),
+    weakCaches_(this),
+    gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
+    typeDescrObjects_(this, this),
     regExps(this),
-    markedAtoms_(group),
-    atomCache_(group),
-    externalStringCache_(group),
-    functionToStringCache_(group),
+    markedAtoms_(this),
+    atomCache_(this),
+    externalStringCache_(this),
+    functionToStringCache_(this),
     usage(&rt->gc.usage),
     threshold(),
     gcDelayBytes(0),
-    tenuredStrings(group, 0),
-    allocNurseryStrings(group, true),
-    propertyTree_(group, this),
-    baseShapes_(group, this),
-    initialShapes_(group, this),
-    nurseryShapes_(group),
-    data(group, nullptr),
-    isSystem(group, false),
+    tenuredStrings(this, 0),
+    allocNurseryStrings(this, true),
+    propertyTree_(this, this),
+    baseShapes_(this, this),
+    initialShapes_(this, this),
+    nurseryShapes_(this),
+    data(this, nullptr),
+    isSystem(this, false),
+    helperThreadOwnerContext_(nullptr),
+    helperThreadUse(HelperThreadUse::None),
 #ifdef DEBUG
-    gcLastSweepGroupIndex(group, 0),
+    gcLastSweepGroupIndex(this, 0),
 #endif
-    jitZone_(group, nullptr),
+    jitZone_(this, nullptr),
     gcScheduled_(false),
     gcScheduledSaved_(false),
-    gcPreserveCode_(group, false),
-    keepShapeTables_(group, false),
+    gcPreserveCode_(this, false),
+    keepShapeTables_(this, false),
     listNext_(NotOnList)
 {
     /* Ensure that there are no vtables to mess us up here. */
     MOZ_ASSERT(reinterpret_cast<JS::shadow::Zone*>(this) ==
                static_cast<JS::shadow::Zone*>(this));
 
     AutoLockGC lock(rt);
     threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables, rt->gc.schedulingState, lock);
     setGCMaxMallocBytes(rt->gc.tunables.maxMallocBytes(), lock);
     jitCodeCounter.setMax(jit::MaxCodeBytesPerProcess * 0.8, lock);
 }
 
 Zone::~Zone()
 {
+    MOZ_ASSERT(helperThreadUse == HelperThreadUse::None);
+
     JSRuntime* rt = runtimeFromAnyThread();
     if (this == rt->gc.systemZone)
         rt->gc.systemZone = nullptr;
 
     js_delete(debuggers.ref());
     js_delete(jitZone_.ref());
 
 #ifdef DEBUG
@@ -130,17 +133,17 @@ Zone::getOrCreateDebuggers(JSContext* cx
     if (!debuggers)
         ReportOutOfMemory(cx);
     return debuggers;
 }
 
 void
 Zone::sweepBreakpoints(FreeOp* fop)
 {
-    if (!group() || fop->runtime()->debuggerList().isEmpty())
+    if (fop->runtime()->debuggerList().isEmpty())
         return;
 
     /*
      * Sweep all compartments in a zone at the same time, since there is no way
      * to iterate over the scripts belonging to a single compartment in a zone.
      */
 
     MOZ_ASSERT(isGCSweepingOrCompacting());
@@ -303,17 +306,17 @@ Zone::canCollect()
 {
     // The atoms zone cannot be collected while off-thread parsing is taking
     // place.
     if (isAtomsZone())
         return !runtimeFromAnyThread()->hasHelperThreadZones();
 
     // Zones that will be or are currently used by other threads cannot be
     // collected.
-    return !group()->createdForHelperThread();
+    return !createdForHelperThread();
 }
 
 void
 Zone::notifyObservingDebuggers()
 {
     JSRuntime* rt = runtimeFromActiveCooperatingThread();
     JSContext* cx = rt->mainContextFromOwnThread();
 
@@ -394,16 +397,31 @@ Zone::deleteEmptyCompartment(JSCompartme
             compartments().erase(&i);
             comp->destroy(runtimeFromActiveCooperatingThread()->defaultFreeOp());
             return;
         }
     }
     MOZ_CRASH("Compartment not found");
 }
 
+void
+Zone::setHelperThreadOwnerContext(JSContext* cx)
+{
+    MOZ_ASSERT_IF(cx, TlsContext.get() == cx);
+    helperThreadOwnerContext_ = cx;
+}
+
+bool
+Zone::ownedByCurrentHelperThread()
+{
+    MOZ_ASSERT(usedByHelperThread());
+    MOZ_ASSERT(TlsContext.get());
+    return helperThreadOwnerContext_ == TlsContext.get();
+}
+
 ZoneList::ZoneList()
   : head(nullptr), tail(nullptr)
 {}
 
 ZoneList::ZoneList(Zone* zone)
   : head(zone), tail(zone)
 {
     MOZ_RELEASE_ASSERT(!zone->isOnList());
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -141,33 +141,21 @@ namespace JS {
 // shapes within it are alive.
 //
 // We always guarantee that a zone has at least one live compartment by refusing
 // to delete the last compartment in a live zone.
 struct Zone : public JS::shadow::Zone,
               public js::gc::GraphNodeBase<JS::Zone>,
               public js::MallocProvider<JS::Zone>
 {
-    explicit Zone(JSRuntime* rt, js::ZoneGroup* group);
+    explicit Zone(JSRuntime* rt);
     ~Zone();
     MOZ_MUST_USE bool init(bool isSystem);
     void destroy(js::FreeOp *fop);
 
-  private:
-    js::ZoneGroup* const group_;
-  public:
-    js::ZoneGroup* group() const {
-        return group_;
-    }
-
-    // For JIT use.
-    static size_t offsetOfGroup() {
-        return offsetof(Zone, group_);
-    }
-
     void findOutgoingEdges(js::gc::ZoneComponentFinder& finder);
 
     void discardJitCode(js::FreeOp* fop, bool discardBaselineCode = true);
 
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 size_t* typePool,
                                 size_t* regexpZone,
                                 size_t* jitZone,
@@ -520,18 +508,56 @@ struct Zone : public JS::shadow::Zone,
     void fixupInitialShapeTable();
     void fixupAfterMovingGC();
 
     // Per-zone data for use by an embedder.
     js::ZoneGroupData<void*> data;
 
     js::ZoneGroupData<bool> isSystem;
 
+  private:
+    // The helper thread context with exclusive access to this zone, if
+    // usedByHelperThread(), or nullptr when on the main thread.
+    js::UnprotectedData<JSContext*> helperThreadOwnerContext_;
+
+  public:
+    bool ownedByCurrentHelperThread();
+    void setHelperThreadOwnerContext(JSContext* cx);
+
+  private:
+    enum class HelperThreadUse : uint32_t
+    {
+        None,
+        Pending,
+        Active
+    };
+
+    mozilla::Atomic<HelperThreadUse> helperThreadUse;
+
+  public:
+    // Whether this zone was created for use by a helper thread.
+    bool createdForHelperThread() const {
+        return helperThreadUse != HelperThreadUse::None;
+    }
+    // Whether this zone is currently in use by a helper thread.
     bool usedByHelperThread() {
-        return !isAtomsZone() && group()->usedByHelperThread();
+        MOZ_ASSERT_IF(isAtomsZone(), helperThreadUse == HelperThreadUse::None);
+        return helperThreadUse == HelperThreadUse::Active;
+    }
+    void setCreatedForHelperThread() {
+        MOZ_ASSERT(helperThreadUse == HelperThreadUse::None);
+        helperThreadUse = HelperThreadUse::Pending;
+    }
+    void setUsedByHelperThread() {
+        MOZ_ASSERT(helperThreadUse == HelperThreadUse::Pending);
+        helperThreadUse = HelperThreadUse::Active;
+    }
+    void clearUsedByHelperThread() {
+        MOZ_ASSERT(helperThreadUse != HelperThreadUse::None);
+        helperThreadUse = HelperThreadUse::None;
     }
 
 #ifdef DEBUG
     js::ZoneGroupData<unsigned> gcLastSweepGroupIndex;
 #endif
 
     static js::HashNumber UniqueIdToHash(uint64_t uid) {
         return mozilla::HashGeneric(uid);
--- a/js/src/gc/ZoneGroup.cpp
+++ b/js/src/gc/ZoneGroup.cpp
@@ -7,58 +7,8 @@
 #include "gc/ZoneGroup.h"
 
 #include "gc/PublicIterators.h"
 #include "jit/IonBuilder.h"
 #include "jit/JitCompartment.h"
 #include "vm/JSContext.h"
 
 using namespace js;
-
-namespace js {
-
-ZoneGroup::ZoneGroup(JSRuntime* runtime)
-  : runtime(runtime),
-    helperThreadOwnerContext_(nullptr),
-    zones_(this),
-    helperThreadUse(HelperThreadUse::None)
-{}
-
-ZoneGroup::~ZoneGroup()
-{
-    MOZ_ASSERT(helperThreadUse == HelperThreadUse::None);
-
-    if (this == runtime->gc.systemZoneGroup)
-        runtime->gc.systemZoneGroup = nullptr;
-}
-
-void
-ZoneGroup::setHelperThreadOwnerContext(JSContext* cx)
-{
-    MOZ_ASSERT_IF(cx, TlsContext.get() == cx);
-    helperThreadOwnerContext_ = cx;
-}
-
-bool
-ZoneGroup::ownedByCurrentHelperThread()
-{
-    MOZ_ASSERT(usedByHelperThread());
-    MOZ_ASSERT(TlsContext.get());
-    return helperThreadOwnerContext_ == TlsContext.get();
-}
-
-void
-ZoneGroup::deleteEmptyZone(Zone* zone)
-{
-    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime));
-    MOZ_ASSERT(zone->group() == this);
-    MOZ_ASSERT(zone->compartments().empty());
-    for (auto& i : zones()) {
-        if (i == zone) {
-            zones().erase(&i);
-            zone->destroy(runtime->defaultFreeOp());
-            return;
-        }
-    }
-    MOZ_CRASH("Zone not found");
-}
-
-} // namespace js
--- a/js/src/gc/ZoneGroup.h
+++ b/js/src/gc/ZoneGroup.h
@@ -8,84 +8,11 @@
 #define gc_ZoneGroup_h
 
 #include "gc/Statistics.h"
 #include "vm/Caches.h"
 #include "vm/Stack.h"
 
 namespace js {
 
-namespace jit { class JitZoneGroup; }
-
-class AutoKeepAtoms;
-
-typedef Vector<JS::Zone*, 4, SystemAllocPolicy> ZoneVector;
-
-// Zone groups encapsulate data about a group of zones that are logically
-// related in some way.
-//
-// Zone groups are the primary means by which threads ensure exclusive access
-// to the data they are using. Most data in a zone group, its zones,
-// compartments, GC things and so forth may only be used by the thread that has
-// entered the zone group.
-
-class ZoneGroup
-{
-  public:
-    JSRuntime* const runtime;
-
-  private:
-    // The helper thread context with exclusive access to this zone group, if
-    // usedByHelperThread(), or nullptr when on the main thread.
-    UnprotectedData<JSContext*> helperThreadOwnerContext_;
-
-  public:
-    bool ownedByCurrentHelperThread();
-    void setHelperThreadOwnerContext(JSContext* cx);
-
-    // All zones in the group.
-  private:
-    ZoneGroupOrGCTaskData<ZoneVector> zones_;
-  public:
-    ZoneVector& zones() { return zones_.ref(); }
-
-  private:
-    enum class HelperThreadUse : uint32_t
-    {
-        None,
-        Pending,
-        Active
-    };
-
-    mozilla::Atomic<HelperThreadUse> helperThreadUse;
-
-  public:
-    // Whether a zone in this group was created for use by a helper thread.
-    bool createdForHelperThread() const {
-        return helperThreadUse != HelperThreadUse::None;
-    }
-    // Whether a zone in this group is currently in use by a helper thread.
-    bool usedByHelperThread() const {
-        return helperThreadUse == HelperThreadUse::Active;
-    }
-    void setCreatedForHelperThread() {
-        MOZ_ASSERT(helperThreadUse == HelperThreadUse::None);
-        helperThreadUse = HelperThreadUse::Pending;
-    }
-    void setUsedByHelperThread() {
-        MOZ_ASSERT(helperThreadUse == HelperThreadUse::Pending);
-        helperThreadUse = HelperThreadUse::Active;
-    }
-    void clearUsedByHelperThread() {
-        MOZ_ASSERT(helperThreadUse != HelperThreadUse::None);
-        helperThreadUse = HelperThreadUse::None;
-    }
-
-    explicit ZoneGroup(JSRuntime* runtime);
-    ~ZoneGroup();
-
-    // Delete an empty zone after its contents have been merged.
-    void deleteEmptyZone(Zone* zone);
-};
-
 } // namespace js
 
 #endif // gc_Zone_h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1837,49 +1837,33 @@ JS::CompartmentBehaviors::extraWarnings(
 {
     return extraWarningsOverride_.get(cx->options().extraWarnings());
 }
 
 JS::CompartmentCreationOptions&
 JS::CompartmentCreationOptions::setSystemZone()
 {
     zoneSpec_ = JS::SystemZone;
-    zonePointer_ = nullptr;
+    zone_ = nullptr;
     return *this;
 }
 
 JS::CompartmentCreationOptions&
 JS::CompartmentCreationOptions::setExistingZone(JSObject* obj)
 {
     zoneSpec_ = JS::ExistingZone;
-    zonePointer_ = obj->zone();
-    return *this;
-}
-
-JS::CompartmentCreationOptions&
-JS::CompartmentCreationOptions::setNewZoneInNewZoneGroup()
-{
-    zoneSpec_ = JS::NewZoneInNewZoneGroup;
-    zonePointer_ = nullptr;
+    zone_ = obj->zone();
     return *this;
 }
 
 JS::CompartmentCreationOptions&
-JS::CompartmentCreationOptions::setNewZoneInSystemZoneGroup()
-{
-    zoneSpec_ = JS::NewZoneInSystemZoneGroup;
-    zonePointer_ = nullptr;
-    return *this;
-}
-
-JS::CompartmentCreationOptions&
-JS::CompartmentCreationOptions::setNewZoneInExistingZoneGroup(JSObject* obj)
-{
-    zoneSpec_ = JS::NewZoneInExistingZoneGroup;
-    zonePointer_ = obj->zone()->group();
+JS::CompartmentCreationOptions::setNewZone()
+{
+    zoneSpec_ = JS::NewZone;
+    zone_ = nullptr;
     return *this;
 }
 
 const JS::CompartmentCreationOptions&
 JS::CompartmentCreationOptionsRef(JSCompartment* compartment)
 {
     return compartment->creationOptions();
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1909,41 +1909,35 @@ namespace JS {
 enum ZoneSpecifier {
     // Use the single runtime wide system zone. The meaning of this zone is
     // left to the embedder.
     SystemZone,
 
     // Use a particular existing zone.
     ExistingZone,
 
-    // Create a new zone with its own new zone group.
-    NewZoneInNewZoneGroup,
-
-    // Create a new zone in the same zone group as the system zone.
-    NewZoneInSystemZoneGroup,
-
-    // Create a new zone in the same zone group as another existing zone.
-    NewZoneInExistingZoneGroup
+    // Create a new zone.
+    NewZone
 };
 
 /**
  * CompartmentCreationOptions specifies options relevant to creating a new
  * compartment, that are either immutable characteristics of that compartment
  * or that are discarded after the compartment has been created.
  *
  * Access to these options on an existing compartment is read-only: if you
  * need particular selections, make them before you create the compartment.
  */
 class JS_PUBLIC_API(CompartmentCreationOptions)
 {
   public:
     CompartmentCreationOptions()
       : traceGlobal_(nullptr),
-        zoneSpec_(NewZoneInSystemZoneGroup),
-        zonePointer_(nullptr),
+        zoneSpec_(NewZone),
+        zone_(nullptr),
         invisibleToDebugger_(false),
         mergeable_(false),
         preserveJitCode_(false),
         cloneSingletons_(false),
         sharedMemoryAndAtomics_(false),
         secureContext_(false),
         clampAndJitterTime_(true)
     {}
@@ -1951,25 +1945,23 @@ class JS_PUBLIC_API(CompartmentCreationO
     JSTraceOp getTrace() const {
         return traceGlobal_;
     }
     CompartmentCreationOptions& setTrace(JSTraceOp op) {
         traceGlobal_ = op;
         return *this;
     }
 
-    void* zonePointer() const { return zonePointer_; }
+    JS::Zone* zone() const { return zone_; }
     ZoneSpecifier zoneSpecifier() const { return zoneSpec_; }
 
     // Set the zone to use for the compartment. See ZoneSpecifier above.
     CompartmentCreationOptions& setSystemZone();
     CompartmentCreationOptions& setExistingZone(JSObject* obj);
-    CompartmentCreationOptions& setNewZoneInNewZoneGroup();
-    CompartmentCreationOptions& setNewZoneInSystemZoneGroup();
-    CompartmentCreationOptions& setNewZoneInExistingZoneGroup(JSObject* obj);
+    CompartmentCreationOptions& setNewZone();
 
     // Certain scopes (i.e. XBL compilation scopes) are implementation details
     // of the embedding, and references to them should never leak out to script.
     // This flag causes the this compartment to skip firing onNewGlobalObject
     // and makes addDebuggee a no-op for this global.
     bool invisibleToDebugger() const { return invisibleToDebugger_; }
     CompartmentCreationOptions& setInvisibleToDebugger(bool flag) {
         invisibleToDebugger_ = flag;
@@ -2017,17 +2009,17 @@ class JS_PUBLIC_API(CompartmentCreationO
     CompartmentCreationOptions& setClampAndJitterTime(bool flag) {
         clampAndJitterTime_ = flag;
         return *this;
     }
 
   private:
     JSTraceOp traceGlobal_;
     ZoneSpecifier zoneSpec_;
-    void* zonePointer_; // Per zoneSpec_, either a Zone, ZoneGroup, or null.
+    JS::Zone* zone_;
     bool invisibleToDebugger_;
     bool mergeable_;
     bool preserveJitCode_;
     bool cloneSingletons_;
     bool sharedMemoryAndAtomics_;
     bool secureContext_;
     bool clampAndJitterTime_;
 };
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5041,17 +5041,17 @@ NewGlobal(JSContext* cx, unsigned argc, 
 {
     JSPrincipals* principals = nullptr;
 
     JS::CompartmentOptions options;
     JS::CompartmentCreationOptions& creationOptions = options.creationOptions();
     JS::CompartmentBehaviors& behaviors = options.behaviors();
 
     SetStandardCompartmentOptions(options);
-    options.creationOptions().setNewZoneInExistingZoneGroup(cx->global());
+    options.creationOptions().setNewZone();
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() == 1 && args[0].isObject()) {
         RootedObject opts(cx, &args[0].toObject());
         RootedValue v(cx);
 
         if (!JS_GetProperty(cx, opts, "invisibleToDebugger", &v))
             return false;
--- a/js/src/threading/ProtectedData.cpp
+++ b/js/src/threading/ProtectedData.cpp
@@ -66,35 +66,26 @@ template class CheckActiveThread<Allowed
 template <AllowedHelperThread Helper>
 void
 CheckZoneGroup<Helper>::check() const
 {
     if (OnHelperThread<Helper>())
         return;
 
     JSContext* cx = TlsContext.get();
-    if (group) {
-        if (group->usedByHelperThread()) {
-            MOZ_ASSERT(group->ownedByCurrentHelperThread());
-        } else {
-            // In a cooperatively scheduled runtime the active thread is
-            // permitted access to all zone groups --- even those it has not
-            // entered --- for GC and similar purposes. Since all other
-            // cooperative threads are suspended, these accesses are threadsafe
-            // if the zone group is not in use by a helper thread.
-            //
-            // A corollary to this is that suspended cooperative threads may
-            // not access anything in a zone group, even zone groups they own,
-            // because they're not allowed to interact with the JS API.
-            MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
-        }
+    if (zone->isAtomsZone()) {
+        // The atoms zone is protected by the exclusive access lock.
+        MOZ_ASSERT(cx->runtime()->currentThreadHasExclusiveAccess());
+    } else if (zone->usedByHelperThread()) {
+        // This may only be accessed by the helper thread using this zone.
+        MOZ_ASSERT(zone->ownedByCurrentHelperThread());
     } else {
-        // |group| will be null for data in the atoms zone. This is protected
-        // by the exclusive access lock.
-        MOZ_ASSERT(cx->runtime()->currentThreadHasExclusiveAccess());
+        // The main thread is permitted access to all zones. These accesses
+        // are threadsafe if the zone is not in use by a helper thread.
+        MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
     }
 }
 
 template class CheckZoneGroup<AllowedHelperThread::None>;
 template class CheckZoneGroup<AllowedHelperThread::GCTask>;
 template class CheckZoneGroup<AllowedHelperThread::IonCompile>;
 template class CheckZoneGroup<AllowedHelperThread::GCTaskOrIonCompile>;
 
--- a/js/src/threading/ProtectedData.h
+++ b/js/src/threading/ProtectedData.h
@@ -4,16 +4,18 @@
  * 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 threading_ProtectedData_h
 #define threading_ProtectedData_h
 
 #include "threading/Thread.h"
 
+namespace JS { struct Zone; }
+
 namespace js {
 
 // This file provides classes for encapsulating pieces of data with a check
 // that ensures the data is only accessed if certain conditions are met.
 // Checking is only done in debug builds; in release builds these classes
 // have no space or time overhead. These classes are mainly used for ensuring
 // that data is used in threadsafe ways.
 //
@@ -132,28 +134,26 @@ class ProtectedDataNoCheckArgs : public 
     explicit ProtectedDataNoCheckArgs(Args&&... args)
       : ProtectedData<Check, T>(Check(), mozilla::Forward<Args>(args)...)
     {}
 
     template <typename U>
     ThisType& operator=(const U& p) { this->ref() = p; return *this; }
 };
 
-class ZoneGroup;
-
 // Intermediate class for protected data whose checks take a ZoneGroup constructor argument.
 template <typename Check, typename T>
 class ProtectedDataZoneGroupArg : public ProtectedData<Check, T>
 {
     typedef ProtectedDataZoneGroupArg<Check, T> ThisType;
 
   public:
     template <typename... Args>
-    explicit ProtectedDataZoneGroupArg(ZoneGroup* group, Args&&... args)
-      : ProtectedData<Check, T>(Check(group), mozilla::Forward<Args>(args)...)
+    explicit ProtectedDataZoneGroupArg(JS::Zone* zone, Args&&... args)
+      : ProtectedData<Check, T>(Check(zone), mozilla::Forward<Args>(args)...)
     {}
 
     template <typename U>
     ThisType& operator=(const U& p) { this->ref() = p; return *this; }
 };
 
 class CheckUnprotected
 {
@@ -218,24 +218,24 @@ using ActiveThreadOrGCTaskData =
 template <typename T>
 using ActiveThreadOrIonCompileData =
     ProtectedDataNoCheckArgs<CheckActiveThread<AllowedHelperThread::IonCompile>, T>;
 
 template <AllowedHelperThread Helper>
 class CheckZoneGroup
 {
 #ifdef JS_HAS_PROTECTED_DATA_CHECKS
-    ZoneGroup* group;
+    JS::Zone* zone;
 
   public:
-    explicit CheckZoneGroup(ZoneGroup* group) : group(group) {}
+    explicit CheckZoneGroup(JS::Zone* zone) : zone(zone) {}
     void check() const;
 #else
   public:
-    explicit CheckZoneGroup(ZoneGroup* group) {}
+    explicit CheckZoneGroup(JS::Zone* zone) {}
 #endif
 };
 
 // Data which may only be accessed by threads with exclusive access to the
 // associated zone group, or by the runtime's cooperatively scheduled
 // active thread for zone groups which are not in use by a helper thread.
 template <typename T>
 using ZoneGroupData =
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -116,17 +116,17 @@ GlobalObject::skipDeselectedConstructor(
     }
 }
 
 /* static*/ bool
 GlobalObject::resolveConstructor(JSContext* cx, Handle<GlobalObject*> global, JSProtoKey key)
 {
     MOZ_ASSERT(!global->isStandardClassResolved(key));
 
-    if (global->zone()->group()->createdForHelperThread())
+    if (global->zone()->createdForHelperThread())
         return resolveOffThreadConstructor(cx, global, key);
 
     MOZ_ASSERT(!cx->helperThread());
 
     // Prohibit collection of allocation metadata. Metadata builders shouldn't
     // need to observe lazily-constructed prototype objects coming into
     // existence. And assertions start to fail when the builder itself attempts
     // an allocation that re-entrantly tries to create the same prototype.
@@ -275,17 +275,17 @@ GlobalObject::resolveConstructor(JSConte
     }
 
     return true;
 }
 
 /* static */ JSObject*
 GlobalObject::createObject(JSContext* cx, Handle<GlobalObject*> global, unsigned slot, ObjectInitOp init)
 {
-    if (global->zone()->group()->createdForHelperThread())
+    if (global->zone()->createdForHelperThread())
         return createOffThreadObject(cx, global, slot);
 
     MOZ_ASSERT(!cx->helperThread());
     if (!init(cx, global))
         return nullptr;
 
     return &global->getSlot(slot).toObject();
 }
@@ -319,17 +319,17 @@ GlobalObject::resolveOffThreadConstructo
                                           Handle<GlobalObject*> global,
                                           JSProtoKey key)
 {
     // Don't resolve constructors for off-thread parse globals. Instead create a
     // placeholder object for the prototype which we can use to find the real
     // prototype when the off-thread compartment is merged back into the target
     // compartment.
 
-    MOZ_ASSERT(global->zone()->group()->createdForHelperThread());
+    MOZ_ASSERT(global->zone()->createdForHelperThread());
     MOZ_ASSERT(key == JSProto_Object ||
                key == JSProto_Function ||
                key == JSProto_Array ||
                key == JSProto_RegExp);
 
     Rooted<OffThreadPlaceholderObject*> placeholder(cx);
     placeholder = OffThreadPlaceholderObject::New(cx, prototypeSlot(key));
     if (!placeholder)
@@ -349,17 +349,17 @@ GlobalObject::resolveOffThreadConstructo
 /* static */ JSObject*
 GlobalObject::createOffThreadObject(JSContext* cx, Handle<GlobalObject*> global, unsigned slot)
 {
     // Don't create prototype objects for off-thread parse globals. Instead
     // create a placeholder object which we can use to find the real prototype
     // when the off-thread compartment is merged back into the target
     // compartment.
 
-    MOZ_ASSERT(global->zone()->group()->createdForHelperThread());
+    MOZ_ASSERT(global->zone()->createdForHelperThread());
     MOZ_ASSERT(slot == GENERATOR_FUNCTION_PROTO ||
                slot == MODULE_PROTO ||
                slot == IMPORT_ENTRY_PROTO ||
                slot == EXPORT_ENTRY_PROTO ||
                slot == REQUESTED_MODULE_PROTO);
 
     auto placeholder = OffThreadPlaceholderObject::New(cx, slot);
     if (!placeholder)
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -669,48 +669,48 @@ EnsureParserCreatedClasses(JSContext* cx
     if (kind == ParseTaskKind::Module && !GlobalObject::ensureModulePrototypesCreated(cx, global))
         return false;
 
     return true;
 }
 
 class MOZ_RAII AutoSetCreatedForHelperThread
 {
-    ZoneGroup* group;
+    Zone* zone;
 
   public:
     explicit AutoSetCreatedForHelperThread(JSObject* global)
-      : group(global->zone()->group())
+      : zone(global->zone())
     {
-        group->setCreatedForHelperThread();
+        zone->setCreatedForHelperThread();
     }
 
     void forget() {
-        group = nullptr;
+        zone = nullptr;
     }
 
     ~AutoSetCreatedForHelperThread() {
-        if (group)
-            group->clearUsedByHelperThread();
+        if (zone)
+            zone->clearUsedByHelperThread();
     }
 };
 
 static JSObject*
 CreateGlobalForOffThreadParse(JSContext* cx, const gc::AutoSuppressGC& nogc)
 {
     JSCompartment* currentCompartment = cx->compartment();
 
     JS::CompartmentOptions compartmentOptions(currentCompartment->creationOptions(),
                                               currentCompartment->behaviors());
 
     auto& creationOptions = compartmentOptions.creationOptions();
 
     creationOptions.setInvisibleToDebugger(true)
                    .setMergeable(true)
-                   .setNewZoneInNewZoneGroup();
+                   .setNewZone();
 
     // Don't falsely inherit the host's global trace hook.
     creationOptions.setTrace(nullptr);
 
     JSObject* obj = JS_NewGlobalObject(cx, &parseTaskGlobalClass, nullptr,
                                        JS::DontFireOnNewGlobalHook, compartmentOptions);
     if (!obj)
         return nullptr;
@@ -1922,20 +1922,20 @@ HelperThread::handleParseWorkload(AutoLo
     ParseTask* task = parseTask();
 
     {
         AutoUnlockHelperThreadState unlock(locked);
         AutoSetContextRuntime ascr(task->parseGlobal->runtimeFromAnyThread());
 
         JSContext* cx = TlsContext.get();
 
-        ZoneGroup* zoneGroup = task->parseGlobal->zoneFromAnyThread()->group();
-        zoneGroup->setHelperThreadOwnerContext(cx);
+        Zone* zone = task->parseGlobal->zoneFromAnyThread();
+        zone->setHelperThreadOwnerContext(cx);
         auto resetOwnerContext = mozilla::MakeScopeExit([&] {
-            zoneGroup->setHelperThreadOwnerContext(nullptr);
+            zone->setHelperThreadOwnerContext(nullptr);
         });
 
         AutoCompartment ac(cx, task->parseGlobal);
 
         task->parse(cx);
 
         cx->frontendCollectionPool().purge();
     }
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -749,19 +749,17 @@ FindNotableScriptSources(JS::RuntimeSize
 static bool
 CollectRuntimeStatsHelper(JSContext* cx, RuntimeStats* rtStats, ObjectPrivateVisitor* opv,
                           bool anonymize, IterateCellCallback statsCellCallback)
 {
     JSRuntime* rt = cx->runtime();
     if (!rtStats->compartmentStatsVector.reserve(rt->numCompartments))
         return false;
 
-    size_t totalZones = 1; // For the atoms zone.
-    for (ZoneGroupsIter group(rt); !group.done(); group.next())
-        totalZones += group->zones().length();
+    size_t totalZones = rt->gc.zones().length() + 1; // + 1 for the atoms zone.
     if (!rtStats->zoneStatsVector.reserve(totalZones))
         return false;
 
     rtStats->gcHeapChunkTotal =
         size_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
 
     rtStats->gcHeapUnusedChunks =
         size_t(JS_GetGCParameter(cx, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize;
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -210,17 +210,17 @@ JSRuntime::init(JSContext* cx, uint32_t 
 
     defaultFreeOp_ = js_new<js::FreeOp>(this);
     if (!defaultFreeOp_)
         return false;
 
     if (!gc.init(maxbytes, maxNurseryBytes))
         return false;
 
-    ScopedJSDeletePtr<Zone> atomsZone(js_new<Zone>(this, nullptr));
+    ScopedJSDeletePtr<Zone> atomsZone(js_new<Zone>(this));
     if (!atomsZone || !atomsZone->init(true))
         return false;
 
     JS::CompartmentOptions options;
     ScopedJSDeletePtr<JSCompartment> atomsCompartment(js_new<JSCompartment>(atomsZone.get(), options));
     if (!atomsCompartment || !atomsCompartment->init(nullptr))
         return false;
 
@@ -787,27 +787,27 @@ JSRuntime::destroyAtomsAddedWhileSweepin
 
     js_delete(atomsAddedWhileSweeping_.ref());
     atomsAddedWhileSweeping_ = nullptr;
 }
 
 void
 JSRuntime::setUsedByHelperThread(Zone* zone)
 {
-    MOZ_ASSERT(!zone->group()->usedByHelperThread());
+    MOZ_ASSERT(!zone->usedByHelperThread());
     MOZ_ASSERT(!zone->wasGCStarted());
-    zone->group()->setUsedByHelperThread();
+    zone->setUsedByHelperThread();
     numActiveHelperThreadZones++;
 }
 
 void
 JSRuntime::clearUsedByHelperThread(Zone* zone)
 {
-    MOZ_ASSERT(zone->group()->usedByHelperThread());
-    zone->group()->clearUsedByHelperThread();
+    MOZ_ASSERT(zone->usedByHelperThread());
+    zone->clearUsedByHelperThread();
     numActiveHelperThreadZones--;
     JSContext* cx = mainContextFromOwnThread();
     if (gc.fullGCForAtomsRequested() && cx->canCollectAtoms())
         gc.triggerFullGCForAtoms(cx);
 }
 
 bool
 js::CurrentThreadCanAccessRuntime(const JSRuntime* rt)
@@ -815,17 +815,17 @@ js::CurrentThreadCanAccessRuntime(const 
     return rt->mainContextFromAnyThread() == TlsContext.get();
 }
 
 bool
 js::CurrentThreadCanAccessZone(Zone* zone)
 {
     // Helper thread zones can only be used by their owning thread.
     if (zone->usedByHelperThread())
-        return zone->group()->ownedByCurrentHelperThread();
+        return zone->ownedByCurrentHelperThread();
 
     // Other zones can only be accessed by the runtime's active context.
     return CurrentThreadCanAccessRuntime(zone->runtime_);
 }
 
 #ifdef DEBUG
 bool
 js::CurrentThreadIsPerformingGC()
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2656,17 +2656,17 @@ js::FillSelfHostingCompileOptions(Compil
 
 GlobalObject*
 JSRuntime::createSelfHostingGlobal(JSContext* cx)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
     MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
 
     JS::CompartmentOptions options;
-    options.creationOptions().setNewZoneInSystemZoneGroup();
+    options.creationOptions().setNewZone();
     options.behaviors().setDiscardSource(true);
 
     JSCompartment* compartment = NewCompartment(cx, nullptr, options);
     if (!compartment)
         return nullptr;
 
     static const ClassOps shgClassOps = {
         nullptr, nullptr, nullptr, nullptr,
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -4479,24 +4479,24 @@ Zone::addSizeOfIncludingThis(mozilla::Ma
     *uniqueIdMap += uniqueIds().sizeOfExcludingThis(mallocSizeOf);
     *shapeTables += baseShapes().sizeOfExcludingThis(mallocSizeOf)
                   + initialShapes().sizeOfExcludingThis(mallocSizeOf);
     *atomsMarkBitmaps += markedAtoms().sizeOfExcludingThis(mallocSizeOf);
 }
 
 TypeZone::TypeZone(Zone* zone)
   : zone_(zone),
-    typeLifoAlloc_(zone->group(), (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
-    currentCompilationId_(zone->group()),
-    generation(zone->group(), 0),
-    sweepTypeLifoAlloc(zone->group(), (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
-    sweepReleaseTypes(zone->group(), false),
-    sweepingTypes(zone->group(), false),
-    keepTypeScripts(zone->group(), false),
-    activeAnalysis(zone->group(), nullptr)
+    typeLifoAlloc_(zone, (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+    currentCompilationId_(zone),
+    generation(zone, 0),
+    sweepTypeLifoAlloc(zone, (size_t) TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+    sweepReleaseTypes(zone, false),
+    sweepingTypes(zone, false),
+    keepTypeScripts(zone, false),
+    activeAnalysis(zone, nullptr)
 {
 }
 
 TypeZone::~TypeZone()
 {
     MOZ_RELEASE_ASSERT(!sweepingTypes);
     MOZ_ASSERT(!keepTypeScripts);
 }
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -1010,17 +1010,17 @@ xpc::CreateSandboxObject(JSContext* cx, 
         creationOptions.setClampAndJitterTime(false);
 
     if (xpc::SharedMemoryEnabled())
         creationOptions.setSharedMemoryAndAtomicsEnabled(true);
 
     if (options.sameZoneAs)
         creationOptions.setExistingZone(js::UncheckedUnwrap(options.sameZoneAs));
     else if (options.freshZone)
-        creationOptions.setNewZoneInSystemZoneGroup();
+        creationOptions.setNewZone();
     else
         creationOptions.setSystemZone();
 
     creationOptions.setInvisibleToDebugger(options.invisibleToDebugger)
                    .setTrace(TraceXPCGlobal);
 
     compartmentOptions.behaviors().setDiscardSource(options.discardSource);
 
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -1278,17 +1278,17 @@ XRE_XPCShellMain(int argc, char** argv, 
             fprintf(gErrFile, "+++ Failed to create BackstagePass: %8x\n",
                     static_cast<uint32_t>(rv));
             return 1;
         }
 
         // Make the default XPCShell global use a fresh zone (rather than the
         // System Zone) to improve cross-zone test coverage.
         JS::CompartmentOptions options;
-        options.creationOptions().setNewZoneInSystemZoneGroup();
+        options.creationOptions().setNewZone();
         if (xpc::SharedMemoryEnabled())
             options.creationOptions().setSharedMemoryAndAtomicsEnabled(true);
         JS::Rooted<JSObject*> glob(cx);
         rv = xpc::InitClassesWithNewWrappedGlobal(cx,
                                                   static_cast<nsIGlobalObject*>(backstagePass),
                                                   systemprincipal,
                                                   0,
                                                   options,