Backed out 5 changesets (bug 1099152, bug 1117098) for causing frequent B2G debug timeouts.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 05 Jan 2015 12:38:18 -0500
changeset 247867 06ec26af14f35965ce9eddecbb70ec6ad2e9f326
parent 247866 db47b360e898aa6640daef18f23dc15e54afcd07
child 247868 d7f308cbcbeb6a35718d61d9a01f72192a1c9bb5
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1099152, 1117098
milestone37.0a1
backs out4feee07c34ad82a3bdfe7719b785b201f7ca79a3
168e5b9bf1983090ef06932a8e1dbe2b8bdff549
9192d432d87e5718443f09a6f9fe336e61fd075f
8b891ebcd21f2761b07b4add7861c75d28818112
24dae3ce853db3851f98647b88d24adc8a8379f5
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
Backed out 5 changesets (bug 1099152, bug 1117098) for causing frequent B2G debug timeouts. Backed out changeset 4feee07c34ad (bug 1117098) Backed out changeset 168e5b9bf198 (bug 1099152) Backed out changeset 9192d432d87e (bug 1099152) Backed out changeset 8b891ebcd21f (bug 1099152) Backed out changeset 24dae3ce853d (bug 1099152)
dom/base/nsJSEnvironment.cpp
dom/workers/WorkerPrivate.cpp
js/public/GCAPI.h
js/src/builtin/TestingFunctions.cpp
js/src/devtools/rootAnalysis/loadCallgraph.js
js/src/gc/GCRuntime.h
js/src/jsapi-tests/testGCFinalizeCallback.cpp
js/src/jsapi-tests/testPreserveJitCode.cpp
js/src/jsapi-tests/tests.h
js/src/jsfriendapi.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/xpconnect/src/XPCComponents.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1454,33 +1454,34 @@ nsJSContext::GarbageCollectNow(JS::gcrea
 
   if (!nsContentUtils::XPConnect() || !sRuntime) {
     return;
   }
 
   if (sCCLockedOut && aIncremental == IncrementalGC) {
     // We're in the middle of incremental GC. Do another slice.
     JS::PrepareForIncrementalGC(sRuntime);
-    JS::IncrementalGCSlice(sRuntime, aReason, aSliceMillis);
+    JS::IncrementalGC(sRuntime, aReason, aSliceMillis);
     return;
   }
 
   if (sNeedsFullGC || aReason != JS::gcreason::CC_WAITING) {
     sNeedsFullGC = false;
     JS::PrepareForFullGC(sRuntime);
   } else {
     CycleCollectedJSRuntime::Get()->PrepareWaitingZonesForGC();
   }
 
   if (aIncremental == IncrementalGC) {
     MOZ_ASSERT(aShrinking == NonShrinkingGC);
-    JS::StartIncrementalGC(sRuntime, GC_NORMAL, aReason, aSliceMillis);
+    JS::IncrementalGC(sRuntime, aReason, aSliceMillis);
+  } else if (aShrinking == ShrinkingGC) {
+    JS::ShrinkingGC(sRuntime, aReason);
   } else {
-    JSGCInvocationKind gckind = aShrinking == ShrinkingGC ? GC_SHRINK : GC_NORMAL;
-    JS::GCForReason(sRuntime, gckind, aReason);
+    JS::GCForReason(sRuntime, aReason);
   }
 }
 
 //static
 void
 nsJSContext::ShrinkGCBuffersNow()
 {
   PROFILER_LABEL("nsJSContext", "ShrinkGCBuffersNow",
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -6021,24 +6021,24 @@ WorkerPrivate::GarbageCollectInternal(JS
     return;
   }
 
   if (aShrinking || aCollectChildren) {
     JSRuntime* rt = JS_GetRuntime(aCx);
     JS::PrepareForFullGC(rt);
 
     if (aShrinking) {
-      JS::GCForReason(rt, GC_SHRINK, JS::gcreason::DOM_WORKER);
+      JS::ShrinkingGC(rt, JS::gcreason::DOM_WORKER);
 
       if (!aCollectChildren) {
         LOG(("Worker %p collected idle garbage\n", this));
       }
     }
     else {
-      JS::GCForReason(rt, GC_NORMAL, JS::gcreason::DOM_WORKER);
+      JS::GCForReason(rt, JS::gcreason::DOM_WORKER);
       LOG(("Worker %p collected garbage\n", this));
     }
   }
   else {
     JS_MaybeGC(aCx);
     LOG(("Worker %p collected periodic garbage\n", this));
   }
 
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -26,27 +26,16 @@ typedef enum JSGCMode {
 
     /*
      * Collect in short time slices rather than all at once. Implies
      * JSGC_MODE_COMPARTMENT.
      */
     JSGC_MODE_INCREMENTAL = 2
 } JSGCMode;
 
-/*
- * Kinds of js_GC invocation.
- */
-typedef enum JSGCInvocationKind {
-    /* Normal invocation. */
-    GC_NORMAL = 0,
-
-    /* Minimize GC triggers and release empty GC chunks right away. */
-    GC_SHRINK = 1
-} JSGCInvocationKind;
-
 namespace JS {
 
 #define GCREASONS(D)                            \
     /* Reasons internal to the JS engine */     \
     D(API)                                      \
     D(MAYBEGC)                                  \
     D(DESTROY_RUNTIME)                          \
     D(DESTROY_CONTEXT)                          \
@@ -55,17 +44,16 @@ namespace JS {
     D(ALLOC_TRIGGER)                            \
     D(DEBUG_GC)                                 \
     D(COMPARTMENT_REVIVED)                      \
     D(RESET)                                    \
     D(OUT_OF_NURSERY)                           \
     D(EVICT_NURSERY)                            \
     D(FULL_STORE_BUFFER)                        \
     D(SHARED_MEMORY_LIMIT)                      \
-    D(INCREMENTAL_ALLOC_TRIGGER)                \
                                                 \
     /* These are reserved for future use. */    \
     D(RESERVED0)                                \
     D(RESERVED1)                                \
     D(RESERVED2)                                \
     D(RESERVED3)                                \
     D(RESERVED4)                                \
     D(RESERVED5)                                \
@@ -76,16 +64,17 @@ namespace JS {
     D(RESERVED10)                               \
     D(RESERVED11)                               \
     D(RESERVED12)                               \
     D(RESERVED13)                               \
     D(RESERVED14)                               \
     D(RESERVED15)                               \
     D(RESERVED16)                               \
     D(RESERVED17)                               \
+    D(RESERVED18)                               \
                                                 \
     /* Reasons from Firefox */                  \
     D(DOM_WINDOW_UTILS)                         \
     D(COMPONENT_UTILS)                          \
     D(MEM_PRESSURE)                             \
     D(CC_WAITING)                               \
     D(CC_FORCED)                                \
     D(LOAD_END)                                 \
@@ -136,64 +125,68 @@ enum Reason {
  * to select the zones to be collected. To do this, you can call
  * PrepareZoneForGC on each zone, or you can call PrepareForFullGC to select
  * all zones. Failing to select any zone is an error.
  */
 
 /*
  * Schedule the given zone to be collected as part of the next GC.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 PrepareZoneForGC(Zone *zone);
 
 /*
  * Schedule all zones to be collected in the next GC.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 PrepareForFullGC(JSRuntime *rt);
 
 /*
  * When performing an incremental GC, the zones that were selected for the
  * previous incremental slice must be selected in subsequent slices as well.
  * This function selects those slices automatically.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 PrepareForIncrementalGC(JSRuntime *rt);
 
 /*
  * Returns true if any zone in the system has been scheduled for GC with one of
  * the functions above or by the JS engine.
  */
-extern JS_PUBLIC_API(bool)
+extern JS_FRIEND_API(bool)
 IsGCScheduled(JSRuntime *rt);
 
 /*
  * Undoes the effect of the Prepare methods above. The given zone will not be
  * collected in the next GC.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 SkipZoneForGC(Zone *zone);
 
 /*
  * Non-Incremental GC:
  *
  * The following functions perform a non-incremental GC.
  */
 
 /*
- * Performs a non-incremental collection of all selected zones.
- *
- * If the gckind argument is GC_NORMAL, then some objects that are unreachable
- * from the program may still be alive afterwards because of internal
- * references; if GC_SHRINK is passed then caches and other temporary references
- * to objects will be cleared and all unreferenced objects will be removed from
- * the system.
+ * Performs a non-incremental collection of all selected zones. Some objects
+ * that are unreachable from the program may still be alive afterwards because
+ * of internal references.
  */
-extern JS_PUBLIC_API(void)
-GCForReason(JSRuntime *rt, JSGCInvocationKind gckind, gcreason::Reason reason);
+extern JS_FRIEND_API(void)
+GCForReason(JSRuntime *rt, gcreason::Reason reason);
+
+/*
+ * Perform a non-incremental collection after clearing caches and other
+ * temporary references to objects. This will remove all unreferenced objects
+ * in the system.
+ */
+extern JS_FRIEND_API(void)
+ShrinkingGC(JSRuntime *rt, gcreason::Reason reason);
 
 /*
  * Incremental GC:
  *
  * Incremental GC divides the full mark-and-sweep collection into multiple
  * slices, allowing client JavaScript code to run between each slice. This
  * allows interactive apps to avoid long collection pauses. Incremental GC does
  * not make collection take less time, it merely spreads that time out so that
@@ -209,46 +202,34 @@ GCForReason(JSRuntime *rt, JSGCInvocatio
  *    implement read and write barriers with the JSCLASS_IMPLEMENTS_BARRIERS
  *    flag.
  *
  * Note: Even if incremental GC is enabled and working correctly,
  *       non-incremental collections can still happen when low on memory.
  */
 
 /*
- * Begin an incremental collection and perform one slice worth of work. When
- * this function returns, the collection may not be complete.
- * IncrementalGCSlice() must be called repeatedly until
- * !IsIncrementalGCInProgress(rt).
+ * Begin an incremental collection and perform one slice worth of work or
+ * perform a slice of an ongoing incremental collection. When this function
+ * returns, the collection is not complete. This function must be called
+ * repeatedly until !IsIncrementalGCInProgress(rt).
  *
  * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or
  *       shorter than the requested interval.
  */
-extern JS_PUBLIC_API(void)
-StartIncrementalGC(JSRuntime *rt, JSGCInvocationKind gckind, gcreason::Reason reason,
-                   int64_t millis = 0);
-
-/*
- * Perform a slice of an ongoing incremental collection. When this function
- * returns, the collection may not be complete. It must be called repeatedly
- * until !IsIncrementalGCInProgress(rt).
- *
- * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or
- *       shorter than the requested interval.
- */
-extern JS_PUBLIC_API(void)
-IncrementalGCSlice(JSRuntime *rt, gcreason::Reason reason, int64_t millis = 0);
+extern JS_FRIEND_API(void)
+IncrementalGC(JSRuntime *rt, gcreason::Reason reason, int64_t millis = 0);
 
 /*
  * If IsIncrementalGCInProgress(rt), this call finishes the ongoing collection
  * by performing an arbitrarily long slice. If !IsIncrementalGCInProgress(rt),
  * this is equivalent to GCForReason. When this function returns,
  * IsIncrementalGCInProgress(rt) will always be false.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 FinishIncrementalGC(JSRuntime *rt, gcreason::Reason reason);
 
 enum GCProgress {
     /*
      * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END
      * callbacks. During an incremental GC, the sequence of callbacks is as
      * follows:
      *   JSGC_CYCLE_BEGIN, JSGC_SLICE_END  (first slice)
@@ -258,17 +239,17 @@ enum GCProgress {
      */
 
     GC_CYCLE_BEGIN,
     GC_SLICE_BEGIN,
     GC_SLICE_END,
     GC_CYCLE_END
 };
 
-struct JS_PUBLIC_API(GCDescription) {
+struct JS_FRIEND_API(GCDescription) {
     bool isCompartment_;
 
     explicit GCDescription(bool isCompartment)
       : isCompartment_(isCompartment) {}
 
     char16_t *formatMessage(JSRuntime *rt) const;
     char16_t *formatJSON(JSRuntime *rt, uint64_t timestamp) const;
 };
@@ -276,133 +257,133 @@ struct JS_PUBLIC_API(GCDescription) {
 typedef void
 (* GCSliceCallback)(JSRuntime *rt, GCProgress progress, const GCDescription &desc);
 
 /*
  * The GC slice callback is called at the beginning and end of each slice. This
  * callback may be used for GC notifications as well as to perform additional
  * marking.
  */
-extern JS_PUBLIC_API(GCSliceCallback)
+extern JS_FRIEND_API(GCSliceCallback)
 SetGCSliceCallback(JSRuntime *rt, GCSliceCallback callback);
 
 /*
  * Incremental GC defaults to enabled, but may be disabled for testing or in
  * embeddings that have not yet implemented barriers on their native classes.
  * There is not currently a way to re-enable incremental GC once it has been
  * disabled on the runtime.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 DisableIncrementalGC(JSRuntime *rt);
 
 /*
  * Returns true if incremental GC is enabled. Simply having incremental GC
  * enabled is not sufficient to ensure incremental collections are happening.
  * See the comment "Incremental GC" above for reasons why incremental GC may be
  * suppressed. Inspection of the "nonincremental reason" field of the
  * GCDescription returned by GCSliceCallback may help narrow down the cause if
  * collections are not happening incrementally when expected.
  */
-extern JS_PUBLIC_API(bool)
+extern JS_FRIEND_API(bool)
 IsIncrementalGCEnabled(JSRuntime *rt);
 
 /*
  * Compacting GC defaults to enabled, but may be disabled for testing or in
  * embeddings that have not implemented the necessary object moved hooks or weak
  * pointer callbacks.  There is not currently a way to re-enable compacting GC
  * once it has been disabled on the runtime.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 DisableCompactingGC(JSRuntime *rt);
 
 /*
  * Returns true if compacting GC is enabled.
  */
-extern JS_PUBLIC_API(bool)
+extern JS_FRIEND_API(bool)
 IsCompactingGCEnabled(JSRuntime *rt);
 
 /*
  * Returns true while an incremental GC is ongoing, both when actively
  * collecting and between slices.
  */
-extern JS_PUBLIC_API(bool)
+JS_FRIEND_API(bool)
 IsIncrementalGCInProgress(JSRuntime *rt);
 
 /*
  * Returns true when writes to GC things must call an incremental (pre) barrier.
  * This is generally only true when running mutator code in-between GC slices.
  * At other times, the barrier may be elided for performance.
  */
-extern JS_PUBLIC_API(bool)
+extern JS_FRIEND_API(bool)
 IsIncrementalBarrierNeeded(JSRuntime *rt);
 
-extern JS_PUBLIC_API(bool)
+extern JS_FRIEND_API(bool)
 IsIncrementalBarrierNeeded(JSContext *cx);
 
 /*
  * Notify the GC that a reference to a GC thing is about to be overwritten.
  * These methods must be called if IsIncrementalBarrierNeeded.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 IncrementalReferenceBarrier(GCCellPtr thing);
 
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 IncrementalValueBarrier(const Value &v);
 
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 IncrementalObjectBarrier(JSObject *obj);
 
 /*
  * Returns true if the most recent GC ran incrementally.
  */
-extern JS_PUBLIC_API(bool)
+extern JS_FRIEND_API(bool)
 WasIncrementalGC(JSRuntime *rt);
 
 /*
  * Generational GC:
  *
  * Note: Generational GC is not yet enabled by default. The following class
  *       is non-functional unless SpiderMonkey was configured with
  *       --enable-gcgenerational.
  */
 
 /* Ensure that generational GC is disabled within some scope. */
-class JS_PUBLIC_API(AutoDisableGenerationalGC)
+class JS_FRIEND_API(AutoDisableGenerationalGC)
 {
     js::gc::GCRuntime *gc;
 #ifdef JS_GC_ZEAL
     bool restartVerifier;
 #endif
 
   public:
     explicit AutoDisableGenerationalGC(JSRuntime *rt);
     ~AutoDisableGenerationalGC();
 };
 
 /*
  * Returns true if generational allocation and collection is currently enabled
  * on the given runtime.
  */
-extern JS_PUBLIC_API(bool)
+extern JS_FRIEND_API(bool)
 IsGenerationalGCEnabled(JSRuntime *rt);
 
 /*
  * Returns the GC's "number". This does not correspond directly to the number
  * of GCs that have been run, but is guaranteed to be monotonically increasing
  * with GC activity.
  */
-extern JS_PUBLIC_API(size_t)
+extern JS_FRIEND_API(size_t)
 GetGCNumber();
 
 /*
  * The GC does not immediately return the unused memory freed by a collection
  * back to the system incase it is needed soon afterwards. This call forces the
  * GC to return this memory immediately.
  */
-extern JS_PUBLIC_API(void)
+extern JS_FRIEND_API(void)
 ShrinkGCBuffers(JSRuntime *rt);
 
 /*
  * Assert if a GC occurs while this class is live. This class does not disable
  * the static rooting hazard analysis.
  */
 class JS_PUBLIC_API(AutoAssertOnGC)
 {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -233,18 +233,20 @@ GC(JSContext *cx, unsigned argc, jsval *
     size_t preBytes = cx->runtime()->gc.usage.gcBytes();
 #endif
 
     if (compartment)
         PrepareForDebugGC(cx->runtime());
     else
         JS::PrepareForFullGC(cx->runtime());
 
-    JSGCInvocationKind gckind = shrinking ? GC_SHRINK : GC_NORMAL;
-    JS::GCForReason(cx->runtime(), gckind, JS::gcreason::API);
+    if (shrinking)
+        JS::ShrinkingGC(cx->runtime(), JS::gcreason::API);
+    else
+        JS::GCForReason(cx->runtime(), JS::gcreason::API);
 
     char buf[256] = { '\0' };
 #ifndef JS_MORE_DETERMINISTIC
     JS_snprintf(buf, sizeof(buf), "before %lu, after %lu\n",
                 (unsigned long)preBytes, (unsigned long)cx->runtime()->gc.usage.gcBytes());
 #endif
     JSString *str = JS_NewStringCopyZ(cx, buf);
     if (!str)
--- a/js/src/devtools/rootAnalysis/loadCallgraph.js
+++ b/js/src/devtools/rootAnalysis/loadCallgraph.js
@@ -155,17 +155,17 @@ function loadCallgraph(file)
         if (name in suppressedFunctions)
             delete gcFunctions[name];
     }
 
     for (var name in suppressedFieldCalls) {
         suppressedFunctions[name] = true;
     }
 
-    for (var gcName of [ 'void js::gc::GCRuntime::collect(uint8, js::SliceBudget, uint32)',
+    for (var gcName of [ 'void js::gc::GCRuntime::collect(uint8, js::SliceBudget*, uint32, uint32)',
                          'void js::gc::GCRuntime::minorGC(uint32)',
                          'void js::gc::GCRuntime::minorGC(uint32)' ])
     {
         assert(gcName in mangledName, "GC function not found: " + gcName);
         addGCFunction(mangledName[gcName], "GC");
     }
 
     // Initialize the worklist to all known gcFunctions.
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -321,19 +321,18 @@ class GCRuntime
     }
     void minorGC(JSContext *cx, JS::gcreason::Reason reason);
     void evictNursery(JS::gcreason::Reason reason = JS::gcreason::EVICT_NURSERY) {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_EVICT_NURSERY);
         minorGCImpl(reason, nullptr);
     }
     bool gcIfNeeded(JSContext *cx = nullptr);
     void gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason);
-    void startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0);
-    void gcSlice(JS::gcreason::Reason reason, int64_t millis = 0);
-    void finishGC(JS::gcreason::Reason reason);
+    void gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis = 0);
+    void gcFinalSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason);
     void gcDebugSlice(SliceBudget &budget);
 
     void runDebugGC();
     inline void poke();
 
     enum TraceOrMarkRuntime {
         TraceRuntime,
         MarkRuntime
@@ -563,19 +562,20 @@ class GCRuntime
     void releaseChunk(Chunk *chunk);
 
     friend class BackgroundAllocTask;
     friend class AutoMaybeStartBackgroundAllocation;
     inline bool wantBackgroundAllocation(const AutoLockGC &lock) const;
     void startBackgroundAllocTaskIfIdle();
 
     void requestMajorGC(JS::gcreason::Reason reason);
-    SliceBudget defaultBudget(JS::gcreason::Reason reason, int64_t millis);
-    void collect(bool incremental, SliceBudget budget, JS::gcreason::Reason reason);
-    bool gcCycle(bool incremental, SliceBudget &budget, JS::gcreason::Reason reason);
+    void collect(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
+                 JS::gcreason::Reason reason);
+    bool gcCycle(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
+                 JS::gcreason::Reason reason);
     gcstats::ZoneGCStats scanZonesBeforeGC();
     void budgetIncrementalGC(SliceBudget &budget);
     void resetIncrementalGC(const char *reason);
     void incrementalCollectSlice(SliceBudget &budget, JS::gcreason::Reason reason);
     void pushZealSelectedObjects();
     void purgeRuntime();
     bool beginMarkPhase(JS::gcreason::Reason reason);
     bool shouldPreserveJITCode(JSCompartment *comp, int64_t currentTime,
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -19,66 +19,66 @@ BEGIN_TEST(testGCFinalizeCallback)
     CHECK(rt->gc.isFullGc());
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(false));
 
     /* Full GC, incremental. */
     FinalizeCalls = 0;
     JS::PrepareForFullGC(rt);
-    JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000);
+    JS::IncrementalGC(rt, JS::gcreason::API, 1000000);
     CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(rt->gc.isFullGc());
     CHECK(checkMultipleGroups());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(false));
 
     JS::RootedObject global1(cx, createTestGlobal());
     JS::RootedObject global2(cx, createTestGlobal());
     JS::RootedObject global3(cx, createTestGlobal());
     CHECK(global1);
     CHECK(global2);
     CHECK(global3);
 
     /* Compartment GC, non-incremental, single compartment. */
     FinalizeCalls = 0;
     JS::PrepareZoneForGC(global1->zone());
-    JS::GCForReason(rt, GC_NORMAL, JS::gcreason::API);
+    JS::GCForReason(rt, JS::gcreason::API);
     CHECK(!rt->gc.isFullGc());
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
     /* Compartment GC, non-incremental, multiple compartments. */
     FinalizeCalls = 0;
     JS::PrepareZoneForGC(global1->zone());
     JS::PrepareZoneForGC(global2->zone());
     JS::PrepareZoneForGC(global3->zone());
-    JS::GCForReason(rt, GC_NORMAL, JS::gcreason::API);
+    JS::GCForReason(rt, JS::gcreason::API);
     CHECK(!rt->gc.isFullGc());
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
     /* Compartment GC, incremental, single compartment. */
     FinalizeCalls = 0;
     JS::PrepareZoneForGC(global1->zone());
-    JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000);
+    JS::IncrementalGC(rt, JS::gcreason::API, 1000000);
     CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(!rt->gc.isFullGc());
     CHECK(checkSingleGroup());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
     /* Compartment GC, incremental, multiple compartments. */
     FinalizeCalls = 0;
     JS::PrepareZoneForGC(global1->zone());
     JS::PrepareZoneForGC(global2->zone());
     JS::PrepareZoneForGC(global3->zone());
-    JS::StartIncrementalGC(rt, GC_NORMAL, JS::gcreason::API, 1000000);
+    JS::IncrementalGC(rt, JS::gcreason::API, 1000000);
     CHECK(!rt->gc.isIncrementalGCInProgress());
     CHECK(!rt->gc.isFullGc());
     CHECK(checkMultipleGroups());
     CHECK(checkFinalizeStatus());
     CHECK(checkFinalizeIsCompartmentGC(true));
 
 #ifdef JS_GC_ZEAL
 
--- a/js/src/jsapi-tests/testPreserveJitCode.cpp
+++ b/js/src/jsapi-tests/testPreserveJitCode.cpp
@@ -60,20 +60,20 @@ testPreserveJitCode(bool preserveJitCode
 			      source, length, &fun));
 
     RootedValue value(cx);
     for (unsigned i = 0; i < 1500; ++i)
         CHECK(JS_CallFunction(cx, global, fun, JS::HandleValueArray::empty(), &value));
     CHECK_EQUAL(value.toInt32(), 45);
     CHECK_EQUAL(countIonScripts(global), 1u);
 
-    GCForReason(rt, GC_NORMAL, gcreason::API);
+    GCForReason(rt, gcreason::API);
     CHECK_EQUAL(countIonScripts(global), remainingIonScripts);
 
-    GCForReason(rt, GC_SHRINK, gcreason::API);
+    ShrinkingGC(rt, gcreason::API);
     CHECK_EQUAL(countIonScripts(global), 0u);
 
     return true;
 }
 
 JSObject *
 createTestGlobal(bool preserveJitCode)
 {
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -423,17 +423,17 @@ class AutoLeaveZeal
     uint8_t zeal_;
     uint32_t frequency_;
 
   public:
     explicit AutoLeaveZeal(JSContext *cx) : cx_(cx) {
         JS_GetGCZeal(cx_, &zeal_, &frequency_);
         JS_SetGCZeal(cx_, 0, 0);
         JS::PrepareForFullGC(JS_GetRuntime(cx_));
-        JS::GCForReason(JS_GetRuntime(cx_), GC_SHRINK, JS::gcreason::DEBUG_GC);
+        JS::ShrinkingGC(JS_GetRuntime(cx_), JS::gcreason::DEBUG_GC);
     }
     ~AutoLeaveZeal() {
         JS_SetGCZeal(cx_, zeal_, frequency_);
     }
 };
 #endif /* JS_GC_ZEAL */
 
 #endif /* jsapi_tests_tests_h */
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -148,16 +148,82 @@ JS_NewObjectWithUniqueType(JSContext *cx
                                                  parent, SingletonObject));
     if (!obj)
         return nullptr;
     if (!JS_SplicePrototype(cx, obj, proto))
         return nullptr;
     return obj;
 }
 
