Bug 1122640 - Free nursery huge slots off main thread r=terrence
authorJon Coppeard <jcoppeard@mozilla.com>
Wed, 04 Feb 2015 16:12:06 +0000
changeset 231851 4c2473a9d7ba1c9692cdc7eb617d0257decbae94
parent 231850 f42cf3d85fab2cd2766b4b772700a874035d2e70
child 231852 085b7d36e31d30904edf5742f420b8ab9eeba171
push id28362
push userryanvm@gmail.com
push dateWed, 04 Mar 2015 21:35:51 +0000
treeherdermozilla-central@56492f7244a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1122640
milestone39.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 1122640 - Free nursery huge slots off main thread r=terrence
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
js/src/jsapi-tests/testGCAllocator.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/vm/HelperThreads.cpp
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -3,16 +3,17 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gc/Nursery-inl.h"
 
 #include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Move.h"
 
 #include "jscompartment.h"
 #include "jsgc.h"
 #include "jsutil.h"
 #include "prmjtime.h"
 
 #include "gc/GCInternals.h"
 #include "gc/Memory.h"
@@ -31,16 +32,29 @@
 
 using namespace js;
 using namespace gc;
 
 using mozilla::ArrayLength;
 using mozilla::PodCopy;
 using mozilla::PodZero;
 
