Bug 1259850 - Make ZoneCellIter variants to communicate nogc to the hazard analysis, r=jonco
☠☠ backed out by 69518db96a4d ☠ ☠
authorSteve Fink <sfink@mozilla.com>
Fri, 27 May 2016 22:00:10 -0700
changeset 324241 c95bdd426ced9a71bc64694ea236b46d035bb0df
parent 324240 a73f74f718e7464d40c17df3f4228c96d5819d1a
child 324242 5aa94eb8c2df98df19bfb52f1f1e3587a5354e28
push id9671
push userraliiev@mozilla.com
push dateMon, 06 Jun 2016 20:27:52 +0000
treeherdermozilla-aurora@cea65ca3d0bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1259850
milestone49.0a1
Bug 1259850 - Make ZoneCellIter variants to communicate nogc to the hazard analysis, r=jonco Also create accessors on gc::Zone for a nicer API to ZoneCellIters. MozReview-Commit-ID: AxRTTUfWrND
js/src/gc/Iteration.cpp
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jit/BaselineJIT.cpp
js/src/jit/Ion.cpp
js/src/jscompartment.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
js/src/jsopcode.cpp
js/src/vm/Debugger.cpp
js/src/vm/HelperThreads.cpp
js/src/vm/NativeObject.cpp
js/src/vm/TypeInference.cpp
--- a/js/src/gc/Iteration.cpp
+++ b/js/src/gc/Iteration.cpp
@@ -90,46 +90,44 @@ js::IterateChunks(JSRuntime* rt, void* d
         chunkCallback(rt, data, chunk);
 }
 
 void
 js::IterateScripts(JSRuntime* rt, JSCompartment* compartment,
                    void* data, IterateScriptCallback scriptCallback)
 {
     MOZ_ASSERT(!rt->mainThread.suppressGC);
-    rt->gc.evictNursery();
-    MOZ_ASSERT(rt->gc.nursery.isEmpty());
-
+    AutoEmptyNursery empty(rt);
     AutoPrepareForTracing prep(rt, SkipAtoms);
 
     if (compartment) {
-        for (ZoneCellIterUnderGC i(compartment->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        Zone* zone = compartment->zone();
+        for (auto script = zone->cellIter<JSScript>(empty); !script.done(); script.next()) {
             if (script->compartment() == compartment)
                 scriptCallback(rt, data, script);
         }
     } else {
         for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-            for (ZoneCellIterUnderGC i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next())
-                scriptCallback(rt, data, i.get<JSScript>());
+            for (auto script = zone->cellIter<JSScript>(empty); !script.done(); script.next())
+                scriptCallback(rt, data, script);
         }
     }
 }
 
 void
 js::IterateGrayObjects(Zone* zone, GCThingCallback cellCallback, void* data)
 {
-    zone->runtimeFromMainThread()->gc.evictNursery();
-    AutoPrepareForTracing prep(zone->runtimeFromMainThread(), SkipAtoms);
+    JSRuntime* rt = zone->runtimeFromMainThread();
+    AutoEmptyNursery empty(rt);
+    AutoPrepareForTracing prep(rt, SkipAtoms);
 
     for (auto thingKind : ObjectAllocKinds()) {
-        for (ZoneCellIterUnderGC i(zone, thingKind); !i.done(); i.next()) {
-            JSObject* obj = i.get<JSObject>();
+        for (auto obj = zone->cellIter<JSObject>(thingKind, empty); !obj.done(); obj.next()) {
             if (obj->asTenured().isMarked(GRAY))
-                cellCallback(data, JS::GCCellPtr(obj));
+                cellCallback(data, JS::GCCellPtr(obj.get()));
         }
     }
 }
 
 JS_PUBLIC_API(void)
 JS_IterateCompartments(JSRuntime* rt, void* data,
                        JSIterateCompartmentCallback compartmentCallback)
 {
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -756,17 +756,17 @@ Statistics::Statistics(JSRuntime* rt)
     fp(nullptr),
     gcDepth(0),
     nonincrementalReason_(nullptr),
     timedGCStart(0),
     preBytes(0),
     maxPauseInInterval(0),
     phaseNestingDepth(0),
     activeDagSlot(PHASE_DAG_NONE),
-    suspendedPhaseNestingDepth(0),
+    suspended(0),
     sliceCallback(nullptr),
     nurseryCollectionCallback(nullptr),
     aborted(false)
 {
     PodArrayZero(phaseTotals);
     PodArrayZero(counts);
     PodArrayZero(phaseStartTimes);
     for (auto d : MakeRange(NumTimingArrays))
@@ -1064,17 +1064,17 @@ Statistics::startTimingMutator()
 {
     if (phaseNestingDepth != 0) {
         // Should only be called from outside of GC.
         MOZ_ASSERT(phaseNestingDepth == 1);
         MOZ_ASSERT(phaseNesting[0] == PHASE_MUTATOR);
         return false;
     }
 
-    MOZ_ASSERT(suspendedPhaseNestingDepth == 0);
+    MOZ_ASSERT(suspended == 0);
 
     timedGCTime = 0;
     phaseStartTimes[PHASE_MUTATOR] = 0;
     phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR] = 0;
     timedGCStart = 0;
 
     beginPhase(PHASE_MUTATOR);
     return true;
@@ -1090,29 +1090,56 @@ Statistics::stopTimingMutator(double& mu
     endPhase(PHASE_MUTATOR);
     mutator_ms = t(phaseTimes[PHASE_DAG_NONE][PHASE_MUTATOR]);
     gc_ms = t(timedGCTime);
 
     return true;
 }
 
 void
+Statistics::suspendPhases(Phase suspension)
+{
+    MOZ_ASSERT(suspension == PHASE_EXPLICIT_SUSPENSION || suspension == PHASE_IMPLICIT_SUSPENSION);
+    while (phaseNestingDepth) {
+        MOZ_ASSERT(suspended < mozilla::ArrayLength(suspendedPhases));
+        Phase parent = phaseNesting[phaseNestingDepth - 1];
+        suspendedPhases[suspended++] = parent;
+        recordPhaseEnd(parent);
+    }
+    suspendedPhases[suspended++] = suspension;
+}
+
+void
+Statistics::resumePhases()
+{
+    Phase popped = suspendedPhases[--suspended];
+    MOZ_ASSERT(popped == PHASE_EXPLICIT_SUSPENSION || popped == PHASE_IMPLICIT_SUSPENSION);
+    while (suspended &&
+           suspendedPhases[suspended - 1] != PHASE_EXPLICIT_SUSPENSION &&
+           suspendedPhases[suspended - 1] != PHASE_IMPLICIT_SUSPENSION)
+    {
+        Phase resumePhase = suspendedPhases[--suspended];
+        if (resumePhase == PHASE_MUTATOR)
+            timedGCTime += PRMJ_Now() - timedGCStart;
+        beginPhase(resumePhase);
+    }
+}
+
+void
 Statistics::beginPhase(Phase phase)
 {
     Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
 
     // Re-entry is allowed during callbacks, so pause callback phases while
     // other phases are in progress, auto-resuming after they end. As a result,
     // nested GC time will not be accounted against the callback phases.
     //
     // Reuse this mechanism for managing PHASE_MUTATOR.
     if (parent == PHASE_GC_BEGIN || parent == PHASE_GC_END || parent == PHASE_MUTATOR) {
-        MOZ_ASSERT(suspendedPhaseNestingDepth < mozilla::ArrayLength(suspendedPhases));
-        suspendedPhases[suspendedPhaseNestingDepth++] = parent;
-        recordPhaseEnd(parent);
+        suspendPhases(PHASE_IMPLICIT_SUSPENSION);
         parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT;
     }
 
     // Guard against any other re-entry.
     MOZ_ASSERT(!phaseStartTimes[phase]);
 
     MOZ_ASSERT(phases[phase].index == phase);
     MOZ_ASSERT(phaseNestingDepth < MAX_NESTING);
@@ -1149,22 +1176,18 @@ Statistics::endPhase(Phase phase)
 {
     recordPhaseEnd(phase);
 
     if (phases[phase].parent == PHASE_MULTI_PARENTS)
         activeDagSlot = PHASE_DAG_NONE;
 
     // When emptying the stack, we may need to resume a callback phase
     // (PHASE_GC_BEGIN/END) or return to timing the mutator (PHASE_MUTATOR).
-    if (phaseNestingDepth == 0 && suspendedPhaseNestingDepth > 0) {
-        Phase resumePhase = suspendedPhases[--suspendedPhaseNestingDepth];
-        if (resumePhase == PHASE_MUTATOR)
-            timedGCTime += PRMJ_Now() - timedGCStart;
-        beginPhase(resumePhase);
-    }
+    if (phaseNestingDepth == 0 && suspended > 0 && suspendedPhases[suspended - 1] == PHASE_IMPLICIT_SUSPENSION)
+        resumePhases();
 }
 
 void
 Statistics::endParallelPhase(Phase phase, const GCParallelTask* task)
 {
     phaseNestingDepth--;
 
     if (!slices.empty())
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -81,16 +81,18 @@ enum Phase : uint8_t {
     PHASE_MARK_CCWS,
     PHASE_MARK_ROOTERS,
     PHASE_MARK_RUNTIME_DATA,
     PHASE_MARK_EMBEDDING,
     PHASE_MARK_COMPARTMENTS,
 
     PHASE_LIMIT,
     PHASE_NONE = PHASE_LIMIT,
+    PHASE_EXPLICIT_SUSPENSION = PHASE_LIMIT,
+    PHASE_IMPLICIT_SUSPENSION,
     PHASE_MULTI_PARENTS
 };
 
 enum Stat {
     STAT_NEW_CHUNK,
     STAT_DESTROY_CHUNK,
     STAT_MINOR_GC,
 
@@ -164,16 +166,32 @@ struct Statistics
 
     explicit Statistics(JSRuntime* rt);
     ~Statistics();
 
     void beginPhase(Phase phase);
     void endPhase(Phase phase);
     void endParallelPhase(Phase phase, const GCParallelTask* task);
 
+    // Occasionally, we may be in the middle of something that is tracked by
+    // this class, and we need to do something unusual (eg evict the nursery)
+    // that doesn't normally nest within the current phase. Suspend the
+    // currently tracked phase stack, at which time the caller is free to do
+    // other tracked operations.
+    //
+    // This also happens internally with PHASE_GC_BEGIN and other "non-GC"
+    // phases. While in these phases, any beginPhase will automatically suspend
+    // the non-GC phase, until that inner stack is complete, at which time it
+    // will automatically resume the non-GC phase. Explicit suspensions do not
+    // get auto-resumed.
+    void suspendPhases(Phase suspension = PHASE_EXPLICIT_SUSPENSION);
+
+    // Resume a suspended stack of phases.
+    void resumePhases();
+
     void beginSlice(const ZoneGCStats& zoneStats, JSGCInvocationKind gckind,
                     SliceBudget budget, JS::gcreason::Reason reason);
     void endSlice();
     void setSliceCycleCount(unsigned cycleCount);
 
     MOZ_MUST_USE bool startTimingMutator();
     MOZ_MUST_USE bool stopTimingMutator(double& mutator_ms, double& gc_ms);
 
@@ -313,23 +331,24 @@ struct Statistics
     mutable int64_t maxPauseInInterval;
 
     /* Phases that are currently on stack. */
     Phase phaseNesting[MAX_NESTING];
     size_t phaseNestingDepth;
     size_t activeDagSlot;
 
     /*
-     * To avoid recursive nesting, we discontinue a callback phase when any
-     * other phases are started. Remember what phase to resume when the inner
-     * phases are complete. (And because GCs can nest within the callbacks any
-     * number of times, we need a whole stack of of phases to resume.)
+     * Certain phases can interrupt the phase stack, eg callback phases. When
+     * this happens, we move the suspended phases over to a sepearate list,
+     * terminated by a dummy PHASE_SUSPENSION phase (so that we can nest
+     * suspensions by suspending multiple stacks with a PHASE_SUSPENSION in
+     * between).
      */
-    Phase suspendedPhases[MAX_NESTING];
-    size_t suspendedPhaseNestingDepth;
+    Phase suspendedPhases[MAX_NESTING * 3];
+    size_t suspended;
 
     /* Sweep times for SCCs of compartments. */
     Vector<int64_t, 0, SystemAllocPolicy> sccTimes;
 
     JS::GCSliceCallback sliceCallback;
     JS::GCNurseryCollectionCallback nurseryCollectionCallback;
 
     /*
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -148,23 +148,23 @@ Zone::sweepBreakpoints(FreeOp* fop)
         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());
-    for (ZoneCellIterUnderGC i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
-        JSScript* script = i.get<JSScript>();
+    for (auto iter = cellIter<JSScript>(); !iter.done(); iter.next()) {
+        JSScript* script = iter;
         if (!script->hasAnyBreakpointsOrStepMode())
             continue;
 
         bool scriptGone = IsAboutToBeFinalizedUnbarriered(&script);
-        MOZ_ASSERT(script == i.get<JSScript>());
+        MOZ_ASSERT(script == iter);
         for (unsigned i = 0; i < script->length(); i++) {
             BreakpointSite* site = script->getBreakpointSite(script->offsetToPC(i));
             if (!site)
                 continue;
 
             Breakpoint* nextbp;
             for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) {
                 nextbp = bp->nextInSite();
@@ -202,30 +202,27 @@ Zone::discardJitCode(FreeOp* fop)
         return;
 
     if (isPreservingCode()) {
         PurgeJITCaches(this);
     } else {
 
 #ifdef DEBUG
         /* Assert no baseline scripts are marked as active. */
-        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = cellIter<JSScript>(); !script.done(); script.next())
             MOZ_ASSERT_IF(script->hasBaselineScript(), !script->baselineScript()->active());
-        }
 #endif
 
         /* Mark baseline scripts on the stack as active. */
         jit::MarkActiveBaselineScripts(this);
 
         /* Only mark OSI points if code is being discarded. */
         jit::InvalidateAll(fop, this);
 
-        for (ZoneCellIter i(this, AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = cellIter<JSScript>(); !script.done(); script.next())  {
             jit::FinishInvalidation(fop, script);
 
             /*
              * Discard baseline script if it's not marked as active. Note that
              * this also resets the active flag.
              */
             jit::FinishDiscardBaselineScript(fop, script);
 
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -79,16 +79,19 @@ struct UniqueIdGCPolicy {
 using UniqueIdMap = GCHashMap<Cell*,
                               uint64_t,
                               PointerHasher<Cell*, 3>,
                               SystemAllocPolicy,
                               UniqueIdGCPolicy>;
 
 extern uint64_t NextCellUniqueId(JSRuntime* rt);
 
+template <typename T>
+class ZoneCellIter;
+
 } // namespace gc
 } // namespace js
 
 namespace JS {
 
 // A zone is a collection of compartments. Every compartment belongs to exactly
 // one zone. In Firefox, there is roughly one zone per tab along with a system
 // zone for everything else. Zones mainly serve as boundaries for garbage
@@ -152,16 +155,23 @@ struct Zone : public JS::shadow::Zone,
     void updateMallocCounter(size_t nbytes) {
         // Note: this code may be run from worker threads. We tolerate any
         // thread races when updating gcMallocBytes.
         gcMallocBytes -= ptrdiff_t(nbytes);
         if (MOZ_UNLIKELY(isTooMuchMalloc()))
             onTooMuchMalloc();
     }
 
+    // Iterate over all cells in the zone. See the definition of ZoneCellIter
+    // in jsgcinlines.h for the possible arguments and documentation.
+    template <typename T, typename... Args>
+    js::gc::ZoneCellIter<T> cellIter(Args... args) {
+        return js::gc::ZoneCellIter<T>(const_cast<Zone*>(this), mozilla::Forward<Args>(args)...);
+    }
+
     bool isTooMuchMalloc() const { return gcMallocBytes <= 0; }
     void onTooMuchMalloc();
 
     MOZ_MUST_USE void* onOutOfMemory(js::AllocFunction allocFunc, size_t nbytes,
                                                void* reallocPtr = nullptr) {
         if (!js::CurrentThreadCanAccessRuntime(runtime_))
             return nullptr;
         return runtimeFromMainThread()->onOutOfMemory(allocFunc, nbytes, reallocPtr);
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -483,16 +483,17 @@ void
 BaselineScript::Trace(JSTracer* trc, BaselineScript* script)
 {
     script->trace(trc);
 }
 
 void
 BaselineScript::Destroy(FreeOp* fop, BaselineScript* script)
 {
+
     MOZ_ASSERT(!script->hasPendingIonBuilder());
 
     script->unlinkDependentWasmModules(fop);
 
     /*
      * When the script contains pointers to nursery things, the store buffer can
      * contain entries that point into the fallback stub space. Since we can
      * destroy scripts outside the context of a GC, this situation could result
@@ -1166,46 +1167,43 @@ jit::AddSizeOfBaselineData(JSScript* scr
 void
 jit::ToggleBaselineProfiling(JSRuntime* runtime, bool enable)
 {
     JitRuntime* jrt = runtime->jitRuntime();
     if (!jrt)
         return;
 
     for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) {
-        for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
             if (!script->hasBaselineScript())
                 continue;
             AutoWritableJitCode awjc(script->baselineScript()->method());
             script->baselineScript()->toggleProfilerInstrumentation(enable);
         }
     }
 }
 
 #ifdef JS_TRACE_LOGGING
 void
 jit::ToggleBaselineTraceLoggerScripts(JSRuntime* runtime, bool enable)
 {
     for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) {
-        for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
             if (!script->hasBaselineScript())
                 continue;
             script->baselineScript()->toggleTraceLoggerScripts(runtime, script, enable);
         }
     }
 }
 
 void
 jit::ToggleBaselineTraceLoggerEngine(JSRuntime* runtime, bool enable)
 {
     for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) {
-        for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
             if (!script->hasBaselineScript())
                 continue;
             script->baselineScript()->toggleTraceLoggerEngine(enable);
         }
     }
 }
 #endif
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -6,16 +6,17 @@
 
 #include "jit/Ion.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/ThreadLocal.h"
 
 #include "jscompartment.h"
+#include "jsgc.h"
 #include "jsprf.h"
 
 #include "gc/Marking.h"
 #include "jit/AliasAnalysis.h"
 #include "jit/AlignmentMaskAnalysis.h"
 #include "jit/BacktrackingAllocator.h"
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineInspector.h"
@@ -584,18 +585,18 @@ jit::LazyLinkTopActivation(JSContext* cx
     return calleeScript->baselineOrIonRawPointer();
 }
 
 /* static */ void
 JitRuntime::Mark(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
     MOZ_ASSERT(!trc->runtime()->isHeapMinorCollecting());
     Zone* zone = trc->runtime()->atomsCompartment(lock)->zone();
-    for (gc::ZoneCellIterUnderGC i(zone, gc::AllocKind::JITCODE); !i.done(); i.next()) {
-        JitCode* code = i.get<JitCode>();
+    for (auto i = zone->cellIter<JitCode>(); !i.done(); i.next()) {
+        JitCode* code = i;
         TraceRoot(trc, &code, "wrapper");
     }
 }
 
 /* static */ void
 JitRuntime::MarkJitcodeGlobalTableUnconditionally(JSTracer* trc)
 {
     if (trc->runtime()->spsProfiler.enabled() &&
@@ -1346,18 +1347,17 @@ IonScript::unlinkFromRuntime(FreeOp* fop
 
 void
 jit::ToggleBarriers(JS::Zone* zone, bool needs)
 {
     JSRuntime* rt = zone->runtimeFromMainThread();
     if (!rt->hasJitRuntime())
         return;
 
-    for (gc::ZoneCellIterUnderGC i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-        JSScript* script = i.get<JSScript>();
+    for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
         if (script->hasIonScript())
             script->ionScript()->toggleBarriers(needs);
         if (script->hasBaselineScript())
             script->baselineScript()->toggleBarriers(needs);
     }
 
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
         if (comp->jitCompartment())
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -979,18 +979,18 @@ AddLazyFunctionsForCompartment(JSContext
     // an uncompiled enclosing script. The last condition is so that we don't
     // compile lazy scripts whose enclosing scripts failed to compile,
     // indicating that the lazy script did not escape the script.
     //
     // Some LazyScripts have a non-null |JSScript* script| pointer. We still
     // want to delazify in that case: this pointer is weak so the JSScript
     // could be destroyed at the next GC.
 
-    for (gc::ZoneCellIter i(cx->zone(), kind); !i.done(); i.next()) {
-        JSFunction* fun = &i.get<JSObject>()->as<JSFunction>();
+    for (auto i = cx->zone()->cellIter<JSObject>(kind); !i.done(); i.next()) {
+        JSFunction* fun = &i->as<JSFunction>();
 
         // Sweeping is incremental; take care to not delazify functions that
         // are about to be finalized. GC things referenced by objects that are
         // about to be finalized (e.g., in slots) may already be freed.
         if (gc::IsAboutToBeFinalizedUnbarriered(&fun) ||
             fun->compartment() != cx->compartment())
         {
             continue;
@@ -1152,18 +1152,17 @@ JSCompartment::clearScriptCounts()
 
     js_delete(scriptCountsMap);
     scriptCountsMap = nullptr;
 }
 
 void
 JSCompartment::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, HandleObject handler)
 {
-    for (gc::ZoneCellIter i(zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-        JSScript* script = i.get<JSScript>();
+    for (auto script = zone()->cellIter<JSScript>(); !script.done(); script.next()) {
         if (script->compartment() == this && script->hasAnyBreakpointsOrStepMode())
             script->clearBreakpointsIn(fop, dbg, handler);
     }
 }
 
 void
 JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                       size_t* tiAllocationSiteTables,
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2376,25 +2376,20 @@ Zone::prepareForCompacting()
 void
 GCRuntime::sweepTypesAfterCompacting(Zone* zone)
 {
     FreeOp* fop = rt->defaultFreeOp();
     zone->beginSweepTypes(fop, rt->gc.releaseObservedTypes && !zone->isPreservingCode());
 
     AutoClearTypeInferenceStateOnOOM oom(zone);
 
-    for (ZoneCellIterUnderGC i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
-        JSScript* script = i.get<JSScript>();
+    for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
         script->maybeSweepTypes(&oom);
-    }
-
-    for (ZoneCellIterUnderGC i(zone, AllocKind::OBJECT_GROUP); !i.done(); i.next()) {
-        ObjectGroup* group = i.get<ObjectGroup>();
+    for (auto group = zone->cellIter<ObjectGroup>(); !group.done(); group.next())
         group->maybeSweep(&oom);
-    }
 
     zone->types.endSweep(rt);
 }
 
 void
 GCRuntime::sweepZoneAfterCompacting(Zone* zone)
 {
     MOZ_ASSERT(zone->isCollecting());
@@ -3976,20 +3971,21 @@ CompartmentCheckTracer::onChild(const JS
 
 void
 GCRuntime::checkForCompartmentMismatches()
 {
     if (disableStrictProxyCheckingCount)
         return;
 
     CompartmentCheckTracer trc(rt);
+    AutoAssertEmptyNursery empty(rt);
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
         trc.zone = zone;
         for (auto thingKind : AllAllocKinds()) {
-            for (ZoneCellIterUnderGC i(zone, thingKind); !i.done(); i.next()) {
+            for (auto i = zone->cellIter<TenuredCell>(thingKind, empty); !i.done(); i.next()) {
                 trc.src = i.getCell();
                 trc.srcKind = MapAllocToTraceKind(thingKind);
                 trc.compartment = DispatchTraceKindTyped(MaybeCompartmentFunctor(),
                                                          trc.src, trc.srcKind);
                 js::TraceChildren(&trc, trc.src, trc.srcKind);
             }
         }
     }
@@ -3998,19 +3994,20 @@ GCRuntime::checkForCompartmentMismatches
 
 static void
 RelazifyFunctions(Zone* zone, AllocKind kind)
 {
     MOZ_ASSERT(kind == AllocKind::FUNCTION ||
                kind == AllocKind::FUNCTION_EXTENDED);
 
     JSRuntime* rt = zone->runtimeFromMainThread();
-
-    for (ZoneCellIterUnderGC i(zone, kind); !i.done(); i.next()) {
-        JSFunction* fun = &i.get<JSObject>()->as<JSFunction>();
+    AutoAssertEmptyNursery empty(rt);
+
+    for (auto i = zone->cellIter<JSObject>(kind, empty); !i.done(); i.next()) {
+        JSFunction* fun = &i->as<JSFunction>();
         if (fun->hasScript())
             fun->maybeRelazify(rt);
     }
 }
 
 bool
 GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock)
 {
@@ -6914,18 +6911,17 @@ gc::MergeCompartments(JSCompartment* sou
     // Fixup compartment pointers in source to refer to target, and make sure
     // type information generations are in sync.
 
     // Get the static global lexical scope of the target compartment. Static
     // scopes need to be fixed up below.
     RootedObject targetStaticGlobalLexicalScope(rt);
     targetStaticGlobalLexicalScope = &target->maybeGlobal()->lexicalScope().staticBlock();
 
-    for (ZoneCellIter iter(source->zone(), AllocKind::SCRIPT); !iter.done(); iter.next()) {
-        JSScript* script = iter.get<JSScript>();
+    for (auto script = source->zone()->cellIter<JSScript>(); !script.done(); script.next()) {
         MOZ_ASSERT(script->compartment() == source);
         script->compartment_ = target;
         script->setTypesGeneration(target->zone()->types.generation);
 
         // If the script failed to compile, no need to fix up.
         if (!script->code())
             continue;
 
@@ -6947,24 +6943,22 @@ gc::MergeCompartments(JSCompartment* sou
                     JSObject* enclosing = scope->enclosingScope();
                     if (IsStaticGlobalLexicalScope(enclosing))
                         scope->setEnclosingScope(targetStaticGlobalLexicalScope);
                 }
             }
         }
     }
 
-    for (ZoneCellIter iter(source->zone(), AllocKind::BASE_SHAPE); !iter.done(); iter.next()) {
-        BaseShape* base = iter.get<BaseShape>();
+    for (auto base = source->zone()->cellIter<BaseShape>(); !base.done(); base.next()) {
         MOZ_ASSERT(base->compartment() == source);
         base->compartment_ = target;
     }
 
-    for (ZoneCellIter iter(source->zone(), AllocKind::OBJECT_GROUP); !iter.done(); iter.next()) {
-        ObjectGroup* group = iter.get<ObjectGroup>();
+    for (auto group = source->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
         group->setGeneration(target->zone()->types.generation);
         group->compartment_ = target;
 
         // Remove any unboxed layouts from the list in the off thread
         // compartment. These do not need to be reinserted in the target
         // compartment's list, as the list is not required to be complete.
         if (UnboxedLayout* layout = group->maybeUnboxedLayoutDontCheckGeneration())
             layout->detachFromCompartment();
@@ -6976,18 +6970,17 @@ gc::MergeCompartments(JSCompartment* sou
         for (ArenaIter aiter(source->zone(), thingKind); !aiter.done(); aiter.next()) {
             Arena* arena = aiter.get();
             arena->zone = target->zone();
         }
     }
 
     // After fixing JSFunctions' compartments, we can fix LazyScripts'
     // enclosing scopes.
-    for (ZoneCellIter iter(source->zone(), AllocKind::LAZY_SCRIPT); !iter.done(); iter.next()) {
-        LazyScript* lazy = iter.get<LazyScript>();
+    for (auto lazy = source->zone()->cellIter<LazyScript>(); !lazy.done(); lazy.next()) {
         MOZ_ASSERT(lazy->functionNonDelazifying()->compartment() == target);
 
         // See warning in handleParseWorkload. If we start optimizing global
         // lexicals, we would need to merge the contents of the static global
         // lexical scope.
         if (JSObject* enclosing = lazy->enclosingScope()) {
             if (IsStaticGlobalLexicalScope(enclosing))
                 lazy->fixEnclosingStaticGlobalLexicalScope();
@@ -7111,22 +7104,19 @@ js::ReleaseAllJITCode(FreeOp* fop)
         zone->setPreservingCode(false);
         zone->discardJitCode(fop);
     }
 }
 
 void
 js::PurgeJITCaches(Zone* zone)
 {
-    for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
-        JSScript* script = i.get<JSScript>();
-
-        /* Discard Ion caches. */
+    /* Discard Ion caches. */
+    for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
         jit::PurgeCaches(script);
-    }
 }
 
 void
 ArenaLists::normalizeBackgroundFinalizeState(AllocKind thingKind)
 {
     ArenaLists::BackgroundFinalizeState* bfs = &backgroundFinalizeState[thingKind];
     switch (*bfs) {
       case BFS_DONE:
@@ -7386,18 +7376,17 @@ js::gc::CheckHashTablesAfterMovingGC(JSR
     /*
      * Check that internal hash tables no longer have any pointers to things
      * that have been moved.
      */
     rt->spsProfiler.checkStringsMapAfterMovingGC();
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
         zone->checkUniqueIdTableAfterMovingGC();
 
-        for (ZoneCellIterUnderGC i(zone, AllocKind::BASE_SHAPE); !i.done(); i.next()) {
-            BaseShape* baseShape = i.get<BaseShape>();
+        for (auto baseShape = zone->cellIter<BaseShape>(); !baseShape.done(); baseShape.next()) {
             if (baseShape->hasTable())
                 baseShape->table().checkAfterMovingGC();
         }
     }
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         c->objectGroups.checkTablesAfterMovingGC();
         c->dtoaCache.checkCacheAfterMovingGC();
         c->checkInitialShapesTableAfterMovingGC();
@@ -7865,10 +7854,33 @@ StateName(State state)
         "Finalize",
         "Compact"
     };
     MOZ_ASSERT(ArrayLength(names) == NUM_STATES);
     MOZ_ASSERT(state < NUM_STATES);
     return names[state];
 }
 
+void
+AutoAssertHeapBusy::checkCondition(JSRuntime *rt)
+{
+    this->rt = rt;
+    MOZ_ASSERT(rt->isHeapBusy());
+}
+
+void
+AutoAssertEmptyNursery::checkCondition(JSRuntime *rt) {
+    this->rt = rt;
+    MOZ_ASSERT(rt->gc.nursery.isEmpty());
+}
+
+AutoEmptyNursery::AutoEmptyNursery(JSRuntime *rt)
+  : AutoAssertEmptyNursery()
+{
+    MOZ_ASSERT(!rt->mainThread.suppressGC);
+    rt->gc.stats.suspendPhases();
+    rt->gc.evictNursery();
+    rt->gc.stats.resumePhases();
+    checkCondition(rt);
+}
+
 } /* namespace gc */
 } /* namespace js */
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1323,16 +1323,105 @@ struct MOZ_RAII AutoAssertNoNurseryAlloc
 
   private:
     gc::GCRuntime& gc;
 #else
     explicit AutoAssertNoNurseryAlloc(JSRuntime* rt) {}
 #endif
 };
 
+/*
+ * There are a couple of classes here that serve mostly as "tokens" indicating
+ * that a condition holds. Some functions force the caller to possess such a
+ * token because they would misbehave if the condition were false, and it is
+ * far more clear to make the condition visible at the point where it can be
+ * affected rather than just crashing in an assertion down in the place where
+ * it is relied upon.
+ */
+
+/*
+ * Token meaning that the heap is busy and no allocations will be made.
+ *
+ * This class may be instantiated directly if it is known that the condition is
+ * already true, or it can be used as a base class for another RAII class that
+ * causes the condition to become true. Such base classes will use the no-arg
+ * constructor, establish the condition, then call checkCondition() to assert
+ * it and possibly record data needed to re-check the condition during
+ * destruction.
+ *
+ * Ordinarily, you would do something like this with a Maybe<> member that is
+ * emplaced during the constructor, but token-requiring functions want to
+ * require a reference to a base class instance. That said, you can always pass
+ * in the Maybe<> field as the token.
+ */
+class MOZ_RAII AutoAssertHeapBusy {
+  protected:
+    JSRuntime* rt;
+
+    // Check that the heap really is busy, and record the rt for the check in
+    // the destructor.
+    void checkCondition(JSRuntime *rt);
+
+    AutoAssertHeapBusy() : rt(nullptr) {
+    }
+
+  public:
+    explicit AutoAssertHeapBusy(JSRuntime* rt) {
+        checkCondition(rt);
+    }
+
+    ~AutoAssertHeapBusy() {
+        MOZ_ASSERT(rt); // checkCondition must always be called.
+        checkCondition(rt);
+    }
+};
+
+/*
+ * A class that serves as a token that the nursery is empty. It descends from
+ * AutoAssertHeapBusy, which means that it additionally requires the heap to be
+ * busy (which is not necessarily linked, but turns out to be true in practice
+ * for all users and simplifies the usage of these classes.)
+ */
+class MOZ_RAII AutoAssertEmptyNursery
+{
+  protected:
+    JSRuntime* rt;
+
+    // Check that the nursery is empty.
+    void checkCondition(JSRuntime *rt);
+
+    // For subclasses that need to empty the nursery in their constructors.
+    AutoAssertEmptyNursery() : rt(nullptr) {
+    }
+
+  public:
+    explicit AutoAssertEmptyNursery(JSRuntime* rt) {
+        checkCondition(rt);
+    }
+
+    ~AutoAssertEmptyNursery() {
+        checkCondition(rt);
+    }
+};
+
+/*
+ * Evict the nursery upon construction. Serves as a token indicating that the
+ * nursery is empty. (See AutoAssertEmptyNursery, above.)
+ *
+ * Note that this is very improper subclass of AutoAssertHeapBusy, in that the
+ * heap is *not* busy within the scope of an AutoEmptyNursery. I will most
+ * likely fix this by removing AutoAssertHeapBusy, but that is currently
+ * waiting on jonco's review.
+ */
+class MOZ_RAII AutoEmptyNursery : public AutoAssertEmptyNursery
+{
+  public:
+    explicit AutoEmptyNursery(JSRuntime *rt);
+};
+
 const char*
 StateName(State state);
 
 } /* namespace gc */
 
 #ifdef DEBUG
 /* Use this to avoid assertions when manipulating the wrapper map. */
 class MOZ_RAII AutoDisableProxyCheck
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -190,103 +190,165 @@ class ArenaCellIterUnderGC : public Aren
 };
 
 class ArenaCellIterUnderFinalize : public ArenaCellIterImpl
 {
   public:
     explicit ArenaCellIterUnderFinalize(Arena* arena) : ArenaCellIterImpl(arena) {}
 };
 
-class ZoneCellIterImpl
-{
+template <typename T>
+class ZoneCellIter;
+
+template <>
+class ZoneCellIter<TenuredCell> {
     ArenaIter arenaIter;
     ArenaCellIterImpl cellIter;
+    JS::AutoAssertNoAlloc noAlloc;
 
-  public:
-    ZoneCellIterImpl(JS::Zone* zone, AllocKind kind) {
+  protected:
+    // For use when a subclass wants to insert some setup before init().
+    ZoneCellIter() {}
+
+    void init(JS::Zone* zone, AllocKind kind) {
         JSRuntime* rt = zone->runtimeFromAnyThread();
         MOZ_ASSERT(zone);
         MOZ_ASSERT_IF(IsNurseryAllocable(kind), rt->gc.nursery.isEmpty());
 
+        // If called from outside a GC, ensure that the heap is in a state
+        // that allows us to iterate.
+        if (!rt->isHeapBusy()) {
+            // Assert that no GCs can occur while a ZoneCellIter is live.
+            noAlloc.disallowAlloc(rt);
+        }
+
         // We have a single-threaded runtime, so there's no need to protect
         // against other threads iterating or allocating. However, we do have
         // background finalization; we may have to wait for this to finish if
         // it's currently active.
         if (IsBackgroundFinalized(kind) && zone->arenas.needBackgroundFinalizeWait(kind))
             rt->gc.waitBackgroundSweepEnd();
-
         arenaIter.init(zone, kind);
         if (!arenaIter.done())
             cellIter.init(arenaIter.get());
     }
 
+  public:
+    ZoneCellIter(JS::Zone* zone, AllocKind kind) {
+        // If we are iterating a nursery-allocated kind then we need to
+        // evict first so that we can see all things.
+        if (IsNurseryAllocable(kind)) {
+            JSRuntime* rt = zone->runtimeFromMainThread();
+            rt->gc.evictNursery();
+        }
+
+        init(zone, kind);
+    }
+
+    ZoneCellIter(JS::Zone* zone, AllocKind kind, const js::gc::AutoAssertEmptyNursery&) {
+        // No need to evict the nursery. (This constructor is known statically
+        // to not GC.)
+        init(zone, kind);
+    }
+
     bool done() const {
         return arenaIter.done();
     }
 
-    template<typename T> T* get() const {
+    template<typename T>
+    T* get() const {
         MOZ_ASSERT(!done());
         return cellIter.get<T>();
     }
 
-    Cell* getCell() const {
+    TenuredCell* getCell() const {
         MOZ_ASSERT(!done());
         return cellIter.getCell();
     }
 
     void next() {
         MOZ_ASSERT(!done());
         cellIter.next();
         if (cellIter.done()) {
             MOZ_ASSERT(!arenaIter.done());
             arenaIter.next();
             if (!arenaIter.done())
                 cellIter.reset(arenaIter.get());
         }
     }
 };
 
-class ZoneCellIterUnderGC : public ZoneCellIterImpl
-{
-  public:
-    ZoneCellIterUnderGC(JS::Zone* zone, AllocKind kind)
-      : ZoneCellIterImpl(zone, kind)
-    {
-        MOZ_ASSERT(zone->runtimeFromAnyThread()->isHeapBusy());
-    }
-};
-
-class ZoneCellIter
-{
-    mozilla::Maybe<ZoneCellIterImpl> impl;
-    JS::AutoAssertNoAlloc noAlloc;
-
+// Iterator over the cells in a Zone, where the GC type (JSString, JSObject) is
+// known, for a single AllocKind. Example usages:
+//
+//   for (auto obj = zone->cellIter<JSObject>(AllocKind::OBJECT0); !obj.done(); obj.next())
+//       ...
+//
+//   for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next())
+//       f(script->code());
+//
+// As this code demonstrates, you can use 'script' as if it were a JSScript*.
+// Its actual type is ZoneCellIter<JSScript>, but for most purposes it will
+// autoconvert to JSScript*.
+//
+// Note that in the JSScript case, ZoneCellIter is able to infer the AllocKind
+// from the type 'JSScript', whereas in the JSObject case, the kind must be
+// given (because there are multiple AllocKinds for objects).
+//
+// Also, the static rooting hazard analysis knows that the JSScript case will
+// not GC during construction. The JSObject case needs to GC, or more precisely
+// to empty the nursery and clear out the store buffer, so that it can see all
+// objects to iterate over (the nursery is not iterable) and remove the
+// possibility of having pointers from the store buffer to data hanging off
+// stuff we're iterating over that we are going to delete. (The latter should
+// not be a problem, since such instances should be using RelocatablePtr do
+// remove themselves from the store buffer on deletion, but currently for
+// subtle reasons that isn't good enough.)
+//
+// If the iterator is used within a GC, then there is no need to evict the
+// nursery (again). You may select a variant that will skip the eviction either
+// by specializing on a GCType that is never allocated in the nursery, or
+// explicitly by passing in a trailing AutoAssertEmptyNursery argument.
+//
+template <typename GCType>
+class ZoneCellIter : public ZoneCellIter<TenuredCell> {
   public:
-    ZoneCellIter(JS::Zone* zone, AllocKind kind) {
-        // If called from outside a GC, ensure that the heap is in a state
-        // that allows us to iterate.
-        JSRuntime* rt = zone->runtimeFromMainThread();
-        if (!rt->isHeapBusy()) {
-            // If we are iterating a nursery-allocated kind then we need to
-            // evict first so that we can see all things.
-            if (IsNurseryAllocable(kind))
-                rt->gc.evictNursery();
-
-            // Assert that no GCs can occur while a ZoneCellIter is live.
-            noAlloc.disallowAlloc(rt);
-        }
-
-        impl.emplace(zone, kind);
+    // Non-nursery allocated (equivalent to having an entry in
+    // MapTypeToFinalizeKind). The template declaration here is to discard this
+    // constructor overload if MapTypeToFinalizeKind<GCType>::kind does not
+    // exist. Note that there will be no remaining overloads that will work,
+    // which makes sense given that you haven't specified which of the
+    // AllocKinds to use for GCType.
+    //
+    // If we later add a nursery allocable GCType with a single AllocKind, we
+    // will want to add an overload of this constructor that does the right
+    // thing (ie, it empties the nursery before iterating.)
+    explicit ZoneCellIter(JS::Zone* zone) : ZoneCellIter<TenuredCell>() {
+        init(zone, MapTypeToFinalizeKind<GCType>::kind);
     }
 
-    bool done() const { return impl->done(); }
-    template<typename T>
-    T* get() const { return impl->get<T>(); }
-    Cell* getCell() const { return impl->getCell(); }
-    void next() { impl->next(); }
+    // Non-nursery allocated, nursery is known to be empty: same behavior as above.
+    ZoneCellIter(JS::Zone* zone, const js::gc::AutoAssertEmptyNursery&) : ZoneCellIter(zone) {
+    }
+
+    // Arbitrary kind, which will be assumed to be nursery allocable (and
+    // therefore the nursery will be emptied before iterating.)
+    ZoneCellIter(JS::Zone* zone, AllocKind kind) : ZoneCellIter<TenuredCell>(zone, kind) {
+    }
+
+    // Arbitrary kind, which will be assumed to be nursery allocable, but the
+    // nursery is known to be empty already: same behavior as non-nursery types.
+    ZoneCellIter(JS::Zone* zone, AllocKind kind, const js::gc::AutoAssertEmptyNursery& empty)
+      : ZoneCellIter<TenuredCell>(zone, kind, empty)
+    {
+    }
+
+    GCType* get() const { return ZoneCellIter<TenuredCell>::get<GCType>(); }
+    operator GCType*() const { return get(); }
+    GCType* operator ->() const { return get(); }
 };
 
 class GCZonesIter
 {
   private:
     ZonesIter zone;
 
   public:
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -196,18 +196,19 @@ js::DumpPCCounts(JSContext* cx, HandleSc
         DumpIonScriptCounts(sp, script, ionCounts);
         ionCounts = ionCounts->previous();
     }
 }
 
 void
 js::DumpCompartmentPCCounts(JSContext* cx)
 {
-    for (ZoneCellIter i(cx->zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-        RootedScript script(cx, i.get<JSScript>());
+    RootedScript script(cx);
+    for (auto iter = cx->zone()->cellIter<JSScript>(); !iter.done(); iter.next()) {
+        script = iter;
         if (script->compartment() != cx->compartment())
             continue;
 
         if (script->hasScriptCounts()) {
             Sprinter sprinter(cx);
             if (!sprinter.init())
                 return;
 
@@ -1652,18 +1653,17 @@ js::StopPCCountProfiling(JSContext* cx)
     ReleaseAllJITCode(rt->defaultFreeOp());
 
     auto* vec = cx->new_<PersistentRooted<ScriptAndCountsVector>>(cx,
         ScriptAndCountsVector(SystemAllocPolicy()));
     if (!vec)
         return;
 
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-        for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
             if (script->hasScriptCounts() && script->types()) {
                 if (!vec->append(script))
                     return;
             }
         }
     }
 
     rt->profilingScripts = false;
@@ -2020,18 +2020,17 @@ GenerateLcovInfo(JSContext* cx, JSCompar
     JSRuntime* rt = cx->runtime();
 
     // Collect the list of scripts which are part of the current compartment.
     {
         js::gc::AutoPrepareForTracing apft(rt, SkipAtoms);
     }
     Rooted<ScriptVector> topScripts(cx, ScriptVector(cx));
     for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-        for (ZoneCellIter i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
-            JSScript* script = i.get<JSScript>();
+        for (auto script = zone->cellIter<JSScript>(); !script.done(); script.next()) {
             if (script->compartment() != comp ||
                 !script->isTopLevel() ||
                 !script->filename())
             {
                 continue;
             }
 
             if (!topScripts.append(script))
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2347,18 +2347,18 @@ UpdateExecutionObservabilityOfScriptsInZ
     {
         AutoEnterAnalysis enter(fop, zone);
         if (JSScript* script = obs.singleScriptForZoneInvalidation()) {
             if (obs.shouldRecompileOrInvalidate(script)) {
                 if (!AppendAndInvalidateScript(cx, zone, script, scripts))
                     return false;
             }
         } else {
-            for (gc::ZoneCellIter iter(zone, gc::AllocKind::SCRIPT); !iter.done(); iter.next()) {
-                JSScript* script = iter.get<JSScript>();
+            for (auto iter = zone->cellIter<JSScript>(); !iter.done(); iter.next()) {
+                JSScript* script = iter;
                 if (obs.shouldRecompileOrInvalidate(script) &&
                     !gc::IsAboutToBeFinalizedUnbarriered(&script))
                 {
                     if (!AppendAndInvalidateScript(cx, zone, script, scripts))
                         return false;
                 }
             }
         }
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1266,35 +1266,32 @@ GlobalHelperThreadState::mergeParseTaskC
     // destination compartment.  Finish any ongoing incremental GC first and
     // assert that no allocation can occur.
     gc::FinishGC(rt);
     JS::AutoAssertNoAlloc noAlloc(rt);
 
     LeaveParseTaskZone(rt, parseTask);
 
     {
-        gc::ZoneCellIter iter(parseTask->cx->zone(), gc::AllocKind::OBJECT_GROUP);
-
         // Generator functions don't have Function.prototype as prototype but a
         // different function object, so the IdentifyStandardPrototype trick
         // below won't work.  Just special-case it.
         GlobalObject* parseGlobal = &parseTask->exclusiveContextGlobal->as<GlobalObject>();
         JSObject* parseTaskStarGenFunctionProto = parseGlobal->getStarGeneratorFunctionPrototype();
 
         // Module objects don't have standard prototypes either.
         JSObject* moduleProto = parseGlobal->maybeGetModulePrototype();
         JSObject* importEntryProto = parseGlobal->maybeGetImportEntryPrototype();
         JSObject* exportEntryProto = parseGlobal->maybeGetExportEntryPrototype();
 
         // Point the prototypes of any objects in the script's compartment to refer
         // to the corresponding prototype in the new compartment. This will briefly
         // create cross compartment pointers, which will be fixed by the
         // MergeCompartments call below.
-        for (; !iter.done(); iter.next()) {
-            ObjectGroup* group = iter.get<ObjectGroup>();
+        for (auto group = parseTask->cx->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
             TaggedProto proto(group->proto());
             if (!proto.isObject())
                 continue;
 
             JSObject* protoObj = proto.toObject();
 
             JSObject* newProto;
             JSProtoKey key = JS::IdentifyStandardPrototype(protoObj);
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1044,17 +1044,17 @@ CallAddPropertyHookDense(ExclusiveContex
             obj->setDenseElementHole(cx, index);
             return false;
         }
     }
     return true;
 }
 
 static bool
-UpdateShapeTypeAndValue(ExclusiveContext* cx, NativeObject* obj, Shape* shape, const Value& value)
+UpdateShapeTypeAndValue(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape, const Value& value)
 {
     jsid id = shape->propid();
     if (shape->hasSlot()) {
         obj->setSlotWithType(cx, shape, value, /* overwriting = */ false);
 
         // Per the acquired properties analysis, when the shape of a partially
         // initialized object is changed to its fully initialized shape, its
         // group can be updated as well.
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2540,26 +2540,25 @@ js::PrintTypes(JSContext* cx, JSCompartm
     JSAutoRequest request(cx);
 
     Zone* zone = comp->zone();
     AutoEnterAnalysis enter(nullptr, zone);
 
     if (!force && !InferSpewActive(ISpewResult))
         return;
 
-    for (gc::ZoneCellIter i(zone, gc::AllocKind::SCRIPT); !i.done(); i.next()) {
-        RootedScript script(cx, i.get<JSScript>());
+    RootedScript script(cx);
+    for (auto iter = zone->cellIter<JSScript>(); !iter.done(); iter.next()) {
+        script = iter;
         if (script->types())
             script->types()->printTypes(cx, script);
     }
 
-    for (gc::ZoneCellIter i(zone, gc::AllocKind::OBJECT_GROUP); !i.done(); i.next()) {
-        ObjectGroup* group = i.get<ObjectGroup>();
+    for (auto group = zone->cellIter<ObjectGroup>(); !group.done(); group.next())
         group->print();
-    }
 #endif
 }
 
 /////////////////////////////////////////////////////////////////////
 // ObjectGroup
 /////////////////////////////////////////////////////////////////////
 
 static inline void
@@ -4420,30 +4419,29 @@ TypeZone::endSweep(JSRuntime* rt)
     sweepReleaseTypes = false;
 
     rt->gc.freeAllLifoBlocksAfterSweeping(&sweepTypeLifoAlloc);
 }
 
 void
 TypeZone::clearAllNewScriptsOnOOM()
 {
-    for (gc::ZoneCellIter iter(zone(), gc::AllocKind::OBJECT_GROUP);
-         !iter.done(); iter.next())
-    {
-        ObjectGroup* group = iter.get<ObjectGroup>();
+    for (auto iter = zone()->cellIter<ObjectGroup>(); !iter.done(); iter.next()) {
+        ObjectGroup* group = iter;
         if (!IsAboutToBeFinalizedUnbarriered(&group))
             group->maybeClearNewScriptOnOOM();
     }
 }
 
 AutoClearTypeInferenceStateOnOOM::~AutoClearTypeInferenceStateOnOOM()
 {
     if (oom) {
+        JSRuntime* rt = zone->runtimeFromMainThread();
         zone->setPreservingCode(false);
-        zone->discardJitCode(zone->runtimeFromMainThread()->defaultFreeOp());
+        zone->discardJitCode(rt->defaultFreeOp());
         zone->types.clearAllNewScriptsOnOOM();
     }
 }
 
 #ifdef DEBUG
 void
 TypeScript::printTypes(JSContext* cx, HandleScript script) const
 {