Bug 957723 - Decommit unused portions of the nursery; r=jonco
authorTerrence Cole <terrence@mozilla.com>
Wed, 08 Jan 2014 13:43:55 -0800
changeset 173969 6311a62c590148d3b41a35b6ea64b06d3a76717d
parent 173968 2a015b45d8085e960f6bdd88e891989d96a2ee9e
child 173970 850dd9773e9dad258d94e6a71beac2cc225f8352
push id26438
push userphilringnalda@gmail.com
push dateTue, 18 Mar 2014 05:39:07 +0000
treeherderautoland@89275f0ae29f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs957723
milestone31.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 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.");