+struct js::Nursery::FreeHugeSlotsTask : public GCParallelTask
+{
+    explicit FreeHugeSlotsTask(FreeOp *fop) : fop_(fop) {}
+    bool init() { return slots_.init(); }
+    void transferSlotsToFree(HugeSlotsSet &slotsToFree);
+
+  private:
+    FreeOp *fop_;
+    HugeSlotsSet slots_;
+
+    virtual void run() MOZ_OVERRIDE;
+};
+
 bool
 js::Nursery::init(uint32_t maxNurseryBytes)
 {
     /* maxNurseryBytes parameter is rounded down to a multiple of chunk size. */
     numNurseryChunks_ = maxNurseryBytes >> ChunkShift;
 
     /* If no chunks are specified then the nursery is permenantly disabled. */
     if (numNurseryChunks_ == 0)
@@ -48,16 +62,20 @@ js::Nursery::init(uint32_t maxNurseryByt
 
     if (!hugeSlots.init())
         return false;
 
     void *heap = MapAlignedPages(nurserySize(), Alignment);
     if (!heap)
         return false;
 
+    freeHugeSlotsTask = js_new<FreeHugeSlotsTask>(runtime()->defaultFreeOp());
+    if (!freeHugeSlotsTask || !freeHugeSlotsTask->init())
+        return false;
+
     heapStart_ = uintptr_t(heap);
     heapEnd_ = heapStart_ + nurserySize();
     currentStart_ = start();
     numActiveChunks_ = 1;
     JS_POISON(heap, JS_FRESH_NURSERY_PATTERN, nurserySize());
     setCurrentChunk(0);
     updateDecommittedRegion();
 
@@ -75,16 +93,18 @@ js::Nursery::init(uint32_t maxNurseryByt
     MOZ_ASSERT(isEnabled());
     return true;
 }
 
 js::Nursery::~Nursery()
 {
     if (start())
         UnmapPages((void *)start(), nurserySize());
+
+    js_delete(freeHugeSlotsTask);
 }
 
 void
 js::Nursery::updateDecommittedRegion()
 {
 #ifndef JS_GC_ZEAL
     if (numActiveChunks_ < numNurseryChunks_) {
         // Bug 994054: madvise on MacOS is too slow to make this
@@ -959,22 +979,57 @@ js::Nursery::collect(JSRuntime *rt, JS::
     }
 }
 
 #undef TIME_START
 #undef TIME_END
 #undef TIME_TOTAL
 
 void
+js::Nursery::FreeHugeSlotsTask::transferSlotsToFree(HugeSlotsSet &slotsToFree)
+{
+    // Transfer the contents of the source set to the task's slots_ member by
+    // swapping the sets, which also clears the source.
+    MOZ_ASSERT(!isRunning());
+    MOZ_ASSERT(slots_.empty());
+    mozilla::Swap(slots_, slotsToFree);
+}
+
+void
+js::Nursery::FreeHugeSlotsTask::run()
+{
+    for (HugeSlotsSet::Range r = slots_.all(); !r.empty(); r.popFront())
+        fop_->free_(r.front());
+    slots_.clear();
+}
+
+void
 js::Nursery::freeHugeSlots()
 {
-    FreeOp *fop = runtime()->defaultFreeOp();
-    for (HugeSlotsSet::Range r = hugeSlots.all(); !r.empty(); r.popFront())
-        fop->free_(r.front());
-    hugeSlots.clear();
+    if (hugeSlots.empty())
+        return;
+
+    bool started;
+    {
+        AutoLockHelperThreadState lock;
+        freeHugeSlotsTask->joinWithLockHeld();
+        freeHugeSlotsTask->transferSlotsToFree(hugeSlots);
+        started = freeHugeSlotsTask->startWithLockHeld();
+    }
+
+    if (!started)
+        freeHugeSlotsTask->runFromMainThread(runtime());
+
+    MOZ_ASSERT(hugeSlots.empty());
+}
+
+void
+js::Nursery::waitBackgroundFreeEnd()
+{
+    freeHugeSlotsTask->join();
 }
 
 void
 js::Nursery::runFinalizers()
 {
     verifyFinalizerList();
 
     FreeOp *fop = runtime()->defaultFreeOp();
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -61,17 +61,18 @@ class Nursery
         currentEnd_(0),
         heapStart_(0),
         heapEnd_(0),
         currentChunk_(0),
         numActiveChunks_(0),
         numNurseryChunks_(0),
         finalizers_(nullptr),
         profileThreshold_(0),
-        enableProfiling_(false)
+        enableProfiling_(false),
+        freeHugeSlotsTask(nullptr)
     {}
     ~Nursery();
 
     bool init(uint32_t maxNurseryBytes);
 
     bool exists() const { return numNurseryChunks_ != 0; }
     size_t numChunks() const { return numNurseryChunks_; }
     size_t nurserySize() const { return numNurseryChunks_ << ChunkShift; }
@@ -134,16 +135,18 @@ class Nursery
     /* Forward a slots/elements pointer stored in an Ion frame. */
     void forwardBufferPointer(HeapSlot **pSlotsElems);
 
     void maybeSetForwardingPointer(JSTracer *trc, void *oldData, void *newData, bool direct) {
         if (IsMinorCollectionTracer(trc) && isInside(oldData))
             setForwardingPointer(oldData, newData, direct);
     }
 
+    void waitBackgroundFreeEnd();
+
     size_t sizeOfHeapCommitted() const {
         return numActiveChunks_ * gc::ChunkSize;
     }
     size_t sizeOfHeapDecommitted() const {
         return (numNurseryChunks_ - numActiveChunks_) * gc::ChunkSize;
     }
     size_t sizeOfHugeSlots(mozilla::MallocSizeOf mallocSizeOf) const {
         size_t total = 0;
@@ -217,16 +220,20 @@ class Nursery
     /*
      * The set of externally malloced slots potentially kept live by objects
      * stored in the nursery. Any external slots that do not belong to a
      * tenured thing at the end of a minor GC must be freed.
      */
     typedef HashSet<HeapSlot *, PointerHasher<HeapSlot *, 3>, SystemAllocPolicy> HugeSlotsSet;
     HugeSlotsSet hugeSlots;
 
+    /* A task structure used to free the huge slots on a background thread. */
+    struct FreeHugeSlotsTask;
+    FreeHugeSlotsTask *freeHugeSlotsTask;
+
     /*
      * During a collection most hoisted slot and element buffers indicate their
      * new location with a forwarding pointer at the base. This does not work
      * for buffers whose length is less than pointer width, or when different
      * buffers might overlap each other. For these, an entry in the following
      * table is used.
      */
     typedef HashMap<void *, void *, PointerHasher<void *, 1>, SystemAllocPolicy> ForwardedBufferMap;
--- a/js/src/jsapi-tests/testGCAllocator.cpp
+++ b/js/src/jsapi-tests/testGCAllocator.cpp
@@ -1,15 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include <cstdlib>
+
+#include "gc/GCInternals.h"
 #include "gc/Memory.h"
 #include "jsapi-tests/tests.h"
 
 #if defined(XP_WIN)
 #include "jswin.h"
 #include <psapi.h>
 #elif defined(SOLARIS)
 // This test doesn't apply to Solaris.
@@ -33,34 +36,69 @@ BEGIN_TEST(testGCAllocator)
     const size_t PageSize = sysinfo.dwPageSize;
 #elif defined(SOLARIS)
     return true;
 #elif defined(XP_UNIX)
     const size_t PageSize = size_t(sysconf(_SC_PAGESIZE));
 #else
     return true;
 #endif
-    if (addressesGrowUp())
+
+    /* Finish any ongoing background free activity. */
+    js::gc::AutoFinishGC finishGC(rt);
+
+    bool growUp;
+    CHECK(addressesGrowUp(&growUp));
+
+    if (growUp)
         return testGCAllocatorUp(PageSize);
     return testGCAllocatorDown(PageSize);
 }
 
 static const size_t Chunk = 512 * 1024;
 static const size_t Alignment = 2 * Chunk;
 static const int MaxTempChunks = 4096;
 static const size_t StagingSize = 16 * Chunk;
 
 bool
-addressesGrowUp()
+addressesGrowUp(bool *resultOut)
 {
-    void *p1 = mapMemory(2 * Chunk);
-    void *p2 = mapMemory(2 * Chunk);
-    unmapPages(p1, 2 * Chunk);
-    unmapPages(p2, 2 * Chunk);
-    return p1 < p2;
+    /*
+     * Try to detect whether the OS allocates memory in increasing or decreasing
+     * address order by making several allocations and comparing the addresses.
+     */
+
+    static const unsigned ChunksToTest = 20;
+    static const int ThresholdCount = 15;
+
+    void *chunks[ChunksToTest];
+    for (unsigned i = 0; i < ChunksToTest; i++) {
+        chunks[i] = mapMemory(2 * Chunk);
+        CHECK(chunks[i]);
+    }
+
+    int upCount = 0;
+    int downCount = 0;
+
+    for (unsigned i = 0; i < ChunksToTest - 1; i++) {
+        if (chunks[i] < chunks[i + 1])
+            upCount++;
+        else
+            downCount++;
+    }
+
+    for (unsigned i = 0; i < ChunksToTest; i++)
+        unmapPages(chunks[i], 2 * Chunk);
+
+    /* Check results were mostly consistent. */
+    CHECK(abs(upCount - downCount) >= ThresholdCount);
+
+    *resultOut = upCount > downCount;
+
+    return true;
 }
 
 size_t
 offsetFromAligned(void *p)
 {
     return uintptr_t(p) % Alignment;
 }
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6399,16 +6399,19 @@ GCRuntime::shrinkBuffers()
 }
 
 void
 GCRuntime::onOutOfMallocMemory()
 {
     // Stop allocating new chunks.
     allocTask.cancel(GCParallelTask::CancelAndWait);
 
+    // Wait for background free of nursery huge slots to finish.
+    nursery.waitBackgroundFreeEnd();
+
     AutoLockGC lock(rt);
     onOutOfMallocMemory(lock);
 }
 
 void
 GCRuntime::onOutOfMallocMemory(const AutoLockGC &lock)
 {
     // Release any relocated arenas we may be holding on to, without releasing
@@ -6501,16 +6504,17 @@ GCRuntime::gcIfRequested(JSContext *cx /
 AutoFinishGC::AutoFinishGC(JSRuntime *rt)
 {
     if (JS::IsIncrementalGCInProgress(rt)) {
         JS::PrepareForIncrementalGC(rt);
         JS::FinishIncrementalGC(rt, JS::gcreason::API);
     }
 
     rt->gc.waitBackgroundSweepEnd();
+    rt->gc.nursery.waitBackgroundFreeEnd();
 }
 
 AutoPrepareForTracing::AutoPrepareForTracing(JSRuntime *rt, ZoneSelector selector)
   : finish(rt),
     session(rt),
     copy(rt, selector)
 {
 }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -990,16 +990,17 @@ class GCParallelTask
   protected:
     // A flag to signal a request for early completion of the off-thread task.
     mozilla::Atomic<bool> cancel_;
 
     virtual void run() = 0;
 
   public:
     GCParallelTask() : state(NotStarted), duration_(0) {}
+    virtual ~GCParallelTask();
 
     // Time spent in the most recent invocation of this task.
     int64_t duration() const { return duration_; }
 
     // The simple interface to a parallel task works exactly like pthreads.
     bool start();
     void join();
 
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -735,16 +735,21 @@ GlobalHelperThreadState::canStartGCHelpe
 }
 
 bool
 GlobalHelperThreadState::canStartGCParallelTask()
 {
     return !gcParallelWorklist().empty();
 }
 
+js::GCParallelTask::~GCParallelTask()
+{
+    join();
+}
+
 bool
 js::GCParallelTask::startWithLockHeld()
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
 
     // Tasks cannot be started twice.
     MOZ_ASSERT(state == NotStarted);