+JS_FRIEND_API(void)
+JS::PrepareZoneForGC(Zone *zone)
+{
+    zone->scheduleGC();
+}
+
+JS_FRIEND_API(void)
+JS::PrepareForFullGC(JSRuntime *rt)
+{
+    for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
+        zone->scheduleGC();
+}
+
+JS_FRIEND_API(void)
+JS::PrepareForIncrementalGC(JSRuntime *rt)
+{
+    if (!JS::IsIncrementalGCInProgress(rt))
+        return;
+
+    for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
+        if (zone->wasGCStarted())
+            PrepareZoneForGC(zone);
+    }
+}
+
+JS_FRIEND_API(bool)
+JS::IsGCScheduled(JSRuntime *rt)
+{
+    for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
+        if (zone->isGCScheduled())
+            return true;
+    }
+
+    return false;
+}
+
+JS_FRIEND_API(void)
+JS::SkipZoneForGC(Zone *zone)
+{
+    zone->unscheduleGC();
+}
+
+JS_FRIEND_API(void)
+JS::GCForReason(JSRuntime *rt, gcreason::Reason reason)
+{
+    rt->gc.gc(GC_NORMAL, reason);
+}
+
+JS_FRIEND_API(void)
+JS::ShrinkingGC(JSRuntime *rt, gcreason::Reason reason)
+{
+    rt->gc.gc(GC_SHRINK, reason);
+}
+
+JS_FRIEND_API(void)
+JS::IncrementalGC(JSRuntime *rt, gcreason::Reason reason, int64_t millis)
+{
+    rt->gc.gcSlice(GC_NORMAL, reason, millis);
+}
+
+JS_FRIEND_API(void)
+JS::FinishIncrementalGC(JSRuntime *rt, gcreason::Reason reason)
+{
+    rt->gc.gcFinalSlice(GC_NORMAL, reason);
+}
+
 JS_FRIEND_API(JSPrincipals *)
 JS_GetCompartmentPrincipals(JSCompartment *compartment)
 {
     return compartment->principals;
 }
 
 JS_FRIEND_API(void)
 JS_SetCompartmentPrincipals(JSCompartment *compartment, JSPrincipals *principals)
