Bug 1287869 - Add zeal mode to check nursery integrity r=terrence
authorJon Coppeard <jcoppeard@mozilla.com>
Thu, 21 Jul 2016 09:58:48 +0100
changeset 346081 bdbb5822afe1851c0df23d33a591b4001e221831
parent 346080 904a6eb36f1ba6d8d1d79f445f25d6eeef021688
child 346082 fff92b2a76ecb3b4d81a33e10fa98dcfd167ed93
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1287869
milestone50.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 1287869 - Add zeal mode to check nursery integrity r=terrence
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
js/src/jit-test/tests/gc/bug-1287869.js
js/src/jsgc.cpp
js/src/jsgc.h
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -36,31 +36,53 @@
 using namespace js;
 using namespace gc;
 
 using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 using mozilla::PodCopy;
 using mozilla::PodZero;
 
+static const uintptr_t CanaryMagicValue = 0xDEADB15D;
+
 struct js::Nursery::FreeMallocedBuffersTask : public GCParallelTask
 {
     explicit FreeMallocedBuffersTask(FreeOp* fop) : fop_(fop) {}
     bool init() { return buffers_.init(); }
     void transferBuffersToFree(MallocedBuffersSet& buffersToFree,
                                const AutoLockHelperThreadState& lock);
     ~FreeMallocedBuffersTask() override { join(); }
 
   private:
     FreeOp* fop_;
     MallocedBuffersSet buffers_;
 
     virtual void run() override;
 };
 
+js::Nursery::Nursery(JSRuntime* rt)
+  : runtime_(rt)
+  , position_(0)
+  , currentStart_(0)
+  , currentEnd_(0)
+  , heapStart_(0)
+  , heapEnd_(0)
+  , currentChunk_(0)
+  , numActiveChunks_(0)
+  , numNurseryChunks_(0)
+  , previousPromotionRate_(0)
+  , profileThreshold_(0)
+  , enableProfiling_(false)
+  , minorGcCount_(0)
+  , freeMallocedBuffersTask(nullptr)
+#ifdef JS_GC_ZEAL
+  , lastCanary_(nullptr)
+#endif
+{}
+
 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)
