Bug 1376891 - Support idle runnable for nursery collection. r=jonco, r=smaug
authorChia-Hung Duan<cduan@mozilla.com>
Mon, 30 Oct 2017 18:07:42 +0800
changeset 443164 5877e17e1b98b5ecb574f1b935910f328038a666
parent 443163 463e74189171d39a14a748c8af6b50456879ec06
child 443197 5a05948f281bb49710e69b04d0286ff9c5f39d85
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco, smaug
bugs1376891
milestone58.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 1376891 - Support idle runnable for nursery collection. r=jonco, r=smaug
js/public/GCAPI.h
js/src/gc/Nursery.h
js/src/jsapi.cpp
js/src/jsapi.h
xpcom/base/CycleCollectedJSContext.cpp
xpcom/base/CycleCollectedJSContext.h
xpcom/base/CycleCollectedJSRuntime.cpp
xpcom/base/CycleCollectedJSRuntime.h
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -62,17 +62,17 @@ namespace JS {
     D(ALLOC_TRIGGER)                            \
     D(DEBUG_GC)                                 \
     D(COMPARTMENT_REVIVED)                      \
     D(RESET)                                    \
     D(OUT_OF_NURSERY)                           \
     D(EVICT_NURSERY)                            \
     D(DELAYED_ATOMS_GC)                         \
     D(SHARED_MEMORY_LIMIT)                      \
-    D(UNUSED1)                                  \
+    D(IDLE_TIME_COLLECTION)                     \
     D(INCREMENTAL_TOO_SLOW)                     \
     D(ABORT_GC)                                 \
     D(FULL_WHOLE_CELL_BUFFER)                   \
     D(FULL_GENERIC_BUFFER)                      \
     D(FULL_VALUE_BUFFER)                        \
     D(FULL_CELL_PTR_BUFFER)                     \
     D(FULL_SLOT_BUFFER)                         \
     D(FULL_SHAPE_BUFFER)                        \
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -274,22 +274,30 @@ class Nursery
     void* addressOfPosition() const { return (void*)&position_; }
 
     void requestMinorGC(JS::gcreason::Reason reason) const;
 
     bool minorGCRequested() const { return minorGCTriggerReason_ != JS::gcreason::NO_REASON; }
     JS::gcreason::Reason minorGCTriggerReason() const { return minorGCTriggerReason_; }
     void clearMinorGCRequest() { minorGCTriggerReason_ = JS::gcreason::NO_REASON; }
 
+    bool needIdleTimeCollection() const {
+        return minorGCRequested() ||
+               (freeSpace() < kIdleTimeCollectionThreshold);
+    }
+
     bool enableProfiling() const { return enableProfiling_; }
 
   private:
     /* The amount of space in the mapped nursery available to allocations. */
     static const size_t NurseryChunkUsableSize = gc::ChunkSize - gc::ChunkTrailerSize;
 
+    /* Attemp to run a minor GC in the idle time if the free space falls below this threshold. */
+    static constexpr size_t kIdleTimeCollectionThreshold = NurseryChunkUsableSize / 4;
+
     JSRuntime* runtime_;
 
     /* Vector of allocated chunks to allocate from. */
     Vector<NurseryChunk*, 0, SystemAllocPolicy> chunks_;
 
     /* Pointer to the first unallocated byte in the nursery. */
     uintptr_t position_;
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1434,16 +1434,30 @@ JS_AddExtraGCRootsTracer(JSContext* cx, 
 }
 
 JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSContext* cx, JSTraceDataOp traceOp, void* data)
 {
     return cx->runtime()->gc.removeBlackRootsTracer(traceOp, data);
 }
 
+JS_PUBLIC_API(bool)
+JS::IsIdleGCTaskNeeded(JSRuntime* rt) {
+  // Currently, we only collect nursery during idle time.
+  return rt->gc.nursery().needIdleTimeCollection();
+}
+
+JS_PUBLIC_API(void)
+JS::RunIdleTimeGCTask(JSRuntime* rt) {
+  GCRuntime& gc = rt->gc;
+  if (gc.nursery().needIdleTimeCollection()) {
+    gc.minorGC(JS::gcreason::IDLE_TIME_COLLECTION);
+  }
+}
+
 JS_PUBLIC_API(void)
 JS_GC(JSContext* cx)
 {
     AssertHeapIsIdle();
     JS::PrepareForFullGC(cx);
     cx->runtime()->gc.gc(GC_NORMAL, JS::gcreason::API);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1743,16 +1743,26 @@ JS_AddExtraGCRootsTracer(JSContext* cx, 
 
 /** Undo a call to JS_AddExtraGCRootsTracer. */
 extern JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSContext* cx, JSTraceDataOp traceOp, void* data);
 
 /*
  * Garbage collector API.
  */
+namespace JS {
+
+extern JS_PUBLIC_API(bool)
+IsIdleGCTaskNeeded(JSRuntime* rt);
+
+extern JS_PUBLIC_API(void)
+RunIdleTimeGCTask(JSRuntime* rt);
+
+} // namespace JS
+
 extern JS_PUBLIC_API(void)
 JS_GC(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_SetGCCallback(JSContext* cx, JSGCCallback cb, void* data);
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -365,25 +365,58 @@ CycleCollectedJSContext::AfterProcessTas
       Promise::PerformMicroTaskCheckpoint();
     } else {
       Promise::PerformWorkerMicroTaskCheckpoint();
     }
   }
 
   // Step 4.2 Execute any events that were waiting for a stable state.
   ProcessStableStateQueue();