@@ -992,34 +1058,188 @@ js::SetActivityCallback(JSRuntime *rt, A
 }
 
 JS_FRIEND_API(bool)
 js::IsContextRunningJS(JSContext *cx)
 {
     return cx->currentlyRunning();
 }
 
+JS_FRIEND_API(JS::GCSliceCallback)
+JS::SetGCSliceCallback(JSRuntime *rt, GCSliceCallback callback)
+{
+    return rt->gc.setSliceCallback(callback);
+}
+
 JS_FRIEND_API(int64_t)
 GetMaxGCPauseSinceClear(JSRuntime *rt)
 {
     return rt->gc.stats.getMaxGCPauseSinceClear();
 }
 
 JS_FRIEND_API(int64_t)
 ClearMaxGCPauseAccumulator(JSRuntime *rt)
 {
     return rt->gc.stats.clearMaxGCPauseAccumulator();
 }
 
+JS_FRIEND_API(bool)
+JS::WasIncrementalGC(JSRuntime *rt)
+{
+    return rt->gc.isIncrementalGc();
+}
+
+char16_t *
+JS::GCDescription::formatMessage(JSRuntime *rt) const
+{
+    return rt->gc.stats.formatMessage();
+}
+
+char16_t *
+JS::GCDescription::formatJSON(JSRuntime *rt, uint64_t timestamp) const
+{
+    return rt->gc.stats.formatJSON(timestamp);
+}
+
 JS_FRIEND_API(void)
 JS::NotifyDidPaint(JSRuntime *rt)
 {
     rt->gc.notifyDidPaint();
 }
 
