Bug 1476239 - Make MarkStack code more robust and add assertions. r=sfink, a=lizzard
authorJon Coppeard <jcoppeard@mozilla.com>
Thu, 26 Jul 2018 15:33:46 +0100
changeset 480824 bc83bb0212b344803ac00c416052b2520709f7f9
parent 480823 33c02e487f58709e85e1ef53824cd145fd043a40
child 480825 218d36910b9231648062034b52f49e1e4af43388
push id1757
push userffxbld-merge
push dateFri, 24 Aug 2018 17:02:43 +0000
treeherdermozilla-release@736023aebdb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink, lizzard
bugs1476239
milestone62.0
Bug 1476239 - Make MarkStack code more robust and add assertions. r=sfink, a=lizzard
js/src/gc/GCMarker.h
js/src/gc/Marking.cpp
js/src/jsutil.h
--- a/js/src/gc/GCMarker.h
+++ b/js/src/gc/GCMarker.h
@@ -87,133 +87,148 @@ class MarkStack
 
     class TaggedPtr
     {
         uintptr_t bits;
 
         Cell* ptr() const;
 
       public:
+        TaggedPtr() {}
         TaggedPtr(Tag tag, Cell* ptr);
         Tag tag() const;
         template <typename T> T* as() const;
 
         JSObject* asValueArrayObject() const;
         JSObject* asSavedValueArrayObject() const;
         JSRope* asTempRope() const;
+
+        void assertValid() const;
     };
 
     struct ValueArray
     {
         ValueArray(JSObject* obj, HeapSlot* start, HeapSlot* end);
+        void assertValid() const;
 
         HeapSlot* end;
         HeapSlot* start;
         TaggedPtr ptr;
     };
 
     struct SavedValueArray
     {
         SavedValueArray(JSObject* obj, size_t index, HeapSlot::Kind kind);
+        void assertValid() const;
 
         uintptr_t kind;
         uintptr_t index;
         TaggedPtr ptr;
     };
 
     explicit MarkStack(size_t maxCapacity = DefaultCapacity);
     ~MarkStack();
 
     static const size_t DefaultCapacity = SIZE_MAX;
 
-    size_t capacity() { return end_ - stack_; }
+    size_t capacity() { return stack().length(); }
 
     size_t position() const {
-        auto result = tos_ - stack_;
-        MOZ_ASSERT(result >= 0);
-        return size_t(result);
+        return topIndex_;
     }
 
-    void setStack(TaggedPtr* stack, size_t tosIndex, size_t capacity);
-
     MOZ_MUST_USE bool init(JSGCMode gcMode);
 
-    void setBaseCapacity(JSGCMode mode);
+    MOZ_MUST_USE bool setCapacityForMode(JSGCMode mode);
+
     size_t maxCapacity() const { return maxCapacity_; }
     void setMaxCapacity(size_t maxCapacity);
 
     template <typename T>
     MOZ_MUST_USE bool push(T* ptr);
 
     MOZ_MUST_USE bool push(JSObject* obj, HeapSlot* start, HeapSlot* end);
     MOZ_MUST_USE bool push(const ValueArray& array);
     MOZ_MUST_USE bool push(const SavedValueArray& array);
 
     // GCMarker::eagerlyMarkChildren uses unused marking stack as temporary
     // storage to hold rope pointers.
     MOZ_MUST_USE bool pushTempRope(JSRope* ptr);
 
     bool isEmpty() const {
-        return tos_ == stack_;
+        return topIndex_ == 0;
     }
 
     Tag peekTag() const;
     TaggedPtr popPtr();
     ValueArray popValueArray();
     SavedValueArray popSavedValueArray();
 
-    void reset();
+    void clear() {
+        topIndex_ = 0;
+    }
 
     void setGCMode(JSGCMode gcMode);
 
+    void poisonUnused();
+
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
   private:
+    using StackVector = Vector<TaggedPtr, 0, SystemAllocPolicy>;
+    const StackVector& stack() const { return stack_.ref(); }
+    StackVector& stack() { return stack_.ref(); }
+
     MOZ_MUST_USE bool ensureSpace(size_t count);
 
     /* Grow the stack, ensuring there is space for at least count elements. */
     MOZ_MUST_USE bool enlarge(size_t count);
 
+    MOZ_MUST_USE bool resize(size_t newCapacity);
+
+    TaggedPtr* topPtr();
+
     const TaggedPtr& peekPtr() const;
     MOZ_MUST_USE bool pushTaggedPtr(Tag tag, Cell* ptr);
 
