Bug 957723 - Decommit unused portions of the nursery; r=jonco
authorTerrence Cole <terrence@mozilla.com>
Wed, 08 Jan 2014 13:43:55 -0800
changeset 173982 6311a62c590148d3b41a35b6ea64b06d3a76717d
parent 173981 2a015b45d8085e960f6bdd88e891989d96a2ee9e
child 173983 850dd9773e9dad258d94e6a71beac2cc225f8352
push id5753
push userphilringnalda@gmail.com
push dateTue, 18 Mar 2014 05:47:32 +0000
treeherderfx-team@c8ec14fefc0a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs957723
milestone31.0a1
Bug 957723 - Decommit unused portions of the nursery; r=jonco
js/public/MemoryMetrics.h
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
js/src/vm/Runtime.cpp
js/xpconnect/src/XPCJSRuntime.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -176,17 +176,18 @@ struct CodeSizes
 #undef FOR_EACH_SIZE
 };
 
 // Data for tracking GC memory usage.
 struct GCSizes
 {
 #define FOR_EACH_SIZE(macro) \
     macro(_, _, marker) \
-    macro(_, _, nursery) \
+    macro(_, _, nurseryCommitted) \
+    macro(_, _, nurseryDecommitted) \
     macro(_, _, storeBufferVals) \
     macro(_, _, storeBufferCells) \
     macro(_, _, storeBufferSlots) \
     macro(_, _, storeBufferWholeCells) \
     macro(_, _, storeBufferRelocVals) \
     macro(_, _, storeBufferRelocCells) \
     macro(_, _, storeBufferGenerics)
 
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -65,24 +65,24 @@ js::Nursery::init()
     while (IsPoisonedPtr(heap) || IsPoisonedPtr((void*)(uintptr_t(heap) + NurserySize)))
         heap = MapAlignedPages(runtime(), NurserySize, Alignment);
 #endif
     if (!heap)
         return false;
 
     JSRuntime *rt = runtime();
     rt->gcNurseryStart_ = uintptr_t(heap);
+    currentStart_ = start();
     rt->gcNurseryEnd_ = chunk(LastNurseryChunk).end();
     numActiveChunks_ = 1;
-    setCurrentChunk(0);
 #ifdef JS_GC_ZEAL
     JS_POISON(heap, FreshNursery, NurserySize);
 #endif
-    for (int i = 0; i < NumNurseryChunks; ++i)
-        chunk(i).trailer.runtime = rt;
+    setCurrentChunk(0);
+    updateDecommittedRegion();
 
 #ifdef PROFILE_NURSERY
     char *env = getenv("JS_MINORGC_TIME");
     if (env)
         GCReportThreshold = atoi(env);
 #endif
 
     JS_ASSERT(isEnabled());
@@ -98,30 +98,32 @@ js::Nursery::~Nursery()
 void
 js::Nursery::enable()
 {
     JS_ASSERT(isEmpty());
     if (isEnabled())
         return;
     numActiveChunks_ = 1;
     setCurrentChunk(0);
+    currentStart_ = position();
 #ifdef JS_GC_ZEAL
     if (runtime()->gcZeal_ == ZealGenerationalGCValue)
         enterZealMode();
 #endif
 }
 
 void
 js::Nursery::disable()
 {
+    JS_ASSERT(isEmpty());
     if (!isEnabled())
         return;
-    JS_ASSERT(isEmpty());
     numActiveChunks_ = 0;
     currentEnd_ = 0;
+    updateDecommittedRegion();
 }
 
 bool
 js::Nursery::isEmpty() const
 {
     JS_ASSERT(runtime_);
     if (!isEnabled())
         return true;
@@ -163,16 +165,17 @@ js::Nursery::allocateObject(JSContext *c
     return obj;
 }
 
 void *
 js::Nursery::allocate(size_t size)
 {
     JS_ASSERT(isEnabled());
     JS_ASSERT(!runtime()->isHeapBusy());
+    JS_ASSERT(position() >= currentStart_);
 
     if (position() + size > currentEnd()) {
         if (currentChunk_ + 1 == numActiveChunks_)
             return nullptr;
         setCurrentChunk(currentChunk_ + 1);
     }
 
     void *thing = (void *)position();
@@ -857,38 +860,40 @@ js::Nursery::sweep(JSRuntime *rt)
 {
 #ifdef JS_GC_ZEAL
     /* Poison the nursery contents so touching a freed object will crash. */
     JS_POISON((void *)start(), SweptNursery, NurserySize - sizeof(JSRuntime *));
     for (int i = 0; i < NumNurseryChunks; ++i)
         chunk(i).trailer.runtime = runtime();
 
     if (rt->gcZeal_ == ZealGenerationalGCValue) {
-        /* Undo any grow or shrink the collection may have done. */
-        numActiveChunks_ = NumNurseryChunks;
+        MOZ_ASSERT(numActiveChunks_ == NumNurseryChunks);
 
         /* Only reset the alloc point when we are close to the end. */
         if (currentChunk_ + 1 == NumNurseryChunks)
             setCurrentChunk(0);
-
-        /* Set current start position for isEmpty checks. */
-        currentStart_ = position();
+    } else
+#endif
+    {
+        setCurrentChunk(0);
+    }
 
-        return;
-    }
-#endif
-
-    setCurrentChunk(0);
+    /* Set current start position for isEmpty checks. */
+    currentStart_ = position();
 }
 
 void
 js::Nursery::growAllocableSpace()
 {
+    MOZ_ASSERT_IF(runtime()->gcZeal_ == ZealGenerationalGCValue, numActiveChunks_ == NumNurseryChunks);
     numActiveChunks_ = Min(numActiveChunks_ * 2, NumNurseryChunks);
 }
 
 void
 js::Nursery::shrinkAllocableSpace()
 {
+    if (runtime()->gcZeal_ == ZealGenerationalGCValue)
+        return;
     numActiveChunks_ = Max(numActiveChunks_ - 1, 1);
+    updateDecommittedRegion();
 }
 
 #endif /* JSGC_GENERATIONAL */
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -10,30 +10,32 @@
 
 #ifdef JSGC_GENERATIONAL
 
 #include "jsalloc.h"
 #include "jspubtd.h"
 
 #include "ds/BitArray.h"
 #include "gc/Heap.h"
+#include "gc/Memory.h"
 #include "js/GCAPI.h"
 #include "js/HashTable.h"
 #include "js/HeapAPI.h"
 #include "js/Value.h"
 #include "js/Vector.h"
 
 namespace JS {
 struct Zone;
 }
 
 namespace js {
 
 class ObjectElements;
 class HeapSlot;
+void SetGCZeal(JSRuntime *, uint8_t, uint32_t);
 
 namespace gc {
 class Cell;
 class MinorCollectionTracer;
 } /* namespace gc */
 
 namespace types {
 struct TypeObject;
@@ -118,50 +120,31 @@ class Nursery
      * returns false and leaves |*ref| unset.
      */
     template <typename T>
     MOZ_ALWAYS_INLINE bool getForwardedPointer(T **ref);
 
     /* Forward a slots/elements pointer stored in an Ion frame. */
     void forwardBufferPointer(HeapSlot **pSlotsElems);
 
-    size_t sizeOfHeap() { return start() ? NurserySize : 0; }
-
-#ifdef JS_GC_ZEAL
-    /*
-     * In debug and zeal builds, these bytes indicate the state of an unused
-     * segment of nursery-allocated memory.
-     */
-    static const uint8_t FreshNursery = 0x2a;
-    static const uint8_t SweptNursery = 0x2b;
-    static const uint8_t AllocatedThing = 0x2c;
-    void enterZealMode() {
-        if (isEnabled())
-            numActiveChunks_ = NumNurseryChunks;
-    }
-    void leaveZealMode() {
-        if (isEnabled()) {
-            JS_ASSERT(isEmpty());
-            setCurrentChunk(0);
-        }
-    }
-#endif
+    size_t sizeOfHeapCommitted() { return numActiveChunks_ * gc::ChunkSize; }
+    size_t sizeOfHeapDecommitted() { return (NumNurseryChunks - numActiveChunks_) * gc::ChunkSize; }
 
   private:
     /*
      * The start and end pointers are stored under the runtime so that we can
      * inline the isInsideNursery check into embedder code. Use the start()
      * and heapEnd() functions to access these values.
      */
     JSRuntime *runtime_;
 
     /* Pointer to the first unallocated byte in the nursery. */
     uintptr_t position_;
 
-    /* Pointer to the logic start of the Nursery. */
+    /* Pointer to the logical start of the Nursery. */
     uintptr_t currentStart_;
 
     /* Pointer to the last byte of space in the current chunk. */
     uintptr_t currentEnd_;
 
     /* The index of the chunk that is currently being allocated from. */
     int currentChunk_;
 
@@ -206,18 +189,28 @@ class Nursery
         return ((JS::shadow::Runtime *)runtime_)->gcNurseryEnd_;
     }
 
     MOZ_ALWAYS_INLINE void setCurrentChunk(int chunkno) {
         JS_ASSERT(chunkno < NumNurseryChunks);
         JS_ASSERT(chunkno < numActiveChunks_);
         currentChunk_ = chunkno;
         position_ = chunk(chunkno).start();
-        currentStart_ = chunk(0).start();
         currentEnd_ = chunk(chunkno).end();
+        chunk(chunkno).trailer.runtime = runtime();
+    }
+
+    void updateDecommittedRegion() {
+#ifndef JS_GC_ZEAL
+        if (numActiveChunks_ < NumNurseryChunks) {
+            uintptr_t decommitStart = chunk(numActiveChunks_).start();
+            JS_ASSERT(decommitStart == AlignBytes(decommitStart, 1 << 20));
+            gc::MarkPagesUnused(runtime(), (void *)decommitStart, heapEnd() - decommitStart);
+        }
+#endif
     }
 
     MOZ_ALWAYS_INLINE uintptr_t allocationEnd() const {
         JS_ASSERT(numActiveChunks_ > 0);
         return chunk(numActiveChunks_ - 1).end();
     }
 
     MOZ_ALWAYS_INLINE bool isFullyGrown() const {
@@ -279,19 +272,44 @@ class Nursery
     void sweep(JSRuntime *rt);
 
     /* Change the allocable space provided by the nursery. */
     void growAllocableSpace();
     void shrinkAllocableSpace();
 
     static void MinorGCCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind);
 
+#ifdef JS_GC_ZEAL
+    /*
+     * In debug and zeal builds, these bytes indicate the state of an unused
+     * segment of nursery-allocated memory.
+     */
+    static const uint8_t FreshNursery = 0x2a;
+    static const uint8_t SweptNursery = 0x2b;
+    static const uint8_t AllocatedThing = 0x2c;
+    void enterZealMode() {
+        if (isEnabled())
+            numActiveChunks_ = NumNurseryChunks;
+    }
+    void leaveZealMode() {
+        if (isEnabled()) {
+            JS_ASSERT(isEmpty());
+            setCurrentChunk(0);
+            currentStart_ = start();
+        }
+    }
+#else
+    void enterZealMode() {}
+    void leaveZealMode() {}
+#endif
+
     friend class gc::MinorCollectionTracer;
     friend class jit::CodeGenerator;
     friend class jit::MacroAssembler;
     friend class jit::ICStubCompiler;
     friend class jit::BaselineCompiler;
+    friend void SetGCZeal(JSRuntime *, uint8_t, uint32_t);
 };
 
 } /* namespace js */
 
 #endif /* JSGC_GENERATIONAL */
 #endif /* gc_Nursery_h */
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -623,17 +623,18 @@ JSRuntime::addSizeOfIncludingThis(mozill
             if (JSC::ExecutableAllocator *ionAlloc = jitRuntime()->ionAlloc(this))
                 ionAlloc->addSizeOfCode(&rtSizes->code);
         }
     }
 #endif
 
     rtSizes->gc.marker += gcMarker.sizeOfExcludingThis(mallocSizeOf);
 #ifdef JSGC_GENERATIONAL