+JS_FRIEND_API(bool)
+JS::IsIncrementalGCEnabled(JSRuntime *rt)
+{
+    return rt->gc.isIncrementalGCEnabled();
+}
+
+JS_FRIEND_API(bool)
+JS::IsIncrementalGCInProgress(JSRuntime *rt)
+{
+    return rt->gc.isIncrementalGCInProgress() && !rt->gc.isVerifyPreBarriersEnabled();
+}
+
+JS_FRIEND_API(void)
+JS::DisableIncrementalGC(JSRuntime *rt)
+{
+    rt->gc.disallowIncrementalGC();
+}
+
+JS_FRIEND_API(void)
+JS::DisableCompactingGC(JSRuntime *rt)
+{
+#ifdef JSGC_COMPACTING
+    rt->gc.disableCompactingGC();
+#endif
+}
+
+JS_FRIEND_API(bool)
+JS::IsCompactingGCEnabled(JSRuntime *rt)
+{
+#ifdef JSGC_COMPACTING
+    return rt->gc.isCompactingGCEnabled();
+#else
+    return false;
+#endif
+}
+
+JS::AutoDisableGenerationalGC::AutoDisableGenerationalGC(JSRuntime *rt)
+  : gc(&rt->gc)
+#ifdef JS_GC_ZEAL
+  , restartVerifier(false)
+#endif
+{
+#ifdef JS_GC_ZEAL
+    restartVerifier = gc->endVerifyPostBarriers();
+#endif
+    gc->disableGenerationalGC();
+}
+
+JS::AutoDisableGenerationalGC::~AutoDisableGenerationalGC()
+{
+    gc->enableGenerationalGC();
+#ifdef JS_GC_ZEAL
+    if (restartVerifier) {
+        MOZ_ASSERT(gc->isGenerationalGCEnabled());
+        gc->startVerifyPostBarriers();
+    }
+#endif
+}
+
+extern JS_FRIEND_API(bool)
+JS::IsGenerationalGCEnabled(JSRuntime *rt)
+{
+    return rt->gc.isGenerationalGCEnabled();
+}
+
+JS_FRIEND_API(bool)
+JS::IsIncrementalBarrierNeeded(JSRuntime *rt)
+{
+    return rt->gc.state() == gc::MARK && !rt->isHeapBusy();
+}
+
+JS_FRIEND_API(bool)
+JS::IsIncrementalBarrierNeeded(JSContext *cx)
+{
+    return IsIncrementalBarrierNeeded(cx->runtime());
+}
+
+JS_FRIEND_API(void)
+JS::IncrementalObjectBarrier(JSObject *obj)
+{
+    if (!obj)
+        return;
+
+    MOZ_ASSERT(!obj->zone()->runtimeFromMainThread()->isHeapMajorCollecting());
+
+    JSObject::writeBarrierPre(obj);
+}
+
+JS_FRIEND_API(void)
+JS::IncrementalReferenceBarrier(GCCellPtr thing)
+{
+    if (!thing)
+        return;
+
+    if (thing.isString() && StringIsPermanentAtom(thing.toString()))
+        return;
+
+#ifdef DEBUG
+    Zone *zone = thing.isObject()
+                 ? thing.toObject()->zone()
+                 : thing.asCell()->asTenured().zone();
+    MOZ_ASSERT(!zone->runtimeFromMainThread()->isHeapMajorCollecting());
+#endif
+
+    switch(thing.kind()) {
+      case JSTRACE_OBJECT: return JSObject::writeBarrierPre(thing.toObject());
+      case JSTRACE_STRING: return JSString::writeBarrierPre(thing.toString());
+      case JSTRACE_SCRIPT: return JSScript::writeBarrierPre(thing.toScript());
+      case JSTRACE_SYMBOL: return JS::Symbol::writeBarrierPre(thing.toSymbol());
+      case JSTRACE_LAZY_SCRIPT:
+        return LazyScript::writeBarrierPre(static_cast<LazyScript*>(thing.asCell()));
+      case JSTRACE_JITCODE:
+        return jit::JitCode::writeBarrierPre(static_cast<jit::JitCode*>(thing.asCell()));
+      case JSTRACE_SHAPE:
+        return Shape::writeBarrierPre(static_cast<Shape*>(thing.asCell()));
+      case JSTRACE_BASE_SHAPE:
+        return BaseShape::writeBarrierPre(static_cast<BaseShape*>(thing.asCell()));
+      case JSTRACE_TYPE_OBJECT:
+        return types::TypeObject::writeBarrierPre(static_cast<types::TypeObject *>(thing.asCell()));
+      default:
+        MOZ_CRASH("Invalid trace kind in IncrementalReferenceBarrier.");
+    }
+}
+
+JS_FRIEND_API(void)
+JS::IncrementalValueBarrier(const Value &v)
+{
+    js::HeapValue::writeBarrierPre(v);
+}
+
 JS_FRIEND_API(void)
 JS::PokeGC(JSRuntime *rt)
 {
     rt->gc.poke();
 }
 
 JS_FRIEND_API(JSCompartment *)
 js::GetAnyCompartmentInZone(JS::Zone *zone)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3258,17 +3258,17 @@ GCRuntime::maybeAllocTriggerZoneGC(Zone 
         else
             zone->gcDelayBytes -= ArenaSize;
 
         if (!zone->gcDelayBytes) {
             // Start or continue an in progress incremental GC. We do this
             // to try to avoid performing non-incremental GCs on zones
             // which allocate a lot of data, even when incremental slices
             // can't be triggered via scheduling in the event loop.
-            triggerZoneGC(zone, JS::gcreason::INCREMENTAL_ALLOC_TRIGGER);
+            triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER);
 
             // Delay the next slice until a certain amount of allocation
             // has been performed.
             zone->gcDelayBytes = tunables.zoneAllocDelayBytes();
         }
     }
 }
 