-    MainThreadData<TaggedPtr*> stack_;
-    MainThreadData<TaggedPtr*> tos_;
-    MainThreadData<TaggedPtr*> end_;
+    // Index of the top of the stack.
+    MainThreadData<size_t> topIndex_;
 
-    // The capacity we start with and reset() to.
-    MainThreadData<size_t> baseCapacity_;
+    // The maximum stack capacity to grow to.
     MainThreadData<size_t> maxCapacity_;
 
+    // Vector containing allocated stack memory. Unused beyond topIndex_.
+    MainThreadData<StackVector> stack_;
+
 #ifdef DEBUG
     mutable size_t iteratorCount_;
 #endif
 
     friend class MarkStackIter;
 };
 
 class MarkStackIter
 {
-    const MarkStack& stack_;
-    MarkStack::TaggedPtr* pos_;
+    MarkStack& stack_;
+    size_t pos_;
 
   public:
-    explicit MarkStackIter(const MarkStack& stack);
+    explicit MarkStackIter(MarkStack& stack);
     ~MarkStackIter();
 
     bool done() const;
     MarkStack::Tag peekTag() const;
     MarkStack::TaggedPtr peekPtr() const;
     MarkStack::ValueArray peekValueArray() const;
     void next();
     void nextPtr();
     void nextArray();
 
     // Mutate the current ValueArray to a SavedValueArray.
-    void saveValueArray(NativeObject* obj, uintptr_t index, HeapSlot::Kind kind);
+    void saveValueArray(const MarkStack::SavedValueArray& savedArray);
 
   private:
     size_t position() const;
 };
 
 } /* namespace gc */
 
 class GCMarker : public JSTracer
@@ -283,17 +298,17 @@ class GCMarker : public JSTracer
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                const AutoAccessAtomsZone& access) const;
 
 #ifdef DEBUG
 
     bool shouldCheckCompartments() { return strictCompartmentChecking; }
 