-    rtSizes->gc.nursery += gcNursery.sizeOfHeap();
+    rtSizes->gc.nurseryCommitted += gcNursery.sizeOfHeapCommitted();
+    rtSizes->gc.nurseryDecommitted += gcNursery.sizeOfHeapDecommitted();
     gcStoreBuffer.addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc);
 #endif
 }
 
 static bool
 SignalBasedTriggersDisabled()
 {
   // Don't bother trying to cache the getenv lookup; this should be called
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2342,19 +2342,19 @@ ReportJSRuntimeExplicitTreeStats(const J
         KIND_NONHEAP, rtStats.runtime.code.unused,
         "Memory allocated by one of the JITs to hold code, but which is "
         "currently unused.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/marker"),
         KIND_HEAP, rtStats.runtime.gc.marker,
         "The GC mark stack and gray roots.");
 
-    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery"),
-        KIND_NONHEAP, rtStats.runtime.gc.nursery,
-        "The GC nursery.");
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-committed"),
+        KIND_NONHEAP, rtStats.runtime.gc.nurseryCommitted,
+        "Memory being used by the GC's nursery.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/vals"),
         KIND_HEAP, rtStats.runtime.gc.storeBufferVals,
         "Values in the store buffer.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/cells"),
         KIND_HEAP, rtStats.runtime.gc.storeBufferCells,
         "Cells in the store buffer.");
@@ -2388,16 +2388,21 @@ ReportJSRuntimeExplicitTreeStats(const J
     // change the leading "explicit/" to "decommitted/".
     nsCString rtPath2(rtPath);
     rtPath2.Replace(0, strlen("explicit"), NS_LITERAL_CSTRING("decommitted"));
     REPORT_GC_BYTES(rtPath2 + NS_LITERAL_CSTRING("gc-heap/decommitted-arenas"),
         rtStats.gcHeapDecommittedArenas,
         "GC arenas in non-empty chunks that is decommitted, i.e. it takes up "
         "address space but no physical memory or swap space.");
 
+    REPORT_GC_BYTES(rtPath2 + NS_LITERAL_CSTRING("runtime/gc/nursery-decommitted"),
+        rtStats.runtime.gc.nurseryDecommitted,
+        "Memory allocated to the GC's nursery this is decommitted, i.e. it takes up "
+        "address space but no physical memory or swap space.");
+
     REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-chunks"),
         rtStats.gcHeapUnusedChunks,
         "Empty GC chunks which will soon be released unless claimed for new "
         "allocations.");
 
     REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-arenas"),
         rtStats.gcHeapUnusedArenas,
         "Empty GC arenas within non-empty chunks.");