@@ -3327,17 +3327,17 @@ GCRuntime::maybeGC(Zone *zone)
         return true;
 
     if (zone->usage.gcBytes() > 1024 * 1024 &&
         zone->threshold.isCloseToAllocTrigger(zone->usage, schedulingState.inHighFrequencyGCMode()) &&
         !isIncrementalGCInProgress() &&
         !isBackgroundSweeping())
     {
         PrepareZoneForGC(zone);
-        gc(GC_NORMAL, JS::gcreason::MAYBEGC);
+        gcSlice(GC_NORMAL, JS::gcreason::MAYBEGC);
         return true;
     }
 
     return false;
 }
 
 void
 GCRuntime::maybePeriodicFullGC()
@@ -3353,17 +3353,17 @@ GCRuntime::maybePeriodicFullGC()
      */
 #ifndef JS_MORE_DETERMINISTIC
     int64_t now = PRMJ_Now();
     if (nextFullGCTime && nextFullGCTime <= now) {
         if (chunkAllocationSinceLastGC ||
             numArenasFreeCommitted > decommitThreshold)
         {
             JS::PrepareForFullGC(rt);
-            gc(GC_SHRINK, JS::gcreason::MAYBEGC);
+            gcSlice(GC_SHRINK, JS::gcreason::MAYBEGC);
         } else {
             nextFullGCTime = now + GC_IDLE_FULL_SPAN;
         }
     }
 #endif
 }
 
 // Do all possible decommit immediately from the current thread without
@@ -5780,26 +5780,19 @@ GCRuntime::resetIncrementalGC(const char
       case 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;
-
-        /* Don't perform any compaction after sweeping. */
-        JSGCInvocationKind oldInvocationKind = invocationKind;
-        invocationKind = GC_NORMAL;
-
         SliceBudget budget;
         incrementalCollectSlice(budget, JS::gcreason::RESET);
 
-        invocationKind = oldInvocationKind;
-
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
         break;
       }
 
 #ifdef JSGC_COMPACTING
@@ -6112,17 +6105,18 @@ class AutoDisableStoreBuffer
  * non-incremental GC. We disable inlining to ensure that the bottom of the
  * stack with possible GC roots recorded in MarkRuntime excludes any pointers we
  * use during the marking implementation.
  *
  * Returns true if we "reset" an existing incremental GC, which would force us
  * to run another cycle.
  */
 MOZ_NEVER_INLINE bool
