Bug 1288793 - Part 1: Convert js::gc::State to an enum class; r=jonco
authorTerrence Cole <terrence@mozilla.com>
Mon, 25 Jul 2016 11:10:15 -0700
changeset 347216 c85767662b633604936c5b5e4869770423f5ec01
parent 347215 a83df31aa5bd32260b8f2789a240424f09f876da
child 347217 2ffdc17e68e06e837dc9d8cea0741e3f6bda8631
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1288793
milestone50.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 1288793 - Part 1: Convert js::gc::State to an enum class; r=jonco * * * Bug _ - Add a test to ensure that abort works during decommit slices; NOT_REVIEWED
js/src/builtin/TestingFunctions.cpp
js/src/gc/GCRuntime.h
js/src/gc/Marking.cpp
js/src/gc/Statistics.h
js/src/gc/Verifier.cpp
js/src/jit-test/tests/gc/bug-1138390.js
js/src/jit-test/tests/gc/incremental-abort.js
js/src/jit-test/tests/gc/incremental-compacting.js
js/src/jit-test/tests/gc/incremental-state.js
js/src/jsapi-tests/testGCFinalizeCallback.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/vm/Debugger.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -859,33 +859,17 @@ GCState(JSContext* cx, unsigned argc, Va
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() != 0) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Too many arguments");
         return false;
     }
 
-    const char* state;
-    gc::State globalState = cx->runtime()->gc.state();
-    if (globalState == gc::NO_INCREMENTAL)
-        state = "none";
-    else if (globalState == gc::MARK)
-        state = "mark";
-    else if (globalState == gc::SWEEP)
-        state = "sweep";
-    else if (globalState == gc::FINALIZE)
-        state = "finalize";
-    else if (globalState == gc::COMPACT)
-        state = "compact";
-    else if (globalState == gc::DECOMMIT)
-        state = "decommit";
-    else
-        MOZ_CRASH("Unobserveable global GC state");
-
+    const char* state = StateName(cx->runtime()->gc.state());
     JSString* str = JS_NewStringCopyZ(cx, state);
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
 static bool
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -684,18 +684,18 @@ class GCRuntime
         MOZ_ASSERT(nextCellUniqueId_ > 0);
         uint64_t uid = ++nextCellUniqueId_;
         return uid;
     }
 
   public:
     // Internal public interface
     State state() const { return incrementalState; }
-    bool isHeapCompacting() const { return state() == COMPACT; }
-    bool isForegroundSweeping() const { return state() == SWEEP; }
+    bool isHeapCompacting() const { return state() == State::Compact; }
+    bool isForegroundSweeping() const { return state() == State::Sweep; }
     bool isBackgroundSweeping() { return helperState.isBackgroundSweeping(); }
     void waitBackgroundSweepEnd() { helperState.waitBackgroundSweepEnd(); }
     void waitBackgroundSweepOrAllocEnd() {
         helperState.waitBackgroundSweepEnd();
         allocTask.cancel(GCParallelTask::CancelAndWait);
     }
 
     void requestMinorGC(JS::gcreason::Reason reason);
@@ -761,17 +761,17 @@ class GCRuntime
 #endif // DEBUG
 
     void setAlwaysPreserveCode() { alwaysPreserveCode = true; }
 
     bool isIncrementalGCAllowed() const { return incrementalAllowed; }
     void disallowIncrementalGC() { incrementalAllowed = false; }
 
     bool isIncrementalGCEnabled() const { return mode == JSGC_MODE_INCREMENTAL && incrementalAllowed; }
-    bool isIncrementalGCInProgress() const { return state() != NO_INCREMENTAL; }
+    bool isIncrementalGCInProgress() const { return state() != State::NotActive; }
 
     bool isGenerationalGCEnabled() const { return generationalDisabled == 0; }
     void disableGenerationalGC();
     void enableGenerationalGC();
 
     void disableCompactingGC();
     void enableCompactingGC();
     bool isCompactingGCEnabled() const;
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -341,18 +341,18 @@ AssertZoneIsMarking(JS::Symbol* sym)
     MOZ_ASSERT(zone->isGCMarking() || zone->isAtomsZone());
 #endif
 }
 
 static void
 AssertRootMarkingPhase(JSTracer* trc)
 {
     MOZ_ASSERT_IF(trc->isMarkingTracer(),
-                  trc->runtime()->gc.state() == NO_INCREMENTAL ||
-                  trc->runtime()->gc.state() == MARK_ROOTS);
+                  trc->runtime()->gc.state() == State::NotActive ||
+                  trc->runtime()->gc.state() == State::MarkRoots);
 }
 
 
 /*** Tracing Interface ***************************************************************************/
 
 // The second parameter to BaseGCType is derived automatically based on T. The
 // relation here is that for any T, the TraceKind will automatically,
 // statically select the correct Cell layout for marking. Below, we instantiate