@@ -210,26 +232,46 @@ js::Nursery::allocateObject(JSContext* c
 
 void*
 js::Nursery::allocate(size_t size)
 {
     MOZ_ASSERT(isEnabled());
     MOZ_ASSERT(!runtime()->isHeapBusy());
     MOZ_ASSERT(position() >= currentStart_);
 
+#ifdef JS_GC_ZEAL
+    static const size_t CanarySize = (sizeof(Nursery::Canary) + CellSize - 1) & ~CellMask;
+    if (runtime()->gc.hasZealMode(ZealMode::CheckNursery))
+        size += CanarySize;
+#endif
+
     if (currentEnd() < position() + size) {
         if (currentChunk_ + 1 == numActiveChunks_)
             return nullptr;
         setCurrentChunk(currentChunk_ + 1);
     }
 
     void* thing = (void*)position();
     position_ = position() + size;
 
     JS_EXTRA_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size);
+
+#ifdef JS_GC_ZEAL
+    if (runtime()->gc.hasZealMode(ZealMode::CheckNursery)) {
+        auto canary = reinterpret_cast<Canary*>(position() - CanarySize);
+        canary->magicValue = CanaryMagicValue;
+        canary->next = nullptr;
+        if (lastCanary_) {
+            MOZ_ASSERT(!lastCanary_->next);
+            lastCanary_->next = canary;
+        }
+        lastCanary_ = canary;
+    }
+#endif
+
     MemProfiler::SampleNursery(reinterpret_cast<void*>(thing), size);
     return thing;
 }
 
 void*
 js::Nursery::allocateBuffer(Zone* zone, uint32_t nbytes)
 {
     MOZ_ASSERT(nbytes > 0);
@@ -447,16 +489,24 @@ js::Nursery::collect(JSRuntime* rt, JS::
          * may be freed after this point.
          */
         sb.clear();
         return;
     }
 
     rt->gc.incMinorGcNumber();
 
+#ifdef JS_GC_ZEAL
+    if (rt->gc.hasZealMode(ZealMode::CheckNursery)) {
+        for (auto canary = lastCanary_; canary; canary = canary->next)
+            MOZ_ASSERT(canary->magicValue == CanaryMagicValue);
+    }
+    lastCanary_ = nullptr;
+#endif
+
     rt->gc.stats.beginNurseryCollection(reason);
     TraceMinorGCStart();
 
     startProfile(ProfileKey::Total);
 
     AutoTraceSession session(rt, JS::HeapState::MinorCollecting);
     AutoStopVerifyingBarriers av(rt, false);
     AutoDisableProxyCheck disableStrictProxyChecking(rt);
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -114,32 +114,17 @@ class TenuringTracer : public JSTracer
 };
 
 class Nursery
 {
   public:
     static const size_t Alignment = gc::ChunkSize;
     static const size_t ChunkShift = gc::ChunkShift;
 
-    explicit Nursery(JSRuntime* rt)
-      : runtime_(rt),
-        position_(0),
-        currentStart_(0),
-        currentEnd_(0),
-        heapStart_(0),
-        heapEnd_(0),
-        currentChunk_(0),
-        numActiveChunks_(0),
-        numNurseryChunks_(0),
-        previousPromotionRate_(0),
-        profileThreshold_(0),
-        enableProfiling_(false),
-        minorGcCount_(0),
-        freeMallocedBuffersTask(nullptr)
-    {}
+    explicit Nursery(JSRuntime* rt);
     ~Nursery();
 
     MOZ_MUST_USE bool init(uint32_t maxNurseryBytes);
 
     bool exists() const { return numNurseryChunks_ != 0; }
     size_t numChunks() const { return numNurseryChunks_; }
     size_t nurserySize() const { return numNurseryChunks_ << ChunkShift; }
 
@@ -345,16 +330,26 @@ class Nursery
      *
      * Note: we store the pointers as Cell* here, resulting in an ugly cast in
      *       sweep. This is because this structure is used to help implement
      *       stable object hashing and we have to break the cycle somehow.
      */
     using CellsWithUniqueIdSet = HashSet<gc::Cell*, PointerHasher<gc::Cell*, 3>, SystemAllocPolicy>;
     CellsWithUniqueIdSet cellsWithUid_;
 
+#ifdef JS_GC_ZEAL
+    struct Canary
+    {
+        uintptr_t magicValue;
+        Canary* next;
+    };
+
+    Canary* lastCanary_;
+#endif
+
     /* The maximum number of bytes allowed to reside in nursery buffers. */
     static const size_t MaxNurseryBufferSize = 1024;
 
     /* The amount of space in the mapped nursery available to allocations. */
     static const size_t NurseryChunkUsableSize = gc::ChunkSize - sizeof(gc::ChunkTrailer);
 
     struct NurseryChunkLayout {
         char data[NurseryChunkUsableSize];
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/gc/bug-1287869.js
@@ -0,0 +1,8 @@
+if (!('gczeal' in this))
+    quit();
+
+gczeal(16);
+let a = [];
+for (let i = 0; i < 1000; i++)
+    a.push({x: i});
+gc();
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -918,17 +918,18 @@ const char* gc::ZealModeHelpText =
     "    7: (GenerationalGC) Collect the nursery every N nursery allocations\n"
     "    8: (IncrementalRootsThenFinish) Incremental GC in two slices: 1) mark roots 2) finish collection\n"
     "    9: (IncrementalMarkAllThenFinish) Incremental GC in two slices: 1) mark all 2) new marking and finish\n"
     "   10: (IncrementalMultipleSlices) Incremental GC in multiple slices\n"
     "   11: (IncrementalMarkingValidator) Verify incremental marking\n"
     "   12: (ElementsBarrier) Always use the individual element post-write barrier, regardless of elements size\n"
     "   13: (CheckHashTablesOnMinorGC) Check internal hashtables on minor GC\n"
     "   14: (Compact) Perform a shrinking collection every N allocations\n"
-    "   15: (CheckHeapOnMovingGC) Walk the heap to check all pointers have been updated\n";
+    "   15: (CheckHeapOnMovingGC) Walk the heap to check all pointers have been updated\n"
+    "   16: (CheckNursery) Check nursery integrity on minor GC\n";
 
 void
 GCRuntime::setZeal(uint8_t zeal, uint32_t frequency)
 {
     MOZ_ASSERT(zeal <= unsigned(ZealMode::Limit));
 
     if (verifyPreData)
         VerifyBarriers(rt, PreBarrierVerifier);
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1222,23 +1222,24 @@ CheckValueAfterMovingGC(const JS::Value&
             D(GenerationalGC, 7)               \
             D(IncrementalRootsThenFinish, 8)   \
             D(IncrementalMarkAllThenFinish, 9) \
             D(IncrementalMultipleSlices, 10)   \
             D(IncrementalMarkingValidator, 11) \
             D(ElementsBarrier, 12)             \
             D(CheckHashTablesOnMinorGC, 13)    \
             D(Compact, 14)                     \
-            D(CheckHeapOnMovingGC, 15)
+            D(CheckHeapOnMovingGC, 15)         \
+            D(CheckNursery, 16)
 
 enum class ZealMode {
 #define ZEAL_MODE(name, value) name = value,
     JS_FOR_EACH_ZEAL_MODE(ZEAL_MODE)
 #undef ZEAL_MODE
-    Limit = 15
+    Limit = 16
 };
 
 enum VerifierType {
     PreBarrierVerifier
 };
 
 #ifdef JS_GC_ZEAL