-GCRuntime::gcCycle(bool incremental, SliceBudget &budget, JS::gcreason::Reason reason)
+GCRuntime::gcCycle(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
+                   JS::gcreason::Reason reason)
 {
     evictNursery(reason);
 
     /*
      * Marking can trigger many incidental post barriers, some of them for
      * objects which are not going to be live after the GC.
      */
     AutoDisableStoreBuffer adsb(this);
@@ -6175,16 +6169,20 @@ GCRuntime::gcCycle(bool incremental, Sli
     }
 
     /* The GC was reset, so we need a do-over. */
     if (prevState != NO_INCREMENTAL && !isIncrementalGCInProgress())
         return true;
 
     TraceMajorGCStart();
 
+    /* Set the invocation kind in the first slice. */
+    if (!isIncrementalGCInProgress())
+        invocationKind = gckind;
+
     incrementalCollectSlice(budget, reason);
 
 #ifndef JS_MORE_DETERMINISTIC
     nextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN;
 #endif
 
     chunkAllocationSinceLastGC = false;
 
@@ -6259,17 +6257,18 @@ GCRuntime::scanZonesBeforeGC()
 
     for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next())
         zoneStats.compartmentCount++;
 
     return zoneStats;
 }
 
 void
-GCRuntime::collect(bool incremental, SliceBudget budget, JS::gcreason::Reason reason)
+GCRuntime::collect(bool incremental, SliceBudget &budget, JSGCInvocationKind gckind,
+                   JS::gcreason::Reason reason)
 {
     /* GC shouldn't be running in parallel execution mode */
     MOZ_ALWAYS_TRUE(!InParallelSection());
 
     JS_AbortIfWrongThread(rt);
 
     /* If we attempt to invoke the GC while we are running in the GC, assert. */
     MOZ_ALWAYS_TRUE(!rt->isHeapBusy());
@@ -6286,34 +6285,34 @@ GCRuntime::collect(bool incremental, Sli
 #ifdef JS_GC_ZEAL
     if (deterministicOnly && !IsDeterministicGCReason(reason))
         return;
 #endif
 
     AutoStopVerifyingBarriers av(rt, reason == JS::gcreason::SHUTDOWN_CC ||
                                      reason == JS::gcreason::DESTROY_RUNTIME);
 
-    gcstats::AutoGCSlice agc(stats, scanZonesBeforeGC(), invocationKind, reason);
-
-    cleanUpEverything = ShouldCleanUpEverything(reason, invocationKind);
+    gcstats::AutoGCSlice agc(stats, scanZonesBeforeGC(), gckind, reason);
+
+    cleanUpEverything = ShouldCleanUpEverything(reason, gckind);
 
     bool repeat = false;
     do {
         /*
          * Let the API user decide to defer a GC if it wants to (unless this
          * is the last context). Invoke the callback regardless.
          */
         if (!isIncrementalGCInProgress()) {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_GC_BEGIN);
             if (gcCallback.op)
                 gcCallback.op(rt, JSGC_BEGIN, gcCallback.data);
         }
 
         poked = false;
-        bool wasReset = gcCycle(incremental, budget, reason);
+        bool wasReset = gcCycle(incremental, budget, gckind, reason);
 
         if (!isIncrementalGCInProgress()) {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_GC_END);
             if (gcCallback.op)
                 gcCallback.op(rt, JSGC_END, gcCallback.data);
         }
 
         /* Need to re-schedule all zones for GC. */
@@ -6345,58 +6344,44 @@ GCRuntime::collect(bool incremental, Sli
          */
         repeat = (poked && cleanUpEverything) || wasReset || repeatForDeadZone;
     } while (repeat);
 
     if (!isIncrementalGCInProgress())
         EnqueuePendingParseTasksAfterGC(rt);
 }
 
-SliceBudget
-GCRuntime::defaultBudget(JS::gcreason::Reason reason, int64_t millis)
-{
-    if (millis == 0) {
-        if (reason == JS::gcreason::ALLOC_TRIGGER)
-            millis = sliceBudget;
-        else if (schedulingState.inHighFrequencyGCMode() && tunables.isDynamicMarkSliceEnabled())
-            millis = sliceBudget * IGC_MARK_SLICE_MULTIPLIER;
-        else
-            millis = sliceBudget;
-    }
-
-    return SliceBudget(TimeBudget(millis));
-}
-
 void
 GCRuntime::gc(JSGCInvocationKind gckind, JS::gcreason::Reason reason)
 {
-    invocationKind = gckind;
-    collect(false, SliceBudget(), reason);
-}
-
-void
-GCRuntime::startGC(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis)
-{
-    MOZ_ASSERT(!isIncrementalGCInProgress());
-    invocationKind = gckind;
-    collect(true, defaultBudget(reason, millis), reason);
-}
-
-void
-GCRuntime::gcSlice(JS::gcreason::Reason reason, int64_t millis)
-{
-    MOZ_ASSERT(isIncrementalGCInProgress());
-    collect(true, defaultBudget(reason, millis), reason);
-}
-
-void
-GCRuntime::finishGC(JS::gcreason::Reason reason)
-{
-    MOZ_ASSERT(isIncrementalGCInProgress());
-    collect(true, SliceBudget(), reason);
+    SliceBudget budget;
+    collect(false, budget, gckind, reason);
+}
+
+void
+GCRuntime::gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis)
+{
+    SliceBudget budget;
+    if (millis)
+        budget = SliceBudget(TimeBudget(millis));
+    else if (reason == JS::gcreason::ALLOC_TRIGGER)
+        budget = SliceBudget(TimeBudget(sliceBudget));
+    else if (schedulingState.inHighFrequencyGCMode() && tunables.isDynamicMarkSliceEnabled())
+        budget = SliceBudget(TimeBudget(sliceBudget * IGC_MARK_SLICE_MULTIPLIER));
+    else
+        budget = SliceBudget(TimeBudget(sliceBudget));
+
+    collect(true, budget, gckind, reason);
+}
+
+void
+GCRuntime::gcFinalSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason)
+{
+    SliceBudget budget;
+    collect(true, budget, gckind, reason);
 }
 
 void
 GCRuntime::notifyDidPaint()
 {
 #ifdef JS_GC_ZEAL
     if (zealMode == ZealFrameVerifierPreValue) {
         verifyPreBarriers();
@@ -6405,24 +6390,24 @@ GCRuntime::notifyDidPaint()
 
     if (zealMode == ZealFrameVerifierPostValue) {
         verifyPostBarriers();
         return;
     }
 
     if (zealMode == ZealFrameGCValue) {
         JS::PrepareForFullGC(rt);
-        gc(GC_NORMAL, JS::gcreason::REFRESH_FRAME);
+        gcSlice(GC_NORMAL, JS::gcreason::REFRESH_FRAME);
         return;
     }
 #endif
 
-    if (isIncrementalGCInProgress() && !interFrameGC) {
+    if (JS::IsIncrementalGCInProgress(rt) && !interFrameGC) {
         JS::PrepareForIncrementalGC(rt);
-        gcSlice(JS::gcreason::REFRESH_FRAME);
+        gcSlice(GC_NORMAL, JS::gcreason::REFRESH_FRAME);
     }
 
     interFrameGC = false;
 }
 
 static bool
 ZonesSelected(JSRuntime *rt)
 {
@@ -6432,35 +6417,33 @@ ZonesSelected(JSRuntime *rt)
     }
     return false;
 }
 
 void
 GCRuntime::gcDebugSlice(SliceBudget &budget)
 {
     if (!ZonesSelected(rt)) {
-        if (isIncrementalGCInProgress())
+        if (JS::IsIncrementalGCInProgress(rt))
             JS::PrepareForIncrementalGC(rt);
         else
             JS::PrepareForFullGC(rt);
     }
-    if (!isIncrementalGCInProgress())
-        invocationKind = GC_NORMAL;
-    collect(true, budget, JS::gcreason::DEBUG_GC);
+    collect(true, budget, GC_NORMAL, JS::gcreason::DEBUG_GC);
 }
 
 /* Schedule a full GC unless a zone will already be collected. */
 void
 js::PrepareForDebugGC(JSRuntime *rt)
 {
     if (!ZonesSelected(rt))
         JS::PrepareForFullGC(rt);
 }
 
-JS_PUBLIC_API(void)
+JS_FRIEND_API(void)
 JS::ShrinkGCBuffers(JSRuntime *rt)
 {
     rt->gc.shrinkBuffers();
 }
 
 void
 GCRuntime::shrinkBuffers()
 {
@@ -6558,24 +6541,17 @@ GCRuntime::gcIfNeeded(JSContext *cx /* =
     if (minorGCRequested) {
         if (cx)
             minorGC(cx, minorGCTriggerReason);
         else
             minorGC(minorGCTriggerReason);
     }
 
     if (majorGCRequested) {
-        if (majorGCTriggerReason == JS::gcreason::INCREMENTAL_ALLOC_TRIGGER) {
-            if (!isIncrementalGCInProgress())
-                startGC(GC_NORMAL, majorGCTriggerReason);
-            else
-                gcSlice(majorGCTriggerReason);
-        } else {
-            gc(GC_NORMAL, majorGCTriggerReason);
-        }
+        gcSlice(GC_NORMAL, rt->gc.majorGCTriggerReason);
         return true;
     }
 
     return false;
 }
 
 AutoFinishGC::AutoFinishGC(JSRuntime *rt)
 {
@@ -6730,33 +6706,31 @@ GCRuntime::runDebugGC()
             else
                 incrementalLimit *= 2;
             budget = SliceBudget(WorkBudget(incrementalLimit));
         } else {
             // This triggers incremental GC but is actually ignored by IncrementalMarkSlice.
             budget = SliceBudget(WorkBudget(1));
         }
 
-        if (!isIncrementalGCInProgress())
-            invocationKind = GC_NORMAL;
-        collect(true, budget, JS::gcreason::DEBUG_GC);
+        collect(true, budget, GC_NORMAL, JS::gcreason::DEBUG_GC);
 
         /*
          * For multi-slice zeal, reset the slice size when we get to the sweep
          * phase.
          */
         if (type == ZealIncrementalMultipleSlices &&
             initialState == MARK && incrementalState == SWEEP)
         {
             incrementalLimit = zealFrequency / 2;
         }
     } else if (type == ZealCompactValue) {
-        gc(GC_SHRINK, JS::gcreason::DEBUG_GC);
+        collect(false, budget, GC_SHRINK, JS::gcreason::DEBUG_GC);
     } else {
-        gc(GC_NORMAL, JS::gcreason::DEBUG_GC);
+        collect(false, budget, GC_NORMAL, JS::gcreason::DEBUG_GC);
     }
 
 #endif
 }
 
 void
 GCRuntime::setValidate(bool enabled)
 {
@@ -6973,17 +6947,17 @@ js::gc::AssertGCThingHasType(js::gc::Cel
     if (!cell)
         MOZ_ASSERT(kind == JSTRACE_NULL);
     else if (IsInsideNursery(cell))
         MOZ_ASSERT(kind == JSTRACE_OBJECT);
     else
         MOZ_ASSERT(MapAllocToTraceKind(cell->asTenured().getAllocKind()) == kind);
 }
 
-JS_PUBLIC_API(size_t)
+JS_FRIEND_API(size_t)
 JS::GetGCNumber()
 {
     JSRuntime *rt = js::TlsPerThreadData.get()->runtimeFromMainThread();
     if (!rt)
         return 0;
     return rt->gc.gcNumber();
 }
 #endif
@@ -7111,230 +7085,8 @@ js::gc::CheckHashTablesAfterMovingGC(JSR
         c->checkTypeObjectTablesAfterMovingGC();
         c->checkInitialShapesTableAfterMovingGC();
         c->checkWrapperMapAfterMovingGC();
         if (c->debugScopes)
             c->debugScopes->checkHashTablesAfterMovingGC(rt);
     }
 }
 #endif
-
-JS_PUBLIC_API(void)
-JS::PrepareZoneForGC(Zone *zone)
-{
-    zone->scheduleGC();
-}
-
-JS_PUBLIC_API(void)
-JS::PrepareForFullGC(JSRuntime *rt)
-{
-    for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
-        zone->scheduleGC();
-}
-
-JS_PUBLIC_API(void)
-JS::PrepareForIncrementalGC(JSRuntime *rt)
-{
-    if (!JS::IsIncrementalGCInProgress(rt))
-        return;
-
-    for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
-        if (zone->wasGCStarted())
-            PrepareZoneForGC(zone);
-    }
-}
-
-JS_PUBLIC_API(bool)
-JS::IsGCScheduled(JSRuntime *rt)
-{
-    for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
-        if (zone->isGCScheduled())
-            return true;
-    }
-
-    return false;
-}
-
-JS_PUBLIC_API(void)
-JS::SkipZoneForGC(Zone *zone)
-{
-    zone->unscheduleGC();
-}
-
-JS_PUBLIC_API(void)
-JS::GCForReason(JSRuntime *rt, JSGCInvocationKind gckind, gcreason::Reason reason)
-{
-    MOZ_ASSERT(gckind == GC_NORMAL || gckind == GC_SHRINK);
-    rt->gc.gc(gckind, reason);
-}
-
-JS_PUBLIC_API(void)
-JS::StartIncrementalGC(JSRuntime *rt, JSGCInvocationKind gckind, gcreason::Reason reason, int64_t millis)
-{
-    MOZ_ASSERT(gckind == GC_NORMAL || gckind == GC_SHRINK);
-    rt->gc.startGC(gckind, reason, millis);
-}
-
-JS_PUBLIC_API(void)
-JS::IncrementalGCSlice(JSRuntime *rt, gcreason::Reason reason, int64_t millis)
-{
-    rt->gc.gcSlice(reason, millis);
-}
-
-JS_PUBLIC_API(void)
-JS::FinishIncrementalGC(JSRuntime *rt, gcreason::Reason reason)
-{
-    rt->gc.finishGC(reason);
-}
-
-char16_t *
-JS::GCDescription::formatMessage(JSRuntime *rt) const
-{
-    return rt->gc.stats.formatMessage();
-}
-
-char16_t *
-JS::GCDescription::formatJSON(JSRuntime *rt, uint64_t timestamp) const
-{
-    return rt->gc.stats.formatJSON(timestamp);
-}
-
-JS_PUBLIC_API(JS::GCSliceCallback)
-JS::SetGCSliceCallback(JSRuntime *rt, GCSliceCallback callback)
-{
-    return rt->gc.setSliceCallback(callback);
-}
-
-JS_PUBLIC_API(void)
-JS::DisableIncrementalGC(JSRuntime *rt)
-{
-    rt->gc.disallowIncrementalGC();
-}
-
-JS_PUBLIC_API(bool)
-JS::IsIncrementalGCEnabled(JSRuntime *rt)
-{
-    return rt->gc.isIncrementalGCEnabled();
-}
-
-JS_PUBLIC_API(void)
-JS::DisableCompactingGC(JSRuntime *rt)
-{
-#ifdef JSGC_COMPACTING
-    rt->gc.disableCompactingGC();
-#endif
-}
-
-JS_PUBLIC_API(bool)
-JS::IsCompactingGCEnabled(JSRuntime *rt)
-{
-#ifdef JSGC_COMPACTING
-    return rt->gc.isCompactingGCEnabled();
-#else
-    return false;
-#endif
-}
-
-JS_PUBLIC_API(bool)
-JS::IsIncrementalGCInProgress(JSRuntime *rt)
-{
-    return rt->gc.isIncrementalGCInProgress() && !rt->gc.isVerifyPreBarriersEnabled();
-}
-
-JS_PUBLIC_API(bool)
-JS::IsIncrementalBarrierNeeded(JSRuntime *rt)
-{
-    return rt->gc.state() == gc::MARK && !rt->isHeapBusy();
-}
-
-JS_PUBLIC_API(bool)
-JS::IsIncrementalBarrierNeeded(JSContext *cx)
-{
-    return IsIncrementalBarrierNeeded(cx->runtime());
-}
-
-JS_PUBLIC_API(void)
-JS::IncrementalReferenceBarrier(GCCellPtr thing)
-{
-    if (!thing)
-        return;
-
-    if (thing.isString() && StringIsPermanentAtom(thing.toString()))
-        return;
-
-#ifdef DEBUG
-    Zone *zone = thing.isObject()
-                 ? thing.toObject()->zone()
-                 : thing.asCell()->asTenured().zone();
-    MOZ_ASSERT(!zone->runtimeFromMainThread()->isHeapMajorCollecting());
-#endif
-
-    switch(thing.kind()) {
-      case JSTRACE_OBJECT: return JSObject::writeBarrierPre(thing.toObject());
-      case JSTRACE_STRING: return JSString::writeBarrierPre(thing.toString());
-      case JSTRACE_SCRIPT: return JSScript::writeBarrierPre(thing.toScript());
-      case JSTRACE_SYMBOL: return JS::Symbol::writeBarrierPre(thing.toSymbol());
-      case JSTRACE_LAZY_SCRIPT:
-        return LazyScript::writeBarrierPre(static_cast<LazyScript*>(thing.asCell()));
-      case JSTRACE_JITCODE:
-        return jit::JitCode::writeBarrierPre(static_cast<jit::JitCode*>(thing.asCell()));
-      case JSTRACE_SHAPE:
-        return Shape::writeBarrierPre(static_cast<Shape*>(thing.asCell()));
-      case JSTRACE_BASE_SHAPE:
-        return BaseShape::writeBarrierPre(static_cast<BaseShape*>(thing.asCell()));
-      case JSTRACE_TYPE_OBJECT:
-        return types::TypeObject::writeBarrierPre(static_cast<types::TypeObject *>(thing.asCell()));
-      default:
-        MOZ_CRASH("Invalid trace kind in IncrementalReferenceBarrier.");
-    }
-}
-
-JS_PUBLIC_API(void)
-JS::IncrementalValueBarrier(const Value &v)
-{
-    js::HeapValue::writeBarrierPre(v);
-}
-
-JS_PUBLIC_API(void)
-JS::IncrementalObjectBarrier(JSObject *obj)
-{
-    if (!obj)
-        return;
-
-    MOZ_ASSERT(!obj->zone()->runtimeFromMainThread()->isHeapMajorCollecting());
-
-    JSObject::writeBarrierPre(obj);
-}
-
-JS_PUBLIC_API(bool)
-JS::WasIncrementalGC(JSRuntime *rt)
-{
-    return rt->gc.isIncrementalGc();
-}
-
-JS::AutoDisableGenerationalGC::AutoDisableGenerationalGC(JSRuntime *rt)
-  : gc(&rt->gc)
-#ifdef JS_GC_ZEAL
-  , restartVerifier(false)
-#endif
-{
-#ifdef JS_GC_ZEAL
-    restartVerifier = gc->endVerifyPostBarriers();
-#endif
-    gc->disableGenerationalGC();
-}
-
-JS::AutoDisableGenerationalGC::~AutoDisableGenerationalGC()
-{
-    gc->enableGenerationalGC();
-#ifdef JS_GC_ZEAL
-    if (restartVerifier) {
-        MOZ_ASSERT(gc->isGenerationalGCEnabled());
-        gc->startVerifyPostBarriers();
-    }
-#endif
-}
-
-JS_PUBLIC_API(bool)
-JS::IsGenerationalGCEnabled(JSRuntime *rt)
-{
-    return rt->gc.isGenerationalGCEnabled();
-}
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -940,16 +940,27 @@ extern void
 MarkCompartmentActive(js::InterpreterFrame *fp);
 
 extern void
 TraceRuntime(JSTracer *trc);
 
 extern void
 ReleaseAllJITCode(FreeOp *op);
 
+/*
+ * Kinds of js_GC invocation.
+ */
+typedef enum JSGCInvocationKind {
+    /* Normal invocation. */
+    GC_NORMAL           = 0,
+
+    /* Minimize GC triggers and release empty GC chunks right away. */
+    GC_SHRINK             = 1
+} JSGCInvocationKind;
+
 extern void
 PrepareForDebugGC(JSRuntime *rt);
 
 /* Functions for managing cross compartment gray pointers. */
 
 extern void
 DelayCrossCompartmentGrayMarking(JSObject *src);
 
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2745,17 +2745,17 @@ nsXPCComponents_Utils::GetWeakReference(
 }
 
 /* void forceGC (); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::ForceGC()
 {
     JSRuntime* rt = nsXPConnect::GetRuntimeInstance()->Runtime();
     PrepareForFullGC(rt);
-    GCForReason(rt, GC_NORMAL, gcreason::COMPONENT_UTILS);
+    GCForReason(rt, gcreason::COMPONENT_UTILS);
     return NS_OK;
 }
 
 /* void forceCC (); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::ForceCC()
 {
     nsJSContext::CycleCollectNow();
@@ -2795,17 +2795,17 @@ nsXPCComponents_Utils::ClearMaxCCTime()
 }
 
 /* void forceShrinkingGC (); */
 NS_IMETHODIMP
 nsXPCComponents_Utils::ForceShrinkingGC()
 {
     JSRuntime* rt = nsXPConnect::GetRuntimeInstance()->Runtime();
     PrepareForFullGC(rt);
-    GCForReason(rt, GC_SHRINK, gcreason::COMPONENT_UTILS);
+    ShrinkingGC(rt, gcreason::COMPONENT_UTILS);
     return NS_OK;
 }
 
 class PreciseGCRunnable : public nsRunnable
 {
   public:
     PreciseGCRunnable(ScheduledGCCallback* aCallback, bool aShrinking)
     : mCallback(aCallback), mShrinking(aShrinking) {}
@@ -2818,18 +2818,20 @@ class PreciseGCRunnable : public nsRunna
         JSContext *iter = nullptr;
         while ((cx = JS_ContextIterator(rt, &iter)) != nullptr) {
             if (JS_IsRunning(cx)) {
                 return NS_DispatchToMainThread(this);
             }
         }
 
         PrepareForFullGC(rt);
-        JSGCInvocationKind gckind = mShrinking ? GC_SHRINK : GC_NORMAL;
-        GCForReason(rt, gckind, gcreason::COMPONENT_UTILS);
+        if (mShrinking)
+            ShrinkingGC(rt, gcreason::COMPONENT_UTILS);
+        else
+            GCForReason(rt, gcreason::COMPONENT_UTILS);
 
         mCallback->Callback();
         return NS_OK;
     }
 
   private:
     nsRefPtr<ScheduledGCCallback> mCallback;
     bool mShrinking;
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -1020,17 +1020,17 @@ CycleCollectedJSRuntime::AreGCGrayBitsVa
 
 void
 CycleCollectedJSRuntime::GarbageCollect(uint32_t aReason) const
 {
   MOZ_ASSERT(aReason < JS::gcreason::NUM_REASONS);
   JS::gcreason::Reason gcreason = static_cast<JS::gcreason::Reason>(aReason);
 
   JS::PrepareForFullGC(mJSRuntime);
-  JS::GCForReason(mJSRuntime, GC_NORMAL, gcreason);
+  JS::GCForReason(mJSRuntime, gcreason);
 }
 
 void
 CycleCollectedJSRuntime::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
                                           DeferredFinalizeFunction aFunc,
                                           void* aThing)
 {
   void* thingArray = nullptr;