@@ -1940,17 +1940,17 @@ GCMarker::markDelayedChildren(Arena* are
      * allocatedDuringIncremental flag if we continue marking.
      */
 }
 
 bool
 GCMarker::markDelayedChildren(SliceBudget& budget)
 {
     GCRuntime& gc = runtime()->gc;
-    gcstats::AutoPhase ap(gc.stats, gc.state() == MARK, gcstats::PHASE_MARK_DELAYED);
+    gcstats::AutoPhase ap(gc.stats, gc.state() == State::Mark, gcstats::PHASE_MARK_DELAYED);
 
     MOZ_ASSERT(unmarkedArenaStackTop);
     do {
         /*
          * If marking gets delayed at the same arena again, we must repeat
          * marking of its things. For that we pop arena from the stack and
          * clear its hasDelayedMarking flag before we begin the marking.
          */
@@ -2434,17 +2434,17 @@ CheckIsMarkedThing(T* thingp)
 #undef IS_SAME_TYPE_OR
 
 #ifdef DEBUG
     MOZ_ASSERT(thingp);
     MOZ_ASSERT(*thingp);
     JSRuntime* rt = (*thingp)->runtimeFromAnyThread();
     MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(*thingp),
                   CurrentThreadCanAccessRuntime(rt) ||
-                  (rt->isHeapCollecting() && rt->gc.state() == SWEEP));
+                  (rt->isHeapCollecting() && rt->gc.state() == State::Sweep));
 #endif
 }
 
 template <typename T>
 static bool
 IsMarkedInternalCommon(T* thingp)
 {
     CheckIsMarkedThing(thingp);
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -248,17 +248,17 @@ struct Statistics
 
     static const size_t MAX_NESTING = 20;
 
     struct SliceData {
         SliceData(SliceBudget budget, JS::gcreason::Reason reason, int64_t start,
                   double startTimestamp, size_t startFaults, gc::State initialState)
           : budget(budget), reason(reason),
             initialState(initialState),
-            finalState(gc::NO_INCREMENTAL),
+            finalState(gc::State::NotActive),
             resetReason(nullptr),
             start(start), startTimestamp(startTimestamp),
             startFaults(startFaults)
         {
             for (auto i : mozilla::MakeRange(NumTimingArrays))
                 mozilla::PodArrayZero(phaseTimes[i]);
         }
 
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -198,17 +198,17 @@ gc::GCRuntime::startVerifyPreBarriers()
     trc->term = trc->edgeptr + size;
 
     if (!trc->nodemap.init())
         goto oom;
 
     /* Create the root node. */
     trc->curnode = MakeNode(trc, nullptr, JS::TraceKind(0));
 
-    incrementalState = MARK_ROOTS;
+    incrementalState = State::MarkRoots;
 
     /* Make all the roots be edges emanating from the root node. */
     markRuntime(trc, TraceRuntime, prep.session().lock);
 
     VerifyNode* node;
     node = trc->curnode;
     if (trc->edgeptr == trc->term)
         goto oom;
@@ -225,31 +225,31 @@ gc::GCRuntime::startVerifyPreBarriers()
             if (trc->edgeptr == trc->term)
                 goto oom;
         }
 
         node = NextNode(node);
     }
 
     verifyPreData = trc;
-    incrementalState = MARK;
+    incrementalState = State::Mark;
     marker.start();
 
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         PurgeJITCaches(zone);
         if (!zone->usedByExclusiveThread) {
             zone->setNeedsIncrementalBarrier(true, Zone::UpdateJit);
             zone->arenas.purge();
         }
     }
 
     return;
 
 oom:
-    incrementalState = NO_INCREMENTAL;
+    incrementalState = State::NotActive;
     js_delete(trc);
     verifyPreData = nullptr;
 }
 
 static bool
 IsMarkedOrAllocated(TenuredCell* cell)
 {
     return cell->isMarked() || cell->arena()->allocatedDuringIncremental;
@@ -337,17 +337,17 @@ gc::GCRuntime::endVerifyPreBarriers()
     /*
      * We need to bump gcNumber so that the methodjit knows that jitcode has
      * been discarded.
      */
     MOZ_ASSERT(trc->number == number);
     number++;
 
     verifyPreData = nullptr;
-    incrementalState = NO_INCREMENTAL;
+    incrementalState = State::NotActive;
 
     if (!compartmentCreated && IsIncrementalGCSafe(rt)) {
         CheckEdgeTracer cetrc(rt);
 
         /* Start after the roots. */
         VerifyNode* node = NextNode(trc->root);
         while ((char*)node < trc->edgeptr) {
             cetrc.node = node;
--- a/js/src/jit-test/tests/gc/bug-1138390.js
+++ b/js/src/jit-test/tests/gc/bug-1138390.js
@@ -12,17 +12,17 @@ gczeal(0);
 gc();
 
 // Start an incremental GC that includes the atoms zone
 startgc(0);
 var g = newGlobal();
 
 // Start an off thread compilation that will not run until GC has finished
 if ("gcstate" in this)
-   assertEq(gcstate(), "mark");
+   assertEq(gcstate(), "Mark");
 g.offThreadCompileScript('23;', {});
 
 // Wait for the compilation to finish, which must finish the GC first
 assertEq(23, g.runOffThreadScript());
 if ("gcstate" in this)
-   assertEq(gcstate(), "none");
+   assertEq(gcstate(), "NotActive");
 
 print("done");
--- a/js/src/jit-test/tests/gc/incremental-abort.js
+++ b/js/src/jit-test/tests/gc/incremental-abort.js
@@ -22,31 +22,33 @@ function testAbort(zoneCount, objectCoun
                 "}",
                  { global: zone });
         zone.makeObjectGraph(objectCount);
         zones.push(zone);
     }
 
     var didAbort = false;
     startgc(sliceCount, "shrinking");
-    while (gcstate() !== "none") {
+    while (gcstate() !== "NotActive") {
         var state = gcstate();
         if (state == abortState) {
             abortgc();
             didAbort = true;
             break;
         }
 
         gcslice(sliceCount);
     }
 
-    assertEq(gcstate(), "none");
+    assertEq(gcstate(), "NotActive");
     if (abortState)
         assertEq(didAbort, true);
 
     return zones;
 }
 
 gczeal(0);
 testAbort(10, 10000, 10000);
-testAbort(10, 10000, 10000, "mark");
-testAbort(10, 10000, 10000, "sweep");
-testAbort(10, 10000, 10000, "compact");
+testAbort(10, 10000, 10000, "Mark");
+testAbort(10, 10000, 10000, "Sweep");
+testAbort(10, 10000, 10000, "Compact");
+// Note: we do not yield automatically before Finalize or Decommit, as they yield internally.
+// Thus, we may not witness an incremental state in this phase and cannot test it explicitly.
--- a/js/src/jit-test/tests/gc/incremental-compacting.js
+++ b/js/src/jit-test/tests/gc/incremental-compacting.js
@@ -22,21 +22,21 @@ function testCompacting(zoneCount, objec
                  "        objects.push({ serial: i }); " +
                 "}",
                  { global: zone });
         zone.makeObjectGraph(objectCount);
         zones.push(zone);
     }
 
     // Finish any alloc-triggered incremental GC
-    if (gcstate() !== "none")
+    if (gcstate() !== "NotActive")
         gc();
 
     startgc(sliceCount, "shrinking");
-    while (gcstate() !== "none") {
+    while (gcstate() !== "NotActive") {
         gcslice(sliceCount);
     }
 
     return zones;
 }
 
 testCompacting(1, 100000, 100000);
 testCompacting(2, 100000, 100000);
--- a/js/src/jit-test/tests/gc/incremental-state.js
+++ b/js/src/jit-test/tests/gc/incremental-state.js
@@ -1,63 +1,63 @@
 // Test expected state changes during collection.
 if (!("gcstate" in this))
     quit();
 
 gczeal(0);
 
 // Non-incremental GC.
 gc();
-assertEq(gcstate(), "none");
+assertEq(gcstate(), "NotActive");
 
 // Incremental GC in minimal slice. Note that finalization always uses zero-
 // sized slices while background finalization is on-going, so we need to loop.
 gcslice(1000000);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Incremental GC in multiple slices: if marking takes more than one slice,
 // we yield before we start sweeping.
 gczeal(0);
 gcslice(1);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1000000);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1000000);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Zeal mode 8: Incremental GC in two main slices:
 //   1) mark roots
 //   2) mark and sweep
 //   *) finalize.
 gczeal(8, 0);
 gcslice(1);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Zeal mode 9: Incremental GC in two main slices:
 //   1) mark roots and marking
 //   2) new marking and sweeping
 //   *) finalize.
 gczeal(9, 0);
 gcslice(1);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Zeal mode 10: Incremental GC in multiple slices (always yeilds before
 // sweeping). This test uses long slices to prove that this zeal mode yields
 // in sweeping, where normal IGC (above) does not.
 gczeal(10, 0);
 gcslice(1000000);
-assertEq(gcstate(), "sweep");
+assertEq(gcstate(), "Sweep");
 gcslice(1000000);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -98,17 +98,17 @@ BEGIN_TEST(testGCFinalizeCallback)
 
     /* Full GC with reset due to new compartment, becoming compartment GC. */
 
     FinalizeCalls = 0;
     JS_SetGCZeal(cx, 9, 1000000);
     JS::PrepareForFullGC(cx);
     js::SliceBudget budget(js::WorkBudget(1));
     cx->gc.startDebugGC(GC_NORMAL, budget);
-    CHECK(cx->gc.state() == js::gc::MARK);
+    CHECK(cx->gc.state() == js::gc::State::Mark);
     CHECK(cx->gc.isFullGc());
 
     JS::RootedObject global4(cx, createTestGlobal());
     budget = js::SliceBudget(js::WorkBudget(1));
     cx->gc.debugGCSlice(budget);
     while (cx->gc.isIncrementalGCInProgress())
         cx->gc.debugGCSlice(budget);
     CHECK(!cx->gc.isIncrementalGCInProgress());
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -48,56 +48,59 @@
  * passed.
  *
  * Collector states
  * ----------------
  *
  * The collector proceeds through the following states, the current state being
  * held in JSRuntime::gcIncrementalState:
  *
- *  - MARK_ROOTS - marks the stack and other roots
- *  - MARK       - incrementally marks reachable things
- *  - SWEEP      - sweeps zones in groups and continues marking unswept zones
+ *  - MarkRoots  - marks the stack and other roots
+ *  - Mark       - incrementally marks reachable things
+ *  - Sweep      - sweeps zones in groups and continues marking unswept zones
+ *  - Finalize   - performs background finalization, concurrent with mutator
+ *  - Compact    - incrementally compacts by zone
+ *  - Decommit   - performs background decommit and chunk removal
  *
- * The MARK_ROOTS activity always takes place in the first slice. The next two
+ * The MarkRoots activity always takes place in the first slice. The next two
  * states can take place over one or more slices.
  *
  * In other words an incremental collection proceeds like this:
  *
- * Slice 1:   MARK_ROOTS: Roots pushed onto the mark stack.
- *            MARK:       The mark stack is processed by popping an element,
+ * Slice 1:   MarkRoots:  Roots pushed onto the mark stack.
+ *            Mark:       The mark stack is processed by popping an element,
  *                        marking it, and pushing its children.
  *
  *          ... JS code runs ...
  *
- * Slice 2:   MARK:       More mark stack processing.
+ * Slice 2:   Mark:       More mark stack processing.
  *
  *          ... JS code runs ...
  *
- * Slice n-1: MARK:       More mark stack processing.
+ * Slice n-1: Mark:       More mark stack processing.
  *
  *          ... JS code runs ...
  *
- * Slice n:   MARK:       Mark stack is completely drained.
- *            SWEEP:      Select first group of zones to sweep and sweep them.
+ * Slice n:   Mark:       Mark stack is completely drained.
+ *            Sweep:      Select first group of zones to sweep and sweep them.
  *
  *          ... JS code runs ...
  *
- * Slice n+1: SWEEP:      Mark objects in unswept zones that were newly
+ * Slice n+1: Sweep:      Mark objects in unswept zones that were newly
  *                        identified as alive (see below). Then sweep more zone
  *                        groups.
  *
  *          ... JS code runs ...
  *
- * Slice n+2: SWEEP:      Mark objects in unswept zones that were newly
+ * Slice n+2: Sweep:      Mark objects in unswept zones that were newly
  *                        identified as alive. Then sweep more zone groups.
  *
  *          ... JS code runs ...
  *
- * Slice m:   SWEEP:      Sweeping is finished, and background sweeping
+ * Slice m:   Sweep:      Sweeping is finished, and background sweeping
  *                        started on the helper thread.
  *
  *          ... JS code runs, remaining sweeping done on background thread ...
  *
  * When background sweeping finishes the GC is complete.
  *
  * Incremental marking
  * -------------------
@@ -136,17 +139,17 @@
  * about to be destroyed.
  *
  * Sweeping all finalizable objects in one go would introduce long pauses, so
  * instead sweeping broken up into groups of zones. Zones which are not yet
  * being swept are still marked, so the issue above does not apply.
  *
  * The order of sweeping is restricted by cross compartment pointers - for
  * example say that object |a| from zone A points to object |b| in zone B and
- * neither object was marked when we transitioned to the SWEEP phase. Imagine we
+ * neither object was marked when we transitioned to the Sweep phase. Imagine we
  * sweep B first and then return to the mutator. It's possible that the mutator
  * could cause |a| to become alive through a read barrier (perhaps it was a
  * shape that was accessed via a shape table). Then we would need to mark |b|,
  * which |a| points to, but |b| has already been swept.
  *
  * So if there is such a pointer then marking of zone B must not finish before
  * marking of zone A.  Pointers which form a cycle between zones therefore
  * restrict those zones to being swept at the same time, and these are found
@@ -817,17 +820,17 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
     majorGCNumber(0),
     jitReleaseNumber(0),
     number(0),
     startNumber(0),
     isFull(false),
 #ifdef DEBUG
     disableStrictProxyCheckingCount(0),
 #endif
-    incrementalState(gc::NO_INCREMENTAL),
+    incrementalState(gc::State::NotActive),
     lastMarkSlice(false),
     sweepOnBackgroundThread(false),
     foundBlackGrayEdges(false),
     blocksToFreeAfterSweeping(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     blocksToFreeAfterMinorGC(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     zoneGroupIndex(0),
     zoneGroups(nullptr),
     currentZoneGroup(nullptr),
@@ -4171,17 +4174,17 @@ js::gc::MarkingValidator::nonIncremental
     /*
      * After this point, the function should run to completion, so we shouldn't
      * do anything fallible.
      */
     initialized = true;
 
     /* Re-do all the marking, but non-incrementally. */
     js::gc::State state = gc->incrementalState;
-    gc->incrementalState = MARK_ROOTS;
+    gc->incrementalState = State::MarkRoots;
 
     {
         gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_MARK);
 
         {
             gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_UNMARK);
 
             for (GCZonesIter zone(runtime); !zone.done(); zone.next())
@@ -4191,22 +4194,22 @@ js::gc::MarkingValidator::nonIncremental
             gcmarker->reset();
 
             for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next())
                 chunk->bitmap.clear();
         }
 
         gc->markRuntime(gcmarker, GCRuntime::MarkRuntime, lock);
 
-        gc->incrementalState = MARK;
+        gc->incrementalState = State::Mark;
         auto unlimited = SliceBudget::unlimited();
         MOZ_RELEASE_ASSERT(gc->marker.drainMarkStack(unlimited));
     }
 