+
+  // This should be a fast test so that it won't affect the next task processing.
+  IsIdleGCTaskNeeded();
 }
 
 void
 CycleCollectedJSContext::AfterProcessMicrotask()
 {
   MOZ_ASSERT(mJSContext);
   AfterProcessMicrotask(RecursionDepth());
 }
 
+void CycleCollectedJSContext::IsIdleGCTaskNeeded()
+{
+  class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable
+  {
+  public:
+    using mozilla::IdleRunnable::IdleRunnable;
+
+  public:
+    NS_IMETHOD Run() override
+    {
+      CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get();
+      if (ccrt) {
+        ccrt->RunIdleTimeGCTask();
+      }
+      return NS_OK;
+    }
+
+    nsresult Cancel() override
+    {
+      return NS_OK;
+    }
+  };
+
+  if (Runtime()->IsIdleGCTaskNeeded()) {
+    nsCOMPtr<nsIRunnable> gc_task = new IdleTimeGCTaskRunnable();
+    NS_IdleDispatchToCurrentThread(gc_task.forget());
+    Runtime()->SetPendingIdleGCTask();
+  }
+}
+
 void
 CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
 {
   MOZ_ASSERT(mJSContext);
 
   // Between microtasks, execute any events that were waiting for a microtask
   // to complete.
   ProcessMetastableStateQueue(aRecursionDepth);
--- a/xpcom/base/CycleCollectedJSContext.h
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -189,16 +189,19 @@ protected:
 public:
   // nsThread entrypoints
   virtual void BeforeProcessTask(bool aMightBlock) { };
   virtual void AfterProcessTask(uint32_t aRecursionDepth);
 
   // microtask processor entry point
   void AfterProcessMicrotask();
 
+  // Check whether we need an idle GC task.
+  void IsIdleGCTaskNeeded();
+
   uint32_t RecursionDepth();
 
   // Run in stable state (call through nsContentUtils)
   void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
   // This isn't in the spec at all yet, but this gets the behavior we want for IDB.
   // Runs after the current microtask completes.
   void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable);
 
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -505,16 +505,17 @@ MozCrashWarningReporter(JSContext*, JSEr
 {
   MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
 }
 
 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
   : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal)
   , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
   , mJSRuntime(JS_GetRuntime(aCx))
+  , mHasPendingIdleGCTask(false)
   , mPrevGCSliceCallback(nullptr)
   , mPrevGCNurseryCollectionCallback(nullptr)
   , mJSHolderMap(256)
   , mOutOfMemoryState(OOMState::OK)
   , mLargeAllocationFailureState(OOMState::OK)
 {
   MOZ_COUNT_CTOR(CycleCollectedJSRuntime);
   MOZ_ASSERT(aCx);
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -242,16 +242,44 @@ public:
   void SetLargeAllocationFailure(OOMState aNewState);
 
   void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState);
   void OnGC(JSContext* aContext, JSGCStatus aStatus);
   void OnOutOfMemory();
   void OnLargeAllocationFailure();
 
   JSRuntime* Runtime() { return mJSRuntime; }
+  const JSRuntime* Runtime() const { return mJSRuntime; }
+
+  bool HasPendingIdleGCTask() const
+  {
+    // Idle GC task associates with JSRuntime.
+    MOZ_ASSERT_IF(mHasPendingIdleGCTask, Runtime());
+    return mHasPendingIdleGCTask;
+  }
+  void SetPendingIdleGCTask()
+  {
+    // Idle GC task associates with JSRuntime.
+    MOZ_ASSERT(Runtime());
+    mHasPendingIdleGCTask = true;
+  }
+  void ClearPendingIdleGCTask() { mHasPendingIdleGCTask = false; }
+
+  void RunIdleTimeGCTask()
+  {
+    if (HasPendingIdleGCTask()) {
+      JS::RunIdleTimeGCTask(Runtime());
+      ClearPendingIdleGCTask();
+    }
+  }
+
+  bool IsIdleGCTaskNeeded()
+  {
+    return !HasPendingIdleGCTask() && Runtime() && JS::IsIdleGCTaskNeeded(Runtime());
+  }
 
 public:
   void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer);
   void RemoveJSHolder(void* aHolder);
 #ifdef DEBUG
   bool IsJSHolder(void* aHolder);
   void AssertNoObjectsToTrace(void* aPossibleJSHolder);
 #endif
@@ -303,16 +331,17 @@ public:
 private:
   LinkedList<CycleCollectedJSContext> mContexts;
 
   JSGCThingParticipant mGCThingCycleCollectorGlobal;
 
   JSZoneParticipant mJSZoneCycleCollectorGlobal;
 
   JSRuntime* mJSRuntime;
+  bool mHasPendingIdleGCTask;
 
   JS::GCSliceCallback mPrevGCSliceCallback;
   JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback;
 
   mozilla::TimeStamp mLatestNurseryCollectionStart;
 
   SegmentedVector<JSHolderInfo, 1024, InfallibleAllocPolicy> mJSHolders;
   nsDataHashtable<nsPtrHashKey<void>, JSHolderInfo*> mJSHolderMap;