-    JS::Zone* stackContainsCrossZonePointerTo(const gc::Cell* cell) const;
+    JS::Zone* stackContainsCrossZonePointerTo(const gc::Cell* cell);
 
 #endif
 
     void markEphemeronValues(gc::Cell* markedCell, gc::WeakEntryVector& entry);
 
     static GCMarker* fromTracer(JSTracer* trc) {
         MOZ_ASSERT(trc->isMarkingTracer());
         return static_cast<GCMarker*>(trc);
@@ -338,17 +353,21 @@ class GCMarker : public JSTracer
     inline void pushValueArray(JSObject* obj, HeapSlot* start, HeapSlot* end);
 
     bool isMarkStackEmpty() {
         return stack.isEmpty();
     }
 
     MOZ_MUST_USE bool restoreValueArray(const gc::MarkStack::SavedValueArray& array,
                                         HeapSlot** vpp, HeapSlot** endp);
+    gc::MarkStack::ValueArray restoreValueArray(const gc::MarkStack::SavedValueArray& savedArray);
+
     void saveValueRanges();
+    gc::MarkStack::SavedValueArray saveValueRange(const gc::MarkStack::ValueArray& array);
+
     inline void processMarkStackTop(SliceBudget& budget);
 
     /* The mark stack. Pointers in this stack are "gray" in the GC sense. */
     gc::MarkStack stack;
 
     /* The color is only applied to objects and functions. */
     MainThreadData<gc::MarkColor> color;
 
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1784,109 +1784,131 @@ GCMarker::processMarkStackTop(SliceBudge
 /*
  * During incremental GC, we return from drainMarkStack without having processed
  * the entire stack. At that point, JS code can run and reallocate slot arrays
  * that are stored on the stack. To prevent this from happening, we replace all
  * ValueArrayTag stack items with SavedValueArrayTag. In the latter, slots
  * pointers are replaced with slot indexes, and slot array end pointers are
  * replaced with the kind of index (properties vs. elements).
  */
+
 void
 GCMarker::saveValueRanges()
 {
     MarkStackIter iter(stack);
     while (!iter.done()) {
         auto tag = iter.peekTag();
         if (tag == MarkStack::ValueArrayTag) {
-            auto array = iter.peekValueArray();
-
-            NativeObject* obj = &array.ptr.asValueArrayObject()->as<NativeObject>();
-            MOZ_ASSERT(obj->isNative());
-
-            uintptr_t index;
-            HeapSlot::Kind kind;
-            HeapSlot* vp = obj->getDenseElementsAllowCopyOnWrite();
-            if (array.end == vp + obj->getDenseInitializedLength()) {
-                MOZ_ASSERT(array.start >= vp);
-                // Add the number of shifted elements here (and subtract in
-                // restoreValueArray) to ensure shift() calls on the array
-                // are handled correctly.
-                index = obj->unshiftedIndex(array.start - vp);
-                kind = HeapSlot::Element;
-            } else {
-                HeapSlot* vp = obj->fixedSlots();
-                unsigned nfixed = obj->numFixedSlots();
-                if (array.start == array.end) {
-                    index = obj->slotSpan();
-                } else if (array.start >= vp && array.start < vp + nfixed) {
-                    MOZ_ASSERT(array.end == vp + Min(nfixed, obj->slotSpan()));
-                    index = array.start - vp;
-                } else {
-                    MOZ_ASSERT(array.start >= obj->slots_ &&
-                               array.end == obj->slots_ + obj->slotSpan() - nfixed);
-                    index = (array.start - obj->slots_) + nfixed;
-                }
-                kind = HeapSlot::Slot;
-            }
-            iter.saveValueArray(obj, index, kind);
+            const auto& array = iter.peekValueArray();
+            auto savedArray = saveValueRange(array);
+            iter.saveValueArray(savedArray);
             iter.nextArray();
         } else if (tag == MarkStack::SavedValueArrayTag) {
             iter.nextArray();
         } else {
             iter.nextPtr();
         }
     }
+
+    // This is also a convenient point to poison unused stack memory.
+    stack.poisonUnused();
 }
 
 bool
-GCMarker::restoreValueArray(const MarkStack::SavedValueArray& array,
+GCMarker::restoreValueArray(const MarkStack::SavedValueArray& savedArray,
                             HeapSlot** vpp, HeapSlot** endp)
 {
-    JSObject* objArg = array.ptr.asSavedValueArrayObject();
+    JSObject* objArg = savedArray.ptr.asSavedValueArrayObject();
     if (!objArg->isNative())
         return false;
-    NativeObject* obj = &objArg->as<NativeObject>();
-
-    uintptr_t start = array.index;
-    if (array.kind == HeapSlot::Element) {
+
+    auto array = restoreValueArray(savedArray);
+    *vpp = array.start;
+    *endp = array.end;
+    return true;
+}
+
+MarkStack::SavedValueArray
+GCMarker::saveValueRange(const MarkStack::ValueArray& array)
+{
+    NativeObject* obj = &array.ptr.asValueArrayObject()->as<NativeObject>();
+    MOZ_ASSERT(obj->isNative());
+
+    uintptr_t index;
+    HeapSlot::Kind kind;
+    HeapSlot* vp = obj->getDenseElementsAllowCopyOnWrite();
+    if (array.end == vp + obj->getDenseInitializedLength()) {
+        MOZ_ASSERT(array.start >= vp);
+        // Add the number of shifted elements here (and subtract in
+        // restoreValueArray) to ensure shift() calls on the array
+        // are handled correctly.
+        index = obj->unshiftedIndex(array.start - vp);
+        kind = HeapSlot::Element;
+    } else {
+        HeapSlot* vp = obj->fixedSlots();
+        unsigned nfixed = obj->numFixedSlots();
+        if (array.start == array.end) {
+            index = obj->slotSpan();
+        } else if (array.start >= vp && array.start < vp + nfixed) {
+            MOZ_ASSERT(array.end == vp + Min(nfixed, obj->slotSpan()));
+            index = array.start - vp;
+        } else {
+            MOZ_ASSERT(array.start >= obj->slots_ &&
+                       array.end == obj->slots_ + obj->slotSpan() - nfixed);
+            index = (array.start - obj->slots_) + nfixed;
+        }
+        kind = HeapSlot::Slot;
+    }
+
+    return MarkStack::SavedValueArray(obj, index, kind);
+}
+
+MarkStack::ValueArray
+GCMarker::restoreValueArray(const MarkStack::SavedValueArray& savedArray)
+{
+    NativeObject* obj = &savedArray.ptr.asSavedValueArrayObject()->as<NativeObject>();
+    HeapSlot* start = nullptr;
+    HeapSlot* end = nullptr;
+
+    uintptr_t index = savedArray.index;
+    if (savedArray.kind == HeapSlot::Element) {
         uint32_t initlen = obj->getDenseInitializedLength();
 
         // Account for shifted elements.
         uint32_t numShifted = obj->getElementsHeader()->numShiftedElements();
-        start = (numShifted < start) ? start - numShifted : 0;
+        index = (numShifted < index) ? index - numShifted : 0;
 
         HeapSlot* vp = obj->getDenseElementsAllowCopyOnWrite();
-        if (start < initlen) {
-            *vpp = vp + start;
-            *endp = vp + initlen;
+        if (index < initlen) {
+            start = vp + index;
+            end = vp + initlen;
         } else {
             /* The object shrunk, in which case no scanning is needed. */
-            *vpp = *endp = vp;
+            start = end = vp;
         }
     } else {
-        MOZ_ASSERT(array.kind == HeapSlot::Slot);
+        MOZ_ASSERT(savedArray.kind == HeapSlot::Slot);
         HeapSlot* vp = obj->fixedSlots();
         unsigned nfixed = obj->numFixedSlots();
         unsigned nslots = obj->slotSpan();
-        if (start < nslots) {
-            if (start < nfixed) {
-                *vpp = vp + start;
-                *endp = vp + Min(nfixed, nslots);
+        if (index < nslots) {
+            if (index < nfixed) {
+                start = vp + index;
+                end = vp + Min(nfixed, nslots);
             } else {
-                *vpp = obj->slots_ + start - nfixed;
-                *endp = obj->slots_ + nslots - nfixed;
+                start = obj->slots_ + index - nfixed;
+                end = obj->slots_ + nslots - nfixed;
             }
         } else {
             /* The object shrunk, in which case no scanning is needed. */
-            *vpp = *endp = vp;
+            start = end = vp;
         }
     }
 
-    MOZ_ASSERT(*vpp <= *endp);
-    return true;
+    return MarkStack::ValueArray(obj, start, end);
 }
 
 
 /*** Mark Stack ***********************************************************************************/
 
 static_assert(sizeof(MarkStack::TaggedPtr) == sizeof(uintptr_t),
               "A TaggedPtr should be the same size as a pointer");
 static_assert(sizeof(MarkStack::ValueArray) == sizeof(MarkStack::SavedValueArray),
@@ -1908,53 +1930,44 @@ template <>
 struct MapTypeToMarkStackTag<JSScript*> { static const auto value = MarkStack::ScriptTag; };
 
 static inline bool
 TagIsArrayTag(MarkStack::Tag tag)
 {
     return tag == MarkStack::ValueArrayTag || tag == MarkStack::SavedValueArrayTag;
 }
 
-static inline void
-CheckValueArray(const MarkStack::ValueArray& array)
-{
-    MOZ_ASSERT(array.ptr.tag() == MarkStack::ValueArrayTag);
-    MOZ_ASSERT(uintptr_t(array.start) <= uintptr_t(array.end));
-    MOZ_ASSERT((uintptr_t(array.end) - uintptr_t(array.start)) % sizeof(Value) == 0);
-}
-
-static inline void
-CheckSavedValueArray(const MarkStack::SavedValueArray& array)
-{
-    MOZ_ASSERT(array.ptr.tag() == MarkStack::SavedValueArrayTag);
-    MOZ_ASSERT(array.kind == HeapSlot::Slot || array.kind == HeapSlot::Element);
-}
-
 inline
 MarkStack::TaggedPtr::TaggedPtr(Tag tag, Cell* ptr)
   : bits(tag | uintptr_t(ptr))
 {
-    MOZ_ASSERT(tag <= LastTag);
-    MOZ_ASSERT((uintptr_t(ptr) & CellAlignMask) == 0);
+    assertValid();
 }
 
 inline MarkStack::Tag
 MarkStack::TaggedPtr::tag() const
 {
     auto tag = Tag(bits & TagMask);
     MOZ_ASSERT(tag <= LastTag);
     return tag;
 }
 
 inline Cell*
 MarkStack::TaggedPtr::ptr() const
 {
     return reinterpret_cast<Cell*>(bits & ~TagMask);
 }
 
+inline void
+MarkStack::TaggedPtr::assertValid() const
+{
+    mozilla::Unused << tag();
+    MOZ_ASSERT(IsCellPointerValid(ptr()));
+}
+
 template <typename T>
 inline T*
 MarkStack::TaggedPtr::as() const
 {
     MOZ_ASSERT(tag() == MapTypeToMarkStackTag<T*>::value);
     MOZ_ASSERT(ptr()->isTenured());
     MOZ_ASSERT(ptr()->is<T>());
     return static_cast<T*>(ptr());
@@ -1984,102 +1997,126 @@ MarkStack::TaggedPtr::asTempRope() const
     MOZ_ASSERT(tag() == TempRopeTag);
     MOZ_ASSERT(ptr()->is<JSString>());
     return static_cast<JSRope*>(ptr());
 }
 
 inline
 MarkStack::ValueArray::ValueArray(JSObject* obj, HeapSlot* startArg, HeapSlot* endArg)
   : end(endArg), start(startArg), ptr(ValueArrayTag, obj)
-{}
+{
+    assertValid();
+}
+
+inline void
+MarkStack::ValueArray::assertValid() const
+{
+    ptr.assertValid();
+    MOZ_ASSERT(ptr.tag() == MarkStack::ValueArrayTag);
+    MOZ_ASSERT(start);
+    MOZ_ASSERT(end);
+    MOZ_ASSERT(uintptr_t(start) <= uintptr_t(end));
+    MOZ_ASSERT((uintptr_t(end) - uintptr_t(start)) % sizeof(Value) == 0);
+}
 
 inline
 MarkStack::SavedValueArray::SavedValueArray(JSObject* obj, size_t indexArg, HeapSlot::Kind kindArg)
   : kind(kindArg), index(indexArg), ptr(SavedValueArrayTag, obj)
-{}
+{
+    assertValid();
+}
+
+inline void
+MarkStack::SavedValueArray::assertValid() const
+{
+    ptr.assertValid();
+    MOZ_ASSERT(ptr.tag() == MarkStack::SavedValueArrayTag);
+    MOZ_ASSERT(kind == HeapSlot::Slot || kind == HeapSlot::Element);
+}
 
 MarkStack::MarkStack(size_t maxCapacity)
-  : stack_(nullptr)
-  , tos_(nullptr)
-  , end_(nullptr)
-  , baseCapacity_(0)
+  : topIndex_(0)
   , maxCapacity_(maxCapacity)
 #ifdef DEBUG
   , iteratorCount_(0)
 #endif
 {}
 
 MarkStack::~MarkStack()
 {
+    MOZ_ASSERT(isEmpty());
     MOZ_ASSERT(iteratorCount_ == 0);
-    js_free(stack_);
 }
 
 bool
 MarkStack::init(JSGCMode gcMode)
 {
-    setBaseCapacity(gcMode);
-
-    MOZ_ASSERT(!stack_);
-    auto newStack = js_pod_malloc<TaggedPtr>(baseCapacity_);
-    if (!newStack)
-        return false;
-
-    setStack(newStack, 0, baseCapacity_);
-    return true;
-}
-
-inline void
-MarkStack::setStack(TaggedPtr* stack, size_t tosIndex, size_t capacity)
-{
-    MOZ_ASSERT(iteratorCount_ == 0);
-    stack_ = stack;
-    tos_ = stack + tosIndex;
-    end_ = stack + capacity;
+    MOZ_ASSERT(isEmpty());
+
+    return setCapacityForMode(gcMode);
 }
 
 void
-MarkStack::setBaseCapacity(JSGCMode mode)
+MarkStack::setGCMode(JSGCMode gcMode)
 {
+    // Ignore failure to resize the stack and keep using the existing stack.
+    mozilla::Unused << setCapacityForMode(gcMode);
+}
+
+bool
+MarkStack::setCapacityForMode(JSGCMode mode)
+{
+    size_t capacity;
+
     switch (mode) {
       case JSGC_MODE_GLOBAL:
       case JSGC_MODE_ZONE:
-        baseCapacity_ = NON_INCREMENTAL_MARK_STACK_BASE_CAPACITY;
+        capacity = NON_INCREMENTAL_MARK_STACK_BASE_CAPACITY;
         break;
       case JSGC_MODE_INCREMENTAL:
-        baseCapacity_ = INCREMENTAL_MARK_STACK_BASE_CAPACITY;
+        capacity = INCREMENTAL_MARK_STACK_BASE_CAPACITY;
         break;
       default:
         MOZ_CRASH("bad gc mode");
     }
 
-    if (baseCapacity_ > maxCapacity_)
-        baseCapacity_ = maxCapacity_;
+    if (capacity > maxCapacity_)
+        capacity = maxCapacity_;
+
+    return resize(capacity);
 }
 
 void
 MarkStack::setMaxCapacity(size_t maxCapacity)
 {
     MOZ_ASSERT(maxCapacity != 0);
     MOZ_ASSERT(isEmpty());
+
     maxCapacity_ = maxCapacity;
-    if (baseCapacity_ > maxCapacity_)
-        baseCapacity_ = maxCapacity_;
-
-    reset();
+    if (capacity() > maxCapacity_) {
+        // If the realloc fails, just keep using the existing stack; it's
+        // not ideal but better than failing.
+        mozilla::Unused << resize(maxCapacity_);
+    }
+}
+
+inline MarkStack::TaggedPtr*
+MarkStack::topPtr()
+{
+    return &stack()[topIndex_];
 }
 
 inline bool
 MarkStack::pushTaggedPtr(Tag tag, Cell* ptr)
 {
     if (!ensureSpace(1))
         return false;
 
-    MOZ_ASSERT(tos_ < end_);
-    *tos_++ = TaggedPtr(tag, ptr);
+    *topPtr() = TaggedPtr(tag, ptr);
+    topIndex_++;
     return true;
 }
 
 template <typename T>
 inline bool
 MarkStack::push(T* ptr)
 {
     return pushTaggedPtr(MapTypeToMarkStackTag<T*>::value, ptr);
@@ -2095,153 +2132,140 @@ inline bool
 MarkStack::push(JSObject* obj, HeapSlot* start, HeapSlot* end)
 {
     return push(ValueArray(obj, start, end));
 }
 
 inline bool
 MarkStack::push(const ValueArray& array)
 {
-    CheckValueArray(array);
+    array.assertValid();
 
     if (!ensureSpace(ValueArrayWords))
         return false;
 
-    *reinterpret_cast<ValueArray*>(tos_.ref()) = array;
-    tos_ += ValueArrayWords;
-    MOZ_ASSERT(tos_ <= end_);
+    *reinterpret_cast<ValueArray*>(topPtr()) = array;
+    topIndex_ += ValueArrayWords;
+    MOZ_ASSERT(position() <= capacity());
     MOZ_ASSERT(peekTag() == ValueArrayTag);
     return true;
 }
 
 inline bool
 MarkStack::push(const SavedValueArray& array)
 {
-    CheckSavedValueArray(array);
+    array.assertValid();
 
     if (!ensureSpace(ValueArrayWords))
         return false;
 
-    *reinterpret_cast<SavedValueArray*>(tos_.ref()) = array;
-    tos_ += ValueArrayWords;
-    MOZ_ASSERT(tos_ <= end_);
+    *reinterpret_cast<SavedValueArray*>(topPtr()) = array;
+    topIndex_ += ValueArrayWords;
+    MOZ_ASSERT(position() <= capacity());
     MOZ_ASSERT(peekTag() == SavedValueArrayTag);
     return true;
 }
 
 inline const MarkStack::TaggedPtr&
 MarkStack::peekPtr() const
 {
-    MOZ_ASSERT(!isEmpty());
-    return tos_[-1];
+    return stack()[topIndex_ - 1];
 }
 
 inline MarkStack::Tag
 MarkStack::peekTag() const
 {
     return peekPtr().tag();
 }
 
 inline MarkStack::TaggedPtr
 MarkStack::popPtr()
 {
     MOZ_ASSERT(!isEmpty());
     MOZ_ASSERT(!TagIsArrayTag(peekTag()));
-    tos_--;
-    return *tos_;
+    peekPtr().assertValid();
+    topIndex_--;
+    return *topPtr();
 }
 
 inline MarkStack::ValueArray
 MarkStack::popValueArray()
 {
     MOZ_ASSERT(peekTag() == ValueArrayTag);
     MOZ_ASSERT(position() >= ValueArrayWords);
 
-    tos_ -= ValueArrayWords;
-    const auto& array = *reinterpret_cast<ValueArray*>(tos_.ref());
-    CheckValueArray(array);
+    topIndex_ -= ValueArrayWords;
+    const auto& array = *reinterpret_cast<ValueArray*>(topPtr());
+    array.assertValid();
     return array;
 }
 
 inline MarkStack::SavedValueArray
 MarkStack::popSavedValueArray()
 {
     MOZ_ASSERT(peekTag() == SavedValueArrayTag);
     MOZ_ASSERT(position() >= ValueArrayWords);
 
-    tos_ -= ValueArrayWords;
-    const auto& array = *reinterpret_cast<SavedValueArray*>(tos_.ref());
-    CheckSavedValueArray(array);
+    topIndex_ -= ValueArrayWords;
+    const auto& array = *reinterpret_cast<SavedValueArray*>(topPtr());
+    array.assertValid();
     return array;
 }
 
-void
-MarkStack::reset()
-{
-    if (capacity() == baseCapacity_) {
-        // No size change; keep the current stack.
-        setStack(stack_, 0, baseCapacity_);
-        return;
-    }
-
-    MOZ_ASSERT(baseCapacity_ != 0);
-    auto newStack = js_pod_realloc<TaggedPtr>(stack_, capacity(), baseCapacity_);
-    if (!newStack) {
-        // If the realloc fails, just keep using the existing stack; it's
-        // not ideal but better than failing.
-        newStack = stack_;
-        baseCapacity_ = capacity();
-    }
-    setStack(newStack, 0, baseCapacity_);
-}
-
 inline bool
 MarkStack::ensureSpace(size_t count)
 {
-    if ((tos_ + count) <= end_)
+    if ((topIndex_ + count) <= capacity())
         return !js::oom::ShouldFailWithOOM();
 
     return enlarge(count);
 }
 
 bool
 MarkStack::enlarge(size_t count)
 {
     size_t newCapacity = Min(maxCapacity_.ref(), capacity() * 2);
     if (newCapacity < capacity() + count)
         return false;
 
-    size_t tosIndex = position();
-
+    return resize(newCapacity);
+}
+
+bool
+MarkStack::resize(size_t newCapacity)
+{
     MOZ_ASSERT(newCapacity != 0);
-    auto newStack = js_pod_realloc<TaggedPtr>(stack_, capacity(), newCapacity);
-    if (!newStack)
+    if (!stack().resize(newCapacity))
         return false;
 
-    setStack(newStack, tosIndex, newCapacity);
+    poisonUnused();
     return true;
 }
 
-void
-MarkStack::setGCMode(JSGCMode gcMode)
+inline void
+MarkStack::poisonUnused()
 {
-    // The mark stack won't be resized until the next call to reset(), but
-    // that will happen at the end of the next GC.
-    setBaseCapacity(gcMode);
+    static_assert((JS_FRESH_MARK_STACK_PATTERN & TagMask) > LastTag,
+                  "The mark stack poison pattern must not look like a valid tagged pointer");
+
+    JS_POISON(&stack()[topIndex_],
+              JS_FRESH_MARK_STACK_PATTERN,
+              stack().capacity() - topIndex_,
+              MemCheckKind::MakeUndefined);
 }
 
 size_t
 MarkStack::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
-    return mallocSizeOf(stack_);
+    return stack().sizeOfExcludingThis(mallocSizeOf);
 }
 
-MarkStackIter::MarkStackIter(const MarkStack& stack)
+MarkStackIter::MarkStackIter(MarkStack& stack)
   : stack_(stack),
-    pos_(stack.tos_)
+    pos_(stack.position())
 {
 #ifdef DEBUG
     stack.iteratorCount_++;
 #endif
 }
 
 MarkStackIter::~MarkStackIter()
 {
@@ -2249,46 +2273,47 @@ MarkStackIter::~MarkStackIter()
     MOZ_ASSERT(stack_.iteratorCount_);
     stack_.iteratorCount_--;
 #endif
 }
 
 inline size_t
 MarkStackIter::position() const
 {
-    return pos_ - stack_.stack_;
+    return pos_;
 }
 
 inline bool
 MarkStackIter::done() const
 {
     return position() == 0;
 }
 
 inline MarkStack::TaggedPtr
 MarkStackIter::peekPtr() const
 {
     MOZ_ASSERT(!done());
-    return pos_[-1];
+    return stack_.stack()[pos_ - 1];
 }
 
 inline MarkStack::Tag
 MarkStackIter::peekTag() const
 {
     return peekPtr().tag();
 }
 
 inline MarkStack::ValueArray
 MarkStackIter::peekValueArray() const
 {
     MOZ_ASSERT(peekTag() == MarkStack::ValueArrayTag);
     MOZ_ASSERT(position() >= ValueArrayWords);
 
-    const auto& array = *reinterpret_cast<MarkStack::ValueArray*>(pos_ - ValueArrayWords);
-    CheckValueArray(array);
+    const MarkStack::TaggedPtr* ptr = &stack_.stack()[pos_ - ValueArrayWords];
+    const auto& array = *reinterpret_cast<const MarkStack::ValueArray*>(ptr);
+    array.assertValid();
     return array;
 }
 
 inline void
 MarkStackIter::nextPtr()
 {
     MOZ_ASSERT(!done());
     MOZ_ASSERT(!TagIsArrayTag(peekTag()));
@@ -2308,25 +2333,25 @@ inline void
 MarkStackIter::nextArray()
 {
     MOZ_ASSERT(TagIsArrayTag(peekTag()));
     MOZ_ASSERT(position() >= ValueArrayWords);
     pos_ -= ValueArrayWords;
 }
 
 void
-MarkStackIter::saveValueArray(NativeObject* obj, uintptr_t index, HeapSlot::Kind kind)
+MarkStackIter::saveValueArray(const MarkStack::SavedValueArray& savedArray)
 {
     MOZ_ASSERT(peekTag() == MarkStack::ValueArrayTag);
-    MOZ_ASSERT(peekPtr().asValueArrayObject() == obj);
+    MOZ_ASSERT(peekPtr().asValueArrayObject() == savedArray.ptr.asSavedValueArrayObject());
     MOZ_ASSERT(position() >= ValueArrayWords);
 
-    auto& array = *reinterpret_cast<MarkStack::SavedValueArray*>(pos_ - ValueArrayWords);
-    array = MarkStack::SavedValueArray(obj, index, kind);
-    CheckSavedValueArray(array);
+    MarkStack::TaggedPtr* ptr = &stack_.stack()[pos_ - ValueArrayWords];
+    auto dest = reinterpret_cast<MarkStack::SavedValueArray*>(ptr);
+    *dest = savedArray;
     MOZ_ASSERT(peekTag() == MarkStack::SavedValueArrayTag);
 }
 
 
 /*** GCMarker *************************************************************************************/
 
 /*
  * ExpandWeakMaps: the GC is recomputing the liveness of WeakMap entries by
@@ -2376,30 +2401,30 @@ GCMarker::stop()
     MOZ_ASSERT(started);
     started = false;
 
     MOZ_ASSERT(!unmarkedArenaStackTop);
     MOZ_ASSERT(markLaterArenas == 0);
 #endif
 
     /* Free non-ballast stack memory. */
-    stack.reset();
+    stack.clear();
     AutoEnterOOMUnsafeRegion oomUnsafe;
     for (GCZonesIter zone(runtime()); !zone.done(); zone.next()) {
         if (!zone->gcWeakKeys().clear())
             oomUnsafe.crash("clearing weak keys in GCMarker::stop()");
     }
 }
 
 void
 GCMarker::reset()
 {
     color = MarkColor::Black;
 
-    stack.reset();
+    stack.clear();
     MOZ_ASSERT(isMarkStackEmpty());
 
     while (unmarkedArenaStackTop) {
         Arena* arena = unmarkedArenaStackTop;
         MOZ_ASSERT(arena->hasDelayedMarking);
         MOZ_ASSERT(markLaterArenas);
         unmarkedArenaStackTop = arena->getNextDelayedMarking();
         arena->unsetDelayedMarking();
@@ -2422,16 +2447,20 @@ GCMarker::pushTaggedPtr(T* ptr)
     if (!stack.push(ptr))
         delayMarkingChildren(ptr);
 }
 
 void
 GCMarker::pushValueArray(JSObject* obj, HeapSlot* start, HeapSlot* end)
 {
     checkZone(obj);
+
+    if (start == end)
+        return;
+
     if (!stack.push(obj, start, end))
         delayMarkingChildren(obj);
 }
 
 void
 GCMarker::repush(JSObject* obj)
 {
     MOZ_ASSERT_IF(markColor() == MarkColor::Gray, gc::TenuredCell::fromPointer(obj)->isMarkedGray());
@@ -2571,17 +2600,17 @@ GCMarker::sizeOfExcludingThis(mozilla::M
     size_t size = stack.sizeOfExcludingThis(mallocSizeOf);
     for (ZonesIter zone(runtime(), WithAtoms); !zone.done(); zone.next())
         size += zone->gcGrayRoots().sizeOfExcludingThis(mallocSizeOf);
     return size;
 }
 
 #ifdef DEBUG
 Zone*
-GCMarker::stackContainsCrossZonePointerTo(const Cell* target) const
+GCMarker::stackContainsCrossZonePointerTo(const Cell* target)
 {
     MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
 
     Zone* targetZone = target->asTenured().zone();
 
     for (MarkStackIter iter(stack); !iter.done(); iter.next()) {
         if (iter.peekTag() != MarkStack::ObjectTag)
             continue;
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -263,17 +263,18 @@ const uint8_t JS_FRESH_NURSERY_PATTERN  
 const uint8_t JS_SWEPT_NURSERY_PATTERN     = 0x2B;
 const uint8_t JS_ALLOCATED_NURSERY_PATTERN = 0x2D;
 const uint8_t JS_FRESH_TENURED_PATTERN     = 0x4F;
 const uint8_t JS_MOVED_TENURED_PATTERN     = 0x49;
 const uint8_t JS_SWEPT_TENURED_PATTERN     = 0x4B;
 const uint8_t JS_ALLOCATED_TENURED_PATTERN = 0x4D;
 const uint8_t JS_FREED_HEAP_PTR_PATTERN    = 0x6B;
 const uint8_t JS_FREED_CHUNK_PATTERN       = 0x8B;
-#define JS_SWEPT_TI_PATTERN 0x6F
+const uint8_t JS_SWEPT_TI_PATTERN          = 0x6F;
+const uint8_t JS_FRESH_MARK_STACK_PATTERN  = 0x9F;
 
 /*
  * Ensure JS_SWEPT_CODE_PATTERN is a byte pattern that will crash immediately
  * when executed, so either an undefined instruction or an instruction that's
  * illegal in user mode.
  */
 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_NONE)
 # define JS_SWEPT_CODE_PATTERN 0xED // IN instruction, crashes in user mode.