-    gc->incrementalState = SWEEP;
+    gc->incrementalState = State::Sweep;
     {
         gcstats::AutoPhase ap1(gc->stats, gcstats::PHASE_SWEEP);
         gcstats::AutoPhase ap2(gc->stats, gcstats::PHASE_SWEEP_MARK);
 
         gc->markAllWeakReferences(gcstats::PHASE_SWEEP_MARK_WEAK);
 
         /* Update zone state for gray marking. */
         for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
@@ -5616,44 +5619,48 @@ AutoTraceSession::~AutoTraceSession()
         runtime->heapState_ = prevState;
     }
 }
 
 void
 GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lock)
 {
     switch (incrementalState) {
-      case NO_INCREMENTAL:
+      case State::NotActive:
         return;
 
-      case MARK: {
+      case State::MarkRoots:
+        MOZ_CRASH("resetIncrementalGC did not expect MarkRoots state");
+        break;
+
+      case State::Mark: {
         /* Cancel any ongoing marking. */
         marker.reset();
         marker.stop();
         clearBufferedGrayRoots();
 
         for (GCCompartmentsIter c(rt); !c.done(); c.next())
             ResetGrayList(c);
 
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
             MOZ_ASSERT(zone->isGCMarking());
             zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit);
             zone->setGCState(Zone::NoGC);
         }
 
         blocksToFreeAfterSweeping.freeAll();
 
-        incrementalState = NO_INCREMENTAL;
+        incrementalState = State::NotActive;
 
         MOZ_ASSERT(!marker.shouldCheckCompartments());
 
         break;
       }
 
-      case SWEEP: {
+      case State::Sweep: {
         marker.reset();
 
         for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
             c->scheduledForDestruction = false;
 
         /* Finish sweeping the current zone group, then abort. */
         abortSweepAfterCurrentGroup = true;
 
@@ -5668,68 +5675,65 @@ GCRuntime::resetIncrementalGC(const char
 
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
         break;
       }
 
-      case FINALIZE: {
+      case State::Finalize: {
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
 
         bool wasCompacting = isCompacting;
         isCompacting = false;
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
 
         isCompacting = wasCompacting;
 
         break;
       }
 
-      case COMPACT: {
+      case State::Compact: {
         bool wasCompacting = isCompacting;
 
         isCompacting = true;
         startedCompacting = true;
         zonesToMaybeCompact.clear();
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
 
         isCompacting = wasCompacting;
         break;
       }
 
-      case DECOMMIT: {
+      case State::Decommit: {
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
         break;
       }
-
-      default:
-        MOZ_CRASH("Invalid incremental GC state");
     }
 
     stats.reset(reason);
 
 #ifdef DEBUG
     assertBackgroundSweepingFinished();
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         MOZ_ASSERT(!zone->isCollecting());
         MOZ_ASSERT(!zone->needsIncrementalBarrier());
         MOZ_ASSERT(!zone->isOnList());
     }
     MOZ_ASSERT(zonesToMaybeCompact.isEmpty());
-    MOZ_ASSERT(incrementalState == NO_INCREMENTAL);
+    MOZ_ASSERT(incrementalState == State::NotActive);
 #endif
 }
 
 namespace {
 
 class AutoGCSlice {
   public:
     explicit AutoGCSlice(JSRuntime* rt);
@@ -5838,70 +5842,70 @@ GCRuntime::incrementalCollectSlice(Slice
         /*
          * Yields between slices occurs at predetermined points in these modes;
          * the budget is not used.
          */
         budget.makeUnlimited();
     }
 
     switch (incrementalState) {
-      case NO_INCREMENTAL:
+      case State::NotActive:
         initialReason = reason;
         cleanUpEverything = ShouldCleanUpEverything(reason, invocationKind);
         isCompacting = shouldCompact();
         lastMarkSlice = false;
 
-        incrementalState = MARK_ROOTS;
+        incrementalState = State::MarkRoots;
 
         MOZ_FALLTHROUGH;
 
-      case MARK_ROOTS:
+      case State::MarkRoots:
         if (!beginMarkPhase(reason, lock)) {
-            incrementalState = NO_INCREMENTAL;
+            incrementalState = State::NotActive;
             return;
         }
 
         if (!destroyingRuntime)
             pushZealSelectedObjects();
 
-        incrementalState = MARK;
+        incrementalState = State::Mark;
 
         if (isIncremental && useZeal && hasZealMode(ZealMode::IncrementalRootsThenFinish))
             break;
 
         MOZ_FALLTHROUGH;
 
-      case MARK:
+      case State::Mark:
         AutoGCRooter::traceAllWrappers(&marker);
 
         /* If we needed delayed marking for gray roots, then collect until done. */
         if (!hasBufferedGrayRoots()) {
             budget.makeUnlimited();
             isIncremental = false;
         }
 
         if (drainMarkStack(budget, gcstats::PHASE_MARK) == NotFinished)
             break;
 
         MOZ_ASSERT(marker.isDrained());
 
         if (!lastMarkSlice && isIncremental && useZeal &&
-            ((initialState == MARK && !hasZealMode(ZealMode::IncrementalRootsThenFinish)) ||
+            ((initialState == State::Mark && !hasZealMode(ZealMode::IncrementalRootsThenFinish)) ||
              hasZealMode(ZealMode::IncrementalMarkAllThenFinish)))
         {
             /*
              * Yield with the aim of starting the sweep in the next
              * slice.  We will need to mark anything new on the stack
-             * when we resume, so we stay in MARK state.
+             * when we resume, so we stay in Mark state.
              */
             lastMarkSlice = true;
             break;
         }
 
-        incrementalState = SWEEP;
+        incrementalState = State::Sweep;
 
         /*
          * This runs to completion, but we don't continue if the budget is
          * now exhasted.
          */
         beginSweepPhase(destroyingRuntime, lock);
         if (budget.isOverBudget())
             break;
@@ -5910,31 +5914,31 @@ GCRuntime::incrementalCollectSlice(Slice
          * Always yield here when running in incremental multi-slice zeal
          * mode, so RunDebugGC can reset the slice buget.
          */
         if (isIncremental && useZeal && hasZealMode(ZealMode::IncrementalMultipleSlices))
             break;
 
         MOZ_FALLTHROUGH;
 
-      case SWEEP:
+      case State::Sweep:
         if (sweepPhase(budget, lock) == NotFinished)
             break;
 
         endSweepPhase(destroyingRuntime, lock);
 
-        incrementalState = FINALIZE;
+        incrementalState = State::Finalize;
 
         /* Yield before compacting since it is not incremental. */
         if (isCompacting && isIncremental)
             break;
 
         MOZ_FALLTHROUGH;
 
-      case FINALIZE:
+      case State::Finalize:
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
 
             // Yield until background finalization is done.
             if (isIncremental) {
                 // Poll for end of background sweeping
                 AutoLockGC lock(rt);
                 if (isBackgroundSweeping())
@@ -5950,53 +5954,50 @@ GCRuntime::incrementalCollectSlice(Slice
             gcstats::AutoPhase ap1(stats, gcstats::PHASE_SWEEP);
             gcstats::AutoPhase ap2(stats, gcstats::PHASE_DESTROY);
             AutoSetThreadIsSweeping threadIsSweeping;
             FreeOp fop(rt);
             sweepZones(&fop, destroyingRuntime);
         }
 
         MOZ_ASSERT(!startedCompacting);
-        incrementalState = COMPACT;
+        incrementalState = State::Compact;
 
         MOZ_FALLTHROUGH;
 
-      case COMPACT:
+      case State::Compact:
         if (isCompacting) {
             if (!startedCompacting)
                 beginCompactPhase();
 
             if (compactPhase(reason, budget, lock) == NotFinished)
                 break;
 
             endCompactPhase(reason);
         }
 
         startDecommit();
-        incrementalState = DECOMMIT;
+        incrementalState = State::Decommit;
 
         MOZ_FALLTHROUGH;
 
-      case DECOMMIT:
+      case State::Decommit:
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
 
             // Yield until background decommit is done.
             if (isIncremental && decommitTask.isRunning())
                 break;
 
             decommitTask.join();
         }
 
         finishCollection(reason);
-        incrementalState = NO_INCREMENTAL;
+        incrementalState = State::NotActive;
         break;
-
-      default:
-        MOZ_CRASH("unexpected GC incrementalState");
     }
 }
 
 IncrementalSafety
 gc::IsIncrementalGCSafe(JSRuntime* rt)
 {
     MOZ_ASSERT(!rt->mainThread.suppressGC);
 
@@ -6149,17 +6150,17 @@ GCRuntime::gcCycle(bool nonincrementalBy
 
         stats.nonincremental("requested");
         budget.makeUnlimited();
     } else {
         budgetIncrementalGC(budget, session.lock);
     }
 
     /* The GC was reset, so we need a do-over. */
-    if (prevState != NO_INCREMENTAL && !isIncrementalGCInProgress())
+    if (prevState != State::NotActive && !isIncrementalGCInProgress())
         return true;
 
     TraceMajorGCStart();
 
     incrementalCollectSlice(budget, reason, session.lock);
 
 #ifndef JS_MORE_DETERMINISTIC
     nextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN;
@@ -6356,17 +6357,17 @@ void
 GCRuntime::finishGC(JS::gcreason::Reason reason)
 {
     MOZ_ASSERT(isIncrementalGCInProgress());
 
     // If we're not collecting because we're out of memory then skip the
     // compacting phase if we need to finish an ongoing incremental GC
     // non-incrementally to avoid janking the browser.
     if (!IsOOMReason(initialReason)) {
-        if (incrementalState == COMPACT) {
+        if (incrementalState == State::Compact) {
             abortGC();
             return;
         }
 
         isCompacting = false;
     }
 
     collect(false, SliceBudget::unlimited(), reason);
@@ -6794,18 +6795,18 @@ GCRuntime::runDebugGC()
             invocationKind = GC_SHRINK;
         collect(false, budget, JS::gcreason::DEBUG_GC);
 
         /*
          * For multi-slice zeal, reset the slice size when we get to the sweep
          * or compact phases.
          */
         if (hasZealMode(ZealMode::IncrementalMultipleSlices)) {
-            if ((initialState == MARK && incrementalState == SWEEP) ||
-                (initialState == SWEEP && incrementalState == COMPACT))
+            if ((initialState == State::Mark && incrementalState == State::Sweep) ||
+                (initialState == State::Sweep && incrementalState == State::Compact))
             {
                 incrementalLimit = zealFrequency / 2;
             }
         }
     } else if (hasZealMode(ZealMode::Compact)) {
         gc(GC_SHRINK, JS::gcreason::DEBUG_GC);
     } else {
         gc(GC_NORMAL, JS::gcreason::DEBUG_GC);
@@ -7308,17 +7309,17 @@ JS_PUBLIC_API(bool)
 JS::IsIncrementalGCInProgress(JSContext* cx)
 {
     return cx->gc.isIncrementalGCInProgress() && !cx->gc.isVerifyPreBarriersEnabled();
 }
 
 JS_PUBLIC_API(bool)
 JS::IsIncrementalBarrierNeeded(JSContext* cx)
 {
-    return cx->gc.state() == gc::MARK && !cx->isHeapBusy();
+    return cx->gc.state() == gc::State::Mark && !cx->isHeapBusy();
 }
 
 struct IncrementalReferenceBarrierFunctor {
     template <typename T> void operator()(T* t) { T::writeBarrierPre(t); }
 };
 
 JS_PUBLIC_API(void)
 JS::IncrementalReferenceBarrier(GCCellPtr thing)
@@ -7591,28 +7592,22 @@ NewMemoryInfoObject(JSContext* cx)
     }
 
     return obj;
 }
 
 const char*
 StateName(State state)
 {
-    static const char* names[] = {
-        "None",
-        "MarkRoots",
-        "Mark",
-        "Sweep",
-        "Finalize",
-        "Compact",
-        "Decommit"
-    };
-    MOZ_ASSERT(ArrayLength(names) == NUM_STATES);
-    MOZ_ASSERT(state < NUM_STATES);
-    return names[state];
+    switch(state) {
+#define MAKE_CASE(name) case State::name: return #name;
+      GCSTATES(MAKE_CASE)
+#undef MAKE_CASE
+    }
+    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalide gc::State enum value");
 }
 
 void
 AutoAssertHeapBusy::checkCondition(JSRuntime *rt)
 {
     this->rt = rt;
     MOZ_ASSERT(rt->isHeapBusy());
 }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -38,26 +38,28 @@ struct Statistics;
 } // namespace gcstats
 
 class Nursery;
 
 namespace gc {
 
 struct FinalizePhase;
 
-enum State {
-    NO_INCREMENTAL,
-    MARK_ROOTS,
-    MARK,
-    SWEEP,
-    FINALIZE,
-    COMPACT,
-    DECOMMIT,
-
-    NUM_STATES
+#define GCSTATES(D) \
+    D(NotActive) \
+    D(MarkRoots) \
+    D(Mark) \
+    D(Sweep) \
+    D(Finalize) \
+    D(Compact) \
+    D(Decommit)
+enum class State {
+#define MAKE_STATE(name) name,
+    GCSTATES(MAKE_STATE)
+#undef MAKE_STATE
 };
 
 /*
  * Map from C++ type to alloc kind for non-object types. JSObject does not have
  * a 1:1 mapping, so must use Arena::thingSize.
  *
  * The AllocKind is available as MapTypeToFinalizeKind<SomeType>::kind.
  */
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2824,22 +2824,22 @@ Debugger::markCrossCompartmentEdges(JSTr
  * This method is also used during compacting GC to update cross compartment
  * pointers in zones that are not currently being compacted.
  */
 /* static */ void
 Debugger::markIncomingCrossCompartmentEdges(JSTracer* trc)
 {
     JSRuntime* rt = trc->runtime();
     gc::State state = rt->gc.state();
-    MOZ_ASSERT(state == gc::MARK_ROOTS || state == gc::COMPACT);
+    MOZ_ASSERT(state == gc::State::MarkRoots || state == gc::State::Compact);
 
     for (Debugger* dbg : rt->debuggerList) {
         Zone* zone = MaybeForwarded(dbg->object.get())->zone();
-        if ((state == gc::MARK_ROOTS && !zone->isCollecting()) ||
-            (state == gc::COMPACT && !zone->isGCCompacting()))
+        if ((state == gc::State::MarkRoots && !zone->isCollecting()) ||
+            (state == gc::State::Compact && !zone->isGCCompacting()))
         {
             dbg->markCrossCompartmentEdges(trc);
         }
     }
 }
 
 /*
  * This method has two tasks: